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