@livo-tv/sdk 1.1.2 → 1.2.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/index.cjs +5 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/studio/index.cjs +2093 -1617
- package/dist/studio/index.cjs.map +1 -1
- package/dist/studio/index.d.cts +33 -11
- package/dist/studio/index.d.ts +33 -11
- package/dist/studio/index.js +2094 -1618
- package/dist/studio/index.js.map +1 -1
- package/package.json +2 -2
package/dist/studio/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { createContext, useContext, useState, useRef, useEffect, useCallback, useMemo } from 'react';
|
|
3
3
|
import { createPortal } from 'react-dom';
|
|
4
|
-
import { Loader2, Share2, MicOff, Mic, Video, VideoOff, X, Link2, EyeOff, Eye, Users, MonitorUp, MessageSquare, Maximize, PictureInPicture2,
|
|
4
|
+
import { Loader2, Share2, MicOff, Mic, Video, VideoOff, Settings, X, Link2, EyeOff, Eye, Users, MonitorUp, MessageSquare, Maximize, PictureInPicture2, PhoneOff, Undo2, Volume2, XIcon, Pin, MessageSquareOff, MoreVertical } from 'lucide-react';
|
|
5
5
|
import { toast } from 'sonner';
|
|
6
6
|
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';
|
|
7
7
|
import { clsx } from 'clsx';
|
|
@@ -9,13 +9,13 @@ import { twMerge } from 'tailwind-merge';
|
|
|
9
9
|
import { Button as Button$1 } from '@base-ui/react/button';
|
|
10
10
|
import { cva } from 'class-variance-authority';
|
|
11
11
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
12
|
-
import { mergeProps } from '@base-ui/react/merge-props';
|
|
13
|
-
import { useRender } from '@base-ui/react/use-render';
|
|
14
|
-
import { Menu } from '@base-ui/react/menu';
|
|
15
12
|
import { Tooltip as Tooltip$1 } from '@base-ui/react/tooltip';
|
|
16
13
|
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
|
17
14
|
import { Input as Input$1 } from '@base-ui/react/input';
|
|
18
15
|
import * as LabelPrimitive from '@radix-ui/react-label';
|
|
16
|
+
import { mergeProps } from '@base-ui/react/merge-props';
|
|
17
|
+
import { useRender } from '@base-ui/react/use-render';
|
|
18
|
+
import { Menu } from '@base-ui/react/menu';
|
|
19
19
|
|
|
20
20
|
// src/public-api.ts
|
|
21
21
|
function defaultApiUrl() {
|
|
@@ -1062,6 +1062,93 @@ function StudioWindowGate({
|
|
|
1062
1062
|
}
|
|
1063
1063
|
return /* @__PURE__ */ jsx(StudioWindowGateContext.Provider, { value: { reportDuplicate }, children: /* @__PURE__ */ jsx("div", { children }, generation) });
|
|
1064
1064
|
}
|
|
1065
|
+
|
|
1066
|
+
// src/studio/lib/devices.ts
|
|
1067
|
+
var STUDIO_DEVICE_CHANGE_DEBOUNCE_MS = 250;
|
|
1068
|
+
var STORAGE_KEYS = {
|
|
1069
|
+
audio: "livo-studio-device-audio",
|
|
1070
|
+
video: "livo-studio-device-video",
|
|
1071
|
+
speaker: "livo-studio-device-speaker"
|
|
1072
|
+
};
|
|
1073
|
+
var DEVICE_KINDS = ["audio", "video", "speaker"];
|
|
1074
|
+
function readStudioDeviceSelection(storage = defaultStorage()) {
|
|
1075
|
+
return {
|
|
1076
|
+
audio: storage?.getItem(STORAGE_KEYS.audio)?.trim() ?? "",
|
|
1077
|
+
video: storage?.getItem(STORAGE_KEYS.video)?.trim() ?? "",
|
|
1078
|
+
speaker: storage?.getItem(STORAGE_KEYS.speaker)?.trim() ?? ""
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
function persistStudioDeviceSelection(kind, deviceId, storage = defaultStorage()) {
|
|
1082
|
+
storage?.setItem(STORAGE_KEYS[kind], deviceId.trim());
|
|
1083
|
+
}
|
|
1084
|
+
function mapMediaDevices(devices) {
|
|
1085
|
+
const next = { audio: [], video: [], speaker: [] };
|
|
1086
|
+
for (const device of devices) {
|
|
1087
|
+
const kind = mediaKindToStudio(device.kind);
|
|
1088
|
+
if (!kind || !device.deviceId) continue;
|
|
1089
|
+
next[kind].push({
|
|
1090
|
+
deviceId: device.deviceId,
|
|
1091
|
+
label: device.label.trim() || `Device ${next[kind].length + 1}`
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
return next;
|
|
1095
|
+
}
|
|
1096
|
+
function resolveDeviceSelection(saved, devices) {
|
|
1097
|
+
const effective = {
|
|
1098
|
+
audio: "",
|
|
1099
|
+
video: "",
|
|
1100
|
+
speaker: ""
|
|
1101
|
+
};
|
|
1102
|
+
const missing = {
|
|
1103
|
+
audio: false,
|
|
1104
|
+
video: false,
|
|
1105
|
+
speaker: false
|
|
1106
|
+
};
|
|
1107
|
+
for (const kind of DEVICE_KINDS) {
|
|
1108
|
+
const id = saved[kind];
|
|
1109
|
+
if (!id) continue;
|
|
1110
|
+
if (devices[kind].some((option) => option.deviceId === id)) {
|
|
1111
|
+
effective[kind] = id;
|
|
1112
|
+
continue;
|
|
1113
|
+
}
|
|
1114
|
+
missing[kind] = true;
|
|
1115
|
+
}
|
|
1116
|
+
return { effective, missing };
|
|
1117
|
+
}
|
|
1118
|
+
function displayDeviceId(value, options) {
|
|
1119
|
+
if (options.some((option) => option.deviceId === value)) return value;
|
|
1120
|
+
return options[0]?.deviceId ?? "";
|
|
1121
|
+
}
|
|
1122
|
+
async function applyStudioDeviceSelection(setDevice, saved, listed) {
|
|
1123
|
+
const { effective } = resolveDeviceSelection(saved, listed);
|
|
1124
|
+
await Promise.allSettled(
|
|
1125
|
+
DEVICE_KINDS.filter((kind) => effective[kind]).map(
|
|
1126
|
+
(kind) => setDevice(kind, effective[kind])
|
|
1127
|
+
)
|
|
1128
|
+
);
|
|
1129
|
+
}
|
|
1130
|
+
function mediaKindToStudio(kind) {
|
|
1131
|
+
switch (kind) {
|
|
1132
|
+
case "audioinput":
|
|
1133
|
+
return "audio";
|
|
1134
|
+
case "videoinput":
|
|
1135
|
+
return "video";
|
|
1136
|
+
case "audiooutput":
|
|
1137
|
+
return "speaker";
|
|
1138
|
+
default: {
|
|
1139
|
+
const _exhaustive = kind;
|
|
1140
|
+
return _exhaustive;
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
function defaultStorage() {
|
|
1145
|
+
if (typeof window === "undefined") return null;
|
|
1146
|
+
try {
|
|
1147
|
+
return window.localStorage;
|
|
1148
|
+
} catch {
|
|
1149
|
+
return null;
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1065
1152
|
function AudioWaveform({
|
|
1066
1153
|
track,
|
|
1067
1154
|
reducedMotion,
|
|
@@ -1135,1211 +1222,868 @@ function AudioWaveform({
|
|
|
1135
1222
|
}
|
|
1136
1223
|
);
|
|
1137
1224
|
}
|
|
1138
|
-
function
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
micLabel,
|
|
1142
|
-
cameraOn = true,
|
|
1143
|
-
muted = false
|
|
1225
|
+
function TooltipProvider({
|
|
1226
|
+
delay = 0,
|
|
1227
|
+
...props
|
|
1144
1228
|
}) {
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1229
|
+
return /* @__PURE__ */ jsx(
|
|
1230
|
+
Tooltip$1.Provider,
|
|
1231
|
+
{
|
|
1232
|
+
"data-slot": "tooltip-provider",
|
|
1233
|
+
delay,
|
|
1234
|
+
...props
|
|
1235
|
+
}
|
|
1236
|
+
);
|
|
1237
|
+
}
|
|
1238
|
+
function Tooltip({ ...props }) {
|
|
1239
|
+
return /* @__PURE__ */ jsx(Tooltip$1.Root, { "data-slot": "tooltip", ...props });
|
|
1240
|
+
}
|
|
1241
|
+
function TooltipTrigger({ ...props }) {
|
|
1242
|
+
return /* @__PURE__ */ jsx(Tooltip$1.Trigger, { "data-slot": "tooltip-trigger", ...props });
|
|
1243
|
+
}
|
|
1244
|
+
function TooltipContent({
|
|
1245
|
+
className,
|
|
1246
|
+
side = "top",
|
|
1247
|
+
sideOffset = 4,
|
|
1248
|
+
align = "center",
|
|
1249
|
+
alignOffset = 0,
|
|
1250
|
+
children,
|
|
1251
|
+
...props
|
|
1252
|
+
}) {
|
|
1253
|
+
return /* @__PURE__ */ jsx(Tooltip$1.Portal, { children: /* @__PURE__ */ jsx(
|
|
1254
|
+
Tooltip$1.Positioner,
|
|
1255
|
+
{
|
|
1256
|
+
align,
|
|
1257
|
+
alignOffset,
|
|
1258
|
+
side,
|
|
1259
|
+
sideOffset,
|
|
1260
|
+
className: "isolate z-50",
|
|
1261
|
+
children: /* @__PURE__ */ jsxs(
|
|
1262
|
+
Tooltip$1.Popup,
|
|
1263
|
+
{
|
|
1264
|
+
"data-slot": "tooltip-content",
|
|
1265
|
+
className: cn(
|
|
1266
|
+
"z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
|
1267
|
+
className
|
|
1268
|
+
),
|
|
1269
|
+
...props,
|
|
1270
|
+
children: [
|
|
1271
|
+
children,
|
|
1272
|
+
/* @__PURE__ */ jsx(Tooltip$1.Arrow, { className: "z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:top-1/2! data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5" })
|
|
1273
|
+
]
|
|
1274
|
+
}
|
|
1275
|
+
)
|
|
1276
|
+
}
|
|
1277
|
+
) });
|
|
1278
|
+
}
|
|
1279
|
+
function ControlIcon({
|
|
1280
|
+
label,
|
|
1281
|
+
pressed = false,
|
|
1282
|
+
disabled = false,
|
|
1283
|
+
pending = false,
|
|
1284
|
+
tone = "default",
|
|
1285
|
+
badge,
|
|
1286
|
+
onClick,
|
|
1287
|
+
children
|
|
1288
|
+
}) {
|
|
1289
|
+
return /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
1197
1290
|
/* @__PURE__ */ jsx(
|
|
1198
|
-
|
|
1291
|
+
TooltipTrigger,
|
|
1199
1292
|
{
|
|
1200
|
-
|
|
1201
|
-
|
|
1293
|
+
render: /* @__PURE__ */ jsx(
|
|
1294
|
+
Button,
|
|
1295
|
+
{
|
|
1296
|
+
type: "button",
|
|
1297
|
+
size: "icon-lg",
|
|
1298
|
+
variant: tone === "danger" ? "destructive" : pressed ? "default" : "secondary",
|
|
1299
|
+
className: "overflow-visible",
|
|
1300
|
+
"aria-label": label,
|
|
1301
|
+
"aria-pressed": pressed,
|
|
1302
|
+
disabled,
|
|
1303
|
+
onClick
|
|
1304
|
+
}
|
|
1305
|
+
),
|
|
1306
|
+
children: /* @__PURE__ */ jsxs("span", { className: "relative overflow-visible", children: [
|
|
1307
|
+
pending ? /* @__PURE__ */ jsx(Loader2, { className: "animate-spin motion-reduce:animate-none" }) : children,
|
|
1308
|
+
badge != null && badge > 0 ? /* @__PURE__ */ jsx("span", { className: "bg-primary text-primary-foreground absolute -top-2 -right-2 flex h-4 min-w-4 items-center justify-center rounded-full px-1 text-[0.65rem]", children: badge }) : null
|
|
1309
|
+
] })
|
|
1202
1310
|
}
|
|
1203
1311
|
),
|
|
1204
|
-
/* @__PURE__ */
|
|
1205
|
-
cameraLabel,
|
|
1206
|
-
": ",
|
|
1207
|
-
cameraOk && cameraOn ? "OK" : "\u2014",
|
|
1208
|
-
" \xB7 ",
|
|
1209
|
-
micLabel,
|
|
1210
|
-
":",
|
|
1211
|
-
" ",
|
|
1212
|
-
micOk && !muted ? "OK" : "\u2014"
|
|
1213
|
-
] })
|
|
1312
|
+
/* @__PURE__ */ jsx(TooltipContent, { children: label })
|
|
1214
1313
|
] });
|
|
1215
1314
|
}
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
{
|
|
1219
|
-
variants: {
|
|
1220
|
-
variant: {
|
|
1221
|
-
default: "border-transparent bg-primary text-primary-foreground",
|
|
1222
|
-
secondary: "border-transparent bg-secondary text-secondary-foreground",
|
|
1223
|
-
destructive: "border-transparent bg-destructive text-destructive-foreground",
|
|
1224
|
-
outline: "text-foreground",
|
|
1225
|
-
success: "border-transparent bg-chart-5/15 text-chart-5 dark:bg-chart-5/25",
|
|
1226
|
-
warning: "border-transparent bg-chart-4/15 text-chart-4 dark:bg-chart-4/25"
|
|
1227
|
-
}
|
|
1228
|
-
},
|
|
1229
|
-
defaultVariants: { variant: "default" }
|
|
1230
|
-
}
|
|
1231
|
-
);
|
|
1232
|
-
function Badge({ className, variant, ...props }) {
|
|
1233
|
-
return /* @__PURE__ */ jsx("span", { className: cn(badgeVariants({ variant }), className), ...props });
|
|
1315
|
+
function Dialog({ ...props }) {
|
|
1316
|
+
return /* @__PURE__ */ jsx(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
|
|
1234
1317
|
}
|
|
1235
|
-
function
|
|
1236
|
-
|
|
1237
|
-
labels,
|
|
1238
|
-
cameraOn,
|
|
1239
|
-
muted,
|
|
1240
|
-
screenOn,
|
|
1241
|
-
showMediaControls = true,
|
|
1242
|
-
showScreenControl = false,
|
|
1243
|
-
canShareFreely = false,
|
|
1244
|
-
screenAvailable = true,
|
|
1245
|
-
screenPending = false,
|
|
1246
|
-
shareGrant = "idle",
|
|
1247
|
-
onAir,
|
|
1248
|
-
elapsed,
|
|
1249
|
-
unread = 0,
|
|
1250
|
-
onToggleMute,
|
|
1251
|
-
onToggleCamera,
|
|
1252
|
-
onToggleScreen,
|
|
1253
|
-
onAskToShare,
|
|
1254
|
-
onCancelShareRequest,
|
|
1255
|
-
onBack
|
|
1318
|
+
function DialogPortal({
|
|
1319
|
+
...props
|
|
1256
1320
|
}) {
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1321
|
+
return /* @__PURE__ */ jsx(DialogPrimitive.Portal, { "data-slot": "dialog-portal", ...props });
|
|
1322
|
+
}
|
|
1323
|
+
function DialogOverlay({
|
|
1324
|
+
className,
|
|
1325
|
+
...props
|
|
1326
|
+
}) {
|
|
1327
|
+
return /* @__PURE__ */ jsx(
|
|
1328
|
+
DialogPrimitive.Overlay,
|
|
1264
1329
|
{
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
{
|
|
1272
|
-
className: cn(
|
|
1273
|
-
"bg-muted relative min-h-0 flex-1 overflow-hidden rounded-xl",
|
|
1274
|
-
speaking && "ring-2 ring-primary"
|
|
1275
|
-
),
|
|
1276
|
-
children: [
|
|
1277
|
-
/* @__PURE__ */ jsx(
|
|
1278
|
-
PipSpotlight,
|
|
1279
|
-
{
|
|
1280
|
-
meeting,
|
|
1281
|
-
spotlight,
|
|
1282
|
-
cameraOffLabel: labels.cameraOff
|
|
1283
|
-
}
|
|
1284
|
-
),
|
|
1285
|
-
onAir ? /* @__PURE__ */ jsxs("div", { className: "absolute top-2 left-2 flex items-center gap-2", children: [
|
|
1286
|
-
/* @__PURE__ */ jsx("span", { className: "rounded-full border bg-background/80 px-2 py-0.5 text-xs backdrop-blur-sm", children: labels.live }),
|
|
1287
|
-
elapsed ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground tabular-nums text-xs", children: elapsed }) : null
|
|
1288
|
-
] }) : null,
|
|
1289
|
-
selfView ? /* @__PURE__ */ jsx(
|
|
1290
|
-
"div",
|
|
1291
|
-
{
|
|
1292
|
-
"data-testid": "pip-self-view",
|
|
1293
|
-
className: "absolute right-2 bottom-2 aspect-video w-[30%] overflow-hidden rounded-lg ring-1 ring-white/40",
|
|
1294
|
-
children: /* @__PURE__ */ jsx(PipTrackVideo, { track: meeting.localVideoTrack(), fit: "cover" })
|
|
1295
|
-
}
|
|
1296
|
-
) : null
|
|
1297
|
-
]
|
|
1298
|
-
}
|
|
1299
|
-
),
|
|
1300
|
-
/* @__PURE__ */ jsxs("footer", { className: "flex flex-wrap items-center justify-center gap-2", children: [
|
|
1301
|
-
showMediaControls ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1302
|
-
/* @__PURE__ */ jsx(
|
|
1303
|
-
PipControl,
|
|
1304
|
-
{
|
|
1305
|
-
label: muted ? labels.unmute : labels.mute,
|
|
1306
|
-
off: muted,
|
|
1307
|
-
onClick: onToggleMute,
|
|
1308
|
-
children: muted ? /* @__PURE__ */ jsx(MicOff, {}) : /* @__PURE__ */ jsx(Mic, {})
|
|
1309
|
-
}
|
|
1310
|
-
),
|
|
1311
|
-
/* @__PURE__ */ jsx(
|
|
1312
|
-
PipControl,
|
|
1313
|
-
{
|
|
1314
|
-
label: labels.camera,
|
|
1315
|
-
off: !cameraOn,
|
|
1316
|
-
onClick: onToggleCamera,
|
|
1317
|
-
children: cameraOn ? /* @__PURE__ */ jsx(Video, {}) : /* @__PURE__ */ jsx(VideoOff, {})
|
|
1318
|
-
}
|
|
1319
|
-
)
|
|
1320
|
-
] }) : null,
|
|
1321
|
-
showMediaControls && (showScreenControl || screenOn) ? pipScreenControls({
|
|
1322
|
-
labels,
|
|
1323
|
-
screenOn,
|
|
1324
|
-
canShareFreely,
|
|
1325
|
-
screenAvailable,
|
|
1326
|
-
screenPending,
|
|
1327
|
-
shareGrant,
|
|
1328
|
-
onToggleScreen,
|
|
1329
|
-
onAskToShare,
|
|
1330
|
-
onCancelShareRequest
|
|
1331
|
-
}) : null,
|
|
1332
|
-
/* @__PURE__ */ jsx(PipControl, { label: backLabel, onClick: onBack, children: /* @__PURE__ */ jsxs("span", { className: "relative", children: [
|
|
1333
|
-
/* @__PURE__ */ jsx(Undo2, {}),
|
|
1334
|
-
unread > 0 ? /* @__PURE__ */ jsx(Badge, { className: "absolute -top-2 -right-2 h-4 min-w-4 px-1 text-[0.65rem]", children: unread }) : null
|
|
1335
|
-
] }) })
|
|
1336
|
-
] })
|
|
1337
|
-
]
|
|
1330
|
+
"data-slot": "dialog-overlay",
|
|
1331
|
+
className: cn(
|
|
1332
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
|
1333
|
+
className
|
|
1334
|
+
),
|
|
1335
|
+
...props
|
|
1338
1336
|
}
|
|
1339
1337
|
);
|
|
1340
1338
|
}
|
|
1341
|
-
function
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1339
|
+
function DialogContent({
|
|
1340
|
+
className,
|
|
1341
|
+
children,
|
|
1342
|
+
showCloseButton = true,
|
|
1343
|
+
...props
|
|
1345
1344
|
}) {
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
{
|
|
1351
|
-
src: spotlight.picture,
|
|
1352
|
-
alt: "",
|
|
1353
|
-
className: "size-14 rounded-full object-cover"
|
|
1354
|
-
}
|
|
1355
|
-
) : /* @__PURE__ */ jsx("div", { className: "bg-background flex size-14 items-center justify-center rounded-full text-lg", children: (spotlight?.name ?? "?").slice(0, 1).toUpperCase() }),
|
|
1356
|
-
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs", children: spotlight?.name ?? cameraOffLabel })
|
|
1357
|
-
] });
|
|
1358
|
-
}
|
|
1359
|
-
const track = spotlight.kind === "screen" ? meeting.participantScreenTrack(spotlight.participantId) : meeting.participantVideoTrack(spotlight.participantId);
|
|
1360
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1361
|
-
/* @__PURE__ */ jsx(
|
|
1362
|
-
PipTrackVideo,
|
|
1345
|
+
return /* @__PURE__ */ jsxs(DialogPortal, { children: [
|
|
1346
|
+
/* @__PURE__ */ jsx(DialogOverlay, {}),
|
|
1347
|
+
/* @__PURE__ */ jsxs(
|
|
1348
|
+
DialogPrimitive.Content,
|
|
1363
1349
|
{
|
|
1364
|
-
|
|
1365
|
-
|
|
1350
|
+
"data-slot": "dialog-content",
|
|
1351
|
+
className: cn(
|
|
1352
|
+
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
|
|
1353
|
+
className
|
|
1354
|
+
),
|
|
1355
|
+
...props,
|
|
1356
|
+
children: [
|
|
1357
|
+
children,
|
|
1358
|
+
showCloseButton ? /* @__PURE__ */ jsxs(DialogPrimitive.Close, { className: "ring-offset-background focus:ring-ring absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none", children: [
|
|
1359
|
+
/* @__PURE__ */ jsx(XIcon, { className: "size-4" }),
|
|
1360
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: "Close" })
|
|
1361
|
+
] }) : null
|
|
1362
|
+
]
|
|
1366
1363
|
}
|
|
1367
|
-
)
|
|
1368
|
-
/* @__PURE__ */ jsx("p", { className: "absolute inset-x-0 bottom-0 bg-linear-to-t from-black/70 to-transparent p-2 text-xs font-medium text-white", children: spotlight.name })
|
|
1364
|
+
)
|
|
1369
1365
|
] });
|
|
1370
1366
|
}
|
|
1371
|
-
function
|
|
1372
|
-
track,
|
|
1373
|
-
fit
|
|
1374
|
-
}) {
|
|
1375
|
-
const ref = useRef(null);
|
|
1376
|
-
useEffect(() => {
|
|
1377
|
-
const el = ref.current;
|
|
1378
|
-
if (!el) return;
|
|
1379
|
-
if (!track) {
|
|
1380
|
-
el.srcObject = null;
|
|
1381
|
-
return;
|
|
1382
|
-
}
|
|
1383
|
-
el.srcObject = new MediaStream([track]);
|
|
1384
|
-
void el.play().catch(() => void 0);
|
|
1385
|
-
return () => {
|
|
1386
|
-
el.srcObject = null;
|
|
1387
|
-
};
|
|
1388
|
-
}, [track]);
|
|
1367
|
+
function DialogHeader({ className, ...props }) {
|
|
1389
1368
|
return /* @__PURE__ */ jsx(
|
|
1390
|
-
"
|
|
1369
|
+
"div",
|
|
1391
1370
|
{
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
muted: true,
|
|
1396
|
-
className: cn(
|
|
1397
|
-
"absolute inset-0 size-full",
|
|
1398
|
-
fit === "contain" ? "object-contain" : "object-cover"
|
|
1399
|
-
)
|
|
1371
|
+
"data-slot": "dialog-header",
|
|
1372
|
+
className: cn("flex flex-col gap-2 text-center sm:text-left", className),
|
|
1373
|
+
...props
|
|
1400
1374
|
}
|
|
1401
1375
|
);
|
|
1402
1376
|
}
|
|
1403
|
-
function
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
canShareFreely,
|
|
1407
|
-
screenAvailable,
|
|
1408
|
-
screenPending,
|
|
1409
|
-
shareGrant,
|
|
1410
|
-
onToggleScreen,
|
|
1411
|
-
onAskToShare,
|
|
1412
|
-
onCancelShareRequest
|
|
1413
|
-
}) {
|
|
1414
|
-
if (canShareFreely || screenOn) {
|
|
1415
|
-
return /* @__PURE__ */ jsx(
|
|
1416
|
-
PipControl,
|
|
1417
|
-
{
|
|
1418
|
-
label: screenOn ? labels.stopScreen : labels.screen,
|
|
1419
|
-
off: screenOn,
|
|
1420
|
-
disabled: screenPending || !screenAvailable && !screenOn,
|
|
1421
|
-
onClick: onToggleScreen,
|
|
1422
|
-
children: /* @__PURE__ */ jsx(MonitorUp, {})
|
|
1423
|
-
}
|
|
1424
|
-
);
|
|
1425
|
-
}
|
|
1426
|
-
if (shareGrant === "requested") {
|
|
1427
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1428
|
-
/* @__PURE__ */ jsx(
|
|
1429
|
-
PipControl,
|
|
1430
|
-
{
|
|
1431
|
-
label: labels.shareRequested,
|
|
1432
|
-
off: true,
|
|
1433
|
-
disabled: true,
|
|
1434
|
-
onClick: () => void 0,
|
|
1435
|
-
children: /* @__PURE__ */ jsx(MonitorUp, {})
|
|
1436
|
-
}
|
|
1437
|
-
),
|
|
1438
|
-
/* @__PURE__ */ jsx(
|
|
1439
|
-
PipControl,
|
|
1440
|
-
{
|
|
1441
|
-
label: labels.cancelShareRequest,
|
|
1442
|
-
onClick: () => onCancelShareRequest?.(),
|
|
1443
|
-
children: /* @__PURE__ */ jsx(X, {})
|
|
1444
|
-
}
|
|
1445
|
-
)
|
|
1446
|
-
] });
|
|
1447
|
-
}
|
|
1448
|
-
return /* @__PURE__ */ jsx(PipControl, { label: labels.askToShare, onClick: () => onAskToShare?.(), children: /* @__PURE__ */ jsx(MonitorUp, {}) });
|
|
1449
|
-
}
|
|
1450
|
-
function PipControl({
|
|
1451
|
-
label,
|
|
1452
|
-
onClick,
|
|
1453
|
-
off = false,
|
|
1454
|
-
disabled = false,
|
|
1455
|
-
children
|
|
1377
|
+
function DialogTitle({
|
|
1378
|
+
className,
|
|
1379
|
+
...props
|
|
1456
1380
|
}) {
|
|
1457
1381
|
return /* @__PURE__ */ jsx(
|
|
1458
|
-
|
|
1382
|
+
DialogPrimitive.Title,
|
|
1459
1383
|
{
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
className: "size-10 rounded-full",
|
|
1464
|
-
"aria-label": label,
|
|
1465
|
-
disabled,
|
|
1466
|
-
onClick,
|
|
1467
|
-
children
|
|
1384
|
+
"data-slot": "dialog-title",
|
|
1385
|
+
className: cn("text-lg leading-none font-semibold", className),
|
|
1386
|
+
...props
|
|
1468
1387
|
}
|
|
1469
1388
|
);
|
|
1470
1389
|
}
|
|
1471
|
-
function
|
|
1472
|
-
|
|
1473
|
-
|
|
1390
|
+
function DialogDescription({
|
|
1391
|
+
className,
|
|
1392
|
+
...props
|
|
1474
1393
|
}) {
|
|
1475
|
-
const ref = useRef(null);
|
|
1476
|
-
useEffect(() => {
|
|
1477
|
-
meeting.attachAudio(participantId, ref.current);
|
|
1478
|
-
return () => meeting.attachAudio(participantId, null);
|
|
1479
|
-
}, [meeting, participantId]);
|
|
1480
|
-
return /* @__PURE__ */ jsx("audio", { ref, autoPlay: true, className: "sr-only" });
|
|
1481
|
-
}
|
|
1482
|
-
function BubbleGroup({ className, ...props }) {
|
|
1483
1394
|
return /* @__PURE__ */ jsx(
|
|
1484
|
-
|
|
1395
|
+
DialogPrimitive.Description,
|
|
1485
1396
|
{
|
|
1486
|
-
"data-slot": "
|
|
1487
|
-
className: cn("
|
|
1397
|
+
"data-slot": "dialog-description",
|
|
1398
|
+
className: cn("text-muted-foreground text-sm", className),
|
|
1488
1399
|
...props
|
|
1489
1400
|
}
|
|
1490
1401
|
);
|
|
1491
1402
|
}
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
destructive: "*:data-[slot=bubble-content]:bg-destructive/10 *:data-[slot=bubble-content]:text-destructive dark:*:data-[slot=bubble-content]:bg-destructive/20 [&>[data-slot=bubble-content]:is(button,a):hover]:bg-destructive/20 dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-destructive/30"
|
|
1504
|
-
}
|
|
1505
|
-
},
|
|
1506
|
-
defaultVariants: {
|
|
1507
|
-
variant: "default"
|
|
1403
|
+
function Input({ className, type, ...props }) {
|
|
1404
|
+
return /* @__PURE__ */ jsx(
|
|
1405
|
+
Input$1,
|
|
1406
|
+
{
|
|
1407
|
+
type,
|
|
1408
|
+
"data-slot": "input",
|
|
1409
|
+
className: cn(
|
|
1410
|
+
"h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
|
|
1411
|
+
className
|
|
1412
|
+
),
|
|
1413
|
+
...props
|
|
1508
1414
|
}
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
function
|
|
1512
|
-
variant = "default",
|
|
1513
|
-
align = "start",
|
|
1415
|
+
);
|
|
1416
|
+
}
|
|
1417
|
+
function Label({
|
|
1514
1418
|
className,
|
|
1515
1419
|
...props
|
|
1516
1420
|
}) {
|
|
1517
1421
|
return /* @__PURE__ */ jsx(
|
|
1518
|
-
|
|
1422
|
+
LabelPrimitive.Root,
|
|
1519
1423
|
{
|
|
1520
|
-
"data-slot": "
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1424
|
+
"data-slot": "label",
|
|
1425
|
+
className: cn(
|
|
1426
|
+
"flex items-center gap-2 text-sm leading-none font-medium select-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
|
1427
|
+
className
|
|
1428
|
+
),
|
|
1524
1429
|
...props
|
|
1525
1430
|
}
|
|
1526
1431
|
);
|
|
1527
1432
|
}
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
}
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
className
|
|
1540
|
-
)
|
|
1541
|
-
},
|
|
1542
|
-
props
|
|
1543
|
-
),
|
|
1544
|
-
render,
|
|
1545
|
-
state: {
|
|
1546
|
-
slot: "bubble-content"
|
|
1433
|
+
var EMPTY_DEVICES = {
|
|
1434
|
+
audio: [],
|
|
1435
|
+
video: [],
|
|
1436
|
+
speaker: []
|
|
1437
|
+
};
|
|
1438
|
+
function useMediaDevices(refreshKey = 0, enabled = true) {
|
|
1439
|
+
const [devices, setDevices] = useState(EMPTY_DEVICES);
|
|
1440
|
+
useEffect(() => {
|
|
1441
|
+
if (!enabled) return;
|
|
1442
|
+
if (typeof navigator === "undefined" || !navigator.mediaDevices) {
|
|
1443
|
+
return;
|
|
1547
1444
|
}
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
bottom: "bottom-0 translate-y-3/4"
|
|
1557
|
-
},
|
|
1558
|
-
align: {
|
|
1559
|
-
start: "left-3",
|
|
1560
|
-
end: "right-3"
|
|
1445
|
+
let cancelled = false;
|
|
1446
|
+
let timer;
|
|
1447
|
+
const load = async () => {
|
|
1448
|
+
try {
|
|
1449
|
+
const list = await navigator.mediaDevices.enumerateDevices();
|
|
1450
|
+
if (!cancelled) setDevices(mapMediaDevices(list));
|
|
1451
|
+
} catch {
|
|
1452
|
+
if (!cancelled) setDevices(EMPTY_DEVICES);
|
|
1561
1453
|
}
|
|
1562
|
-
}
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1454
|
+
};
|
|
1455
|
+
const onChange = () => {
|
|
1456
|
+
window.clearTimeout(timer);
|
|
1457
|
+
timer = window.setTimeout(() => {
|
|
1458
|
+
void load();
|
|
1459
|
+
}, STUDIO_DEVICE_CHANGE_DEBOUNCE_MS);
|
|
1460
|
+
};
|
|
1461
|
+
void load();
|
|
1462
|
+
navigator.mediaDevices.addEventListener("devicechange", onChange);
|
|
1463
|
+
return () => {
|
|
1464
|
+
cancelled = true;
|
|
1465
|
+
window.clearTimeout(timer);
|
|
1466
|
+
navigator.mediaDevices.removeEventListener("devicechange", onChange);
|
|
1467
|
+
};
|
|
1468
|
+
}, [enabled, refreshKey]);
|
|
1469
|
+
return devices;
|
|
1470
|
+
}
|
|
1569
1471
|
|
|
1570
|
-
// src/studio/lib/
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1472
|
+
// src/studio/lib/speaker-test.ts
|
|
1473
|
+
var TEST_MS = 400;
|
|
1474
|
+
var TEST_HZ = 440;
|
|
1475
|
+
async function playSpeakerTest(deviceId) {
|
|
1476
|
+
const AudioCtx = window.AudioContext || window.webkitAudioContext;
|
|
1477
|
+
if (!AudioCtx) {
|
|
1478
|
+
throw new Error("AudioContext unavailable");
|
|
1479
|
+
}
|
|
1480
|
+
const ctx = new AudioCtx();
|
|
1481
|
+
void ctx.resume();
|
|
1482
|
+
const oscillator = ctx.createOscillator();
|
|
1483
|
+
const gain = ctx.createGain();
|
|
1484
|
+
oscillator.type = "sine";
|
|
1485
|
+
oscillator.frequency.value = TEST_HZ;
|
|
1486
|
+
gain.gain.value = 0.18;
|
|
1487
|
+
oscillator.connect(gain);
|
|
1488
|
+
let audio = null;
|
|
1489
|
+
try {
|
|
1490
|
+
const probe = document.createElement("audio");
|
|
1491
|
+
const canRoute = Boolean(deviceId) && typeof probe.setSinkId === "function";
|
|
1492
|
+
if (canRoute && deviceId) {
|
|
1493
|
+
const destination = ctx.createMediaStreamDestination();
|
|
1494
|
+
gain.connect(destination);
|
|
1495
|
+
audio = probe;
|
|
1496
|
+
audio.autoplay = true;
|
|
1497
|
+
audio.playsInline = true;
|
|
1498
|
+
audio.srcObject = destination.stream;
|
|
1499
|
+
try {
|
|
1500
|
+
await audio.setSinkId?.(deviceId);
|
|
1501
|
+
await audio.play();
|
|
1502
|
+
} catch {
|
|
1503
|
+
gain.disconnect();
|
|
1504
|
+
gain.connect(ctx.destination);
|
|
1505
|
+
audio.srcObject = null;
|
|
1506
|
+
audio.remove();
|
|
1507
|
+
audio = null;
|
|
1508
|
+
}
|
|
1509
|
+
} else {
|
|
1510
|
+
gain.connect(ctx.destination);
|
|
1579
1511
|
}
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
mine,
|
|
1584
|
-
rows: [row]
|
|
1512
|
+
oscillator.start();
|
|
1513
|
+
await new Promise((resolve) => {
|
|
1514
|
+
window.setTimeout(resolve, TEST_MS);
|
|
1585
1515
|
});
|
|
1516
|
+
oscillator.stop();
|
|
1517
|
+
} finally {
|
|
1518
|
+
if (audio) {
|
|
1519
|
+
audio.srcObject = null;
|
|
1520
|
+
audio.remove();
|
|
1521
|
+
}
|
|
1522
|
+
await ctx.close();
|
|
1586
1523
|
}
|
|
1587
|
-
return groups;
|
|
1588
1524
|
}
|
|
1589
|
-
function
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1525
|
+
function StudioDeviceSelectGroup({
|
|
1526
|
+
devices,
|
|
1527
|
+
selected,
|
|
1528
|
+
labels,
|
|
1529
|
+
onChange,
|
|
1530
|
+
speakerAction
|
|
1531
|
+
}) {
|
|
1532
|
+
return /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-[8.5rem_minmax(0,1fr)] items-center gap-x-3 gap-y-3", children: [
|
|
1533
|
+
/* @__PURE__ */ jsx(
|
|
1534
|
+
DeviceRow,
|
|
1535
|
+
{
|
|
1536
|
+
icon: /* @__PURE__ */ jsx(Video, {}),
|
|
1537
|
+
label: labels.cameraDevice,
|
|
1538
|
+
options: devices.video,
|
|
1539
|
+
value: selected.video,
|
|
1540
|
+
onChange: (id) => onChange("video", id)
|
|
1541
|
+
}
|
|
1542
|
+
),
|
|
1543
|
+
/* @__PURE__ */ jsx(
|
|
1544
|
+
DeviceRow,
|
|
1545
|
+
{
|
|
1546
|
+
icon: /* @__PURE__ */ jsx(Mic, {}),
|
|
1547
|
+
label: labels.micDevice,
|
|
1548
|
+
options: devices.audio,
|
|
1549
|
+
value: selected.audio,
|
|
1550
|
+
onChange: (id) => onChange("audio", id)
|
|
1551
|
+
}
|
|
1552
|
+
),
|
|
1553
|
+
/* @__PURE__ */ jsx(
|
|
1554
|
+
DeviceRow,
|
|
1555
|
+
{
|
|
1556
|
+
icon: /* @__PURE__ */ jsx(Volume2, {}),
|
|
1557
|
+
label: labels.speakerDevice,
|
|
1558
|
+
options: devices.speaker,
|
|
1559
|
+
value: selected.speaker,
|
|
1560
|
+
onChange: (id) => onChange("speaker", id),
|
|
1561
|
+
action: speakerAction
|
|
1562
|
+
}
|
|
1563
|
+
)
|
|
1564
|
+
] });
|
|
1595
1565
|
}
|
|
1596
|
-
function
|
|
1566
|
+
function DeviceRow({
|
|
1567
|
+
icon,
|
|
1568
|
+
label,
|
|
1569
|
+
options,
|
|
1570
|
+
value,
|
|
1571
|
+
onChange,
|
|
1572
|
+
action
|
|
1573
|
+
}) {
|
|
1574
|
+
const display = displayDeviceId(value, options);
|
|
1575
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1576
|
+
/* @__PURE__ */ jsxs("span", { className: "text-muted-foreground flex min-w-0 items-center gap-2 text-sm", children: [
|
|
1577
|
+
/* @__PURE__ */ jsx("span", { className: "[&_svg]:size-4", "aria-hidden": true, children: icon }),
|
|
1578
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: label })
|
|
1579
|
+
] }),
|
|
1580
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
|
|
1581
|
+
options.length > 0 ? /* @__PURE__ */ jsx(
|
|
1582
|
+
"select",
|
|
1583
|
+
{
|
|
1584
|
+
"aria-label": label,
|
|
1585
|
+
className: cn(
|
|
1586
|
+
"border-input bg-background h-8 min-w-0 flex-1 rounded-md border px-2 text-base md:text-sm"
|
|
1587
|
+
),
|
|
1588
|
+
value: display,
|
|
1589
|
+
onChange: (event) => onChange(event.target.value),
|
|
1590
|
+
children: options.map((option) => /* @__PURE__ */ jsx("option", { value: option.deviceId, children: option.label }, option.deviceId))
|
|
1591
|
+
}
|
|
1592
|
+
) : null,
|
|
1593
|
+
action
|
|
1594
|
+
] })
|
|
1595
|
+
] });
|
|
1596
|
+
}
|
|
1597
|
+
function StudioSettingsDialog({
|
|
1598
|
+
open,
|
|
1599
|
+
onOpenChange,
|
|
1597
1600
|
meeting,
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
labels
|
|
1601
|
+
labels,
|
|
1602
|
+
reducedMotion,
|
|
1603
|
+
cameraOn
|
|
1602
1604
|
}) {
|
|
1603
|
-
const
|
|
1604
|
-
const [
|
|
1605
|
-
|
|
1606
|
-
const listRef = useRef(null);
|
|
1607
|
-
useEffect(
|
|
1608
|
-
() => meeting.subscribeChat(() => setTick((n) => n + 1)),
|
|
1609
|
-
[meeting]
|
|
1610
|
-
);
|
|
1611
|
-
const messages = useMemo(
|
|
1612
|
-
() => meeting.chatMessages().filter((row) => !row.targetUserIds?.length),
|
|
1613
|
-
[meeting, tick]
|
|
1614
|
-
);
|
|
1615
|
-
const pinned = useMemo(() => meeting.pinnedChatMessages(), [meeting, tick]);
|
|
1616
|
-
const limit = meeting.chatMaxTextLimit();
|
|
1617
|
-
const pictures = useMemo(() => {
|
|
1618
|
-
const map = /* @__PURE__ */ new Map();
|
|
1619
|
-
for (const row of participants) {
|
|
1620
|
-
if (row.userId) map.set(row.userId, row.picture);
|
|
1621
|
-
}
|
|
1622
|
-
return map;
|
|
1623
|
-
}, [participants]);
|
|
1624
|
-
const groups = useMemo(
|
|
1625
|
-
() => groupChatMessages(messages, selfId),
|
|
1626
|
-
[messages, selfId]
|
|
1605
|
+
const devices = useMediaDevices(open ? 1 : 0, open);
|
|
1606
|
+
const [saved, setSaved] = useState(
|
|
1607
|
+
() => readStudioDeviceSelection()
|
|
1627
1608
|
);
|
|
1609
|
+
const [audioTrack, setAudioTrack] = useState(null);
|
|
1610
|
+
const [videoTrack, setVideoTrack] = useState(null);
|
|
1611
|
+
const [testingSpeaker, setTestingSpeaker] = useState(false);
|
|
1612
|
+
const [displayName, setDisplayName] = useState("");
|
|
1613
|
+
const [savedDisplayName, setSavedDisplayName] = useState("");
|
|
1614
|
+
const [savingDisplayName, setSavingDisplayName] = useState(false);
|
|
1615
|
+
const { effective } = resolveDeviceSelection(saved, devices);
|
|
1628
1616
|
useEffect(() => {
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
const
|
|
1635
|
-
|
|
1636
|
-
|
|
1617
|
+
if (!open) return;
|
|
1618
|
+
setSaved(readStudioDeviceSelection());
|
|
1619
|
+
}, [open]);
|
|
1620
|
+
useEffect(() => {
|
|
1621
|
+
if (!open) return;
|
|
1622
|
+
const selfName = meeting.participants().find((row) => row.isSelf)?.name?.trim() ?? "";
|
|
1623
|
+
setDisplayName(selfName);
|
|
1624
|
+
setSavedDisplayName(selfName);
|
|
1625
|
+
}, [meeting, open]);
|
|
1626
|
+
useEffect(() => {
|
|
1627
|
+
if (!open) return;
|
|
1628
|
+
const sync = () => {
|
|
1629
|
+
setAudioTrack(meeting.localAudioTrack());
|
|
1630
|
+
setVideoTrack(meeting.localVideoTrack());
|
|
1631
|
+
};
|
|
1632
|
+
sync();
|
|
1633
|
+
return meeting.subscribeMedia(sync);
|
|
1634
|
+
}, [meeting, open]);
|
|
1635
|
+
const trimmedDisplayName = displayName.trim();
|
|
1636
|
+
const displayNameUnchanged = trimmedDisplayName === savedDisplayName;
|
|
1637
|
+
const displayNameSaveDisabled = !trimmedDisplayName || displayNameUnchanged || savingDisplayName;
|
|
1638
|
+
async function onChange(kind, deviceId) {
|
|
1639
|
+
persistStudioDeviceSelection(kind, deviceId);
|
|
1640
|
+
setSaved((prev) => ({ ...prev, [kind]: deviceId }));
|
|
1637
1641
|
try {
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
setEditingId(null);
|
|
1641
|
-
return;
|
|
1642
|
-
}
|
|
1643
|
-
await meeting.sendChatMessage(text);
|
|
1644
|
-
} catch (error) {
|
|
1645
|
-
const message = error instanceof Error ? error.message.toLowerCase() : "";
|
|
1646
|
-
toast.error(
|
|
1647
|
-
message.includes("rate") ? labels.chatRateLimited : labels.chatError
|
|
1648
|
-
);
|
|
1642
|
+
await meeting.setDevice(kind, deviceId);
|
|
1643
|
+
} catch {
|
|
1649
1644
|
}
|
|
1650
1645
|
}
|
|
1651
|
-
function
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
setDraft(row.text);
|
|
1665
|
-
},
|
|
1666
|
-
children: labels.chatEdit
|
|
1667
|
-
}
|
|
1668
|
-
) : null,
|
|
1669
|
-
isModerator2 || mine ? /* @__PURE__ */ jsx(
|
|
1670
|
-
"button",
|
|
1671
|
-
{
|
|
1672
|
-
type: "button",
|
|
1673
|
-
onClick: () => void meeting.deleteChatMessage(row.id).catch(() => toast.error(labels.chatError)),
|
|
1674
|
-
children: labels.chatDelete
|
|
1675
|
-
}
|
|
1676
|
-
) : null,
|
|
1677
|
-
isModerator2 ? /* @__PURE__ */ jsx(
|
|
1678
|
-
"button",
|
|
1679
|
-
{
|
|
1680
|
-
type: "button",
|
|
1681
|
-
onClick: () => void meeting.pinChatMessage(row.id, row.pinned !== true).catch(() => toast.error(labels.chatError)),
|
|
1682
|
-
children: row.pinned ? labels.chatUnpin : labels.chatPin
|
|
1683
|
-
}
|
|
1684
|
-
) : null
|
|
1685
|
-
]
|
|
1686
|
-
}
|
|
1687
|
-
);
|
|
1646
|
+
async function onSaveDisplayName() {
|
|
1647
|
+
if (displayNameSaveDisabled) return;
|
|
1648
|
+
setSavingDisplayName(true);
|
|
1649
|
+
try {
|
|
1650
|
+
await meeting.setDisplayName(trimmedDisplayName);
|
|
1651
|
+
setSavedDisplayName(trimmedDisplayName);
|
|
1652
|
+
setDisplayName(trimmedDisplayName);
|
|
1653
|
+
toast.success(labels.displayNameSaved);
|
|
1654
|
+
} catch {
|
|
1655
|
+
toast.error(labels.displayNameError);
|
|
1656
|
+
} finally {
|
|
1657
|
+
setSavingDisplayName(false);
|
|
1658
|
+
}
|
|
1688
1659
|
}
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
onClick: () => {
|
|
1710
|
-
const oldest = messages[0]?.time;
|
|
1711
|
-
void meeting.fetchChatHistory({
|
|
1712
|
-
timestamp: oldest
|
|
1713
|
-
});
|
|
1714
|
-
},
|
|
1715
|
-
children: labels.chatLoadOlder
|
|
1716
|
-
}
|
|
1717
|
-
),
|
|
1718
|
-
groups.map((group) => {
|
|
1719
|
-
const bubbles = group.rows.map((row) => /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-col gap-1", children: [
|
|
1720
|
-
/* @__PURE__ */ jsx(
|
|
1721
|
-
Bubble,
|
|
1722
|
-
{
|
|
1723
|
-
align: group.mine ? "end" : "start",
|
|
1724
|
-
variant: group.mine ? "default" : "secondary",
|
|
1725
|
-
children: /* @__PURE__ */ jsx(BubbleContent, { children: row.text })
|
|
1726
|
-
}
|
|
1727
|
-
),
|
|
1728
|
-
messageActions(row, group.mine)
|
|
1729
|
-
] }, row.id));
|
|
1730
|
-
if (group.mine) {
|
|
1731
|
-
return /* @__PURE__ */ jsx(BubbleGroup, { className: "items-end", children: bubbles }, group.rows[0].id);
|
|
1732
|
-
}
|
|
1733
|
-
const picture = pictures.get(group.userId) ?? null;
|
|
1734
|
-
return /* @__PURE__ */ jsxs(
|
|
1735
|
-
"div",
|
|
1736
|
-
{
|
|
1737
|
-
className: "flex max-w-full gap-2 self-start",
|
|
1738
|
-
children: [
|
|
1739
|
-
picture ? /* @__PURE__ */ jsx(
|
|
1740
|
-
"img",
|
|
1741
|
-
{
|
|
1742
|
-
src: picture,
|
|
1743
|
-
alt: "",
|
|
1744
|
-
className: "mt-0.5 size-7 shrink-0 rounded-full object-cover"
|
|
1745
|
-
}
|
|
1746
|
-
) : /* @__PURE__ */ jsx("span", { className: "bg-muted text-muted-foreground mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-full text-xs", children: senderInitials(group.name) }),
|
|
1747
|
-
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
1748
|
-
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground mb-1 text-xs", children: group.name }),
|
|
1749
|
-
/* @__PURE__ */ jsx(BubbleGroup, { children: bubbles })
|
|
1750
|
-
] })
|
|
1751
|
-
]
|
|
1752
|
-
},
|
|
1753
|
-
group.rows[0].id
|
|
1754
|
-
);
|
|
1755
|
-
})
|
|
1756
|
-
]
|
|
1757
|
-
}
|
|
1758
|
-
),
|
|
1759
|
-
/* @__PURE__ */ jsxs(
|
|
1760
|
-
"form",
|
|
1761
|
-
{
|
|
1762
|
-
className: "flex flex-col gap-2 border-t p-3",
|
|
1763
|
-
onSubmit: (event) => {
|
|
1764
|
-
event.preventDefault();
|
|
1765
|
-
void onSend();
|
|
1766
|
-
},
|
|
1767
|
-
children: [
|
|
1768
|
-
/* @__PURE__ */ jsx(
|
|
1769
|
-
"input",
|
|
1770
|
-
{
|
|
1771
|
-
className: "border-input bg-background min-w-0 flex-1 rounded-md border px-2 py-1 text-sm",
|
|
1772
|
-
value: draft,
|
|
1773
|
-
maxLength: limit,
|
|
1774
|
-
onChange: (event) => setDraft(event.target.value),
|
|
1775
|
-
placeholder: labels.chatPlaceholder,
|
|
1776
|
-
"aria-label": labels.chatPlaceholder
|
|
1777
|
-
}
|
|
1778
|
-
),
|
|
1779
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
|
|
1780
|
-
/* @__PURE__ */ jsxs("p", { className: "text-muted-foreground text-xs", children: [
|
|
1781
|
-
draft.length,
|
|
1782
|
-
"/",
|
|
1783
|
-
limit
|
|
1784
|
-
] }),
|
|
1785
|
-
/* @__PURE__ */ jsx(Button, { type: "submit", size: "sm", children: editingId ? labels.chatSave : labels.chatSend })
|
|
1786
|
-
] })
|
|
1787
|
-
]
|
|
1788
|
-
}
|
|
1789
|
-
)
|
|
1790
|
-
] });
|
|
1791
|
-
}
|
|
1792
|
-
function StudioWaitingRoom({
|
|
1793
|
-
waitlisted,
|
|
1794
|
-
labels,
|
|
1795
|
-
onAdmitAs,
|
|
1796
|
-
onAdmitAll,
|
|
1797
|
-
onDeny
|
|
1798
|
-
}) {
|
|
1799
|
-
return /* @__PURE__ */ jsxs("section", { "aria-label": labels.waiting, className: "space-y-3", children: [
|
|
1800
|
-
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: labels.waiting }),
|
|
1801
|
-
waitlisted.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: labels.noWaiting }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1802
|
-
/* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
|
|
1660
|
+
async function onTestSpeaker() {
|
|
1661
|
+
if (testingSpeaker) return;
|
|
1662
|
+
const speakerId = effective.speaker || devices.speaker[0]?.deviceId;
|
|
1663
|
+
setTestingSpeaker(true);
|
|
1664
|
+
try {
|
|
1665
|
+
await playSpeakerTest(speakerId || void 0);
|
|
1666
|
+
} catch {
|
|
1667
|
+
toast.error(labels.testSpeakerError);
|
|
1668
|
+
} finally {
|
|
1669
|
+
setTestingSpeaker(false);
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DialogContent, { className: "sm:max-w-md", children: [
|
|
1673
|
+
/* @__PURE__ */ jsxs(DialogHeader, { children: [
|
|
1674
|
+
/* @__PURE__ */ jsx(DialogTitle, { children: labels.settingsTitle }),
|
|
1675
|
+
/* @__PURE__ */ jsx(DialogDescription, { children: labels.settingsDescription })
|
|
1676
|
+
] }),
|
|
1677
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1678
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "studio-display-name", children: labels.guestName }),
|
|
1679
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
1803
1680
|
/* @__PURE__ */ jsx(
|
|
1804
|
-
|
|
1681
|
+
Input,
|
|
1805
1682
|
{
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
children: labels.admitAllPanelists
|
|
1683
|
+
id: "studio-display-name",
|
|
1684
|
+
value: displayName,
|
|
1685
|
+
maxLength: STUDIO_DISPLAY_NAME_MAX,
|
|
1686
|
+
onChange: (event) => setDisplayName(event.target.value)
|
|
1811
1687
|
}
|
|
1812
1688
|
),
|
|
1813
1689
|
/* @__PURE__ */ jsx(
|
|
1814
1690
|
Button,
|
|
1815
1691
|
{
|
|
1816
1692
|
type: "button",
|
|
1817
|
-
size: "xs",
|
|
1818
1693
|
variant: "outline",
|
|
1819
|
-
|
|
1820
|
-
onClick: () =>
|
|
1821
|
-
children: labels.
|
|
1694
|
+
disabled: displayNameSaveDisabled,
|
|
1695
|
+
onClick: () => void onSaveDisplayName(),
|
|
1696
|
+
children: labels.displayNameSave
|
|
1822
1697
|
}
|
|
1823
1698
|
)
|
|
1824
|
-
] })
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
Button,
|
|
1840
|
-
{
|
|
1841
|
-
type: "button",
|
|
1842
|
-
size: "xs",
|
|
1843
|
-
variant: "outline",
|
|
1844
|
-
className: "w-full",
|
|
1845
|
-
onClick: () => onAdmitAs(guest.id, "audience"),
|
|
1846
|
-
children: labels.admitAsAudience
|
|
1847
|
-
}
|
|
1848
|
-
),
|
|
1849
|
-
/* @__PURE__ */ jsx(
|
|
1850
|
-
Button,
|
|
1851
|
-
{
|
|
1852
|
-
type: "button",
|
|
1853
|
-
size: "xs",
|
|
1854
|
-
variant: "ghost",
|
|
1855
|
-
className: "w-full",
|
|
1856
|
-
onClick: () => onDeny(guest.id),
|
|
1857
|
-
children: labels.deny
|
|
1858
|
-
}
|
|
1859
|
-
)
|
|
1860
|
-
] })
|
|
1861
|
-
] }, guest.id)) })
|
|
1862
|
-
] })
|
|
1863
|
-
] });
|
|
1864
|
-
}
|
|
1865
|
-
function StudioSidePanel({
|
|
1866
|
-
tab,
|
|
1867
|
-
onTabChange,
|
|
1868
|
-
onClose,
|
|
1869
|
-
meeting,
|
|
1870
|
-
selfId,
|
|
1871
|
-
participants,
|
|
1872
|
-
waitlisted,
|
|
1873
|
-
stageRequests,
|
|
1874
|
-
isModerator: isModerator2,
|
|
1875
|
-
chatBadge,
|
|
1876
|
-
peopleBadge,
|
|
1877
|
-
labels,
|
|
1878
|
-
onAdmitAs,
|
|
1879
|
-
onAdmitAll,
|
|
1880
|
-
onDeny,
|
|
1881
|
-
onGrantStage,
|
|
1882
|
-
onDenyStage,
|
|
1883
|
-
onTakeOffAir,
|
|
1884
|
-
guestUrl,
|
|
1885
|
-
onShareGuestLink
|
|
1886
|
-
}) {
|
|
1887
|
-
const panelists = participants.filter((row) => isOnStage(row.stageStatus));
|
|
1888
|
-
const audience = participants.filter((row) => !isOnStage(row.stageStatus));
|
|
1889
|
-
return /* @__PURE__ */ jsxs("aside", { className: "bg-background flex w-full flex-col border-t md:w-80 md:border-l md:border-t-0", children: [
|
|
1890
|
-
/* @__PURE__ */ jsxs("div", { className: "flex border-b", children: [
|
|
1891
|
-
/* @__PURE__ */ jsxs(
|
|
1892
|
-
Button,
|
|
1699
|
+
] })
|
|
1700
|
+
] }),
|
|
1701
|
+
/* @__PURE__ */ jsx(
|
|
1702
|
+
SettingsCameraPreview,
|
|
1703
|
+
{
|
|
1704
|
+
track: videoTrack,
|
|
1705
|
+
open,
|
|
1706
|
+
cameraOn,
|
|
1707
|
+
cameraOffLabel: labels.cameraOff
|
|
1708
|
+
}
|
|
1709
|
+
),
|
|
1710
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
1711
|
+
/* @__PURE__ */ jsx("p", { className: "sr-only", children: labels.micLevel }),
|
|
1712
|
+
/* @__PURE__ */ jsx(
|
|
1713
|
+
AudioWaveform,
|
|
1893
1714
|
{
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
className: "relative flex-1 rounded-none",
|
|
1898
|
-
onClick: () => onTabChange("chat"),
|
|
1899
|
-
children: [
|
|
1900
|
-
labels.chat,
|
|
1901
|
-
chatBadge > 0 ? /* @__PURE__ */ jsx("span", { className: "bg-primary text-primary-foreground ml-2 inline-flex h-4 min-w-4 items-center justify-center rounded-full px-1 text-[0.65rem]", children: chatBadge }) : null
|
|
1902
|
-
]
|
|
1903
|
-
}
|
|
1904
|
-
),
|
|
1905
|
-
isModerator2 ? /* @__PURE__ */ jsxs(
|
|
1906
|
-
Button,
|
|
1907
|
-
{
|
|
1908
|
-
type: "button",
|
|
1909
|
-
size: "sm",
|
|
1910
|
-
variant: tab === "people" ? "secondary" : "ghost",
|
|
1911
|
-
className: "relative flex-1 rounded-none",
|
|
1912
|
-
onClick: () => onTabChange("people"),
|
|
1913
|
-
children: [
|
|
1914
|
-
labels.people,
|
|
1915
|
-
peopleBadge > 0 ? /* @__PURE__ */ jsx("span", { className: "bg-primary text-primary-foreground ml-2 inline-flex h-4 min-w-4 items-center justify-center rounded-full px-1 text-[0.65rem]", children: peopleBadge }) : null
|
|
1916
|
-
]
|
|
1917
|
-
}
|
|
1918
|
-
) : null,
|
|
1919
|
-
/* @__PURE__ */ jsx(
|
|
1920
|
-
Button,
|
|
1921
|
-
{
|
|
1922
|
-
type: "button",
|
|
1923
|
-
size: "icon-xs",
|
|
1924
|
-
variant: "ghost",
|
|
1925
|
-
className: "m-1 shrink-0",
|
|
1926
|
-
"aria-label": labels.closePanel,
|
|
1927
|
-
onClick: onClose,
|
|
1928
|
-
children: /* @__PURE__ */ jsx(X, {})
|
|
1715
|
+
track: audioTrack,
|
|
1716
|
+
reducedMotion,
|
|
1717
|
+
className: "w-full"
|
|
1929
1718
|
}
|
|
1930
1719
|
)
|
|
1931
1720
|
] }),
|
|
1932
|
-
|
|
1933
|
-
|
|
1721
|
+
/* @__PURE__ */ jsx(
|
|
1722
|
+
StudioDeviceSelectGroup,
|
|
1934
1723
|
{
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
) : /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 flex-col gap-4 overflow-auto p-3", children: [
|
|
1942
|
-
guestUrl ? /* @__PURE__ */ jsxs(
|
|
1943
|
-
Button,
|
|
1944
|
-
{
|
|
1945
|
-
type: "button",
|
|
1946
|
-
variant: "outline",
|
|
1947
|
-
className: "w-full",
|
|
1948
|
-
onClick: onShareGuestLink,
|
|
1949
|
-
children: [
|
|
1950
|
-
/* @__PURE__ */ jsx(Link2, {}),
|
|
1951
|
-
labels.shareLink
|
|
1952
|
-
]
|
|
1953
|
-
}
|
|
1954
|
-
) : null,
|
|
1955
|
-
/* @__PURE__ */ jsx(
|
|
1956
|
-
StudioWaitingRoom,
|
|
1957
|
-
{
|
|
1958
|
-
waitlisted,
|
|
1959
|
-
labels,
|
|
1960
|
-
onAdmitAs,
|
|
1961
|
-
onAdmitAll,
|
|
1962
|
-
onDeny
|
|
1963
|
-
}
|
|
1964
|
-
),
|
|
1965
|
-
stageRequests.length > 0 ? /* @__PURE__ */ jsxs("section", { "aria-label": labels.stageRequests, className: "space-y-2", children: [
|
|
1966
|
-
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: labels.stageRequests }),
|
|
1967
|
-
/* @__PURE__ */ jsx("ul", { className: "space-y-2", children: stageRequests.map((request) => /* @__PURE__ */ jsxs(
|
|
1968
|
-
"li",
|
|
1724
|
+
devices,
|
|
1725
|
+
selected: effective,
|
|
1726
|
+
labels,
|
|
1727
|
+
onChange: (kind, id) => void onChange(kind, id),
|
|
1728
|
+
speakerAction: /* @__PURE__ */ jsx(
|
|
1729
|
+
Button,
|
|
1969
1730
|
{
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
children: labels.bringOnAir
|
|
1982
|
-
}
|
|
1983
|
-
),
|
|
1984
|
-
/* @__PURE__ */ jsx(
|
|
1985
|
-
Button,
|
|
1986
|
-
{
|
|
1987
|
-
type: "button",
|
|
1988
|
-
size: "xs",
|
|
1989
|
-
variant: "outline",
|
|
1990
|
-
className: "w-full",
|
|
1991
|
-
onClick: () => onDenyStage(request.userId),
|
|
1992
|
-
children: labels.deny
|
|
1993
|
-
}
|
|
1994
|
-
)
|
|
1995
|
-
] })
|
|
1996
|
-
]
|
|
1997
|
-
},
|
|
1998
|
-
request.userId
|
|
1999
|
-
)) })
|
|
2000
|
-
] }) : null,
|
|
2001
|
-
/* @__PURE__ */ jsxs("section", { "aria-label": labels.panelists, className: "space-y-2", children: [
|
|
2002
|
-
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: labels.panelists }),
|
|
2003
|
-
panelists.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: labels.noPanelists }) : /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: panelists.map((row) => {
|
|
2004
|
-
const userId = row.userId;
|
|
2005
|
-
return /* @__PURE__ */ jsxs(
|
|
2006
|
-
"li",
|
|
2007
|
-
{
|
|
2008
|
-
className: "space-y-2 rounded-md border p-2",
|
|
2009
|
-
children: [
|
|
2010
|
-
/* @__PURE__ */ jsx("span", { className: "block truncate text-sm", children: row.name }),
|
|
2011
|
-
!row.isSelf && userId ? /* @__PURE__ */ jsx(
|
|
2012
|
-
Button,
|
|
2013
|
-
{
|
|
2014
|
-
type: "button",
|
|
2015
|
-
size: "xs",
|
|
2016
|
-
variant: "outline",
|
|
2017
|
-
className: "w-full",
|
|
2018
|
-
onClick: () => onTakeOffAir(userId),
|
|
2019
|
-
children: labels.offAir
|
|
2020
|
-
}
|
|
2021
|
-
) : null
|
|
2022
|
-
]
|
|
2023
|
-
},
|
|
2024
|
-
row.id
|
|
2025
|
-
);
|
|
2026
|
-
}) })
|
|
2027
|
-
] }),
|
|
2028
|
-
/* @__PURE__ */ jsxs("section", { "aria-label": labels.audience, className: "space-y-2", children: [
|
|
2029
|
-
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: labels.audience }),
|
|
2030
|
-
audience.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: labels.noAudience }) : /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: audience.map((row) => {
|
|
2031
|
-
const userId = row.userId;
|
|
2032
|
-
return /* @__PURE__ */ jsxs(
|
|
2033
|
-
"li",
|
|
2034
|
-
{
|
|
2035
|
-
className: "space-y-2 rounded-md border p-2",
|
|
2036
|
-
children: [
|
|
2037
|
-
/* @__PURE__ */ jsx("span", { className: "block truncate text-sm", children: row.name }),
|
|
2038
|
-
!row.isSelf && userId ? /* @__PURE__ */ jsx(
|
|
2039
|
-
Button,
|
|
2040
|
-
{
|
|
2041
|
-
type: "button",
|
|
2042
|
-
size: "xs",
|
|
2043
|
-
variant: "outline",
|
|
2044
|
-
className: "w-full",
|
|
2045
|
-
onClick: () => onGrantStage(userId),
|
|
2046
|
-
children: labels.bringOnAir
|
|
2047
|
-
}
|
|
2048
|
-
) : null
|
|
2049
|
-
]
|
|
2050
|
-
},
|
|
2051
|
-
row.id
|
|
2052
|
-
);
|
|
2053
|
-
}) })
|
|
2054
|
-
] })
|
|
2055
|
-
] })
|
|
2056
|
-
] });
|
|
2057
|
-
}
|
|
2058
|
-
function DropdownMenu({ ...props }) {
|
|
2059
|
-
return /* @__PURE__ */ jsx(Menu.Root, { "data-slot": "dropdown-menu", ...props });
|
|
2060
|
-
}
|
|
2061
|
-
function DropdownMenuTrigger({ ...props }) {
|
|
2062
|
-
return /* @__PURE__ */ jsx(Menu.Trigger, { "data-slot": "dropdown-menu-trigger", ...props });
|
|
1731
|
+
type: "button",
|
|
1732
|
+
size: "sm",
|
|
1733
|
+
variant: "outline",
|
|
1734
|
+
disabled: testingSpeaker,
|
|
1735
|
+
onClick: () => void onTestSpeaker(),
|
|
1736
|
+
children: labels.testSpeaker
|
|
1737
|
+
}
|
|
1738
|
+
)
|
|
1739
|
+
}
|
|
1740
|
+
)
|
|
1741
|
+
] }) });
|
|
2063
1742
|
}
|
|
2064
|
-
function
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
className,
|
|
2070
|
-
...props
|
|
1743
|
+
function SettingsCameraPreview({
|
|
1744
|
+
track,
|
|
1745
|
+
open,
|
|
1746
|
+
cameraOn,
|
|
1747
|
+
cameraOffLabel
|
|
2071
1748
|
}) {
|
|
2072
|
-
|
|
2073
|
-
|
|
1749
|
+
const ref = useRef(null);
|
|
1750
|
+
useEffect(() => {
|
|
1751
|
+
const el = ref.current;
|
|
1752
|
+
if (!el) return;
|
|
1753
|
+
if (!open || !cameraOn || !track) {
|
|
1754
|
+
el.srcObject = null;
|
|
1755
|
+
return;
|
|
1756
|
+
}
|
|
1757
|
+
el.srcObject = new MediaStream([track]);
|
|
1758
|
+
void el.play().catch(() => void 0);
|
|
1759
|
+
return () => {
|
|
1760
|
+
el.srcObject = null;
|
|
1761
|
+
};
|
|
1762
|
+
}, [cameraOn, open, track]);
|
|
1763
|
+
if (!cameraOn) {
|
|
1764
|
+
return /* @__PURE__ */ jsx("div", { className: "bg-muted flex aspect-video w-full items-center justify-center rounded-xl", children: /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: cameraOffLabel }) });
|
|
1765
|
+
}
|
|
1766
|
+
if (!track) {
|
|
1767
|
+
return /* @__PURE__ */ jsx("div", { className: "bg-muted aspect-video w-full rounded-xl", "aria-hidden": true });
|
|
1768
|
+
}
|
|
1769
|
+
return /* @__PURE__ */ jsx("div", { className: "bg-muted relative aspect-video w-full overflow-hidden rounded-xl", children: /* @__PURE__ */ jsx(
|
|
1770
|
+
"video",
|
|
2074
1771
|
{
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
children: /* @__PURE__ */ jsx(
|
|
2081
|
-
Menu.Popup,
|
|
2082
|
-
{
|
|
2083
|
-
"data-slot": "dropdown-menu-content",
|
|
2084
|
-
className: cn(
|
|
2085
|
-
"z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:overflow-hidden data-closed:fade-out-0 data-closed:zoom-out-95",
|
|
2086
|
-
className
|
|
2087
|
-
),
|
|
2088
|
-
...props
|
|
2089
|
-
}
|
|
2090
|
-
)
|
|
1772
|
+
ref,
|
|
1773
|
+
autoPlay: true,
|
|
1774
|
+
muted: true,
|
|
1775
|
+
playsInline: true,
|
|
1776
|
+
className: "absolute inset-0 size-full -scale-x-100 object-cover"
|
|
2091
1777
|
}
|
|
2092
1778
|
) });
|
|
2093
1779
|
}
|
|
2094
|
-
function
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
1780
|
+
function StudioGuestWaiting({
|
|
1781
|
+
meeting,
|
|
1782
|
+
reducedMotion,
|
|
1783
|
+
cameraOn,
|
|
1784
|
+
muted,
|
|
1785
|
+
labels,
|
|
1786
|
+
settingsOpen,
|
|
1787
|
+
onToggleCamera,
|
|
1788
|
+
onToggleMute,
|
|
1789
|
+
onOpenSettings,
|
|
1790
|
+
onCloseSettings
|
|
2099
1791
|
}) {
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
}
|
|
2112
|
-
);
|
|
2113
|
-
}
|
|
2114
|
-
function DropdownMenuSeparator({
|
|
2115
|
-
className,
|
|
2116
|
-
...props
|
|
2117
|
-
}) {
|
|
2118
|
-
return /* @__PURE__ */ jsx(
|
|
2119
|
-
Menu.Separator,
|
|
2120
|
-
{
|
|
2121
|
-
"data-slot": "dropdown-menu-separator",
|
|
2122
|
-
className: cn("-mx-1 my-1 h-px bg-border", className),
|
|
2123
|
-
...props
|
|
2124
|
-
}
|
|
2125
|
-
);
|
|
2126
|
-
}
|
|
2127
|
-
function StudioTileMenu({
|
|
2128
|
-
tile,
|
|
2129
|
-
canModerate,
|
|
2130
|
-
canManageStage,
|
|
2131
|
-
takeOffBlocked,
|
|
2132
|
-
labels,
|
|
2133
|
-
onKick,
|
|
2134
|
-
onMute,
|
|
2135
|
-
onStopCamera,
|
|
2136
|
-
onStopScreen,
|
|
2137
|
-
onToggleStage,
|
|
2138
|
-
onTogglePin,
|
|
2139
|
-
onToggleChat
|
|
2140
|
-
}) {
|
|
2141
|
-
const onStage = isOnStage(tile.stageStatus);
|
|
2142
|
-
const showPersonItems = canModerate && tile.kind !== "screen";
|
|
2143
|
-
const showScreenItems = canModerate && tile.kind === "screen";
|
|
2144
|
-
const showStageItems = canManageStage && tile.kind !== "screen";
|
|
2145
|
-
if (!showPersonItems && !showScreenItems && !showStageItems) return null;
|
|
2146
|
-
return /* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
1792
|
+
const [audioTrack, setAudioTrack] = useState(null);
|
|
1793
|
+
const [videoTrack, setVideoTrack] = useState(null);
|
|
1794
|
+
useEffect(() => {
|
|
1795
|
+
const sync = () => {
|
|
1796
|
+
setAudioTrack(meeting.localAudioTrack());
|
|
1797
|
+
setVideoTrack(meeting.localVideoTrack());
|
|
1798
|
+
};
|
|
1799
|
+
sync();
|
|
1800
|
+
return meeting.subscribeMedia(sync);
|
|
1801
|
+
}, [meeting]);
|
|
1802
|
+
return /* @__PURE__ */ jsxs("div", { className: "mx-auto flex min-h-dvh w-full max-w-lg flex-col justify-center gap-6 p-6", children: [
|
|
2147
1803
|
/* @__PURE__ */ jsx(
|
|
2148
|
-
|
|
1804
|
+
WaitingCameraPreview,
|
|
2149
1805
|
{
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
type: "button",
|
|
2154
|
-
size: "icon-xs",
|
|
2155
|
-
variant: "ghost",
|
|
2156
|
-
className: "text-white hover:text-white",
|
|
2157
|
-
"aria-label": labels.tileMenu
|
|
2158
|
-
}
|
|
2159
|
-
),
|
|
2160
|
-
children: /* @__PURE__ */ jsx(MoreVertical, { className: "size-3.5" })
|
|
1806
|
+
track: videoTrack,
|
|
1807
|
+
cameraOn,
|
|
1808
|
+
cameraOffLabel: labels.cameraOff
|
|
2161
1809
|
}
|
|
2162
1810
|
),
|
|
2163
|
-
/* @__PURE__ */
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
1811
|
+
/* @__PURE__ */ jsx(
|
|
1812
|
+
AudioWaveform,
|
|
1813
|
+
{
|
|
1814
|
+
track: muted ? null : audioTrack,
|
|
1815
|
+
reducedMotion
|
|
1816
|
+
}
|
|
1817
|
+
),
|
|
1818
|
+
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-center text-sm", role: "status", children: labels.waitingHost }),
|
|
1819
|
+
/* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs("div", { className: "flex justify-center gap-2", children: [
|
|
1820
|
+
/* @__PURE__ */ jsx(
|
|
1821
|
+
ControlIcon,
|
|
2171
1822
|
{
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
1823
|
+
label: muted ? labels.unmute : labels.mute,
|
|
1824
|
+
pressed: muted,
|
|
1825
|
+
tone: muted ? "danger" : "default",
|
|
1826
|
+
onClick: onToggleMute,
|
|
1827
|
+
children: muted ? /* @__PURE__ */ jsx(MicOff, {}) : /* @__PURE__ */ jsx(Mic, {})
|
|
2175
1828
|
}
|
|
2176
|
-
)
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
1829
|
+
),
|
|
1830
|
+
/* @__PURE__ */ jsx(
|
|
1831
|
+
ControlIcon,
|
|
1832
|
+
{
|
|
1833
|
+
label: labels.camera,
|
|
1834
|
+
pressed: cameraOn,
|
|
1835
|
+
onClick: onToggleCamera,
|
|
1836
|
+
children: cameraOn ? /* @__PURE__ */ jsx(Video, {}) : /* @__PURE__ */ jsx(VideoOff, {})
|
|
1837
|
+
}
|
|
1838
|
+
),
|
|
1839
|
+
/* @__PURE__ */ jsx(
|
|
1840
|
+
ControlIcon,
|
|
1841
|
+
{
|
|
1842
|
+
label: labels.settings,
|
|
1843
|
+
pressed: settingsOpen,
|
|
1844
|
+
onClick: onOpenSettings,
|
|
1845
|
+
children: /* @__PURE__ */ jsx(Settings, {})
|
|
1846
|
+
}
|
|
1847
|
+
)
|
|
1848
|
+
] }) }),
|
|
1849
|
+
/* @__PURE__ */ jsx(
|
|
1850
|
+
StudioSettingsDialog,
|
|
1851
|
+
{
|
|
1852
|
+
open: settingsOpen,
|
|
1853
|
+
onOpenChange: onCloseSettings,
|
|
1854
|
+
meeting,
|
|
1855
|
+
labels,
|
|
1856
|
+
reducedMotion,
|
|
1857
|
+
cameraOn
|
|
1858
|
+
}
|
|
1859
|
+
)
|
|
2182
1860
|
] });
|
|
2183
1861
|
}
|
|
2184
|
-
function
|
|
2185
|
-
|
|
1862
|
+
function WaitingCameraPreview({
|
|
1863
|
+
track,
|
|
1864
|
+
cameraOn,
|
|
1865
|
+
cameraOffLabel
|
|
1866
|
+
}) {
|
|
1867
|
+
const ref = useRef(null);
|
|
1868
|
+
useEffect(() => {
|
|
1869
|
+
const el = ref.current;
|
|
1870
|
+
if (!el) return;
|
|
1871
|
+
if (!cameraOn || !track) {
|
|
1872
|
+
el.srcObject = null;
|
|
1873
|
+
return;
|
|
1874
|
+
}
|
|
1875
|
+
el.srcObject = new MediaStream([track]);
|
|
1876
|
+
void el.play().catch(() => void 0);
|
|
1877
|
+
return () => {
|
|
1878
|
+
el.srcObject = null;
|
|
1879
|
+
};
|
|
1880
|
+
}, [cameraOn, track]);
|
|
1881
|
+
if (!cameraOn) {
|
|
1882
|
+
return /* @__PURE__ */ jsx("div", { className: "bg-muted flex aspect-video w-full items-center justify-center rounded-xl", children: /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: cameraOffLabel }) });
|
|
1883
|
+
}
|
|
1884
|
+
if (!track) {
|
|
1885
|
+
return /* @__PURE__ */ jsx("div", { className: "bg-muted aspect-video w-full rounded-xl", "aria-hidden": true });
|
|
1886
|
+
}
|
|
1887
|
+
return /* @__PURE__ */ jsx("div", { className: "bg-muted relative aspect-video w-full overflow-hidden rounded-xl", children: /* @__PURE__ */ jsx(
|
|
1888
|
+
"video",
|
|
1889
|
+
{
|
|
1890
|
+
ref,
|
|
1891
|
+
autoPlay: true,
|
|
1892
|
+
muted: true,
|
|
1893
|
+
playsInline: true,
|
|
1894
|
+
className: "absolute inset-0 size-full -scale-x-100 object-cover"
|
|
1895
|
+
}
|
|
1896
|
+
) });
|
|
1897
|
+
}
|
|
1898
|
+
function StudioLiveBadge({
|
|
1899
|
+
children,
|
|
1900
|
+
role
|
|
1901
|
+
}) {
|
|
1902
|
+
return /* @__PURE__ */ jsxs(
|
|
1903
|
+
"span",
|
|
1904
|
+
{
|
|
1905
|
+
role,
|
|
1906
|
+
className: "flex items-center gap-1.5 rounded bg-red-600 px-2 py-0.5 text-xs font-semibold tracking-wide text-white",
|
|
1907
|
+
children: [
|
|
1908
|
+
/* @__PURE__ */ jsx("span", { className: "size-1.5 animate-pulse rounded-full bg-white motion-reduce:animate-none" }),
|
|
1909
|
+
children
|
|
1910
|
+
]
|
|
1911
|
+
}
|
|
1912
|
+
);
|
|
1913
|
+
}
|
|
1914
|
+
var badgeVariants = cva(
|
|
1915
|
+
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors",
|
|
1916
|
+
{
|
|
1917
|
+
variants: {
|
|
1918
|
+
variant: {
|
|
1919
|
+
default: "border-transparent bg-primary text-primary-foreground",
|
|
1920
|
+
secondary: "border-transparent bg-secondary text-secondary-foreground",
|
|
1921
|
+
destructive: "border-transparent bg-destructive text-destructive-foreground",
|
|
1922
|
+
outline: "text-foreground",
|
|
1923
|
+
success: "border-transparent bg-chart-5/15 text-chart-5 dark:bg-chart-5/25",
|
|
1924
|
+
warning: "border-transparent bg-chart-4/15 text-chart-4 dark:bg-chart-4/25"
|
|
1925
|
+
}
|
|
1926
|
+
},
|
|
1927
|
+
defaultVariants: { variant: "default" }
|
|
1928
|
+
}
|
|
1929
|
+
);
|
|
1930
|
+
function Badge({ className, variant, ...props }) {
|
|
1931
|
+
return /* @__PURE__ */ jsx("span", { className: cn(badgeVariants({ variant }), className), ...props });
|
|
1932
|
+
}
|
|
1933
|
+
function StudioPipPanel({
|
|
2186
1934
|
meeting,
|
|
2187
|
-
reducedMotion,
|
|
2188
|
-
compact,
|
|
2189
|
-
canModerate,
|
|
2190
|
-
canManageStage,
|
|
2191
|
-
takeOffBlocked,
|
|
2192
|
-
confirmTakeOff,
|
|
2193
1935
|
labels,
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
1936
|
+
cameraOn,
|
|
1937
|
+
muted,
|
|
1938
|
+
screenOn,
|
|
1939
|
+
showMediaControls = true,
|
|
1940
|
+
showScreenControl = false,
|
|
1941
|
+
canShareFreely = false,
|
|
1942
|
+
screenAvailable = true,
|
|
1943
|
+
screenPending = false,
|
|
1944
|
+
shareGrant = "idle",
|
|
1945
|
+
onAir,
|
|
1946
|
+
elapsed,
|
|
1947
|
+
unread = 0,
|
|
1948
|
+
onToggleMute,
|
|
1949
|
+
onToggleCamera,
|
|
1950
|
+
onToggleScreen,
|
|
1951
|
+
onAskToShare,
|
|
1952
|
+
onCancelShareRequest,
|
|
1953
|
+
onBack
|
|
2201
1954
|
}) {
|
|
2202
|
-
const
|
|
2203
|
-
const
|
|
2204
|
-
const
|
|
1955
|
+
const tiles = meeting.participants();
|
|
1956
|
+
const spotlight = resolveStudioPipSpotlight(tiles, meeting.activeSpeakerId());
|
|
1957
|
+
const selfView = resolveStudioPipSelfView(tiles, spotlight);
|
|
1958
|
+
const speaking = spotlight !== null && spotlight.participantId === meeting.activeSpeakerId();
|
|
1959
|
+
const backLabel = unread > 0 ? `${labels.backToStudio} (${unread})` : labels.backToStudio;
|
|
2205
1960
|
return /* @__PURE__ */ jsxs(
|
|
2206
|
-
"
|
|
1961
|
+
"div",
|
|
2207
1962
|
{
|
|
2208
|
-
"
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
compact ? "aspect-video w-40 shrink-0 md:w-full" : tile.kind === "screen" ? "min-h-48 flex-1" : "aspect-video"
|
|
2212
|
-
),
|
|
1963
|
+
role: "region",
|
|
1964
|
+
"aria-label": labels.pipTitle,
|
|
1965
|
+
className: "bg-background flex h-full min-h-0 flex-col gap-2 p-2 text-foreground",
|
|
2213
1966
|
children: [
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
"img",
|
|
2217
|
-
{
|
|
2218
|
-
src: tile.picture,
|
|
2219
|
-
alt: "",
|
|
2220
|
-
className: "size-16 rounded-full object-cover"
|
|
2221
|
-
}
|
|
2222
|
-
) : /* @__PURE__ */ jsx("div", { className: "bg-background flex size-16 items-center justify-center rounded-full text-xl", children: tile.name.slice(0, 1).toUpperCase() }),
|
|
2223
|
-
/* @__PURE__ */ jsx(
|
|
2224
|
-
AudioWaveform,
|
|
2225
|
-
{
|
|
2226
|
-
track: tile.isSelf ? meeting.localAudioTrack() : null,
|
|
2227
|
-
reducedMotion
|
|
2228
|
-
}
|
|
2229
|
-
)
|
|
2230
|
-
] }) : /* @__PURE__ */ jsx(
|
|
2231
|
-
StudioTileVideo,
|
|
1967
|
+
/* @__PURE__ */ jsxs(
|
|
1968
|
+
"div",
|
|
2232
1969
|
{
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
onToggleStage: () => {
|
|
2260
|
-
if (onStage && confirmTakeOff && !takeOffBlocked) {
|
|
2261
|
-
setConfirm("takeOff");
|
|
2262
|
-
return;
|
|
2263
|
-
}
|
|
2264
|
-
onToggleStage();
|
|
2265
|
-
},
|
|
2266
|
-
onTogglePin,
|
|
2267
|
-
onToggleChat
|
|
2268
|
-
}
|
|
2269
|
-
) : null
|
|
2270
|
-
] })
|
|
2271
|
-
] }),
|
|
2272
|
-
/* @__PURE__ */ jsx(
|
|
2273
|
-
ConfirmAction,
|
|
2274
|
-
{
|
|
2275
|
-
open: confirm === "kick",
|
|
2276
|
-
onOpenChange: (open) => {
|
|
2277
|
-
if (!open) setConfirm(null);
|
|
2278
|
-
},
|
|
2279
|
-
triggerLabel: labels.kick,
|
|
2280
|
-
title: labels.kickTitle,
|
|
2281
|
-
description: labels.kickDescription,
|
|
2282
|
-
confirmLabel: labels.kickConfirm,
|
|
2283
|
-
onConfirm: () => {
|
|
2284
|
-
setConfirm(null);
|
|
2285
|
-
onKick();
|
|
2286
|
-
}
|
|
1970
|
+
className: cn(
|
|
1971
|
+
"bg-muted relative min-h-0 flex-1 overflow-hidden rounded-xl",
|
|
1972
|
+
speaking && "ring-2 ring-primary"
|
|
1973
|
+
),
|
|
1974
|
+
children: [
|
|
1975
|
+
/* @__PURE__ */ jsx(
|
|
1976
|
+
PipSpotlight,
|
|
1977
|
+
{
|
|
1978
|
+
meeting,
|
|
1979
|
+
spotlight,
|
|
1980
|
+
cameraOffLabel: labels.cameraOff
|
|
1981
|
+
}
|
|
1982
|
+
),
|
|
1983
|
+
onAir ? /* @__PURE__ */ jsxs("div", { className: "absolute top-2 left-2 flex items-center gap-2", children: [
|
|
1984
|
+
/* @__PURE__ */ jsx(StudioLiveBadge, { children: labels.live }),
|
|
1985
|
+
elapsed ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground tabular-nums text-xs", children: elapsed }) : null
|
|
1986
|
+
] }) : null,
|
|
1987
|
+
selfView ? /* @__PURE__ */ jsx(
|
|
1988
|
+
"div",
|
|
1989
|
+
{
|
|
1990
|
+
"data-testid": "pip-self-view",
|
|
1991
|
+
className: "absolute right-2 bottom-2 aspect-video w-[30%] overflow-hidden rounded-lg ring-1 ring-white/40",
|
|
1992
|
+
children: /* @__PURE__ */ jsx(PipTrackVideo, { track: meeting.localVideoTrack(), fit: "cover" })
|
|
1993
|
+
}
|
|
1994
|
+
) : null
|
|
1995
|
+
]
|
|
2287
1996
|
}
|
|
2288
1997
|
),
|
|
2289
|
-
/* @__PURE__ */
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
1998
|
+
/* @__PURE__ */ jsxs("footer", { className: "flex flex-wrap items-center justify-center gap-2", children: [
|
|
1999
|
+
showMediaControls ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2000
|
+
/* @__PURE__ */ jsx(
|
|
2001
|
+
PipControl,
|
|
2002
|
+
{
|
|
2003
|
+
label: muted ? labels.unmute : labels.mute,
|
|
2004
|
+
off: muted,
|
|
2005
|
+
onClick: onToggleMute,
|
|
2006
|
+
children: muted ? /* @__PURE__ */ jsx(MicOff, {}) : /* @__PURE__ */ jsx(Mic, {})
|
|
2007
|
+
}
|
|
2008
|
+
),
|
|
2009
|
+
/* @__PURE__ */ jsx(
|
|
2010
|
+
PipControl,
|
|
2011
|
+
{
|
|
2012
|
+
label: labels.camera,
|
|
2013
|
+
off: !cameraOn,
|
|
2014
|
+
onClick: onToggleCamera,
|
|
2015
|
+
children: cameraOn ? /* @__PURE__ */ jsx(Video, {}) : /* @__PURE__ */ jsx(VideoOff, {})
|
|
2016
|
+
}
|
|
2017
|
+
)
|
|
2018
|
+
] }) : null,
|
|
2019
|
+
showMediaControls && (showScreenControl || screenOn) ? pipScreenControls({
|
|
2020
|
+
labels,
|
|
2021
|
+
screenOn,
|
|
2022
|
+
canShareFreely,
|
|
2023
|
+
screenAvailable,
|
|
2024
|
+
screenPending,
|
|
2025
|
+
shareGrant,
|
|
2026
|
+
onToggleScreen,
|
|
2027
|
+
onAskToShare,
|
|
2028
|
+
onCancelShareRequest
|
|
2029
|
+
}) : null,
|
|
2030
|
+
/* @__PURE__ */ jsx(PipControl, { label: backLabel, onClick: onBack, children: /* @__PURE__ */ jsxs("span", { className: "relative", children: [
|
|
2031
|
+
/* @__PURE__ */ jsx(Undo2, {}),
|
|
2032
|
+
unread > 0 ? /* @__PURE__ */ jsx(Badge, { className: "absolute -top-2 -right-2 h-4 min-w-4 px-1 text-[0.65rem]", children: unread }) : null
|
|
2033
|
+
] }) })
|
|
2034
|
+
] })
|
|
2306
2035
|
]
|
|
2307
2036
|
}
|
|
2308
2037
|
);
|
|
2309
2038
|
}
|
|
2310
|
-
function
|
|
2311
|
-
|
|
2312
|
-
|
|
2039
|
+
function PipSpotlight({
|
|
2040
|
+
meeting,
|
|
2041
|
+
spotlight,
|
|
2042
|
+
cameraOffLabel
|
|
2313
2043
|
}) {
|
|
2044
|
+
if (!spotlight || spotlight.kind === "idle") {
|
|
2045
|
+
return /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 flex flex-col items-center justify-center gap-2", children: [
|
|
2046
|
+
spotlight?.picture ? /* @__PURE__ */ jsx(
|
|
2047
|
+
"img",
|
|
2048
|
+
{
|
|
2049
|
+
src: spotlight.picture,
|
|
2050
|
+
alt: "",
|
|
2051
|
+
className: "size-14 rounded-full object-cover"
|
|
2052
|
+
}
|
|
2053
|
+
) : /* @__PURE__ */ jsx("div", { className: "bg-background flex size-14 items-center justify-center rounded-full text-lg", children: (spotlight?.name ?? "?").slice(0, 1).toUpperCase() }),
|
|
2054
|
+
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs", children: spotlight?.name ?? cameraOffLabel })
|
|
2055
|
+
] });
|
|
2056
|
+
}
|
|
2057
|
+
const track = spotlight.kind === "screen" ? meeting.participantScreenTrack(spotlight.participantId) : meeting.participantVideoTrack(spotlight.participantId);
|
|
2314
2058
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
label,
|
|
2324
|
-
children
|
|
2325
|
-
}) {
|
|
2326
|
-
return /* @__PURE__ */ jsxs("span", { className: "inline-flex text-white", children: [
|
|
2327
|
-
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "[&_svg]:size-3.5", children }),
|
|
2328
|
-
/* @__PURE__ */ jsx("span", { className: "sr-only", children: label })
|
|
2059
|
+
/* @__PURE__ */ jsx(
|
|
2060
|
+
PipTrackVideo,
|
|
2061
|
+
{
|
|
2062
|
+
track,
|
|
2063
|
+
fit: spotlight.kind === "screen" ? "contain" : "cover"
|
|
2064
|
+
}
|
|
2065
|
+
),
|
|
2066
|
+
/* @__PURE__ */ jsx("p", { className: "absolute inset-x-0 bottom-0 bg-linear-to-t from-black/70 to-transparent p-2 text-xs font-medium text-white", children: spotlight.name })
|
|
2329
2067
|
] });
|
|
2330
2068
|
}
|
|
2331
|
-
function
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
kind,
|
|
2335
|
-
mirror,
|
|
2336
|
-
contain
|
|
2069
|
+
function PipTrackVideo({
|
|
2070
|
+
track,
|
|
2071
|
+
fit
|
|
2337
2072
|
}) {
|
|
2338
2073
|
const ref = useRef(null);
|
|
2339
2074
|
useEffect(() => {
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2075
|
+
const el = ref.current;
|
|
2076
|
+
if (!el) return;
|
|
2077
|
+
if (!track) {
|
|
2078
|
+
el.srcObject = null;
|
|
2079
|
+
return;
|
|
2080
|
+
}
|
|
2081
|
+
el.srcObject = new MediaStream([track]);
|
|
2082
|
+
void el.play().catch(() => void 0);
|
|
2083
|
+
return () => {
|
|
2084
|
+
el.srcObject = null;
|
|
2085
|
+
};
|
|
2086
|
+
}, [track]);
|
|
2343
2087
|
return /* @__PURE__ */ jsx(
|
|
2344
2088
|
"video",
|
|
2345
2089
|
{
|
|
@@ -2347,604 +2091,1063 @@ function StudioTileVideo({
|
|
|
2347
2091
|
autoPlay: true,
|
|
2348
2092
|
playsInline: true,
|
|
2349
2093
|
muted: true,
|
|
2350
|
-
disablePictureInPicture: true,
|
|
2351
|
-
controlsList: "nodownload noplaybackrate noremoteplayback",
|
|
2352
2094
|
className: cn(
|
|
2353
2095
|
"absolute inset-0 size-full",
|
|
2354
|
-
"
|
|
2355
|
-
contain ? "object-contain" : "object-cover",
|
|
2356
|
-
mirror && "-scale-x-100"
|
|
2096
|
+
fit === "contain" ? "object-contain" : "object-cover"
|
|
2357
2097
|
)
|
|
2358
2098
|
}
|
|
2359
2099
|
);
|
|
2360
2100
|
}
|
|
2361
|
-
function
|
|
2362
|
-
participants,
|
|
2363
|
-
meeting,
|
|
2364
|
-
reducedMotion,
|
|
2365
|
-
canModerate,
|
|
2366
|
-
canManageStage,
|
|
2367
|
-
onAir,
|
|
2101
|
+
function pipScreenControls({
|
|
2368
2102
|
labels,
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
onOpenPeople
|
|
2103
|
+
screenOn,
|
|
2104
|
+
canShareFreely,
|
|
2105
|
+
screenAvailable,
|
|
2106
|
+
screenPending,
|
|
2107
|
+
shareGrant,
|
|
2108
|
+
onToggleScreen,
|
|
2109
|
+
onAskToShare,
|
|
2110
|
+
onCancelShareRequest
|
|
2378
2111
|
}) {
|
|
2379
|
-
|
|
2380
|
-
const stage = studioStageLayout(displayTiles, {
|
|
2381
|
-
activeSpeakerId: meeting.activeSpeakerId()
|
|
2382
|
-
});
|
|
2383
|
-
const sharing = stage.spotlight.length > 0;
|
|
2384
|
-
const overflowLabel = labels.stageOverflow.replace(
|
|
2385
|
-
"{count}",
|
|
2386
|
-
String(stage.overflow)
|
|
2387
|
-
);
|
|
2388
|
-
function renderTile(tile, compact) {
|
|
2389
|
-
const takeOffBlocked = !canTakeOff(tile.id);
|
|
2112
|
+
if (canShareFreely || screenOn) {
|
|
2390
2113
|
return /* @__PURE__ */ jsx(
|
|
2391
|
-
|
|
2114
|
+
PipControl,
|
|
2392
2115
|
{
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
canManageStage: canManageStage && !tile.isSelf,
|
|
2399
|
-
takeOffBlocked,
|
|
2400
|
-
confirmTakeOff: onAir,
|
|
2401
|
-
labels,
|
|
2402
|
-
onKick: () => onKick(tile.id),
|
|
2403
|
-
onMute: () => onMute(tile.id),
|
|
2404
|
-
onStopCamera: () => onStopCamera(tile.id),
|
|
2405
|
-
onStopScreen: () => onStopScreen(tile.id),
|
|
2406
|
-
onToggleStage: () => onToggleStage(tile),
|
|
2407
|
-
onTogglePin: () => onTogglePin(tile),
|
|
2408
|
-
onToggleChat: () => onToggleChat(tile)
|
|
2409
|
-
},
|
|
2410
|
-
tile.tileId
|
|
2411
|
-
);
|
|
2412
|
-
}
|
|
2413
|
-
function overflowChip(compact) {
|
|
2414
|
-
if (stage.overflow <= 0) return null;
|
|
2415
|
-
return /* @__PURE__ */ jsx(
|
|
2416
|
-
Button,
|
|
2417
|
-
{
|
|
2418
|
-
type: "button",
|
|
2419
|
-
variant: "secondary",
|
|
2420
|
-
className: cn(
|
|
2421
|
-
"shrink-0",
|
|
2422
|
-
compact ? "aspect-video w-40 md:w-full" : "aspect-video min-h-24"
|
|
2423
|
-
),
|
|
2424
|
-
onClick: onOpenPeople,
|
|
2425
|
-
children: overflowLabel
|
|
2116
|
+
label: screenOn ? labels.stopScreen : labels.screen,
|
|
2117
|
+
off: screenOn,
|
|
2118
|
+
disabled: screenPending || !screenAvailable && !screenOn,
|
|
2119
|
+
onClick: onToggleScreen,
|
|
2120
|
+
children: /* @__PURE__ */ jsx(MonitorUp, {})
|
|
2426
2121
|
}
|
|
2427
2122
|
);
|
|
2428
2123
|
}
|
|
2429
|
-
if (
|
|
2430
|
-
return /* @__PURE__ */ jsxs(
|
|
2431
|
-
/* @__PURE__ */ jsx(
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2124
|
+
if (shareGrant === "requested") {
|
|
2125
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2126
|
+
/* @__PURE__ */ jsx(
|
|
2127
|
+
PipControl,
|
|
2128
|
+
{
|
|
2129
|
+
label: labels.shareRequested,
|
|
2130
|
+
off: true,
|
|
2131
|
+
disabled: true,
|
|
2132
|
+
onClick: () => void 0,
|
|
2133
|
+
children: /* @__PURE__ */ jsx(MonitorUp, {})
|
|
2134
|
+
}
|
|
2135
|
+
),
|
|
2136
|
+
/* @__PURE__ */ jsx(
|
|
2137
|
+
PipControl,
|
|
2138
|
+
{
|
|
2139
|
+
label: labels.cancelShareRequest,
|
|
2140
|
+
onClick: () => onCancelShareRequest?.(),
|
|
2141
|
+
children: /* @__PURE__ */ jsx(X, {})
|
|
2142
|
+
}
|
|
2143
|
+
)
|
|
2436
2144
|
] });
|
|
2437
2145
|
}
|
|
2438
|
-
return /* @__PURE__ */
|
|
2439
|
-
|
|
2146
|
+
return /* @__PURE__ */ jsx(PipControl, { label: labels.askToShare, onClick: () => onAskToShare?.(), children: /* @__PURE__ */ jsx(MonitorUp, {}) });
|
|
2147
|
+
}
|
|
2148
|
+
function PipControl({
|
|
2149
|
+
label,
|
|
2150
|
+
onClick,
|
|
2151
|
+
off = false,
|
|
2152
|
+
disabled = false,
|
|
2153
|
+
children
|
|
2154
|
+
}) {
|
|
2155
|
+
return /* @__PURE__ */ jsx(
|
|
2156
|
+
Button,
|
|
2440
2157
|
{
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2158
|
+
type: "button",
|
|
2159
|
+
size: "icon-lg",
|
|
2160
|
+
variant: off ? "destructive" : "secondary",
|
|
2161
|
+
className: "size-10 rounded-full",
|
|
2162
|
+
"aria-label": label,
|
|
2163
|
+
disabled,
|
|
2164
|
+
onClick,
|
|
2165
|
+
children
|
|
2449
2166
|
}
|
|
2450
2167
|
);
|
|
2451
2168
|
}
|
|
2452
|
-
function
|
|
2453
|
-
|
|
2454
|
-
|
|
2169
|
+
function StudioRemoteAudio({
|
|
2170
|
+
meeting,
|
|
2171
|
+
participantId
|
|
2455
2172
|
}) {
|
|
2173
|
+
const ref = useRef(null);
|
|
2174
|
+
useEffect(() => {
|
|
2175
|
+
meeting.attachAudio(participantId, ref.current);
|
|
2176
|
+
return () => meeting.attachAudio(participantId, null);
|
|
2177
|
+
}, [meeting, participantId]);
|
|
2178
|
+
return /* @__PURE__ */ jsx("audio", { ref, autoPlay: true, className: "sr-only" });
|
|
2179
|
+
}
|
|
2180
|
+
function BubbleGroup({ className, ...props }) {
|
|
2456
2181
|
return /* @__PURE__ */ jsx(
|
|
2457
|
-
|
|
2182
|
+
"div",
|
|
2458
2183
|
{
|
|
2459
|
-
"data-slot": "
|
|
2460
|
-
|
|
2184
|
+
"data-slot": "bubble-group",
|
|
2185
|
+
className: cn("flex min-w-0 flex-col gap-2", className),
|
|
2461
2186
|
...props
|
|
2462
2187
|
}
|
|
2463
2188
|
);
|
|
2464
2189
|
}
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
Tooltip$1.Positioner,
|
|
2482
|
-
{
|
|
2483
|
-
align,
|
|
2484
|
-
alignOffset,
|
|
2485
|
-
side,
|
|
2486
|
-
sideOffset,
|
|
2487
|
-
className: "isolate z-50",
|
|
2488
|
-
children: /* @__PURE__ */ jsxs(
|
|
2489
|
-
Tooltip$1.Popup,
|
|
2490
|
-
{
|
|
2491
|
-
"data-slot": "tooltip-content",
|
|
2492
|
-
className: cn(
|
|
2493
|
-
"z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
|
2494
|
-
className
|
|
2495
|
-
),
|
|
2496
|
-
...props,
|
|
2497
|
-
children: [
|
|
2498
|
-
children,
|
|
2499
|
-
/* @__PURE__ */ jsx(Tooltip$1.Arrow, { className: "z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:top-1/2! data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5" })
|
|
2500
|
-
]
|
|
2501
|
-
}
|
|
2502
|
-
)
|
|
2190
|
+
var bubbleVariants = cva(
|
|
2191
|
+
"group/bubble relative flex w-fit max-w-[80%] min-w-0 flex-col gap-1 group-data-[align=end]/message:self-end data-[align=end]:self-end data-[variant=ghost]:max-w-full",
|
|
2192
|
+
{
|
|
2193
|
+
variants: {
|
|
2194
|
+
variant: {
|
|
2195
|
+
default: "*:data-[slot=bubble-content]:bg-primary *:data-[slot=bubble-content]:text-primary-foreground [&>[data-slot=bubble-content]:is(button,a):hover]:bg-primary/80",
|
|
2196
|
+
secondary: "*:data-[slot=bubble-content]:bg-secondary *:data-[slot=bubble-content]:text-secondary-foreground [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)]",
|
|
2197
|
+
muted: "*:data-[slot=bubble-content]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[color-mix(in_oklch,var(--muted),var(--foreground)_5%)]",
|
|
2198
|
+
tinted: "*:data-[slot=bubble-content]:bg-[oklch(from_var(--primary)_0.93_calc(c*0.4)_h)] *:data-[slot=bubble-content]:text-foreground dark:*:data-[slot=bubble-content]:bg-[oklch(from_var(--primary)_0.3_calc(c*0.4)_h)] [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[oklch(from_var(--primary)_0.88_calc(c*0.5)_h)] dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-[oklch(from_var(--primary)_0.35_calc(c*0.5)_h)]",
|
|
2199
|
+
outline: "*:data-[slot=bubble-content]:border-border *:data-[slot=bubble-content]:bg-background [&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:text-foreground dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-input/30",
|
|
2200
|
+
ghost: "border-none *:data-[slot=bubble-content]:rounded-none *:data-[slot=bubble-content]:bg-transparent *:data-[slot=bubble-content]:p-0 [&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:text-foreground dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted/50",
|
|
2201
|
+
destructive: "*:data-[slot=bubble-content]:bg-destructive/10 *:data-[slot=bubble-content]:text-destructive dark:*:data-[slot=bubble-content]:bg-destructive/20 [&>[data-slot=bubble-content]:is(button,a):hover]:bg-destructive/20 dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-destructive/30"
|
|
2202
|
+
}
|
|
2203
|
+
},
|
|
2204
|
+
defaultVariants: {
|
|
2205
|
+
variant: "default"
|
|
2503
2206
|
}
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
function
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
function DialogPortal({
|
|
2510
|
-
...props
|
|
2511
|
-
}) {
|
|
2512
|
-
return /* @__PURE__ */ jsx(DialogPrimitive.Portal, { "data-slot": "dialog-portal", ...props });
|
|
2513
|
-
}
|
|
2514
|
-
function DialogOverlay({
|
|
2207
|
+
}
|
|
2208
|
+
);
|
|
2209
|
+
function Bubble({
|
|
2210
|
+
variant = "default",
|
|
2211
|
+
align = "start",
|
|
2515
2212
|
className,
|
|
2516
2213
|
...props
|
|
2517
2214
|
}) {
|
|
2518
2215
|
return /* @__PURE__ */ jsx(
|
|
2519
|
-
|
|
2216
|
+
"div",
|
|
2520
2217
|
{
|
|
2521
|
-
"data-slot": "
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
),
|
|
2218
|
+
"data-slot": "bubble",
|
|
2219
|
+
"data-variant": variant,
|
|
2220
|
+
"data-align": align,
|
|
2221
|
+
className: cn(bubbleVariants({ variant }), className),
|
|
2526
2222
|
...props
|
|
2527
2223
|
}
|
|
2528
2224
|
);
|
|
2529
2225
|
}
|
|
2530
|
-
function
|
|
2226
|
+
function BubbleContent({
|
|
2531
2227
|
className,
|
|
2532
|
-
|
|
2533
|
-
showCloseButton = true,
|
|
2228
|
+
render,
|
|
2534
2229
|
...props
|
|
2535
2230
|
}) {
|
|
2536
|
-
return
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
DialogPrimitive.Content,
|
|
2231
|
+
return useRender({
|
|
2232
|
+
defaultTagName: "div",
|
|
2233
|
+
props: mergeProps(
|
|
2540
2234
|
{
|
|
2541
|
-
"data-slot": "dialog-content",
|
|
2542
2235
|
className: cn(
|
|
2543
|
-
"
|
|
2236
|
+
"w-fit max-w-full min-w-0 overflow-hidden rounded-xl border border-transparent px-3 py-2 text-sm leading-relaxed wrap-break-word group-data-[align=end]/bubble:self-end [button]:text-left [button,a]:transition-colors [button,a]:outline-none [button,a]:focus-visible:border-ring [button,a]:focus-visible:ring-3 [button,a]:focus-visible:ring-ring/50",
|
|
2544
2237
|
className
|
|
2545
|
-
)
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2238
|
+
)
|
|
2239
|
+
},
|
|
2240
|
+
props
|
|
2241
|
+
),
|
|
2242
|
+
render,
|
|
2243
|
+
state: {
|
|
2244
|
+
slot: "bubble-content"
|
|
2245
|
+
}
|
|
2246
|
+
});
|
|
2247
|
+
}
|
|
2248
|
+
cva(
|
|
2249
|
+
"absolute z-10 flex w-fit shrink-0 items-center justify-center gap-1 rounded-full bg-muted px-1.5 py-0.5 text-sm ring-3 ring-card has-[button]:p-0",
|
|
2250
|
+
{
|
|
2251
|
+
variants: {
|
|
2252
|
+
side: {
|
|
2253
|
+
top: "top-0 -translate-y-3/4",
|
|
2254
|
+
bottom: "bottom-0 translate-y-3/4"
|
|
2255
|
+
},
|
|
2256
|
+
align: {
|
|
2257
|
+
start: "left-3",
|
|
2258
|
+
end: "right-3"
|
|
2554
2259
|
}
|
|
2555
|
-
|
|
2260
|
+
},
|
|
2261
|
+
defaultVariants: {
|
|
2262
|
+
side: "bottom",
|
|
2263
|
+
align: "end"
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
);
|
|
2267
|
+
|
|
2268
|
+
// src/studio/lib/chat-groups.ts
|
|
2269
|
+
function groupChatMessages(messages, selfUserId) {
|
|
2270
|
+
const groups = [];
|
|
2271
|
+
for (const row of messages) {
|
|
2272
|
+
const mine = Boolean(selfUserId) && row.userId === selfUserId;
|
|
2273
|
+
const last = groups[groups.length - 1];
|
|
2274
|
+
if (last && last.userId === row.userId && last.mine === mine) {
|
|
2275
|
+
last.rows.push(row);
|
|
2276
|
+
continue;
|
|
2277
|
+
}
|
|
2278
|
+
groups.push({
|
|
2279
|
+
userId: row.userId,
|
|
2280
|
+
name: row.name,
|
|
2281
|
+
mine,
|
|
2282
|
+
rows: [row]
|
|
2283
|
+
});
|
|
2284
|
+
}
|
|
2285
|
+
return groups;
|
|
2286
|
+
}
|
|
2287
|
+
function senderInitials(name) {
|
|
2288
|
+
const parts = name.trim().split(/\s+/).filter((part) => part.length > 0);
|
|
2289
|
+
if (parts.length === 0) return "?";
|
|
2290
|
+
const first = parts[0];
|
|
2291
|
+
if (parts.length === 1) return first.slice(0, 2).toUpperCase();
|
|
2292
|
+
return `${first[0]}${parts[1][0]}`.toUpperCase();
|
|
2293
|
+
}
|
|
2294
|
+
function StudioChatPanel({
|
|
2295
|
+
meeting,
|
|
2296
|
+
selfUserId,
|
|
2297
|
+
participants,
|
|
2298
|
+
isModerator: isModerator2,
|
|
2299
|
+
labels
|
|
2300
|
+
}) {
|
|
2301
|
+
const [tick, setTick] = useState(0);
|
|
2302
|
+
const [draft, setDraft] = useState("");
|
|
2303
|
+
const [editingId, setEditingId] = useState(null);
|
|
2304
|
+
const listRef = useRef(null);
|
|
2305
|
+
useEffect(
|
|
2306
|
+
() => meeting.subscribeChat(() => setTick((n) => n + 1)),
|
|
2307
|
+
[meeting]
|
|
2308
|
+
);
|
|
2309
|
+
const messages = useMemo(
|
|
2310
|
+
() => meeting.chatMessages().filter((row) => !row.targetUserIds?.length),
|
|
2311
|
+
[meeting, tick]
|
|
2312
|
+
);
|
|
2313
|
+
const pinned = useMemo(() => meeting.pinnedChatMessages(), [meeting, tick]);
|
|
2314
|
+
const limit = meeting.chatMaxTextLimit();
|
|
2315
|
+
const pictures = useMemo(() => {
|
|
2316
|
+
const map = /* @__PURE__ */ new Map();
|
|
2317
|
+
for (const row of participants) {
|
|
2318
|
+
if (row.userId) map.set(row.userId, row.picture);
|
|
2319
|
+
}
|
|
2320
|
+
return map;
|
|
2321
|
+
}, [participants]);
|
|
2322
|
+
const groups = useMemo(
|
|
2323
|
+
() => groupChatMessages(messages, selfUserId),
|
|
2324
|
+
[messages, selfUserId]
|
|
2325
|
+
);
|
|
2326
|
+
useEffect(() => {
|
|
2327
|
+
const list = listRef.current;
|
|
2328
|
+
if (!list) return;
|
|
2329
|
+
list.scrollTop = list.scrollHeight;
|
|
2330
|
+
}, [messages.length]);
|
|
2331
|
+
async function onSend() {
|
|
2332
|
+
const text = draft.trim();
|
|
2333
|
+
if (!text) return;
|
|
2334
|
+
setDraft("");
|
|
2335
|
+
try {
|
|
2336
|
+
if (editingId) {
|
|
2337
|
+
await meeting.editChatMessage(editingId, text);
|
|
2338
|
+
setEditingId(null);
|
|
2339
|
+
return;
|
|
2340
|
+
}
|
|
2341
|
+
await meeting.sendChatMessage(text);
|
|
2342
|
+
} catch (error) {
|
|
2343
|
+
const message = error instanceof Error ? error.message.toLowerCase() : "";
|
|
2344
|
+
toast.error(
|
|
2345
|
+
message.includes("rate") ? labels.chatRateLimited : labels.chatError
|
|
2346
|
+
);
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2349
|
+
function messageActions(row, mine) {
|
|
2350
|
+
return /* @__PURE__ */ jsxs(
|
|
2351
|
+
"p",
|
|
2352
|
+
{
|
|
2353
|
+
className: mine ? "text-muted-foreground flex flex-wrap justify-end gap-2 text-xs" : "text-muted-foreground flex flex-wrap gap-2 text-xs",
|
|
2354
|
+
children: [
|
|
2355
|
+
row.isEdited ? /* @__PURE__ */ jsx("span", { children: labels.chatEdited }) : null,
|
|
2356
|
+
mine ? /* @__PURE__ */ jsx(
|
|
2357
|
+
"button",
|
|
2358
|
+
{
|
|
2359
|
+
type: "button",
|
|
2360
|
+
onClick: () => {
|
|
2361
|
+
setEditingId(row.id);
|
|
2362
|
+
setDraft(row.text);
|
|
2363
|
+
},
|
|
2364
|
+
children: labels.chatEdit
|
|
2365
|
+
}
|
|
2366
|
+
) : null,
|
|
2367
|
+
isModerator2 || mine ? /* @__PURE__ */ jsx(
|
|
2368
|
+
"button",
|
|
2369
|
+
{
|
|
2370
|
+
type: "button",
|
|
2371
|
+
onClick: () => void meeting.deleteChatMessage(row.id).catch(() => toast.error(labels.chatError)),
|
|
2372
|
+
children: labels.chatDelete
|
|
2373
|
+
}
|
|
2374
|
+
) : null,
|
|
2375
|
+
isModerator2 ? /* @__PURE__ */ jsx(
|
|
2376
|
+
"button",
|
|
2377
|
+
{
|
|
2378
|
+
type: "button",
|
|
2379
|
+
onClick: () => void meeting.pinChatMessage(row.id, row.pinned !== true).catch(() => toast.error(labels.chatError)),
|
|
2380
|
+
children: row.pinned ? labels.chatUnpin : labels.chatPin
|
|
2381
|
+
}
|
|
2382
|
+
) : null
|
|
2383
|
+
]
|
|
2384
|
+
}
|
|
2385
|
+
);
|
|
2386
|
+
}
|
|
2387
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 flex-col", children: [
|
|
2388
|
+
pinned[0] ? /* @__PURE__ */ jsxs("div", { role: "status", className: "bg-muted border-b px-3 py-2 text-sm", children: [
|
|
2389
|
+
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs", children: labels.chatPinned }),
|
|
2390
|
+
/* @__PURE__ */ jsx("p", { children: pinned[0].text })
|
|
2391
|
+
] }) : null,
|
|
2392
|
+
/* @__PURE__ */ jsxs(
|
|
2393
|
+
"div",
|
|
2394
|
+
{
|
|
2395
|
+
ref: listRef,
|
|
2396
|
+
role: "log",
|
|
2397
|
+
"aria-live": "polite",
|
|
2398
|
+
className: "flex min-h-0 flex-1 flex-col gap-3 overflow-auto p-3",
|
|
2399
|
+
children: [
|
|
2400
|
+
/* @__PURE__ */ jsx(
|
|
2401
|
+
Button,
|
|
2402
|
+
{
|
|
2403
|
+
type: "button",
|
|
2404
|
+
size: "xs",
|
|
2405
|
+
variant: "ghost",
|
|
2406
|
+
className: "self-center",
|
|
2407
|
+
onClick: () => {
|
|
2408
|
+
const oldest = messages[0]?.time;
|
|
2409
|
+
void meeting.fetchChatHistory({
|
|
2410
|
+
timestamp: oldest
|
|
2411
|
+
});
|
|
2412
|
+
},
|
|
2413
|
+
children: labels.chatLoadOlder
|
|
2414
|
+
}
|
|
2415
|
+
),
|
|
2416
|
+
groups.map((group) => {
|
|
2417
|
+
const bubbles = group.rows.map((row) => /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-col gap-1", children: [
|
|
2418
|
+
/* @__PURE__ */ jsx(
|
|
2419
|
+
Bubble,
|
|
2420
|
+
{
|
|
2421
|
+
align: group.mine ? "end" : "start",
|
|
2422
|
+
variant: group.mine ? "default" : "secondary",
|
|
2423
|
+
children: /* @__PURE__ */ jsx(BubbleContent, { children: row.text })
|
|
2424
|
+
}
|
|
2425
|
+
),
|
|
2426
|
+
messageActions(row, group.mine)
|
|
2427
|
+
] }, row.id));
|
|
2428
|
+
if (group.mine) {
|
|
2429
|
+
return /* @__PURE__ */ jsx(BubbleGroup, { className: "items-end", children: bubbles }, group.rows[0].id);
|
|
2430
|
+
}
|
|
2431
|
+
const picture = pictures.get(group.userId) ?? null;
|
|
2432
|
+
return /* @__PURE__ */ jsxs(
|
|
2433
|
+
"div",
|
|
2434
|
+
{
|
|
2435
|
+
className: "flex max-w-full gap-2 self-start",
|
|
2436
|
+
children: [
|
|
2437
|
+
picture ? /* @__PURE__ */ jsx(
|
|
2438
|
+
"img",
|
|
2439
|
+
{
|
|
2440
|
+
src: picture,
|
|
2441
|
+
alt: "",
|
|
2442
|
+
className: "mt-0.5 size-7 shrink-0 rounded-full object-cover"
|
|
2443
|
+
}
|
|
2444
|
+
) : /* @__PURE__ */ jsx("span", { className: "bg-muted text-muted-foreground mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-full text-xs", children: senderInitials(group.name) }),
|
|
2445
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
2446
|
+
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground mb-1 text-xs", children: group.name }),
|
|
2447
|
+
/* @__PURE__ */ jsx(BubbleGroup, { children: bubbles })
|
|
2448
|
+
] })
|
|
2449
|
+
]
|
|
2450
|
+
},
|
|
2451
|
+
group.rows[0].id
|
|
2452
|
+
);
|
|
2453
|
+
})
|
|
2454
|
+
]
|
|
2455
|
+
}
|
|
2456
|
+
),
|
|
2457
|
+
/* @__PURE__ */ jsxs(
|
|
2458
|
+
"form",
|
|
2459
|
+
{
|
|
2460
|
+
className: "flex flex-col gap-2 border-t p-3",
|
|
2461
|
+
onSubmit: (event) => {
|
|
2462
|
+
event.preventDefault();
|
|
2463
|
+
void onSend();
|
|
2464
|
+
},
|
|
2465
|
+
children: [
|
|
2466
|
+
/* @__PURE__ */ jsx(
|
|
2467
|
+
Input,
|
|
2468
|
+
{
|
|
2469
|
+
className: "min-w-0 flex-1",
|
|
2470
|
+
value: draft,
|
|
2471
|
+
maxLength: limit,
|
|
2472
|
+
onChange: (event) => setDraft(event.target.value),
|
|
2473
|
+
placeholder: labels.chatPlaceholder,
|
|
2474
|
+
"aria-label": labels.chatPlaceholder
|
|
2475
|
+
}
|
|
2476
|
+
),
|
|
2477
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
|
|
2478
|
+
/* @__PURE__ */ jsxs("p", { className: "text-muted-foreground text-xs", children: [
|
|
2479
|
+
draft.length,
|
|
2480
|
+
"/",
|
|
2481
|
+
limit
|
|
2482
|
+
] }),
|
|
2483
|
+
/* @__PURE__ */ jsx(Button, { type: "submit", size: "sm", children: editingId ? labels.chatSave : labels.chatSend })
|
|
2484
|
+
] })
|
|
2485
|
+
]
|
|
2486
|
+
}
|
|
2487
|
+
)
|
|
2488
|
+
] });
|
|
2489
|
+
}
|
|
2490
|
+
function StudioWaitingRoom({
|
|
2491
|
+
waitlisted,
|
|
2492
|
+
labels,
|
|
2493
|
+
onAdmitAs,
|
|
2494
|
+
onAdmitAll,
|
|
2495
|
+
onDeny
|
|
2496
|
+
}) {
|
|
2497
|
+
return /* @__PURE__ */ jsxs("section", { "aria-label": labels.waiting, className: "space-y-3", children: [
|
|
2498
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: labels.waiting }),
|
|
2499
|
+
waitlisted.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: labels.noWaiting }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2500
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
|
|
2501
|
+
/* @__PURE__ */ jsx(
|
|
2502
|
+
Button,
|
|
2503
|
+
{
|
|
2504
|
+
type: "button",
|
|
2505
|
+
size: "xs",
|
|
2506
|
+
className: "w-full",
|
|
2507
|
+
onClick: () => onAdmitAll("panelist"),
|
|
2508
|
+
children: labels.admitAllPanelists
|
|
2509
|
+
}
|
|
2510
|
+
),
|
|
2511
|
+
/* @__PURE__ */ jsx(
|
|
2512
|
+
Button,
|
|
2513
|
+
{
|
|
2514
|
+
type: "button",
|
|
2515
|
+
size: "xs",
|
|
2516
|
+
variant: "outline",
|
|
2517
|
+
className: "w-full",
|
|
2518
|
+
onClick: () => onAdmitAll("audience"),
|
|
2519
|
+
children: labels.admitAllAudience
|
|
2520
|
+
}
|
|
2521
|
+
)
|
|
2522
|
+
] }),
|
|
2523
|
+
/* @__PURE__ */ jsx("ul", { className: "space-y-2", children: waitlisted.map((guest) => /* @__PURE__ */ jsxs("li", { className: "space-y-2 rounded-md border p-2", children: [
|
|
2524
|
+
/* @__PURE__ */ jsx("span", { className: "block truncate text-sm", children: guest.name }),
|
|
2525
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
|
|
2526
|
+
/* @__PURE__ */ jsx(
|
|
2527
|
+
Button,
|
|
2528
|
+
{
|
|
2529
|
+
type: "button",
|
|
2530
|
+
size: "xs",
|
|
2531
|
+
className: "w-full",
|
|
2532
|
+
onClick: () => onAdmitAs(guest.id, "panelist"),
|
|
2533
|
+
children: labels.admitAsPanelist
|
|
2534
|
+
}
|
|
2535
|
+
),
|
|
2536
|
+
/* @__PURE__ */ jsx(
|
|
2537
|
+
Button,
|
|
2538
|
+
{
|
|
2539
|
+
type: "button",
|
|
2540
|
+
size: "xs",
|
|
2541
|
+
variant: "outline",
|
|
2542
|
+
className: "w-full",
|
|
2543
|
+
onClick: () => onAdmitAs(guest.id, "audience"),
|
|
2544
|
+
children: labels.admitAsAudience
|
|
2545
|
+
}
|
|
2546
|
+
),
|
|
2547
|
+
/* @__PURE__ */ jsx(
|
|
2548
|
+
Button,
|
|
2549
|
+
{
|
|
2550
|
+
type: "button",
|
|
2551
|
+
size: "xs",
|
|
2552
|
+
variant: "ghost",
|
|
2553
|
+
className: "w-full",
|
|
2554
|
+
onClick: () => onDeny(guest.id),
|
|
2555
|
+
children: labels.deny
|
|
2556
|
+
}
|
|
2557
|
+
)
|
|
2558
|
+
] })
|
|
2559
|
+
] }, guest.id)) })
|
|
2560
|
+
] })
|
|
2561
|
+
] });
|
|
2562
|
+
}
|
|
2563
|
+
function StudioSidePanel({
|
|
2564
|
+
tab,
|
|
2565
|
+
onTabChange,
|
|
2566
|
+
onClose,
|
|
2567
|
+
meeting,
|
|
2568
|
+
selfUserId,
|
|
2569
|
+
participants,
|
|
2570
|
+
waitlisted,
|
|
2571
|
+
stageRequests,
|
|
2572
|
+
isModerator: isModerator2,
|
|
2573
|
+
chatBadge,
|
|
2574
|
+
peopleBadge,
|
|
2575
|
+
labels,
|
|
2576
|
+
onAdmitAs,
|
|
2577
|
+
onAdmitAll,
|
|
2578
|
+
onDeny,
|
|
2579
|
+
onGrantStage,
|
|
2580
|
+
onDenyStage,
|
|
2581
|
+
onTakeOffAir,
|
|
2582
|
+
guestUrl,
|
|
2583
|
+
onShareGuestLink
|
|
2584
|
+
}) {
|
|
2585
|
+
const panelists = participants.filter((row) => isOnStage(row.stageStatus));
|
|
2586
|
+
const audience = participants.filter((row) => !isOnStage(row.stageStatus));
|
|
2587
|
+
return /* @__PURE__ */ jsxs("aside", { className: "bg-background flex min-h-0 w-full flex-1 flex-col border-t md:w-80 md:flex-none md:border-l md:border-t-0", children: [
|
|
2588
|
+
/* @__PURE__ */ jsxs("div", { className: "flex border-b", children: [
|
|
2589
|
+
/* @__PURE__ */ jsxs(
|
|
2590
|
+
Button,
|
|
2591
|
+
{
|
|
2592
|
+
type: "button",
|
|
2593
|
+
size: "sm",
|
|
2594
|
+
variant: tab === "chat" ? "secondary" : "ghost",
|
|
2595
|
+
className: "relative flex-1 rounded-none",
|
|
2596
|
+
onClick: () => onTabChange("chat"),
|
|
2597
|
+
children: [
|
|
2598
|
+
labels.chat,
|
|
2599
|
+
chatBadge > 0 ? /* @__PURE__ */ jsx("span", { className: "bg-primary text-primary-foreground ml-2 inline-flex h-4 min-w-4 items-center justify-center rounded-full px-1 text-[0.65rem]", children: chatBadge }) : null
|
|
2600
|
+
]
|
|
2601
|
+
}
|
|
2602
|
+
),
|
|
2603
|
+
isModerator2 ? /* @__PURE__ */ jsxs(
|
|
2604
|
+
Button,
|
|
2605
|
+
{
|
|
2606
|
+
type: "button",
|
|
2607
|
+
size: "sm",
|
|
2608
|
+
variant: tab === "people" ? "secondary" : "ghost",
|
|
2609
|
+
className: "relative flex-1 rounded-none",
|
|
2610
|
+
onClick: () => onTabChange("people"),
|
|
2611
|
+
children: [
|
|
2612
|
+
labels.people,
|
|
2613
|
+
peopleBadge > 0 ? /* @__PURE__ */ jsx("span", { className: "bg-primary text-primary-foreground ml-2 inline-flex h-4 min-w-4 items-center justify-center rounded-full px-1 text-[0.65rem]", children: peopleBadge }) : null
|
|
2614
|
+
]
|
|
2615
|
+
}
|
|
2616
|
+
) : null,
|
|
2617
|
+
/* @__PURE__ */ jsx(
|
|
2618
|
+
Button,
|
|
2619
|
+
{
|
|
2620
|
+
type: "button",
|
|
2621
|
+
size: "icon-xs",
|
|
2622
|
+
variant: "ghost",
|
|
2623
|
+
className: "m-1 shrink-0",
|
|
2624
|
+
"aria-label": labels.closePanel,
|
|
2625
|
+
onClick: onClose,
|
|
2626
|
+
children: /* @__PURE__ */ jsx(X, {})
|
|
2627
|
+
}
|
|
2628
|
+
)
|
|
2629
|
+
] }),
|
|
2630
|
+
tab === "chat" ? /* @__PURE__ */ jsx(
|
|
2631
|
+
StudioChatPanel,
|
|
2632
|
+
{
|
|
2633
|
+
meeting,
|
|
2634
|
+
selfUserId,
|
|
2635
|
+
participants,
|
|
2636
|
+
isModerator: isModerator2,
|
|
2637
|
+
labels
|
|
2638
|
+
}
|
|
2639
|
+
) : /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 flex-col gap-4 overflow-auto p-3", children: [
|
|
2640
|
+
guestUrl ? /* @__PURE__ */ jsxs(
|
|
2641
|
+
Button,
|
|
2642
|
+
{
|
|
2643
|
+
type: "button",
|
|
2644
|
+
variant: "outline",
|
|
2645
|
+
className: "w-full",
|
|
2646
|
+
onClick: onShareGuestLink,
|
|
2647
|
+
children: [
|
|
2648
|
+
/* @__PURE__ */ jsx(Link2, {}),
|
|
2649
|
+
labels.shareLink
|
|
2650
|
+
]
|
|
2651
|
+
}
|
|
2652
|
+
) : null,
|
|
2653
|
+
/* @__PURE__ */ jsx(
|
|
2654
|
+
StudioWaitingRoom,
|
|
2655
|
+
{
|
|
2656
|
+
waitlisted,
|
|
2657
|
+
labels,
|
|
2658
|
+
onAdmitAs,
|
|
2659
|
+
onAdmitAll,
|
|
2660
|
+
onDeny
|
|
2661
|
+
}
|
|
2662
|
+
),
|
|
2663
|
+
stageRequests.length > 0 ? /* @__PURE__ */ jsxs("section", { "aria-label": labels.stageRequests, className: "space-y-2", children: [
|
|
2664
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: labels.stageRequests }),
|
|
2665
|
+
/* @__PURE__ */ jsx("ul", { className: "space-y-2", children: stageRequests.map((request) => /* @__PURE__ */ jsxs(
|
|
2666
|
+
"li",
|
|
2667
|
+
{
|
|
2668
|
+
className: "space-y-2 rounded-md border p-2",
|
|
2669
|
+
children: [
|
|
2670
|
+
/* @__PURE__ */ jsx("span", { className: "block truncate text-sm", children: request.name }),
|
|
2671
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
|
|
2672
|
+
/* @__PURE__ */ jsx(
|
|
2673
|
+
Button,
|
|
2674
|
+
{
|
|
2675
|
+
type: "button",
|
|
2676
|
+
size: "xs",
|
|
2677
|
+
className: "w-full",
|
|
2678
|
+
onClick: () => onGrantStage(request.userId),
|
|
2679
|
+
children: labels.bringOnAir
|
|
2680
|
+
}
|
|
2681
|
+
),
|
|
2682
|
+
/* @__PURE__ */ jsx(
|
|
2683
|
+
Button,
|
|
2684
|
+
{
|
|
2685
|
+
type: "button",
|
|
2686
|
+
size: "xs",
|
|
2687
|
+
variant: "outline",
|
|
2688
|
+
className: "w-full",
|
|
2689
|
+
onClick: () => onDenyStage(request.userId),
|
|
2690
|
+
children: labels.deny
|
|
2691
|
+
}
|
|
2692
|
+
)
|
|
2693
|
+
] })
|
|
2694
|
+
]
|
|
2695
|
+
},
|
|
2696
|
+
request.userId
|
|
2697
|
+
)) })
|
|
2698
|
+
] }) : null,
|
|
2699
|
+
/* @__PURE__ */ jsxs("section", { "aria-label": labels.panelists, className: "space-y-2", children: [
|
|
2700
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: labels.panelists }),
|
|
2701
|
+
panelists.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: labels.noPanelists }) : /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: panelists.map((row) => {
|
|
2702
|
+
const userId = row.userId;
|
|
2703
|
+
return /* @__PURE__ */ jsxs(
|
|
2704
|
+
"li",
|
|
2705
|
+
{
|
|
2706
|
+
className: "space-y-2 rounded-md border p-2",
|
|
2707
|
+
children: [
|
|
2708
|
+
/* @__PURE__ */ jsx("span", { className: "block truncate text-sm", children: row.name }),
|
|
2709
|
+
!row.isSelf && userId ? /* @__PURE__ */ jsx(
|
|
2710
|
+
Button,
|
|
2711
|
+
{
|
|
2712
|
+
type: "button",
|
|
2713
|
+
size: "xs",
|
|
2714
|
+
variant: "outline",
|
|
2715
|
+
className: "w-full",
|
|
2716
|
+
onClick: () => onTakeOffAir(userId),
|
|
2717
|
+
children: labels.offAir
|
|
2718
|
+
}
|
|
2719
|
+
) : null
|
|
2720
|
+
]
|
|
2721
|
+
},
|
|
2722
|
+
row.id
|
|
2723
|
+
);
|
|
2724
|
+
}) })
|
|
2725
|
+
] }),
|
|
2726
|
+
/* @__PURE__ */ jsxs("section", { "aria-label": labels.audience, className: "space-y-2", children: [
|
|
2727
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: labels.audience }),
|
|
2728
|
+
audience.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: labels.noAudience }) : /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: audience.map((row) => {
|
|
2729
|
+
const userId = row.userId;
|
|
2730
|
+
return /* @__PURE__ */ jsxs(
|
|
2731
|
+
"li",
|
|
2732
|
+
{
|
|
2733
|
+
className: "space-y-2 rounded-md border p-2",
|
|
2734
|
+
children: [
|
|
2735
|
+
/* @__PURE__ */ jsx("span", { className: "block truncate text-sm", children: row.name }),
|
|
2736
|
+
!row.isSelf && userId ? /* @__PURE__ */ jsx(
|
|
2737
|
+
Button,
|
|
2738
|
+
{
|
|
2739
|
+
type: "button",
|
|
2740
|
+
size: "xs",
|
|
2741
|
+
variant: "outline",
|
|
2742
|
+
className: "w-full",
|
|
2743
|
+
onClick: () => onGrantStage(userId),
|
|
2744
|
+
children: labels.bringOnAir
|
|
2745
|
+
}
|
|
2746
|
+
) : null
|
|
2747
|
+
]
|
|
2748
|
+
},
|
|
2749
|
+
row.id
|
|
2750
|
+
);
|
|
2751
|
+
}) })
|
|
2752
|
+
] })
|
|
2753
|
+
] })
|
|
2556
2754
|
] });
|
|
2557
2755
|
}
|
|
2558
|
-
function
|
|
2559
|
-
return /* @__PURE__ */ jsx(
|
|
2560
|
-
"div",
|
|
2561
|
-
{
|
|
2562
|
-
"data-slot": "dialog-header",
|
|
2563
|
-
className: cn("flex flex-col gap-2 text-center sm:text-left", className),
|
|
2564
|
-
...props
|
|
2565
|
-
}
|
|
2566
|
-
);
|
|
2756
|
+
function DropdownMenu({ ...props }) {
|
|
2757
|
+
return /* @__PURE__ */ jsx(Menu.Root, { "data-slot": "dropdown-menu", ...props });
|
|
2567
2758
|
}
|
|
2568
|
-
function
|
|
2759
|
+
function DropdownMenuTrigger({ ...props }) {
|
|
2760
|
+
return /* @__PURE__ */ jsx(Menu.Trigger, { "data-slot": "dropdown-menu-trigger", ...props });
|
|
2761
|
+
}
|
|
2762
|
+
function DropdownMenuContent({
|
|
2763
|
+
align = "start",
|
|
2764
|
+
alignOffset = 0,
|
|
2765
|
+
side = "bottom",
|
|
2766
|
+
sideOffset = 4,
|
|
2569
2767
|
className,
|
|
2570
2768
|
...props
|
|
2571
2769
|
}) {
|
|
2572
|
-
return /* @__PURE__ */ jsx(
|
|
2573
|
-
|
|
2770
|
+
return /* @__PURE__ */ jsx(Menu.Portal, { children: /* @__PURE__ */ jsx(
|
|
2771
|
+
Menu.Positioner,
|
|
2574
2772
|
{
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2773
|
+
className: "isolate z-50 outline-none",
|
|
2774
|
+
align,
|
|
2775
|
+
alignOffset,
|
|
2776
|
+
side,
|
|
2777
|
+
sideOffset,
|
|
2778
|
+
children: /* @__PURE__ */ jsx(
|
|
2779
|
+
Menu.Popup,
|
|
2780
|
+
{
|
|
2781
|
+
"data-slot": "dropdown-menu-content",
|
|
2782
|
+
className: cn(
|
|
2783
|
+
"z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:overflow-hidden data-closed:fade-out-0 data-closed:zoom-out-95",
|
|
2784
|
+
className
|
|
2785
|
+
),
|
|
2786
|
+
...props
|
|
2787
|
+
}
|
|
2788
|
+
)
|
|
2578
2789
|
}
|
|
2579
|
-
);
|
|
2790
|
+
) });
|
|
2580
2791
|
}
|
|
2581
|
-
function
|
|
2792
|
+
function DropdownMenuItem({
|
|
2582
2793
|
className,
|
|
2794
|
+
inset,
|
|
2795
|
+
variant = "default",
|
|
2583
2796
|
...props
|
|
2584
2797
|
}) {
|
|
2585
2798
|
return /* @__PURE__ */ jsx(
|
|
2586
|
-
|
|
2587
|
-
{
|
|
2588
|
-
"data-slot": "dialog-description",
|
|
2589
|
-
className: cn("text-muted-foreground text-sm", className),
|
|
2590
|
-
...props
|
|
2591
|
-
}
|
|
2592
|
-
);
|
|
2593
|
-
}
|
|
2594
|
-
function Input({ className, type, ...props }) {
|
|
2595
|
-
return /* @__PURE__ */ jsx(
|
|
2596
|
-
Input$1,
|
|
2799
|
+
Menu.Item,
|
|
2597
2800
|
{
|
|
2598
|
-
|
|
2599
|
-
"data-
|
|
2801
|
+
"data-slot": "dropdown-menu-item",
|
|
2802
|
+
"data-inset": inset,
|
|
2803
|
+
"data-variant": variant,
|
|
2600
2804
|
className: cn(
|
|
2601
|
-
"
|
|
2805
|
+
"group/dropdown-menu-item relative flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive",
|
|
2602
2806
|
className
|
|
2603
2807
|
),
|
|
2604
2808
|
...props
|
|
2605
2809
|
}
|
|
2606
2810
|
);
|
|
2607
2811
|
}
|
|
2608
|
-
function
|
|
2812
|
+
function DropdownMenuSeparator({
|
|
2609
2813
|
className,
|
|
2610
2814
|
...props
|
|
2611
2815
|
}) {
|
|
2612
2816
|
return /* @__PURE__ */ jsx(
|
|
2613
|
-
|
|
2817
|
+
Menu.Separator,
|
|
2614
2818
|
{
|
|
2615
|
-
"data-slot": "
|
|
2616
|
-
className: cn(
|
|
2617
|
-
"flex items-center gap-2 text-sm leading-none font-medium select-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
|
2618
|
-
className
|
|
2619
|
-
),
|
|
2819
|
+
"data-slot": "dropdown-menu-separator",
|
|
2820
|
+
className: cn("-mx-1 my-1 h-px bg-border", className),
|
|
2620
2821
|
...props
|
|
2621
2822
|
}
|
|
2622
2823
|
);
|
|
2623
2824
|
}
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2825
|
+
function StudioTileMenu({
|
|
2826
|
+
tile,
|
|
2827
|
+
canModerate,
|
|
2828
|
+
canManageStage,
|
|
2829
|
+
takeOffBlocked,
|
|
2830
|
+
labels,
|
|
2831
|
+
onKick,
|
|
2832
|
+
onMute,
|
|
2833
|
+
onStopCamera,
|
|
2834
|
+
onStopScreen,
|
|
2835
|
+
onToggleStage,
|
|
2836
|
+
onTogglePin,
|
|
2837
|
+
onToggleChat
|
|
2838
|
+
}) {
|
|
2839
|
+
const onStage = isOnStage(tile.stageStatus);
|
|
2840
|
+
const showPersonItems = canModerate && tile.kind !== "screen";
|
|
2841
|
+
const showScreenItems = canModerate && tile.kind === "screen";
|
|
2842
|
+
const showStageItems = canManageStage && tile.kind !== "screen";
|
|
2843
|
+
if (!showPersonItems && !showScreenItems && !showStageItems) return null;
|
|
2844
|
+
return /* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
2845
|
+
/* @__PURE__ */ jsx(
|
|
2846
|
+
DropdownMenuTrigger,
|
|
2847
|
+
{
|
|
2848
|
+
render: /* @__PURE__ */ jsx(
|
|
2849
|
+
Button,
|
|
2850
|
+
{
|
|
2851
|
+
type: "button",
|
|
2852
|
+
size: "icon-xs",
|
|
2853
|
+
variant: "ghost",
|
|
2854
|
+
className: "text-white hover:text-white",
|
|
2855
|
+
"aria-label": labels.tileMenu
|
|
2856
|
+
}
|
|
2857
|
+
),
|
|
2858
|
+
children: /* @__PURE__ */ jsx(MoreVertical, { className: "size-3.5" })
|
|
2859
|
+
}
|
|
2860
|
+
),
|
|
2861
|
+
/* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", className: "min-w-44", children: [
|
|
2862
|
+
showScreenItems ? /* @__PURE__ */ jsx(DropdownMenuItem, { onClick: onStopScreen, children: labels.stopScreen }) : null,
|
|
2863
|
+
showPersonItems && tile.audioEnabled ? /* @__PURE__ */ jsx(DropdownMenuItem, { onClick: onMute, children: labels.mute }) : null,
|
|
2864
|
+
showPersonItems && tile.kind === "camera" && tile.videoEnabled ? /* @__PURE__ */ jsx(DropdownMenuItem, { onClick: onStopCamera, children: labels.stopCamera }) : null,
|
|
2865
|
+
showPersonItems || showScreenItems ? /* @__PURE__ */ jsx(DropdownMenuItem, { onClick: onTogglePin, children: tile.pinned ? labels.unpin : labels.pin }) : null,
|
|
2866
|
+
showPersonItems ? /* @__PURE__ */ jsx(DropdownMenuItem, { onClick: onToggleChat, children: tile.chatCanSend === false ? labels.allowChat : labels.muteChat }) : null,
|
|
2867
|
+
showStageItems ? /* @__PURE__ */ jsx(
|
|
2868
|
+
DropdownMenuItem,
|
|
2869
|
+
{
|
|
2870
|
+
disabled: onStage && takeOffBlocked,
|
|
2871
|
+
onClick: onToggleStage,
|
|
2872
|
+
children: onStage ? takeOffBlocked ? labels.offAirLastVisible : labels.takeOffAir : labels.bringOnAir
|
|
2873
|
+
}
|
|
2874
|
+
) : null,
|
|
2875
|
+
showPersonItems ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2876
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
2877
|
+
/* @__PURE__ */ jsx(DropdownMenuItem, { variant: "destructive", onClick: onKick, children: labels.kick })
|
|
2878
|
+
] }) : null
|
|
2879
|
+
] })
|
|
2880
|
+
] });
|
|
2662
2881
|
}
|
|
2663
|
-
function
|
|
2664
|
-
|
|
2665
|
-
onOpenChange,
|
|
2882
|
+
function StudioTile({
|
|
2883
|
+
tile,
|
|
2666
2884
|
meeting,
|
|
2667
|
-
labels,
|
|
2668
2885
|
reducedMotion,
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
const
|
|
2685
|
-
const [
|
|
2686
|
-
const
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
/* @__PURE__ */ jsx(DialogDescription, { children: labels.settingsDescription })
|
|
2755
|
-
] }),
|
|
2756
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2757
|
-
/* @__PURE__ */ jsx(Label, { htmlFor: "studio-display-name", children: labels.guestName }),
|
|
2758
|
-
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
2886
|
+
compact,
|
|
2887
|
+
fill = false,
|
|
2888
|
+
canModerate,
|
|
2889
|
+
canManageStage,
|
|
2890
|
+
takeOffBlocked,
|
|
2891
|
+
confirmTakeOff,
|
|
2892
|
+
labels,
|
|
2893
|
+
onKick,
|
|
2894
|
+
onMute,
|
|
2895
|
+
onStopCamera,
|
|
2896
|
+
onStopScreen,
|
|
2897
|
+
onToggleStage,
|
|
2898
|
+
onTogglePin,
|
|
2899
|
+
onToggleChat
|
|
2900
|
+
}) {
|
|
2901
|
+
const onStage = isOnStage(tile.stageStatus);
|
|
2902
|
+
const [confirm, setConfirm] = useState(null);
|
|
2903
|
+
const showMenu = (canModerate || canManageStage) && !tile.isSelf;
|
|
2904
|
+
return /* @__PURE__ */ jsxs(
|
|
2905
|
+
"article",
|
|
2906
|
+
{
|
|
2907
|
+
"aria-label": tile.name,
|
|
2908
|
+
className: cn(
|
|
2909
|
+
"bg-muted relative overflow-hidden rounded-xl",
|
|
2910
|
+
compact ? "aspect-video w-40 shrink-0 md:w-full" : tile.kind === "screen" ? "min-h-0 flex-1 md:min-h-48" : fill ? "size-full min-h-0 md:aspect-video md:h-auto" : "aspect-video"
|
|
2911
|
+
),
|
|
2912
|
+
children: [
|
|
2913
|
+
tile.kind === "idle" ? /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 flex flex-col items-center justify-center gap-3", children: [
|
|
2914
|
+
tile.picture ? /* @__PURE__ */ jsx(
|
|
2915
|
+
"img",
|
|
2916
|
+
{
|
|
2917
|
+
src: tile.picture,
|
|
2918
|
+
alt: "",
|
|
2919
|
+
className: "size-16 rounded-full object-cover"
|
|
2920
|
+
}
|
|
2921
|
+
) : /* @__PURE__ */ jsx("div", { className: "bg-background flex size-16 items-center justify-center rounded-full text-xl", children: tile.name.slice(0, 1).toUpperCase() }),
|
|
2922
|
+
/* @__PURE__ */ jsx(
|
|
2923
|
+
AudioWaveform,
|
|
2924
|
+
{
|
|
2925
|
+
track: tile.isSelf ? meeting.localAudioTrack() : null,
|
|
2926
|
+
reducedMotion
|
|
2927
|
+
}
|
|
2928
|
+
)
|
|
2929
|
+
] }) : /* @__PURE__ */ jsx(
|
|
2930
|
+
StudioTileVideo,
|
|
2931
|
+
{
|
|
2932
|
+
meeting,
|
|
2933
|
+
participantId: tile.id,
|
|
2934
|
+
kind: tile.kind,
|
|
2935
|
+
mirror: tile.kind === "camera" && tile.isSelf,
|
|
2936
|
+
contain: tile.kind === "screen"
|
|
2937
|
+
}
|
|
2938
|
+
),
|
|
2939
|
+
/* @__PURE__ */ jsxs("div", { className: "absolute inset-x-0 bottom-0 z-10 flex items-end justify-between gap-2 bg-linear-to-t from-black/70 to-transparent p-2", children: [
|
|
2940
|
+
/* @__PURE__ */ jsxs("p", { className: "min-w-0 truncate text-xs font-medium text-white", children: [
|
|
2941
|
+
tile.name,
|
|
2942
|
+
!onStage ? /* @__PURE__ */ jsx("span", { className: "ml-1 rounded-full border border-white/40 px-1.5 py-0.5 text-[0.65rem]", children: labels.offAir }) : null
|
|
2943
|
+
] }),
|
|
2944
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
2945
|
+
/* @__PURE__ */ jsx(TileStatusIcons, { tile, labels }),
|
|
2946
|
+
showMenu ? /* @__PURE__ */ jsx(
|
|
2947
|
+
StudioTileMenu,
|
|
2948
|
+
{
|
|
2949
|
+
tile,
|
|
2950
|
+
canModerate,
|
|
2951
|
+
canManageStage,
|
|
2952
|
+
takeOffBlocked,
|
|
2953
|
+
labels,
|
|
2954
|
+
onKick: () => setConfirm("kick"),
|
|
2955
|
+
onMute,
|
|
2956
|
+
onStopCamera,
|
|
2957
|
+
onStopScreen,
|
|
2958
|
+
onToggleStage: () => {
|
|
2959
|
+
if (onStage && confirmTakeOff && !takeOffBlocked) {
|
|
2960
|
+
setConfirm("takeOff");
|
|
2961
|
+
return;
|
|
2962
|
+
}
|
|
2963
|
+
onToggleStage();
|
|
2964
|
+
},
|
|
2965
|
+
onTogglePin,
|
|
2966
|
+
onToggleChat
|
|
2967
|
+
}
|
|
2968
|
+
) : null
|
|
2969
|
+
] })
|
|
2970
|
+
] }),
|
|
2759
2971
|
/* @__PURE__ */ jsx(
|
|
2760
|
-
|
|
2972
|
+
ConfirmAction,
|
|
2761
2973
|
{
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2974
|
+
open: confirm === "kick",
|
|
2975
|
+
onOpenChange: (open) => {
|
|
2976
|
+
if (!open) setConfirm(null);
|
|
2977
|
+
},
|
|
2978
|
+
triggerLabel: labels.kick,
|
|
2979
|
+
title: labels.kickTitle,
|
|
2980
|
+
description: labels.kickDescription,
|
|
2981
|
+
confirmLabel: labels.kickConfirm,
|
|
2982
|
+
onConfirm: () => {
|
|
2983
|
+
setConfirm(null);
|
|
2984
|
+
onKick();
|
|
2985
|
+
}
|
|
2766
2986
|
}
|
|
2767
2987
|
),
|
|
2768
2988
|
/* @__PURE__ */ jsx(
|
|
2769
|
-
|
|
2989
|
+
ConfirmAction,
|
|
2770
2990
|
{
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2991
|
+
open: confirm === "takeOff",
|
|
2992
|
+
onOpenChange: (open) => {
|
|
2993
|
+
if (!open) setConfirm(null);
|
|
2994
|
+
},
|
|
2995
|
+
triggerLabel: labels.takeOffAir,
|
|
2996
|
+
title: labels.takeOffAirTitle,
|
|
2997
|
+
description: labels.takeOffAirDescription,
|
|
2998
|
+
confirmLabel: labels.takeOffAirConfirm,
|
|
2999
|
+
onConfirm: () => {
|
|
3000
|
+
setConfirm(null);
|
|
3001
|
+
onToggleStage();
|
|
3002
|
+
}
|
|
2776
3003
|
}
|
|
2777
3004
|
)
|
|
2778
|
-
]
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
SettingsCameraPreview,
|
|
2782
|
-
{
|
|
2783
|
-
track: videoTrack,
|
|
2784
|
-
open,
|
|
2785
|
-
cameraOn,
|
|
2786
|
-
cameraOffLabel: labels.cameraOff
|
|
2787
|
-
}
|
|
2788
|
-
),
|
|
2789
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
2790
|
-
/* @__PURE__ */ jsx("p", { className: "sr-only", children: labels.micLevel }),
|
|
2791
|
-
/* @__PURE__ */ jsx(
|
|
2792
|
-
AudioWaveform,
|
|
2793
|
-
{
|
|
2794
|
-
track: audioTrack,
|
|
2795
|
-
reducedMotion,
|
|
2796
|
-
className: "w-full"
|
|
2797
|
-
}
|
|
2798
|
-
)
|
|
2799
|
-
] }),
|
|
2800
|
-
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-[8.5rem_minmax(0,1fr)] items-center gap-x-3 gap-y-3", children: [
|
|
2801
|
-
/* @__PURE__ */ jsx(
|
|
2802
|
-
DeviceRow,
|
|
2803
|
-
{
|
|
2804
|
-
icon: /* @__PURE__ */ jsx(Video, {}),
|
|
2805
|
-
label: labels.cameraDevice,
|
|
2806
|
-
options: devices.video,
|
|
2807
|
-
value: selected.video,
|
|
2808
|
-
onChange: (id) => void onChange("video", id)
|
|
2809
|
-
}
|
|
2810
|
-
),
|
|
2811
|
-
/* @__PURE__ */ jsx(
|
|
2812
|
-
DeviceRow,
|
|
2813
|
-
{
|
|
2814
|
-
icon: /* @__PURE__ */ jsx(Mic, {}),
|
|
2815
|
-
label: labels.micDevice,
|
|
2816
|
-
options: devices.audio,
|
|
2817
|
-
value: selected.audio,
|
|
2818
|
-
onChange: (id) => void onChange("audio", id)
|
|
2819
|
-
}
|
|
2820
|
-
),
|
|
2821
|
-
/* @__PURE__ */ jsx(
|
|
2822
|
-
DeviceRow,
|
|
2823
|
-
{
|
|
2824
|
-
icon: /* @__PURE__ */ jsx(Volume2, {}),
|
|
2825
|
-
label: labels.speakerDevice,
|
|
2826
|
-
options: devices.speaker,
|
|
2827
|
-
value: selected.speaker,
|
|
2828
|
-
onChange: (id) => void onChange("speaker", id),
|
|
2829
|
-
action: /* @__PURE__ */ jsx(
|
|
2830
|
-
Button,
|
|
2831
|
-
{
|
|
2832
|
-
type: "button",
|
|
2833
|
-
size: "sm",
|
|
2834
|
-
variant: "outline",
|
|
2835
|
-
disabled: !selected.speaker || testingSpeaker,
|
|
2836
|
-
onClick: () => void onTestSpeaker(),
|
|
2837
|
-
children: labels.testSpeaker
|
|
2838
|
-
}
|
|
2839
|
-
)
|
|
2840
|
-
}
|
|
2841
|
-
)
|
|
2842
|
-
] })
|
|
2843
|
-
] }) });
|
|
3005
|
+
]
|
|
3006
|
+
}
|
|
3007
|
+
);
|
|
2844
3008
|
}
|
|
2845
|
-
function
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
3009
|
+
function TileStatusIcons({
|
|
3010
|
+
tile,
|
|
3011
|
+
labels
|
|
3012
|
+
}) {
|
|
3013
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3014
|
+
tile.audioEnabled ? null : /* @__PURE__ */ jsx(StatusIcon, { label: labels.mute, children: /* @__PURE__ */ jsx(MicOff, {}) }),
|
|
3015
|
+
tile.kind !== "screen" && !tile.videoEnabled ? /* @__PURE__ */ jsx(StatusIcon, { label: labels.cameraOff, children: /* @__PURE__ */ jsx(VideoOff, {}) }) : null,
|
|
3016
|
+
tile.kind !== "screen" && tile.screenShareEnabled ? /* @__PURE__ */ jsx(StatusIcon, { label: labels.stopScreen, children: /* @__PURE__ */ jsx(MonitorUp, {}) }) : null,
|
|
3017
|
+
tile.pinned ? /* @__PURE__ */ jsx(StatusIcon, { label: labels.pin, children: /* @__PURE__ */ jsx(Pin, {}) }) : null,
|
|
3018
|
+
tile.chatCanSend === false ? /* @__PURE__ */ jsx(StatusIcon, { label: labels.muteChat, children: /* @__PURE__ */ jsx(MessageSquareOff, {}) }) : null
|
|
3019
|
+
] });
|
|
3020
|
+
}
|
|
3021
|
+
function StatusIcon({
|
|
3022
|
+
label,
|
|
3023
|
+
children
|
|
3024
|
+
}) {
|
|
3025
|
+
return /* @__PURE__ */ jsxs("span", { className: "inline-flex text-white", children: [
|
|
3026
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "[&_svg]:size-3.5", children }),
|
|
3027
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: label })
|
|
3028
|
+
] });
|
|
3029
|
+
}
|
|
3030
|
+
function StudioTileVideo({
|
|
3031
|
+
meeting,
|
|
3032
|
+
participantId,
|
|
3033
|
+
kind,
|
|
3034
|
+
mirror,
|
|
3035
|
+
contain
|
|
2850
3036
|
}) {
|
|
2851
3037
|
const ref = useRef(null);
|
|
2852
3038
|
useEffect(() => {
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
return;
|
|
2858
|
-
}
|
|
2859
|
-
el.srcObject = new MediaStream([track]);
|
|
2860
|
-
void el.play().catch(() => void 0);
|
|
2861
|
-
return () => {
|
|
2862
|
-
el.srcObject = null;
|
|
2863
|
-
};
|
|
2864
|
-
}, [cameraOn, open, track]);
|
|
2865
|
-
if (!cameraOn) {
|
|
2866
|
-
return /* @__PURE__ */ jsx("div", { className: "bg-muted flex aspect-video w-full items-center justify-center rounded-xl", children: /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: cameraOffLabel }) });
|
|
2867
|
-
}
|
|
2868
|
-
if (!track) {
|
|
2869
|
-
return /* @__PURE__ */ jsx("div", { className: "bg-muted aspect-video w-full rounded-xl", "aria-hidden": true });
|
|
2870
|
-
}
|
|
2871
|
-
return /* @__PURE__ */ jsx("div", { className: "bg-muted relative aspect-video w-full overflow-hidden rounded-xl", children: /* @__PURE__ */ jsx(
|
|
3039
|
+
meeting.attachVideo(participantId, ref.current, kind);
|
|
3040
|
+
return () => meeting.attachVideo(participantId, null, kind);
|
|
3041
|
+
}, [kind, meeting, participantId]);
|
|
3042
|
+
return /* @__PURE__ */ jsx(
|
|
2872
3043
|
"video",
|
|
2873
3044
|
{
|
|
2874
3045
|
ref,
|
|
2875
3046
|
autoPlay: true,
|
|
2876
|
-
muted: true,
|
|
2877
3047
|
playsInline: true,
|
|
2878
|
-
|
|
3048
|
+
muted: true,
|
|
3049
|
+
disablePictureInPicture: true,
|
|
3050
|
+
controlsList: "nodownload noplaybackrate noremoteplayback",
|
|
3051
|
+
className: cn(
|
|
3052
|
+
"absolute inset-0 size-full",
|
|
3053
|
+
"pointer-events-none",
|
|
3054
|
+
contain ? "object-contain" : "object-cover",
|
|
3055
|
+
mirror && "-scale-x-100"
|
|
3056
|
+
)
|
|
2879
3057
|
}
|
|
2880
|
-
)
|
|
2881
|
-
}
|
|
2882
|
-
function DeviceRow({
|
|
2883
|
-
icon,
|
|
2884
|
-
label,
|
|
2885
|
-
options,
|
|
2886
|
-
value,
|
|
2887
|
-
onChange,
|
|
2888
|
-
action
|
|
2889
|
-
}) {
|
|
2890
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2891
|
-
/* @__PURE__ */ jsxs("span", { className: "text-muted-foreground flex min-w-0 items-center gap-2 text-sm", children: [
|
|
2892
|
-
/* @__PURE__ */ jsx("span", { className: "[&_svg]:size-4", "aria-hidden": true, children: icon }),
|
|
2893
|
-
/* @__PURE__ */ jsx("span", { className: "truncate", children: label })
|
|
2894
|
-
] }),
|
|
2895
|
-
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
|
|
2896
|
-
/* @__PURE__ */ jsx(
|
|
2897
|
-
"select",
|
|
2898
|
-
{
|
|
2899
|
-
"aria-label": label,
|
|
2900
|
-
className: cn(
|
|
2901
|
-
"border-input bg-background h-8 min-w-0 flex-1 rounded-md border px-2 text-sm"
|
|
2902
|
-
),
|
|
2903
|
-
disabled: options.length === 0,
|
|
2904
|
-
value,
|
|
2905
|
-
onChange: (event) => onChange(event.target.value),
|
|
2906
|
-
children: options.map((option) => /* @__PURE__ */ jsx("option", { value: option.deviceId, children: option.label }, option.deviceId))
|
|
2907
|
-
}
|
|
2908
|
-
),
|
|
2909
|
-
action
|
|
2910
|
-
] })
|
|
2911
|
-
] });
|
|
3058
|
+
);
|
|
2912
3059
|
}
|
|
2913
|
-
function
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
3060
|
+
function StudioStage({
|
|
3061
|
+
participants,
|
|
3062
|
+
meeting,
|
|
3063
|
+
reducedMotion,
|
|
3064
|
+
canModerate,
|
|
3065
|
+
canManageStage,
|
|
3066
|
+
onAir,
|
|
3067
|
+
labels,
|
|
3068
|
+
onKick,
|
|
3069
|
+
onMute,
|
|
3070
|
+
onStopCamera,
|
|
3071
|
+
onStopScreen,
|
|
3072
|
+
onToggleStage,
|
|
3073
|
+
onTogglePin,
|
|
3074
|
+
onToggleChat,
|
|
3075
|
+
canTakeOff,
|
|
3076
|
+
onOpenPeople
|
|
2922
3077
|
}) {
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
3078
|
+
const displayTiles = expandStudioTiles(participants);
|
|
3079
|
+
const stage = studioStageLayout(displayTiles, {
|
|
3080
|
+
activeSpeakerId: meeting.activeSpeakerId()
|
|
3081
|
+
});
|
|
3082
|
+
const sharing = stage.spotlight.length > 0;
|
|
3083
|
+
const overflowLabel = labels.stageOverflow.replace(
|
|
3084
|
+
"{count}",
|
|
3085
|
+
String(stage.overflow)
|
|
3086
|
+
);
|
|
3087
|
+
function renderTile(tile, compact, fill = false) {
|
|
3088
|
+
const takeOffBlocked = !canTakeOff(tile.id);
|
|
3089
|
+
return /* @__PURE__ */ jsx(
|
|
3090
|
+
StudioTile,
|
|
2926
3091
|
{
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
3092
|
+
tile,
|
|
3093
|
+
meeting,
|
|
3094
|
+
reducedMotion,
|
|
3095
|
+
compact,
|
|
3096
|
+
fill,
|
|
3097
|
+
canModerate: canModerate && !tile.isSelf,
|
|
3098
|
+
canManageStage: canManageStage && !tile.isSelf,
|
|
3099
|
+
takeOffBlocked,
|
|
3100
|
+
confirmTakeOff: onAir,
|
|
3101
|
+
labels,
|
|
3102
|
+
onKick: () => onKick(tile.id),
|
|
3103
|
+
onMute: () => onMute(tile.id),
|
|
3104
|
+
onStopCamera: () => onStopCamera(tile.id),
|
|
3105
|
+
onStopScreen: () => onStopScreen(tile.id),
|
|
3106
|
+
onToggleStage: () => onToggleStage(tile),
|
|
3107
|
+
onTogglePin: () => onTogglePin(tile),
|
|
3108
|
+
onToggleChat: () => onToggleChat(tile)
|
|
3109
|
+
},
|
|
3110
|
+
tile.tileId
|
|
3111
|
+
);
|
|
3112
|
+
}
|
|
3113
|
+
function overflowChip(compact) {
|
|
3114
|
+
if (stage.overflow <= 0) return null;
|
|
3115
|
+
return /* @__PURE__ */ jsx(
|
|
3116
|
+
Button,
|
|
3117
|
+
{
|
|
3118
|
+
type: "button",
|
|
3119
|
+
variant: "secondary",
|
|
3120
|
+
className: cn(
|
|
3121
|
+
"shrink-0",
|
|
3122
|
+
compact ? "aspect-video w-40 md:w-full" : "aspect-video min-h-24"
|
|
2939
3123
|
),
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
badge != null && badge > 0 ? /* @__PURE__ */ jsx("span", { className: "bg-primary text-primary-foreground absolute -top-2 -right-2 flex h-4 min-w-4 items-center justify-center rounded-full px-1 text-[0.65rem]", children: badge }) : null
|
|
2943
|
-
] })
|
|
3124
|
+
onClick: onOpenPeople,
|
|
3125
|
+
children: overflowLabel
|
|
2944
3126
|
}
|
|
2945
|
-
)
|
|
2946
|
-
|
|
2947
|
-
|
|
3127
|
+
);
|
|
3128
|
+
}
|
|
3129
|
+
if (sharing) {
|
|
3130
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 flex-col overflow-hidden md:flex-row", children: [
|
|
3131
|
+
/* @__PURE__ */ jsx("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col gap-2 bg-black p-3", children: stage.spotlight.map((tile) => renderTile(tile, false)) }),
|
|
3132
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 gap-2 overflow-x-auto p-2 md:w-64 md:flex-col md:overflow-auto", children: [
|
|
3133
|
+
stage.strip.map((tile) => renderTile(tile, true)),
|
|
3134
|
+
overflowChip(true)
|
|
3135
|
+
] })
|
|
3136
|
+
] });
|
|
3137
|
+
}
|
|
3138
|
+
return /* @__PURE__ */ jsxs(
|
|
3139
|
+
"div",
|
|
3140
|
+
{
|
|
3141
|
+
className: cn(
|
|
3142
|
+
"mx-auto grid min-h-0 w-full flex-1 auto-rows-fr gap-2 overflow-hidden p-2 md:auto-rows-auto md:content-center md:gap-3 md:overflow-auto md:p-4",
|
|
3143
|
+
stage.strip.length <= 1 ? "max-w-3xl grid-cols-1" : stage.strip.length <= 2 ? "max-w-5xl grid-cols-1 sm:grid-cols-2" : stage.strip.length <= 4 ? "max-w-5xl grid-cols-2 sm:grid-cols-2" : stage.strip.length <= 6 ? "max-w-6xl grid-cols-2 sm:grid-cols-2 xl:grid-cols-3" : "grid-cols-2 sm:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4"
|
|
3144
|
+
),
|
|
3145
|
+
children: [
|
|
3146
|
+
stage.strip.map((tile) => renderTile(tile, false, true)),
|
|
3147
|
+
overflowChip(false)
|
|
3148
|
+
]
|
|
3149
|
+
}
|
|
3150
|
+
);
|
|
2948
3151
|
}
|
|
2949
3152
|
function StudioToolbar({
|
|
2950
3153
|
session,
|
|
@@ -2987,6 +3190,7 @@ function StudioToolbar({
|
|
|
2987
3190
|
onClosePip,
|
|
2988
3191
|
onLeave
|
|
2989
3192
|
}) {
|
|
3193
|
+
const fullscreenSupported = document.fullscreenEnabled === true && typeof document.documentElement.requestFullscreen === "function";
|
|
2990
3194
|
async function enterFullscreen() {
|
|
2991
3195
|
try {
|
|
2992
3196
|
await document.documentElement.requestFullscreen();
|
|
@@ -3095,14 +3299,14 @@ function StudioToolbar({
|
|
|
3095
3299
|
children: /* @__PURE__ */ jsx(Users, {})
|
|
3096
3300
|
}
|
|
3097
3301
|
) : null,
|
|
3098
|
-
/* @__PURE__ */ jsx(
|
|
3302
|
+
fullscreenSupported ? /* @__PURE__ */ jsx(
|
|
3099
3303
|
ControlIcon,
|
|
3100
3304
|
{
|
|
3101
3305
|
label: labels.fullscreen,
|
|
3102
3306
|
onClick: () => void enterFullscreen(),
|
|
3103
3307
|
children: /* @__PURE__ */ jsx(Maximize, {})
|
|
3104
3308
|
}
|
|
3105
|
-
),
|
|
3309
|
+
) : null,
|
|
3106
3310
|
pipSupported ? /* @__PURE__ */ jsx(
|
|
3107
3311
|
ControlIcon,
|
|
3108
3312
|
{
|
|
@@ -3187,7 +3391,8 @@ function StudioRoom({
|
|
|
3187
3391
|
playerOrigin,
|
|
3188
3392
|
windowGate,
|
|
3189
3393
|
initialCameraOn = true,
|
|
3190
|
-
initialMuted = false
|
|
3394
|
+
initialMuted = false,
|
|
3395
|
+
initialDevices
|
|
3191
3396
|
}) {
|
|
3192
3397
|
const [cameraOn, setCameraOn] = useState(initialCameraOn);
|
|
3193
3398
|
const [screenOn, setScreenOn] = useState(false);
|
|
@@ -3250,6 +3455,21 @@ function StudioRoom({
|
|
|
3250
3455
|
postLivoEvent("live", { streamId: session.stream.id });
|
|
3251
3456
|
}
|
|
3252
3457
|
}, [onAir, session.stream.id]);
|
|
3458
|
+
useEffect(() => {
|
|
3459
|
+
const saved = initialDevices ?? readStudioDeviceSelection();
|
|
3460
|
+
let cancelled = false;
|
|
3461
|
+
void (async () => {
|
|
3462
|
+
try {
|
|
3463
|
+
const listed = await meeting.listDevices();
|
|
3464
|
+
if (cancelled) return;
|
|
3465
|
+
await applyStudioDeviceSelection(meeting.setDevice, saved, listed);
|
|
3466
|
+
} catch {
|
|
3467
|
+
}
|
|
3468
|
+
})();
|
|
3469
|
+
return () => {
|
|
3470
|
+
cancelled = true;
|
|
3471
|
+
};
|
|
3472
|
+
}, [initialDevices, meeting]);
|
|
3253
3473
|
useEffect(() => {
|
|
3254
3474
|
return () => {
|
|
3255
3475
|
for (const id of grantCheckTimers.current) window.clearTimeout(id);
|
|
@@ -3449,10 +3669,9 @@ function StudioRoom({
|
|
|
3449
3669
|
const self = participants.find((row) => row.isSelf);
|
|
3450
3670
|
const canShareFreely = session.role === "moderator" || self?.stageStatus === "ON_STAGE" || shareGrant === "granted";
|
|
3451
3671
|
const selfId = self?.id ?? null;
|
|
3672
|
+
const selfUserId = self?.userId || null;
|
|
3452
3673
|
const remoteParticipants = participants.filter((row) => !row.isSelf);
|
|
3453
|
-
const incoming = chatMessages.filter(
|
|
3454
|
-
(row) => row.userId !== selfId && row.userId !== self?.userId
|
|
3455
|
-
);
|
|
3674
|
+
const incoming = chatMessages.filter((row) => row.userId !== selfUserId);
|
|
3456
3675
|
const unread = sideTab === "chat" ? 0 : Math.max(0, incoming.length - seenIncoming);
|
|
3457
3676
|
const peopleBadge = waitlisted.length + stageRequests.length;
|
|
3458
3677
|
useEffect(() => {
|
|
@@ -3792,17 +4011,21 @@ function StudioRoom({
|
|
|
3792
4011
|
) });
|
|
3793
4012
|
}
|
|
3794
4013
|
if (inWaitingRoom && session.role === "guest") {
|
|
3795
|
-
return /* @__PURE__ */
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
4014
|
+
return /* @__PURE__ */ jsx(
|
|
4015
|
+
StudioGuestWaiting,
|
|
4016
|
+
{
|
|
4017
|
+
meeting,
|
|
4018
|
+
reducedMotion,
|
|
4019
|
+
cameraOn,
|
|
4020
|
+
muted,
|
|
4021
|
+
labels,
|
|
4022
|
+
settingsOpen,
|
|
4023
|
+
onToggleCamera,
|
|
4024
|
+
onToggleMute: () => void onToggleMute(),
|
|
4025
|
+
onOpenSettings: () => setSettingsOpen(true),
|
|
4026
|
+
onCloseSettings: setSettingsOpen
|
|
4027
|
+
}
|
|
4028
|
+
);
|
|
3806
4029
|
}
|
|
3807
4030
|
if (participants.length > 0 || waitlisted.length > 0) {
|
|
3808
4031
|
sawParticipants.current = true;
|
|
@@ -3814,7 +4037,14 @@ function StudioRoom({
|
|
|
3814
4037
|
/* @__PURE__ */ jsxs("header", { className: "flex h-12 items-center gap-3 border-b px-3", children: [
|
|
3815
4038
|
/* @__PURE__ */ jsx("p", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: session.stream.title }),
|
|
3816
4039
|
onAir && liveStartedAtRef.current !== null ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground tabular-nums text-xs", children: formatStudioElapsed(Date.now() - liveStartedAtRef.current) }) : null,
|
|
3817
|
-
/* @__PURE__ */ jsx(
|
|
4040
|
+
onAir ? /* @__PURE__ */ jsx(StudioLiveBadge, { role: "status", children: statusChip }) : /* @__PURE__ */ jsx(
|
|
4041
|
+
"span",
|
|
4042
|
+
{
|
|
4043
|
+
className: "rounded-full border px-2 py-0.5 text-xs",
|
|
4044
|
+
role: "status",
|
|
4045
|
+
children: statusChip
|
|
4046
|
+
}
|
|
4047
|
+
),
|
|
3818
4048
|
session.role === "moderator" && (egressLost || !starting && !sending && !onAir && !active) ? /* @__PURE__ */ jsx(
|
|
3819
4049
|
Button,
|
|
3820
4050
|
{
|
|
@@ -3922,7 +4152,7 @@ function StudioRoom({
|
|
|
3922
4152
|
onTabChange: setSideTab,
|
|
3923
4153
|
onClose: () => setSideTab(null),
|
|
3924
4154
|
meeting,
|
|
3925
|
-
|
|
4155
|
+
selfUserId,
|
|
3926
4156
|
participants,
|
|
3927
4157
|
waitlisted,
|
|
3928
4158
|
stageRequests,
|
|
@@ -4099,6 +4329,177 @@ function StudioRoom({
|
|
|
4099
4329
|
) : null
|
|
4100
4330
|
] });
|
|
4101
4331
|
}
|
|
4332
|
+
function isOverconstrained(error) {
|
|
4333
|
+
if (!error || typeof error !== "object") return false;
|
|
4334
|
+
return error.name === "OverconstrainedError";
|
|
4335
|
+
}
|
|
4336
|
+
function trackConstraint(deviceId, exact) {
|
|
4337
|
+
if (exact && deviceId) {
|
|
4338
|
+
return { deviceId: { exact: deviceId } };
|
|
4339
|
+
}
|
|
4340
|
+
return true;
|
|
4341
|
+
}
|
|
4342
|
+
function stopStream(stream) {
|
|
4343
|
+
if (!stream) return;
|
|
4344
|
+
for (const track of stream.getTracks()) {
|
|
4345
|
+
track.onended = null;
|
|
4346
|
+
track.stop();
|
|
4347
|
+
}
|
|
4348
|
+
}
|
|
4349
|
+
async function requestPreviewStream(wantAudio, wantVideo, audioDeviceId, videoDeviceId, exact) {
|
|
4350
|
+
return navigator.mediaDevices.getUserMedia({
|
|
4351
|
+
audio: wantAudio ? trackConstraint(audioDeviceId, exact) : false,
|
|
4352
|
+
video: wantVideo ? trackConstraint(videoDeviceId, exact) : false
|
|
4353
|
+
});
|
|
4354
|
+
}
|
|
4355
|
+
function StudioDevicePreflight({
|
|
4356
|
+
reducedMotion,
|
|
4357
|
+
cameraLabel,
|
|
4358
|
+
micLabel,
|
|
4359
|
+
cameraOn = true,
|
|
4360
|
+
muted = false,
|
|
4361
|
+
audioDeviceId = "",
|
|
4362
|
+
videoDeviceId = "",
|
|
4363
|
+
cameraOffLabel = "Camera is off",
|
|
4364
|
+
onPermission,
|
|
4365
|
+
onError
|
|
4366
|
+
}) {
|
|
4367
|
+
const videoRef = useRef(null);
|
|
4368
|
+
const onPermissionRef = useRef(onPermission);
|
|
4369
|
+
const onErrorRef = useRef(onError);
|
|
4370
|
+
onPermissionRef.current = onPermission;
|
|
4371
|
+
onErrorRef.current = onError;
|
|
4372
|
+
const [audioTrack, setAudioTrack] = useState(null);
|
|
4373
|
+
const [cameraOk, setCameraOk] = useState(false);
|
|
4374
|
+
const [micOk, setMicOk] = useState(false);
|
|
4375
|
+
useEffect(() => {
|
|
4376
|
+
let cancelled = false;
|
|
4377
|
+
let generation = 0;
|
|
4378
|
+
let current = null;
|
|
4379
|
+
function bindEnded(stream, gen) {
|
|
4380
|
+
for (const track of stream.getTracks()) {
|
|
4381
|
+
track.onended = () => {
|
|
4382
|
+
if (cancelled || gen !== generation) return;
|
|
4383
|
+
void acquire();
|
|
4384
|
+
};
|
|
4385
|
+
}
|
|
4386
|
+
}
|
|
4387
|
+
async function acquire() {
|
|
4388
|
+
const gen = ++generation;
|
|
4389
|
+
stopStream(current);
|
|
4390
|
+
current = null;
|
|
4391
|
+
if (cancelled) return;
|
|
4392
|
+
if (!cameraOn && muted) {
|
|
4393
|
+
setAudioTrack(null);
|
|
4394
|
+
setCameraOk(false);
|
|
4395
|
+
setMicOk(false);
|
|
4396
|
+
if (videoRef.current) videoRef.current.srcObject = null;
|
|
4397
|
+
return;
|
|
4398
|
+
}
|
|
4399
|
+
if (!navigator.mediaDevices?.getUserMedia) {
|
|
4400
|
+
setAudioTrack(null);
|
|
4401
|
+
setCameraOk(false);
|
|
4402
|
+
setMicOk(false);
|
|
4403
|
+
onErrorRef.current?.();
|
|
4404
|
+
return;
|
|
4405
|
+
}
|
|
4406
|
+
const wantAudio = !muted;
|
|
4407
|
+
const wantVideo = cameraOn;
|
|
4408
|
+
const hadExact = Boolean(
|
|
4409
|
+
wantAudio && audioDeviceId || wantVideo && videoDeviceId
|
|
4410
|
+
);
|
|
4411
|
+
try {
|
|
4412
|
+
let next;
|
|
4413
|
+
try {
|
|
4414
|
+
next = await requestPreviewStream(
|
|
4415
|
+
wantAudio,
|
|
4416
|
+
wantVideo,
|
|
4417
|
+
audioDeviceId,
|
|
4418
|
+
videoDeviceId,
|
|
4419
|
+
true
|
|
4420
|
+
);
|
|
4421
|
+
} catch (error) {
|
|
4422
|
+
if (!isOverconstrained(error) || !hadExact) {
|
|
4423
|
+
throw error;
|
|
4424
|
+
}
|
|
4425
|
+
next = await requestPreviewStream(
|
|
4426
|
+
wantAudio,
|
|
4427
|
+
wantVideo,
|
|
4428
|
+
audioDeviceId,
|
|
4429
|
+
videoDeviceId,
|
|
4430
|
+
false
|
|
4431
|
+
);
|
|
4432
|
+
}
|
|
4433
|
+
if (cancelled || gen !== generation) {
|
|
4434
|
+
stopStream(next);
|
|
4435
|
+
return;
|
|
4436
|
+
}
|
|
4437
|
+
current = next;
|
|
4438
|
+
const video = next.getVideoTracks()[0] ?? null;
|
|
4439
|
+
const audio = next.getAudioTracks()[0] ?? null;
|
|
4440
|
+
setCameraOk(Boolean(video));
|
|
4441
|
+
setMicOk(Boolean(audio));
|
|
4442
|
+
setAudioTrack(audio);
|
|
4443
|
+
if (videoRef.current) {
|
|
4444
|
+
videoRef.current.srcObject = video ? next : null;
|
|
4445
|
+
}
|
|
4446
|
+
bindEnded(next, gen);
|
|
4447
|
+
onPermissionRef.current?.();
|
|
4448
|
+
} catch {
|
|
4449
|
+
if (cancelled || gen !== generation) return;
|
|
4450
|
+
setAudioTrack(null);
|
|
4451
|
+
setCameraOk(false);
|
|
4452
|
+
setMicOk(false);
|
|
4453
|
+
if (videoRef.current) videoRef.current.srcObject = null;
|
|
4454
|
+
onErrorRef.current?.();
|
|
4455
|
+
}
|
|
4456
|
+
}
|
|
4457
|
+
void acquire();
|
|
4458
|
+
return () => {
|
|
4459
|
+
cancelled = true;
|
|
4460
|
+
generation += 1;
|
|
4461
|
+
stopStream(current);
|
|
4462
|
+
current = null;
|
|
4463
|
+
};
|
|
4464
|
+
}, [audioDeviceId, cameraOn, muted, videoDeviceId]);
|
|
4465
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col items-center gap-3", children: [
|
|
4466
|
+
cameraOn ? /* @__PURE__ */ jsx(
|
|
4467
|
+
"video",
|
|
4468
|
+
{
|
|
4469
|
+
ref: videoRef,
|
|
4470
|
+
autoPlay: true,
|
|
4471
|
+
muted: true,
|
|
4472
|
+
playsInline: true,
|
|
4473
|
+
className: "bg-muted aspect-video w-full rounded-xl object-cover"
|
|
4474
|
+
}
|
|
4475
|
+
) : /* @__PURE__ */ jsx("div", { className: "bg-muted flex aspect-video w-full items-center justify-center rounded-xl", children: /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: cameraOffLabel }) }),
|
|
4476
|
+
/* @__PURE__ */ jsx(
|
|
4477
|
+
AudioWaveform,
|
|
4478
|
+
{
|
|
4479
|
+
track: muted ? null : audioTrack,
|
|
4480
|
+
reducedMotion
|
|
4481
|
+
}
|
|
4482
|
+
),
|
|
4483
|
+
/* @__PURE__ */ jsxs("p", { className: "text-muted-foreground text-sm", role: "status", children: [
|
|
4484
|
+
cameraLabel,
|
|
4485
|
+
": ",
|
|
4486
|
+
cameraOk && cameraOn ? "OK" : "\u2014",
|
|
4487
|
+
" \xB7 ",
|
|
4488
|
+
micLabel,
|
|
4489
|
+
":",
|
|
4490
|
+
" ",
|
|
4491
|
+
micOk && !muted ? "OK" : "\u2014"
|
|
4492
|
+
] })
|
|
4493
|
+
] });
|
|
4494
|
+
}
|
|
4495
|
+
var LOBBY_DEVICE_LABEL_DEFAULTS = {
|
|
4496
|
+
cameraDevice: "Camera",
|
|
4497
|
+
micDevice: "Microphone",
|
|
4498
|
+
speakerDevice: "Speaker",
|
|
4499
|
+
testSpeaker: "Test",
|
|
4500
|
+
testSpeakerError: "Could not play the speaker test",
|
|
4501
|
+
cameraOff: "Camera is off"
|
|
4502
|
+
};
|
|
4102
4503
|
function StudioGuestLobby({
|
|
4103
4504
|
reducedMotion,
|
|
4104
4505
|
cameraOn,
|
|
@@ -4109,11 +4510,48 @@ function StudioGuestLobby({
|
|
|
4109
4510
|
waitingForHost,
|
|
4110
4511
|
error,
|
|
4111
4512
|
labels,
|
|
4513
|
+
savedDevices,
|
|
4112
4514
|
onCameraChange,
|
|
4113
4515
|
onMutedChange,
|
|
4114
4516
|
onDisplayNameChange,
|
|
4517
|
+
onDeviceChange,
|
|
4115
4518
|
onJoin
|
|
4116
4519
|
}) {
|
|
4520
|
+
const [refreshKey, setRefreshKey] = useState(0);
|
|
4521
|
+
const [internalSaved, setInternalSaved] = useState(
|
|
4522
|
+
() => readStudioDeviceSelection()
|
|
4523
|
+
);
|
|
4524
|
+
const [testingSpeaker, setTestingSpeaker] = useState(false);
|
|
4525
|
+
const devices = useMediaDevices(refreshKey);
|
|
4526
|
+
const saved = savedDevices ?? internalSaved;
|
|
4527
|
+
const { effective } = resolveDeviceSelection(saved, devices);
|
|
4528
|
+
const deviceLabels = {
|
|
4529
|
+
cameraDevice: labels.cameraDevice ?? LOBBY_DEVICE_LABEL_DEFAULTS.cameraDevice,
|
|
4530
|
+
micDevice: labels.micDevice ?? LOBBY_DEVICE_LABEL_DEFAULTS.micDevice,
|
|
4531
|
+
speakerDevice: labels.speakerDevice ?? LOBBY_DEVICE_LABEL_DEFAULTS.speakerDevice,
|
|
4532
|
+
testSpeaker: labels.testSpeaker ?? LOBBY_DEVICE_LABEL_DEFAULTS.testSpeaker,
|
|
4533
|
+
testSpeakerError: labels.testSpeakerError ?? LOBBY_DEVICE_LABEL_DEFAULTS.testSpeakerError,
|
|
4534
|
+
cameraOff: labels.cameraOff ?? LOBBY_DEVICE_LABEL_DEFAULTS.cameraOff
|
|
4535
|
+
};
|
|
4536
|
+
function changeDevice(kind, deviceId) {
|
|
4537
|
+
persistStudioDeviceSelection(kind, deviceId);
|
|
4538
|
+
onDeviceChange?.(kind, deviceId);
|
|
4539
|
+
if (!savedDevices) {
|
|
4540
|
+
setInternalSaved((prev) => ({ ...prev, [kind]: deviceId }));
|
|
4541
|
+
}
|
|
4542
|
+
}
|
|
4543
|
+
async function onTestSpeaker() {
|
|
4544
|
+
const speakerId = effective.speaker || devices.speaker[0]?.deviceId || "";
|
|
4545
|
+
if (!speakerId || testingSpeaker) return;
|
|
4546
|
+
setTestingSpeaker(true);
|
|
4547
|
+
try {
|
|
4548
|
+
await playSpeakerTest(speakerId);
|
|
4549
|
+
} catch {
|
|
4550
|
+
toast.error(deviceLabels.testSpeakerError);
|
|
4551
|
+
} finally {
|
|
4552
|
+
setTestingSpeaker(false);
|
|
4553
|
+
}
|
|
4554
|
+
}
|
|
4117
4555
|
return /* @__PURE__ */ jsxs("div", { className: "mx-auto flex min-h-dvh w-full max-w-lg flex-col justify-center gap-6 p-6", children: [
|
|
4118
4556
|
/* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
4119
4557
|
/* @__PURE__ */ jsx("h1", { className: "text-lg font-semibold", children: labels.title }),
|
|
@@ -4125,8 +4563,12 @@ function StudioGuestLobby({
|
|
|
4125
4563
|
reducedMotion,
|
|
4126
4564
|
cameraOn,
|
|
4127
4565
|
muted,
|
|
4566
|
+
audioDeviceId: effective.audio,
|
|
4567
|
+
videoDeviceId: effective.video,
|
|
4128
4568
|
cameraLabel: labels.camera,
|
|
4129
|
-
micLabel: labels.mic
|
|
4569
|
+
micLabel: labels.mic,
|
|
4570
|
+
cameraOffLabel: deviceLabels.cameraOff,
|
|
4571
|
+
onPermission: () => setRefreshKey((n) => n === 0 ? 1 : n)
|
|
4130
4572
|
}
|
|
4131
4573
|
),
|
|
4132
4574
|
/* @__PURE__ */ jsxs("div", { className: "flex justify-center gap-2", children: [
|
|
@@ -4155,6 +4597,26 @@ function StudioGuestLobby({
|
|
|
4155
4597
|
}
|
|
4156
4598
|
)
|
|
4157
4599
|
] }),
|
|
4600
|
+
/* @__PURE__ */ jsx(
|
|
4601
|
+
StudioDeviceSelectGroup,
|
|
4602
|
+
{
|
|
4603
|
+
devices,
|
|
4604
|
+
selected: effective,
|
|
4605
|
+
labels: deviceLabels,
|
|
4606
|
+
onChange: changeDevice,
|
|
4607
|
+
speakerAction: /* @__PURE__ */ jsx(
|
|
4608
|
+
Button,
|
|
4609
|
+
{
|
|
4610
|
+
type: "button",
|
|
4611
|
+
size: "sm",
|
|
4612
|
+
variant: "outline",
|
|
4613
|
+
disabled: !(effective.speaker || devices.speaker[0]?.deviceId) || testingSpeaker,
|
|
4614
|
+
onClick: () => void onTestSpeaker(),
|
|
4615
|
+
children: deviceLabels.testSpeaker
|
|
4616
|
+
}
|
|
4617
|
+
)
|
|
4618
|
+
}
|
|
4619
|
+
),
|
|
4158
4620
|
showName ? /* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
|
|
4159
4621
|
/* @__PURE__ */ jsx(Label, { htmlFor: "studio-guest-name", children: labels.name }),
|
|
4160
4622
|
/* @__PURE__ */ jsx(
|
|
@@ -4200,16 +4662,16 @@ function usePrefersReducedMotion() {
|
|
|
4200
4662
|
// src/studio/lib/guest-identity.ts
|
|
4201
4663
|
var NAME_KEY = "livo-studio-display-name";
|
|
4202
4664
|
var ID_KEY = "livo-studio-guest-id";
|
|
4203
|
-
function readStudioGuestIdentity(storage =
|
|
4665
|
+
function readStudioGuestIdentity(storage = defaultStorage2()) {
|
|
4204
4666
|
const guestId = storage?.getItem(ID_KEY)?.trim() || crypto.randomUUID();
|
|
4205
4667
|
storage?.setItem(ID_KEY, guestId);
|
|
4206
4668
|
const displayName = storage?.getItem(NAME_KEY)?.trim() ?? "";
|
|
4207
4669
|
return { displayName, guestId };
|
|
4208
4670
|
}
|
|
4209
|
-
function persistStudioGuestName(displayName, storage =
|
|
4671
|
+
function persistStudioGuestName(displayName, storage = defaultStorage2()) {
|
|
4210
4672
|
storage?.setItem(NAME_KEY, displayName.trim());
|
|
4211
4673
|
}
|
|
4212
|
-
function
|
|
4674
|
+
function defaultStorage2() {
|
|
4213
4675
|
if (typeof window === "undefined") return null;
|
|
4214
4676
|
try {
|
|
4215
4677
|
return window.localStorage;
|
|
@@ -5370,6 +5832,9 @@ function LivoGuestStudio({
|
|
|
5370
5832
|
const [waitingForHost, setWaitingForHost] = useState(false);
|
|
5371
5833
|
const [cameraOn, setCameraOn] = useState(true);
|
|
5372
5834
|
const [muted, setMuted] = useState(false);
|
|
5835
|
+
const [deviceSelection, setDeviceSelection] = useState(
|
|
5836
|
+
() => readStudioDeviceSelection()
|
|
5837
|
+
);
|
|
5373
5838
|
const [retryAfterMs, setRetryAfterMs] = useState(null);
|
|
5374
5839
|
const [sessionGen, setSessionGen] = useState(0);
|
|
5375
5840
|
const joinInFlight = useRef(false);
|
|
@@ -5384,7 +5849,15 @@ function LivoGuestStudio({
|
|
|
5384
5849
|
const identity = readStudioGuestIdentity();
|
|
5385
5850
|
setDisplayName(identity.displayName);
|
|
5386
5851
|
setGuestId(identity.guestId);
|
|
5852
|
+
setDeviceSelection(readStudioDeviceSelection());
|
|
5387
5853
|
}, []);
|
|
5854
|
+
const onDeviceChange = useCallback(
|
|
5855
|
+
(kind, deviceId) => {
|
|
5856
|
+
persistStudioDeviceSelection(kind, deviceId);
|
|
5857
|
+
setDeviceSelection((prev) => ({ ...prev, [kind]: deviceId }));
|
|
5858
|
+
},
|
|
5859
|
+
[]
|
|
5860
|
+
);
|
|
5388
5861
|
const join = useCallback(async () => {
|
|
5389
5862
|
if (joinInFlight.current) return;
|
|
5390
5863
|
const name = displayName.trim();
|
|
@@ -5473,9 +5946,11 @@ function LivoGuestStudio({
|
|
|
5473
5946
|
waitingForHost,
|
|
5474
5947
|
error,
|
|
5475
5948
|
labels: lobbyLabels,
|
|
5949
|
+
savedDevices: deviceSelection,
|
|
5476
5950
|
onCameraChange: setCameraOn,
|
|
5477
5951
|
onMutedChange: setMuted,
|
|
5478
5952
|
onDisplayNameChange: setDisplayName,
|
|
5953
|
+
onDeviceChange,
|
|
5479
5954
|
onJoin: () => void join()
|
|
5480
5955
|
}
|
|
5481
5956
|
);
|
|
@@ -5499,7 +5974,8 @@ function LivoGuestStudio({
|
|
|
5499
5974
|
embed,
|
|
5500
5975
|
playerOrigin,
|
|
5501
5976
|
initialCameraOn: cameraOn,
|
|
5502
|
-
initialMuted: muted
|
|
5977
|
+
initialMuted: muted,
|
|
5978
|
+
initialDevices: deviceSelection
|
|
5503
5979
|
}
|
|
5504
5980
|
);
|
|
5505
5981
|
}
|