@livo-tv/sdk 1.1.2 → 1.2.1
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 +2127 -1625
- 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 +2128 -1626
- 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,
|
|
4
|
+
import { Loader2, Share2, MicOff, Mic, Video, VideoOff, Settings, X, Link2, EyeOff, Eye, MonitorUp, MessageSquare, Users, 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-max max-w-[80%] 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-max max-w-full overflow-hidden rounded-xl border border-transparent px-3 py-2 text-sm leading-relaxed break-words 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
|
-
|
|
2554
|
-
}
|
|
2555
|
-
)
|
|
2556
|
-
] });
|
|
2238
|
+
)
|
|
2239
|
+
},
|
|
2240
|
+
props
|
|
2241
|
+
),
|
|
2242
|
+
render,
|
|
2243
|
+
state: {
|
|
2244
|
+
slot: "bubble-content"
|
|
2245
|
+
}
|
|
2246
|
+
});
|
|
2557
2247
|
}
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
{
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
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"
|
|
2259
|
+
}
|
|
2260
|
+
},
|
|
2261
|
+
defaultVariants: {
|
|
2262
|
+
side: "bottom",
|
|
2263
|
+
align: "end"
|
|
2565
2264
|
}
|
|
2566
|
-
|
|
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", 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
|
+
] })
|
|
2754
|
+
] });
|
|
2755
|
+
}
|
|
2756
|
+
function DropdownMenu({ ...props }) {
|
|
2757
|
+
return /* @__PURE__ */ jsx(Menu.Root, { "data-slot": "dropdown-menu", ...props });
|
|
2758
|
+
}
|
|
2759
|
+
function DropdownMenuTrigger({ ...props }) {
|
|
2760
|
+
return /* @__PURE__ */ jsx(Menu.Trigger, { "data-slot": "dropdown-menu-trigger", ...props });
|
|
2567
2761
|
}
|
|
2568
|
-
function
|
|
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
|
-
|
|
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
|
|
2670
2900
|
}) {
|
|
2671
|
-
const
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
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
|
-
if (!selected.speaker || testingSpeaker) return;
|
|
2742
|
-
setTestingSpeaker(true);
|
|
2743
|
-
try {
|
|
2744
|
-
await playSpeakerTest(selected.speaker);
|
|
2745
|
-
} catch {
|
|
2746
|
-
toast.error(labels.testSpeakerError);
|
|
2747
|
-
} finally {
|
|
2748
|
-
setTestingSpeaker(false);
|
|
2749
|
-
}
|
|
2750
|
-
}
|
|
2751
|
-
return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DialogContent, { className: "sm:max-w-md", children: [
|
|
2752
|
-
/* @__PURE__ */ jsxs(DialogHeader, { children: [
|
|
2753
|
-
/* @__PURE__ */ jsx(DialogTitle, { children: labels.settingsTitle }),
|
|
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: [
|
|
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();
|
|
@@ -3035,14 +3239,24 @@ function StudioToolbar({
|
|
|
3035
3239
|
children: selfStageStatus === "ON_STAGE" ? /* @__PURE__ */ jsx(EyeOff, {}) : /* @__PURE__ */ jsx(Eye, {})
|
|
3036
3240
|
}
|
|
3037
3241
|
) : null,
|
|
3038
|
-
session.role === "guest" && stageEnabled
|
|
3242
|
+
session.role === "guest" && stageEnabled ? /* @__PURE__ */ jsx(
|
|
3039
3243
|
ControlIcon,
|
|
3040
3244
|
{
|
|
3041
|
-
label: selfStageStatus === "REQUESTED_TO_JOIN_STAGE" ? labels.stageRequested : labels.askToGoOnAir,
|
|
3245
|
+
label: selfStageStatus === "ON_STAGE" ? labels.goOffAir : selfStageStatus === "REQUESTED_TO_JOIN_STAGE" ? labels.stageRequested : labels.askToGoOnAir,
|
|
3042
3246
|
pressed: selfStageStatus === "REQUESTED_TO_JOIN_STAGE",
|
|
3043
|
-
disabled: selfStageStatus === "REQUESTED_TO_JOIN_STAGE",
|
|
3044
|
-
onClick:
|
|
3045
|
-
|
|
3247
|
+
disabled: selfStageStatus === "REQUESTED_TO_JOIN_STAGE" || selfStageStatus === "ON_STAGE" && !canLeaveStage,
|
|
3248
|
+
onClick: () => {
|
|
3249
|
+
if (selfStageStatus === "ON_STAGE") {
|
|
3250
|
+
if (!canLeaveStage) {
|
|
3251
|
+
toast.message(labels.offAirLastVisible);
|
|
3252
|
+
return;
|
|
3253
|
+
}
|
|
3254
|
+
onLeaveStage();
|
|
3255
|
+
return;
|
|
3256
|
+
}
|
|
3257
|
+
onAskStage();
|
|
3258
|
+
},
|
|
3259
|
+
children: selfStageStatus === "ON_STAGE" ? /* @__PURE__ */ jsx(EyeOff, {}) : /* @__PURE__ */ jsx(Eye, {})
|
|
3046
3260
|
}
|
|
3047
3261
|
) : null,
|
|
3048
3262
|
showScreenControl ? canShareFreely ? /* @__PURE__ */ jsx(
|
|
@@ -3095,14 +3309,14 @@ function StudioToolbar({
|
|
|
3095
3309
|
children: /* @__PURE__ */ jsx(Users, {})
|
|
3096
3310
|
}
|
|
3097
3311
|
) : null,
|
|
3098
|
-
/* @__PURE__ */ jsx(
|
|
3312
|
+
fullscreenSupported ? /* @__PURE__ */ jsx(
|
|
3099
3313
|
ControlIcon,
|
|
3100
3314
|
{
|
|
3101
3315
|
label: labels.fullscreen,
|
|
3102
3316
|
onClick: () => void enterFullscreen(),
|
|
3103
3317
|
children: /* @__PURE__ */ jsx(Maximize, {})
|
|
3104
3318
|
}
|
|
3105
|
-
),
|
|
3319
|
+
) : null,
|
|
3106
3320
|
pipSupported ? /* @__PURE__ */ jsx(
|
|
3107
3321
|
ControlIcon,
|
|
3108
3322
|
{
|
|
@@ -3187,7 +3401,8 @@ function StudioRoom({
|
|
|
3187
3401
|
playerOrigin,
|
|
3188
3402
|
windowGate,
|
|
3189
3403
|
initialCameraOn = true,
|
|
3190
|
-
initialMuted = false
|
|
3404
|
+
initialMuted = false,
|
|
3405
|
+
initialDevices
|
|
3191
3406
|
}) {
|
|
3192
3407
|
const [cameraOn, setCameraOn] = useState(initialCameraOn);
|
|
3193
3408
|
const [screenOn, setScreenOn] = useState(false);
|
|
@@ -3250,6 +3465,21 @@ function StudioRoom({
|
|
|
3250
3465
|
postLivoEvent("live", { streamId: session.stream.id });
|
|
3251
3466
|
}
|
|
3252
3467
|
}, [onAir, session.stream.id]);
|
|
3468
|
+
useEffect(() => {
|
|
3469
|
+
const saved = initialDevices ?? readStudioDeviceSelection();
|
|
3470
|
+
let cancelled = false;
|
|
3471
|
+
void (async () => {
|
|
3472
|
+
try {
|
|
3473
|
+
const listed = await meeting.listDevices();
|
|
3474
|
+
if (cancelled) return;
|
|
3475
|
+
await applyStudioDeviceSelection(meeting.setDevice, saved, listed);
|
|
3476
|
+
} catch {
|
|
3477
|
+
}
|
|
3478
|
+
})();
|
|
3479
|
+
return () => {
|
|
3480
|
+
cancelled = true;
|
|
3481
|
+
};
|
|
3482
|
+
}, [initialDevices, meeting]);
|
|
3253
3483
|
useEffect(() => {
|
|
3254
3484
|
return () => {
|
|
3255
3485
|
for (const id of grantCheckTimers.current) window.clearTimeout(id);
|
|
@@ -3449,10 +3679,9 @@ function StudioRoom({
|
|
|
3449
3679
|
const self = participants.find((row) => row.isSelf);
|
|
3450
3680
|
const canShareFreely = session.role === "moderator" || self?.stageStatus === "ON_STAGE" || shareGrant === "granted";
|
|
3451
3681
|
const selfId = self?.id ?? null;
|
|
3682
|
+
const selfUserId = self?.userId || null;
|
|
3452
3683
|
const remoteParticipants = participants.filter((row) => !row.isSelf);
|
|
3453
|
-
const incoming = chatMessages.filter(
|
|
3454
|
-
(row) => row.userId !== selfId && row.userId !== self?.userId
|
|
3455
|
-
);
|
|
3684
|
+
const incoming = chatMessages.filter((row) => row.userId !== selfUserId);
|
|
3456
3685
|
const unread = sideTab === "chat" ? 0 : Math.max(0, incoming.length - seenIncoming);
|
|
3457
3686
|
const peopleBadge = waitlisted.length + stageRequests.length;
|
|
3458
3687
|
useEffect(() => {
|
|
@@ -3792,17 +4021,21 @@ function StudioRoom({
|
|
|
3792
4021
|
) });
|
|
3793
4022
|
}
|
|
3794
4023
|
if (inWaitingRoom && session.role === "guest") {
|
|
3795
|
-
return /* @__PURE__ */
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
4024
|
+
return /* @__PURE__ */ jsx(
|
|
4025
|
+
StudioGuestWaiting,
|
|
4026
|
+
{
|
|
4027
|
+
meeting,
|
|
4028
|
+
reducedMotion,
|
|
4029
|
+
cameraOn,
|
|
4030
|
+
muted,
|
|
4031
|
+
labels,
|
|
4032
|
+
settingsOpen,
|
|
4033
|
+
onToggleCamera,
|
|
4034
|
+
onToggleMute: () => void onToggleMute(),
|
|
4035
|
+
onOpenSettings: () => setSettingsOpen(true),
|
|
4036
|
+
onCloseSettings: setSettingsOpen
|
|
4037
|
+
}
|
|
4038
|
+
);
|
|
3806
4039
|
}
|
|
3807
4040
|
if (participants.length > 0 || waitlisted.length > 0) {
|
|
3808
4041
|
sawParticipants.current = true;
|
|
@@ -3814,7 +4047,14 @@ function StudioRoom({
|
|
|
3814
4047
|
/* @__PURE__ */ jsxs("header", { className: "flex h-12 items-center gap-3 border-b px-3", children: [
|
|
3815
4048
|
/* @__PURE__ */ jsx("p", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: session.stream.title }),
|
|
3816
4049
|
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(
|
|
4050
|
+
onAir ? /* @__PURE__ */ jsx(StudioLiveBadge, { role: "status", children: statusChip }) : /* @__PURE__ */ jsx(
|
|
4051
|
+
"span",
|
|
4052
|
+
{
|
|
4053
|
+
className: "rounded-full border px-2 py-0.5 text-xs",
|
|
4054
|
+
role: "status",
|
|
4055
|
+
children: statusChip
|
|
4056
|
+
}
|
|
4057
|
+
),
|
|
3818
4058
|
session.role === "moderator" && (egressLost || !starting && !sending && !onAir && !active) ? /* @__PURE__ */ jsx(
|
|
3819
4059
|
Button,
|
|
3820
4060
|
{
|
|
@@ -3922,7 +4162,7 @@ function StudioRoom({
|
|
|
3922
4162
|
onTabChange: setSideTab,
|
|
3923
4163
|
onClose: () => setSideTab(null),
|
|
3924
4164
|
meeting,
|
|
3925
|
-
|
|
4165
|
+
selfUserId,
|
|
3926
4166
|
participants,
|
|
3927
4167
|
waitlisted,
|
|
3928
4168
|
stageRequests,
|
|
@@ -4099,6 +4339,177 @@ function StudioRoom({
|
|
|
4099
4339
|
) : null
|
|
4100
4340
|
] });
|
|
4101
4341
|
}
|
|
4342
|
+
function isOverconstrained(error) {
|
|
4343
|
+
if (!error || typeof error !== "object") return false;
|
|
4344
|
+
return error.name === "OverconstrainedError";
|
|
4345
|
+
}
|
|
4346
|
+
function trackConstraint(deviceId, exact) {
|
|
4347
|
+
if (exact && deviceId) {
|
|
4348
|
+
return { deviceId: { exact: deviceId } };
|
|
4349
|
+
}
|
|
4350
|
+
return true;
|
|
4351
|
+
}
|
|
4352
|
+
function stopStream(stream) {
|
|
4353
|
+
if (!stream) return;
|
|
4354
|
+
for (const track of stream.getTracks()) {
|
|
4355
|
+
track.onended = null;
|
|
4356
|
+
track.stop();
|
|
4357
|
+
}
|
|
4358
|
+
}
|
|
4359
|
+
async function requestPreviewStream(wantAudio, wantVideo, audioDeviceId, videoDeviceId, exact) {
|
|
4360
|
+
return navigator.mediaDevices.getUserMedia({
|
|
4361
|
+
audio: wantAudio ? trackConstraint(audioDeviceId, exact) : false,
|
|
4362
|
+
video: wantVideo ? trackConstraint(videoDeviceId, exact) : false
|
|
4363
|
+
});
|
|
4364
|
+
}
|
|
4365
|
+
function StudioDevicePreflight({
|
|
4366
|
+
reducedMotion,
|
|
4367
|
+
cameraLabel,
|
|
4368
|
+
micLabel,
|
|
4369
|
+
cameraOn = true,
|
|
4370
|
+
muted = false,
|
|
4371
|
+
audioDeviceId = "",
|
|
4372
|
+
videoDeviceId = "",
|
|
4373
|
+
cameraOffLabel = "Camera is off",
|
|
4374
|
+
onPermission,
|
|
4375
|
+
onError
|
|
4376
|
+
}) {
|
|
4377
|
+
const videoRef = useRef(null);
|
|
4378
|
+
const onPermissionRef = useRef(onPermission);
|
|
4379
|
+
const onErrorRef = useRef(onError);
|
|
4380
|
+
onPermissionRef.current = onPermission;
|
|
4381
|
+
onErrorRef.current = onError;
|
|
4382
|
+
const [audioTrack, setAudioTrack] = useState(null);
|
|
4383
|
+
const [cameraOk, setCameraOk] = useState(false);
|
|
4384
|
+
const [micOk, setMicOk] = useState(false);
|
|
4385
|
+
useEffect(() => {
|
|
4386
|
+
let cancelled = false;
|
|
4387
|
+
let generation = 0;
|
|
4388
|
+
let current = null;
|
|
4389
|
+
function bindEnded(stream, gen) {
|
|
4390
|
+
for (const track of stream.getTracks()) {
|
|
4391
|
+
track.onended = () => {
|
|
4392
|
+
if (cancelled || gen !== generation) return;
|
|
4393
|
+
void acquire();
|
|
4394
|
+
};
|
|
4395
|
+
}
|
|
4396
|
+
}
|
|
4397
|
+
async function acquire() {
|
|
4398
|
+
const gen = ++generation;
|
|
4399
|
+
stopStream(current);
|
|
4400
|
+
current = null;
|
|
4401
|
+
if (cancelled) return;
|
|
4402
|
+
if (!cameraOn && muted) {
|
|
4403
|
+
setAudioTrack(null);
|
|
4404
|
+
setCameraOk(false);
|
|
4405
|
+
setMicOk(false);
|
|
4406
|
+
if (videoRef.current) videoRef.current.srcObject = null;
|
|
4407
|
+
return;
|
|
4408
|
+
}
|
|
4409
|
+
if (!navigator.mediaDevices?.getUserMedia) {
|
|
4410
|
+
setAudioTrack(null);
|
|
4411
|
+
setCameraOk(false);
|
|
4412
|
+
setMicOk(false);
|
|
4413
|
+
onErrorRef.current?.();
|
|
4414
|
+
return;
|
|
4415
|
+
}
|
|
4416
|
+
const wantAudio = !muted;
|
|
4417
|
+
const wantVideo = cameraOn;
|
|
4418
|
+
const hadExact = Boolean(
|
|
4419
|
+
wantAudio && audioDeviceId || wantVideo && videoDeviceId
|
|
4420
|
+
);
|
|
4421
|
+
try {
|
|
4422
|
+
let next;
|
|
4423
|
+
try {
|
|
4424
|
+
next = await requestPreviewStream(
|
|
4425
|
+
wantAudio,
|
|
4426
|
+
wantVideo,
|
|
4427
|
+
audioDeviceId,
|
|
4428
|
+
videoDeviceId,
|
|
4429
|
+
true
|
|
4430
|
+
);
|
|
4431
|
+
} catch (error) {
|
|
4432
|
+
if (!isOverconstrained(error) || !hadExact) {
|
|
4433
|
+
throw error;
|
|
4434
|
+
}
|
|
4435
|
+
next = await requestPreviewStream(
|
|
4436
|
+
wantAudio,
|
|
4437
|
+
wantVideo,
|
|
4438
|
+
audioDeviceId,
|
|
4439
|
+
videoDeviceId,
|
|
4440
|
+
false
|
|
4441
|
+
);
|
|
4442
|
+
}
|
|
4443
|
+
if (cancelled || gen !== generation) {
|
|
4444
|
+
stopStream(next);
|
|
4445
|
+
return;
|
|
4446
|
+
}
|
|
4447
|
+
current = next;
|
|
4448
|
+
const video = next.getVideoTracks()[0] ?? null;
|
|
4449
|
+
const audio = next.getAudioTracks()[0] ?? null;
|
|
4450
|
+
setCameraOk(Boolean(video));
|
|
4451
|
+
setMicOk(Boolean(audio));
|
|
4452
|
+
setAudioTrack(audio);
|
|
4453
|
+
if (videoRef.current) {
|
|
4454
|
+
videoRef.current.srcObject = video ? next : null;
|
|
4455
|
+
}
|
|
4456
|
+
bindEnded(next, gen);
|
|
4457
|
+
onPermissionRef.current?.();
|
|
4458
|
+
} catch {
|
|
4459
|
+
if (cancelled || gen !== generation) return;
|
|
4460
|
+
setAudioTrack(null);
|
|
4461
|
+
setCameraOk(false);
|
|
4462
|
+
setMicOk(false);
|
|
4463
|
+
if (videoRef.current) videoRef.current.srcObject = null;
|
|
4464
|
+
onErrorRef.current?.();
|
|
4465
|
+
}
|
|
4466
|
+
}
|
|
4467
|
+
void acquire();
|
|
4468
|
+
return () => {
|
|
4469
|
+
cancelled = true;
|
|
4470
|
+
generation += 1;
|
|
4471
|
+
stopStream(current);
|
|
4472
|
+
current = null;
|
|
4473
|
+
};
|
|
4474
|
+
}, [audioDeviceId, cameraOn, muted, videoDeviceId]);
|
|
4475
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col items-center gap-3", children: [
|
|
4476
|
+
cameraOn ? /* @__PURE__ */ jsx(
|
|
4477
|
+
"video",
|
|
4478
|
+
{
|
|
4479
|
+
ref: videoRef,
|
|
4480
|
+
autoPlay: true,
|
|
4481
|
+
muted: true,
|
|
4482
|
+
playsInline: true,
|
|
4483
|
+
className: "bg-muted aspect-video w-full rounded-xl object-cover"
|
|
4484
|
+
}
|
|
4485
|
+
) : /* @__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 }) }),
|
|
4486
|
+
/* @__PURE__ */ jsx(
|
|
4487
|
+
AudioWaveform,
|
|
4488
|
+
{
|
|
4489
|
+
track: muted ? null : audioTrack,
|
|
4490
|
+
reducedMotion
|
|
4491
|
+
}
|
|
4492
|
+
),
|
|
4493
|
+
/* @__PURE__ */ jsxs("p", { className: "text-muted-foreground text-sm", role: "status", children: [
|
|
4494
|
+
cameraLabel,
|
|
4495
|
+
": ",
|
|
4496
|
+
cameraOk && cameraOn ? "OK" : "\u2014",
|
|
4497
|
+
" \xB7 ",
|
|
4498
|
+
micLabel,
|
|
4499
|
+
":",
|
|
4500
|
+
" ",
|
|
4501
|
+
micOk && !muted ? "OK" : "\u2014"
|
|
4502
|
+
] })
|
|
4503
|
+
] });
|
|
4504
|
+
}
|
|
4505
|
+
var LOBBY_DEVICE_LABEL_DEFAULTS = {
|
|
4506
|
+
cameraDevice: "Camera",
|
|
4507
|
+
micDevice: "Microphone",
|
|
4508
|
+
speakerDevice: "Speaker",
|
|
4509
|
+
testSpeaker: "Test",
|
|
4510
|
+
testSpeakerError: "Could not play the speaker test",
|
|
4511
|
+
cameraOff: "Camera is off"
|
|
4512
|
+
};
|
|
4102
4513
|
function StudioGuestLobby({
|
|
4103
4514
|
reducedMotion,
|
|
4104
4515
|
cameraOn,
|
|
@@ -4109,11 +4520,48 @@ function StudioGuestLobby({
|
|
|
4109
4520
|
waitingForHost,
|
|
4110
4521
|
error,
|
|
4111
4522
|
labels,
|
|
4523
|
+
savedDevices,
|
|
4112
4524
|
onCameraChange,
|
|
4113
4525
|
onMutedChange,
|
|
4114
4526
|
onDisplayNameChange,
|
|
4527
|
+
onDeviceChange,
|
|
4115
4528
|
onJoin
|
|
4116
4529
|
}) {
|
|
4530
|
+
const [refreshKey, setRefreshKey] = useState(0);
|
|
4531
|
+
const [internalSaved, setInternalSaved] = useState(
|
|
4532
|
+
() => readStudioDeviceSelection()
|
|
4533
|
+
);
|
|
4534
|
+
const [testingSpeaker, setTestingSpeaker] = useState(false);
|
|
4535
|
+
const devices = useMediaDevices(refreshKey);
|
|
4536
|
+
const saved = savedDevices ?? internalSaved;
|
|
4537
|
+
const { effective } = resolveDeviceSelection(saved, devices);
|
|
4538
|
+
const deviceLabels = {
|
|
4539
|
+
cameraDevice: labels.cameraDevice ?? LOBBY_DEVICE_LABEL_DEFAULTS.cameraDevice,
|
|
4540
|
+
micDevice: labels.micDevice ?? LOBBY_DEVICE_LABEL_DEFAULTS.micDevice,
|
|
4541
|
+
speakerDevice: labels.speakerDevice ?? LOBBY_DEVICE_LABEL_DEFAULTS.speakerDevice,
|
|
4542
|
+
testSpeaker: labels.testSpeaker ?? LOBBY_DEVICE_LABEL_DEFAULTS.testSpeaker,
|
|
4543
|
+
testSpeakerError: labels.testSpeakerError ?? LOBBY_DEVICE_LABEL_DEFAULTS.testSpeakerError,
|
|
4544
|
+
cameraOff: labels.cameraOff ?? LOBBY_DEVICE_LABEL_DEFAULTS.cameraOff
|
|
4545
|
+
};
|
|
4546
|
+
function changeDevice(kind, deviceId) {
|
|
4547
|
+
persistStudioDeviceSelection(kind, deviceId);
|
|
4548
|
+
onDeviceChange?.(kind, deviceId);
|
|
4549
|
+
if (!savedDevices) {
|
|
4550
|
+
setInternalSaved((prev) => ({ ...prev, [kind]: deviceId }));
|
|
4551
|
+
}
|
|
4552
|
+
}
|
|
4553
|
+
async function onTestSpeaker() {
|
|
4554
|
+
const speakerId = effective.speaker || devices.speaker[0]?.deviceId || "";
|
|
4555
|
+
if (!speakerId || testingSpeaker) return;
|
|
4556
|
+
setTestingSpeaker(true);
|
|
4557
|
+
try {
|
|
4558
|
+
await playSpeakerTest(speakerId);
|
|
4559
|
+
} catch {
|
|
4560
|
+
toast.error(deviceLabels.testSpeakerError);
|
|
4561
|
+
} finally {
|
|
4562
|
+
setTestingSpeaker(false);
|
|
4563
|
+
}
|
|
4564
|
+
}
|
|
4117
4565
|
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
4566
|
/* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
4119
4567
|
/* @__PURE__ */ jsx("h1", { className: "text-lg font-semibold", children: labels.title }),
|
|
@@ -4125,8 +4573,12 @@ function StudioGuestLobby({
|
|
|
4125
4573
|
reducedMotion,
|
|
4126
4574
|
cameraOn,
|
|
4127
4575
|
muted,
|
|
4576
|
+
audioDeviceId: effective.audio,
|
|
4577
|
+
videoDeviceId: effective.video,
|
|
4128
4578
|
cameraLabel: labels.camera,
|
|
4129
|
-
micLabel: labels.mic
|
|
4579
|
+
micLabel: labels.mic,
|
|
4580
|
+
cameraOffLabel: deviceLabels.cameraOff,
|
|
4581
|
+
onPermission: () => setRefreshKey((n) => n === 0 ? 1 : n)
|
|
4130
4582
|
}
|
|
4131
4583
|
),
|
|
4132
4584
|
/* @__PURE__ */ jsxs("div", { className: "flex justify-center gap-2", children: [
|
|
@@ -4155,6 +4607,26 @@ function StudioGuestLobby({
|
|
|
4155
4607
|
}
|
|
4156
4608
|
)
|
|
4157
4609
|
] }),
|
|
4610
|
+
/* @__PURE__ */ jsx(
|
|
4611
|
+
StudioDeviceSelectGroup,
|
|
4612
|
+
{
|
|
4613
|
+
devices,
|
|
4614
|
+
selected: effective,
|
|
4615
|
+
labels: deviceLabels,
|
|
4616
|
+
onChange: changeDevice,
|
|
4617
|
+
speakerAction: /* @__PURE__ */ jsx(
|
|
4618
|
+
Button,
|
|
4619
|
+
{
|
|
4620
|
+
type: "button",
|
|
4621
|
+
size: "sm",
|
|
4622
|
+
variant: "outline",
|
|
4623
|
+
disabled: !(effective.speaker || devices.speaker[0]?.deviceId) || testingSpeaker,
|
|
4624
|
+
onClick: () => void onTestSpeaker(),
|
|
4625
|
+
children: deviceLabels.testSpeaker
|
|
4626
|
+
}
|
|
4627
|
+
)
|
|
4628
|
+
}
|
|
4629
|
+
),
|
|
4158
4630
|
showName ? /* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
|
|
4159
4631
|
/* @__PURE__ */ jsx(Label, { htmlFor: "studio-guest-name", children: labels.name }),
|
|
4160
4632
|
/* @__PURE__ */ jsx(
|
|
@@ -4200,16 +4672,16 @@ function usePrefersReducedMotion() {
|
|
|
4200
4672
|
// src/studio/lib/guest-identity.ts
|
|
4201
4673
|
var NAME_KEY = "livo-studio-display-name";
|
|
4202
4674
|
var ID_KEY = "livo-studio-guest-id";
|
|
4203
|
-
function readStudioGuestIdentity(storage =
|
|
4675
|
+
function readStudioGuestIdentity(storage = defaultStorage2()) {
|
|
4204
4676
|
const guestId = storage?.getItem(ID_KEY)?.trim() || crypto.randomUUID();
|
|
4205
4677
|
storage?.setItem(ID_KEY, guestId);
|
|
4206
4678
|
const displayName = storage?.getItem(NAME_KEY)?.trim() ?? "";
|
|
4207
4679
|
return { displayName, guestId };
|
|
4208
4680
|
}
|
|
4209
|
-
function persistStudioGuestName(displayName, storage =
|
|
4681
|
+
function persistStudioGuestName(displayName, storage = defaultStorage2()) {
|
|
4210
4682
|
storage?.setItem(NAME_KEY, displayName.trim());
|
|
4211
4683
|
}
|
|
4212
|
-
function
|
|
4684
|
+
function defaultStorage2() {
|
|
4213
4685
|
if (typeof window === "undefined") return null;
|
|
4214
4686
|
try {
|
|
4215
4687
|
return window.localStorage;
|
|
@@ -4793,7 +5265,15 @@ function studioMeetingFromRtk(raw) {
|
|
|
4793
5265
|
notifyStage();
|
|
4794
5266
|
return;
|
|
4795
5267
|
}
|
|
4796
|
-
|
|
5268
|
+
try {
|
|
5269
|
+
await meeting.stage?.join?.();
|
|
5270
|
+
} catch {
|
|
5271
|
+
const userId = meeting.self?.userId;
|
|
5272
|
+
if (userId) {
|
|
5273
|
+
await meeting.stage?.grantAccess?.([userId]);
|
|
5274
|
+
}
|
|
5275
|
+
await meeting.stage?.join?.();
|
|
5276
|
+
}
|
|
4797
5277
|
joinedStageLocally = true;
|
|
4798
5278
|
await republishCameraIfOn();
|
|
4799
5279
|
notifyStage();
|
|
@@ -5083,7 +5563,11 @@ function useRealtimeKitMeeting(authToken, generation = 0) {
|
|
|
5083
5563
|
const [meeting, setMeeting] = useState(null);
|
|
5084
5564
|
const [error, setError] = useState(null);
|
|
5085
5565
|
useEffect(() => {
|
|
5086
|
-
if (!authToken)
|
|
5566
|
+
if (!authToken) {
|
|
5567
|
+
setMeeting(null);
|
|
5568
|
+
setError(null);
|
|
5569
|
+
return;
|
|
5570
|
+
}
|
|
5087
5571
|
let cancelled = false;
|
|
5088
5572
|
let handle = null;
|
|
5089
5573
|
setError(null);
|
|
@@ -5370,6 +5854,9 @@ function LivoGuestStudio({
|
|
|
5370
5854
|
const [waitingForHost, setWaitingForHost] = useState(false);
|
|
5371
5855
|
const [cameraOn, setCameraOn] = useState(true);
|
|
5372
5856
|
const [muted, setMuted] = useState(false);
|
|
5857
|
+
const [deviceSelection, setDeviceSelection] = useState(
|
|
5858
|
+
() => readStudioDeviceSelection()
|
|
5859
|
+
);
|
|
5373
5860
|
const [retryAfterMs, setRetryAfterMs] = useState(null);
|
|
5374
5861
|
const [sessionGen, setSessionGen] = useState(0);
|
|
5375
5862
|
const joinInFlight = useRef(false);
|
|
@@ -5384,7 +5871,15 @@ function LivoGuestStudio({
|
|
|
5384
5871
|
const identity = readStudioGuestIdentity();
|
|
5385
5872
|
setDisplayName(identity.displayName);
|
|
5386
5873
|
setGuestId(identity.guestId);
|
|
5874
|
+
setDeviceSelection(readStudioDeviceSelection());
|
|
5387
5875
|
}, []);
|
|
5876
|
+
const onDeviceChange = useCallback(
|
|
5877
|
+
(kind, deviceId) => {
|
|
5878
|
+
persistStudioDeviceSelection(kind, deviceId);
|
|
5879
|
+
setDeviceSelection((prev) => ({ ...prev, [kind]: deviceId }));
|
|
5880
|
+
},
|
|
5881
|
+
[]
|
|
5882
|
+
);
|
|
5388
5883
|
const join = useCallback(async () => {
|
|
5389
5884
|
if (joinInFlight.current) return;
|
|
5390
5885
|
const name = displayName.trim();
|
|
@@ -5417,9 +5912,9 @@ function LivoGuestStudio({
|
|
|
5417
5912
|
setError(err instanceof Error ? err.message : lobbyLabels.joinError);
|
|
5418
5913
|
setWaitingForHost(false);
|
|
5419
5914
|
}
|
|
5420
|
-
setJoining(false);
|
|
5421
5915
|
} finally {
|
|
5422
5916
|
joinInFlight.current = false;
|
|
5917
|
+
setJoining(false);
|
|
5423
5918
|
}
|
|
5424
5919
|
}, [apiUrl, displayName, guestId, lobbyLabels, token]);
|
|
5425
5920
|
useEffect(() => {
|
|
@@ -5473,9 +5968,11 @@ function LivoGuestStudio({
|
|
|
5473
5968
|
waitingForHost,
|
|
5474
5969
|
error,
|
|
5475
5970
|
labels: lobbyLabels,
|
|
5971
|
+
savedDevices: deviceSelection,
|
|
5476
5972
|
onCameraChange: setCameraOn,
|
|
5477
5973
|
onMutedChange: setMuted,
|
|
5478
5974
|
onDisplayNameChange: setDisplayName,
|
|
5975
|
+
onDeviceChange,
|
|
5479
5976
|
onJoin: () => void join()
|
|
5480
5977
|
}
|
|
5481
5978
|
);
|
|
@@ -5493,13 +5990,18 @@ function LivoGuestStudio({
|
|
|
5493
5990
|
onRejoin: () => {
|
|
5494
5991
|
setSession(null);
|
|
5495
5992
|
setSessionGen((n) => n + 1);
|
|
5993
|
+
setJoining(false);
|
|
5994
|
+
setWaitingForHost(false);
|
|
5995
|
+
setError(null);
|
|
5996
|
+
joinInFlight.current = false;
|
|
5496
5997
|
},
|
|
5497
5998
|
reducedMotion,
|
|
5498
5999
|
labels,
|
|
5499
6000
|
embed,
|
|
5500
6001
|
playerOrigin,
|
|
5501
6002
|
initialCameraOn: cameraOn,
|
|
5502
|
-
initialMuted: muted
|
|
6003
|
+
initialMuted: muted,
|
|
6004
|
+
initialDevices: deviceSelection
|
|
5503
6005
|
}
|
|
5504
6006
|
);
|
|
5505
6007
|
}
|