@livo-tv/sdk 1.1.2 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +5 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/studio/index.cjs +2127 -1625
- package/dist/studio/index.cjs.map +1 -1
- package/dist/studio/index.d.cts +33 -11
- package/dist/studio/index.d.ts +33 -11
- package/dist/studio/index.js +2128 -1626
- package/dist/studio/index.js.map +1 -1
- package/package.json +2 -2
package/dist/studio/index.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-max max-w-[80%] flex-col gap-1 group-data-[align=end]/message:self-end data-[align=end]:self-end data-[variant=ghost]:max-w-full",
|
|
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-max max-w-full overflow-hidden rounded-xl border border-transparent px-3 py-2 text-sm leading-relaxed break-words group-data-[align=end]/bubble:self-end [button]:text-left [button,a]:transition-colors [button,a]:outline-none [button,a]:focus-visible:border-ring [button,a]:focus-visible:ring-3 [button,a]:focus-visible:ring-ring/50",
|
|
2568
2261
|
className
|
|
2569
|
-
)
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
}
|
|
2579
|
-
)
|
|
2580
|
-
] });
|
|
2262
|
+
)
|
|
2263
|
+
},
|
|
2264
|
+
props
|
|
2265
|
+
),
|
|
2266
|
+
render,
|
|
2267
|
+
state: {
|
|
2268
|
+
slot: "bubble-content"
|
|
2269
|
+
}
|
|
2270
|
+
});
|
|
2581
2271
|
}
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
{
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
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"
|
|
2283
|
+
}
|
|
2284
|
+
},
|
|
2285
|
+
defaultVariants: {
|
|
2286
|
+
side: "bottom",
|
|
2287
|
+
align: "end"
|
|
2589
2288
|
}
|
|
2590
|
-
|
|
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", 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
|
+
] })
|
|
2778
|
+
] });
|
|
2779
|
+
}
|
|
2780
|
+
function DropdownMenu({ ...props }) {
|
|
2781
|
+
return /* @__PURE__ */ jsxRuntime.jsx(menu.Menu.Root, { "data-slot": "dropdown-menu", ...props });
|
|
2782
|
+
}
|
|
2783
|
+
function DropdownMenuTrigger({ ...props }) {
|
|
2784
|
+
return /* @__PURE__ */ jsxRuntime.jsx(menu.Menu.Trigger, { "data-slot": "dropdown-menu-trigger", ...props });
|
|
2591
2785
|
}
|
|
2592
|
-
function
|
|
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
|
-
|
|
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
|
|
2694
2924
|
}) {
|
|
2695
|
-
const
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
if (!selected.speaker || testingSpeaker) return;
|
|
2766
|
-
setTestingSpeaker(true);
|
|
2767
|
-
try {
|
|
2768
|
-
await playSpeakerTest(selected.speaker);
|
|
2769
|
-
} catch {
|
|
2770
|
-
sonner.toast.error(labels.testSpeakerError);
|
|
2771
|
-
} finally {
|
|
2772
|
-
setTestingSpeaker(false);
|
|
2773
|
-
}
|
|
2774
|
-
}
|
|
2775
|
-
return /* @__PURE__ */ jsxRuntime.jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(DialogContent, { className: "sm:max-w-md", children: [
|
|
2776
|
-
/* @__PURE__ */ jsxRuntime.jsxs(DialogHeader, { children: [
|
|
2777
|
-
/* @__PURE__ */ jsxRuntime.jsx(DialogTitle, { children: labels.settingsTitle }),
|
|
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: [
|
|
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();
|
|
@@ -3059,14 +3263,24 @@ function StudioToolbar({
|
|
|
3059
3263
|
children: selfStageStatus === "ON_STAGE" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.EyeOff, {}) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Eye, {})
|
|
3060
3264
|
}
|
|
3061
3265
|
) : null,
|
|
3062
|
-
session.role === "guest" && stageEnabled
|
|
3266
|
+
session.role === "guest" && stageEnabled ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
3063
3267
|
ControlIcon,
|
|
3064
3268
|
{
|
|
3065
|
-
label: selfStageStatus === "REQUESTED_TO_JOIN_STAGE" ? labels.stageRequested : labels.askToGoOnAir,
|
|
3269
|
+
label: selfStageStatus === "ON_STAGE" ? labels.goOffAir : selfStageStatus === "REQUESTED_TO_JOIN_STAGE" ? labels.stageRequested : labels.askToGoOnAir,
|
|
3066
3270
|
pressed: selfStageStatus === "REQUESTED_TO_JOIN_STAGE",
|
|
3067
|
-
disabled: selfStageStatus === "REQUESTED_TO_JOIN_STAGE",
|
|
3068
|
-
onClick:
|
|
3069
|
-
|
|
3271
|
+
disabled: selfStageStatus === "REQUESTED_TO_JOIN_STAGE" || selfStageStatus === "ON_STAGE" && !canLeaveStage,
|
|
3272
|
+
onClick: () => {
|
|
3273
|
+
if (selfStageStatus === "ON_STAGE") {
|
|
3274
|
+
if (!canLeaveStage) {
|
|
3275
|
+
sonner.toast.message(labels.offAirLastVisible);
|
|
3276
|
+
return;
|
|
3277
|
+
}
|
|
3278
|
+
onLeaveStage();
|
|
3279
|
+
return;
|
|
3280
|
+
}
|
|
3281
|
+
onAskStage();
|
|
3282
|
+
},
|
|
3283
|
+
children: selfStageStatus === "ON_STAGE" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.EyeOff, {}) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Eye, {})
|
|
3070
3284
|
}
|
|
3071
3285
|
) : null,
|
|
3072
3286
|
showScreenControl ? canShareFreely ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -3119,14 +3333,14 @@ function StudioToolbar({
|
|
|
3119
3333
|
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Users, {})
|
|
3120
3334
|
}
|
|
3121
3335
|
) : null,
|
|
3122
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3336
|
+
fullscreenSupported ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
3123
3337
|
ControlIcon,
|
|
3124
3338
|
{
|
|
3125
3339
|
label: labels.fullscreen,
|
|
3126
3340
|
onClick: () => void enterFullscreen(),
|
|
3127
3341
|
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Maximize, {})
|
|
3128
3342
|
}
|
|
3129
|
-
),
|
|
3343
|
+
) : null,
|
|
3130
3344
|
pipSupported ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
3131
3345
|
ControlIcon,
|
|
3132
3346
|
{
|
|
@@ -3211,7 +3425,8 @@ function StudioRoom({
|
|
|
3211
3425
|
playerOrigin,
|
|
3212
3426
|
windowGate,
|
|
3213
3427
|
initialCameraOn = true,
|
|
3214
|
-
initialMuted = false
|
|
3428
|
+
initialMuted = false,
|
|
3429
|
+
initialDevices
|
|
3215
3430
|
}) {
|
|
3216
3431
|
const [cameraOn, setCameraOn] = react.useState(initialCameraOn);
|
|
3217
3432
|
const [screenOn, setScreenOn] = react.useState(false);
|
|
@@ -3274,6 +3489,21 @@ function StudioRoom({
|
|
|
3274
3489
|
postLivoEvent("live", { streamId: session.stream.id });
|
|
3275
3490
|
}
|
|
3276
3491
|
}, [onAir, session.stream.id]);
|
|
3492
|
+
react.useEffect(() => {
|
|
3493
|
+
const saved = initialDevices ?? readStudioDeviceSelection();
|
|
3494
|
+
let cancelled = false;
|
|
3495
|
+
void (async () => {
|
|
3496
|
+
try {
|
|
3497
|
+
const listed = await meeting.listDevices();
|
|
3498
|
+
if (cancelled) return;
|
|
3499
|
+
await applyStudioDeviceSelection(meeting.setDevice, saved, listed);
|
|
3500
|
+
} catch {
|
|
3501
|
+
}
|
|
3502
|
+
})();
|
|
3503
|
+
return () => {
|
|
3504
|
+
cancelled = true;
|
|
3505
|
+
};
|
|
3506
|
+
}, [initialDevices, meeting]);
|
|
3277
3507
|
react.useEffect(() => {
|
|
3278
3508
|
return () => {
|
|
3279
3509
|
for (const id of grantCheckTimers.current) window.clearTimeout(id);
|
|
@@ -3473,10 +3703,9 @@ function StudioRoom({
|
|
|
3473
3703
|
const self = participants.find((row) => row.isSelf);
|
|
3474
3704
|
const canShareFreely = session.role === "moderator" || self?.stageStatus === "ON_STAGE" || shareGrant === "granted";
|
|
3475
3705
|
const selfId = self?.id ?? null;
|
|
3706
|
+
const selfUserId = self?.userId || null;
|
|
3476
3707
|
const remoteParticipants = participants.filter((row) => !row.isSelf);
|
|
3477
|
-
const incoming = chatMessages.filter(
|
|
3478
|
-
(row) => row.userId !== selfId && row.userId !== self?.userId
|
|
3479
|
-
);
|
|
3708
|
+
const incoming = chatMessages.filter((row) => row.userId !== selfUserId);
|
|
3480
3709
|
const unread = sideTab === "chat" ? 0 : Math.max(0, incoming.length - seenIncoming);
|
|
3481
3710
|
const peopleBadge = waitlisted.length + stageRequests.length;
|
|
3482
3711
|
react.useEffect(() => {
|
|
@@ -3816,17 +4045,21 @@ function StudioRoom({
|
|
|
3816
4045
|
) });
|
|
3817
4046
|
}
|
|
3818
4047
|
if (inWaitingRoom && session.role === "guest") {
|
|
3819
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
4048
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4049
|
+
StudioGuestWaiting,
|
|
4050
|
+
{
|
|
4051
|
+
meeting,
|
|
4052
|
+
reducedMotion,
|
|
4053
|
+
cameraOn,
|
|
4054
|
+
muted,
|
|
4055
|
+
labels,
|
|
4056
|
+
settingsOpen,
|
|
4057
|
+
onToggleCamera,
|
|
4058
|
+
onToggleMute: () => void onToggleMute(),
|
|
4059
|
+
onOpenSettings: () => setSettingsOpen(true),
|
|
4060
|
+
onCloseSettings: setSettingsOpen
|
|
4061
|
+
}
|
|
4062
|
+
);
|
|
3830
4063
|
}
|
|
3831
4064
|
if (participants.length > 0 || waitlisted.length > 0) {
|
|
3832
4065
|
sawParticipants.current = true;
|
|
@@ -3838,7 +4071,14 @@ function StudioRoom({
|
|
|
3838
4071
|
/* @__PURE__ */ jsxRuntime.jsxs("header", { className: "flex h-12 items-center gap-3 border-b px-3", children: [
|
|
3839
4072
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: session.stream.title }),
|
|
3840
4073
|
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(
|
|
4074
|
+
onAir ? /* @__PURE__ */ jsxRuntime.jsx(StudioLiveBadge, { role: "status", children: statusChip }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
4075
|
+
"span",
|
|
4076
|
+
{
|
|
4077
|
+
className: "rounded-full border px-2 py-0.5 text-xs",
|
|
4078
|
+
role: "status",
|
|
4079
|
+
children: statusChip
|
|
4080
|
+
}
|
|
4081
|
+
),
|
|
3842
4082
|
session.role === "moderator" && (egressLost || !starting && !sending && !onAir && !active) ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
3843
4083
|
Button,
|
|
3844
4084
|
{
|
|
@@ -3946,7 +4186,7 @@ function StudioRoom({
|
|
|
3946
4186
|
onTabChange: setSideTab,
|
|
3947
4187
|
onClose: () => setSideTab(null),
|
|
3948
4188
|
meeting,
|
|
3949
|
-
|
|
4189
|
+
selfUserId,
|
|
3950
4190
|
participants,
|
|
3951
4191
|
waitlisted,
|
|
3952
4192
|
stageRequests,
|
|
@@ -4123,6 +4363,177 @@ function StudioRoom({
|
|
|
4123
4363
|
) : null
|
|
4124
4364
|
] });
|
|
4125
4365
|
}
|
|
4366
|
+
function isOverconstrained(error) {
|
|
4367
|
+
if (!error || typeof error !== "object") return false;
|
|
4368
|
+
return error.name === "OverconstrainedError";
|
|
4369
|
+
}
|
|
4370
|
+
function trackConstraint(deviceId, exact) {
|
|
4371
|
+
if (exact && deviceId) {
|
|
4372
|
+
return { deviceId: { exact: deviceId } };
|
|
4373
|
+
}
|
|
4374
|
+
return true;
|
|
4375
|
+
}
|
|
4376
|
+
function stopStream(stream) {
|
|
4377
|
+
if (!stream) return;
|
|
4378
|
+
for (const track of stream.getTracks()) {
|
|
4379
|
+
track.onended = null;
|
|
4380
|
+
track.stop();
|
|
4381
|
+
}
|
|
4382
|
+
}
|
|
4383
|
+
async function requestPreviewStream(wantAudio, wantVideo, audioDeviceId, videoDeviceId, exact) {
|
|
4384
|
+
return navigator.mediaDevices.getUserMedia({
|
|
4385
|
+
audio: wantAudio ? trackConstraint(audioDeviceId, exact) : false,
|
|
4386
|
+
video: wantVideo ? trackConstraint(videoDeviceId, exact) : false
|
|
4387
|
+
});
|
|
4388
|
+
}
|
|
4389
|
+
function StudioDevicePreflight({
|
|
4390
|
+
reducedMotion,
|
|
4391
|
+
cameraLabel,
|
|
4392
|
+
micLabel,
|
|
4393
|
+
cameraOn = true,
|
|
4394
|
+
muted = false,
|
|
4395
|
+
audioDeviceId = "",
|
|
4396
|
+
videoDeviceId = "",
|
|
4397
|
+
cameraOffLabel = "Camera is off",
|
|
4398
|
+
onPermission,
|
|
4399
|
+
onError
|
|
4400
|
+
}) {
|
|
4401
|
+
const videoRef = react.useRef(null);
|
|
4402
|
+
const onPermissionRef = react.useRef(onPermission);
|
|
4403
|
+
const onErrorRef = react.useRef(onError);
|
|
4404
|
+
onPermissionRef.current = onPermission;
|
|
4405
|
+
onErrorRef.current = onError;
|
|
4406
|
+
const [audioTrack, setAudioTrack] = react.useState(null);
|
|
4407
|
+
const [cameraOk, setCameraOk] = react.useState(false);
|
|
4408
|
+
const [micOk, setMicOk] = react.useState(false);
|
|
4409
|
+
react.useEffect(() => {
|
|
4410
|
+
let cancelled = false;
|
|
4411
|
+
let generation = 0;
|
|
4412
|
+
let current = null;
|
|
4413
|
+
function bindEnded(stream, gen) {
|
|
4414
|
+
for (const track of stream.getTracks()) {
|
|
4415
|
+
track.onended = () => {
|
|
4416
|
+
if (cancelled || gen !== generation) return;
|
|
4417
|
+
void acquire();
|
|
4418
|
+
};
|
|
4419
|
+
}
|
|
4420
|
+
}
|
|
4421
|
+
async function acquire() {
|
|
4422
|
+
const gen = ++generation;
|
|
4423
|
+
stopStream(current);
|
|
4424
|
+
current = null;
|
|
4425
|
+
if (cancelled) return;
|
|
4426
|
+
if (!cameraOn && muted) {
|
|
4427
|
+
setAudioTrack(null);
|
|
4428
|
+
setCameraOk(false);
|
|
4429
|
+
setMicOk(false);
|
|
4430
|
+
if (videoRef.current) videoRef.current.srcObject = null;
|
|
4431
|
+
return;
|
|
4432
|
+
}
|
|
4433
|
+
if (!navigator.mediaDevices?.getUserMedia) {
|
|
4434
|
+
setAudioTrack(null);
|
|
4435
|
+
setCameraOk(false);
|
|
4436
|
+
setMicOk(false);
|
|
4437
|
+
onErrorRef.current?.();
|
|
4438
|
+
return;
|
|
4439
|
+
}
|
|
4440
|
+
const wantAudio = !muted;
|
|
4441
|
+
const wantVideo = cameraOn;
|
|
4442
|
+
const hadExact = Boolean(
|
|
4443
|
+
wantAudio && audioDeviceId || wantVideo && videoDeviceId
|
|
4444
|
+
);
|
|
4445
|
+
try {
|
|
4446
|
+
let next;
|
|
4447
|
+
try {
|
|
4448
|
+
next = await requestPreviewStream(
|
|
4449
|
+
wantAudio,
|
|
4450
|
+
wantVideo,
|
|
4451
|
+
audioDeviceId,
|
|
4452
|
+
videoDeviceId,
|
|
4453
|
+
true
|
|
4454
|
+
);
|
|
4455
|
+
} catch (error) {
|
|
4456
|
+
if (!isOverconstrained(error) || !hadExact) {
|
|
4457
|
+
throw error;
|
|
4458
|
+
}
|
|
4459
|
+
next = await requestPreviewStream(
|
|
4460
|
+
wantAudio,
|
|
4461
|
+
wantVideo,
|
|
4462
|
+
audioDeviceId,
|
|
4463
|
+
videoDeviceId,
|
|
4464
|
+
false
|
|
4465
|
+
);
|
|
4466
|
+
}
|
|
4467
|
+
if (cancelled || gen !== generation) {
|
|
4468
|
+
stopStream(next);
|
|
4469
|
+
return;
|
|
4470
|
+
}
|
|
4471
|
+
current = next;
|
|
4472
|
+
const video = next.getVideoTracks()[0] ?? null;
|
|
4473
|
+
const audio = next.getAudioTracks()[0] ?? null;
|
|
4474
|
+
setCameraOk(Boolean(video));
|
|
4475
|
+
setMicOk(Boolean(audio));
|
|
4476
|
+
setAudioTrack(audio);
|
|
4477
|
+
if (videoRef.current) {
|
|
4478
|
+
videoRef.current.srcObject = video ? next : null;
|
|
4479
|
+
}
|
|
4480
|
+
bindEnded(next, gen);
|
|
4481
|
+
onPermissionRef.current?.();
|
|
4482
|
+
} catch {
|
|
4483
|
+
if (cancelled || gen !== generation) return;
|
|
4484
|
+
setAudioTrack(null);
|
|
4485
|
+
setCameraOk(false);
|
|
4486
|
+
setMicOk(false);
|
|
4487
|
+
if (videoRef.current) videoRef.current.srcObject = null;
|
|
4488
|
+
onErrorRef.current?.();
|
|
4489
|
+
}
|
|
4490
|
+
}
|
|
4491
|
+
void acquire();
|
|
4492
|
+
return () => {
|
|
4493
|
+
cancelled = true;
|
|
4494
|
+
generation += 1;
|
|
4495
|
+
stopStream(current);
|
|
4496
|
+
current = null;
|
|
4497
|
+
};
|
|
4498
|
+
}, [audioDeviceId, cameraOn, muted, videoDeviceId]);
|
|
4499
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full flex-col items-center gap-3", children: [
|
|
4500
|
+
cameraOn ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
4501
|
+
"video",
|
|
4502
|
+
{
|
|
4503
|
+
ref: videoRef,
|
|
4504
|
+
autoPlay: true,
|
|
4505
|
+
muted: true,
|
|
4506
|
+
playsInline: true,
|
|
4507
|
+
className: "bg-muted aspect-video w-full rounded-xl object-cover"
|
|
4508
|
+
}
|
|
4509
|
+
) : /* @__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 }) }),
|
|
4510
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4511
|
+
AudioWaveform,
|
|
4512
|
+
{
|
|
4513
|
+
track: muted ? null : audioTrack,
|
|
4514
|
+
reducedMotion
|
|
4515
|
+
}
|
|
4516
|
+
),
|
|
4517
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-muted-foreground text-sm", role: "status", children: [
|
|
4518
|
+
cameraLabel,
|
|
4519
|
+
": ",
|
|
4520
|
+
cameraOk && cameraOn ? "OK" : "\u2014",
|
|
4521
|
+
" \xB7 ",
|
|
4522
|
+
micLabel,
|
|
4523
|
+
":",
|
|
4524
|
+
" ",
|
|
4525
|
+
micOk && !muted ? "OK" : "\u2014"
|
|
4526
|
+
] })
|
|
4527
|
+
] });
|
|
4528
|
+
}
|
|
4529
|
+
var LOBBY_DEVICE_LABEL_DEFAULTS = {
|
|
4530
|
+
cameraDevice: "Camera",
|
|
4531
|
+
micDevice: "Microphone",
|
|
4532
|
+
speakerDevice: "Speaker",
|
|
4533
|
+
testSpeaker: "Test",
|
|
4534
|
+
testSpeakerError: "Could not play the speaker test",
|
|
4535
|
+
cameraOff: "Camera is off"
|
|
4536
|
+
};
|
|
4126
4537
|
function StudioGuestLobby({
|
|
4127
4538
|
reducedMotion,
|
|
4128
4539
|
cameraOn,
|
|
@@ -4133,11 +4544,48 @@ function StudioGuestLobby({
|
|
|
4133
4544
|
waitingForHost,
|
|
4134
4545
|
error,
|
|
4135
4546
|
labels,
|
|
4547
|
+
savedDevices,
|
|
4136
4548
|
onCameraChange,
|
|
4137
4549
|
onMutedChange,
|
|
4138
4550
|
onDisplayNameChange,
|
|
4551
|
+
onDeviceChange,
|
|
4139
4552
|
onJoin
|
|
4140
4553
|
}) {
|
|
4554
|
+
const [refreshKey, setRefreshKey] = react.useState(0);
|
|
4555
|
+
const [internalSaved, setInternalSaved] = react.useState(
|
|
4556
|
+
() => readStudioDeviceSelection()
|
|
4557
|
+
);
|
|
4558
|
+
const [testingSpeaker, setTestingSpeaker] = react.useState(false);
|
|
4559
|
+
const devices = useMediaDevices(refreshKey);
|
|
4560
|
+
const saved = savedDevices ?? internalSaved;
|
|
4561
|
+
const { effective } = resolveDeviceSelection(saved, devices);
|
|
4562
|
+
const deviceLabels = {
|
|
4563
|
+
cameraDevice: labels.cameraDevice ?? LOBBY_DEVICE_LABEL_DEFAULTS.cameraDevice,
|
|
4564
|
+
micDevice: labels.micDevice ?? LOBBY_DEVICE_LABEL_DEFAULTS.micDevice,
|
|
4565
|
+
speakerDevice: labels.speakerDevice ?? LOBBY_DEVICE_LABEL_DEFAULTS.speakerDevice,
|
|
4566
|
+
testSpeaker: labels.testSpeaker ?? LOBBY_DEVICE_LABEL_DEFAULTS.testSpeaker,
|
|
4567
|
+
testSpeakerError: labels.testSpeakerError ?? LOBBY_DEVICE_LABEL_DEFAULTS.testSpeakerError,
|
|
4568
|
+
cameraOff: labels.cameraOff ?? LOBBY_DEVICE_LABEL_DEFAULTS.cameraOff
|
|
4569
|
+
};
|
|
4570
|
+
function changeDevice(kind, deviceId) {
|
|
4571
|
+
persistStudioDeviceSelection(kind, deviceId);
|
|
4572
|
+
onDeviceChange?.(kind, deviceId);
|
|
4573
|
+
if (!savedDevices) {
|
|
4574
|
+
setInternalSaved((prev) => ({ ...prev, [kind]: deviceId }));
|
|
4575
|
+
}
|
|
4576
|
+
}
|
|
4577
|
+
async function onTestSpeaker() {
|
|
4578
|
+
const speakerId = effective.speaker || devices.speaker[0]?.deviceId || "";
|
|
4579
|
+
if (!speakerId || testingSpeaker) return;
|
|
4580
|
+
setTestingSpeaker(true);
|
|
4581
|
+
try {
|
|
4582
|
+
await playSpeakerTest(speakerId);
|
|
4583
|
+
} catch {
|
|
4584
|
+
sonner.toast.error(deviceLabels.testSpeakerError);
|
|
4585
|
+
} finally {
|
|
4586
|
+
setTestingSpeaker(false);
|
|
4587
|
+
}
|
|
4588
|
+
}
|
|
4141
4589
|
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
4590
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
|
|
4143
4591
|
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-lg font-semibold", children: labels.title }),
|
|
@@ -4149,8 +4597,12 @@ function StudioGuestLobby({
|
|
|
4149
4597
|
reducedMotion,
|
|
4150
4598
|
cameraOn,
|
|
4151
4599
|
muted,
|
|
4600
|
+
audioDeviceId: effective.audio,
|
|
4601
|
+
videoDeviceId: effective.video,
|
|
4152
4602
|
cameraLabel: labels.camera,
|
|
4153
|
-
micLabel: labels.mic
|
|
4603
|
+
micLabel: labels.mic,
|
|
4604
|
+
cameraOffLabel: deviceLabels.cameraOff,
|
|
4605
|
+
onPermission: () => setRefreshKey((n) => n === 0 ? 1 : n)
|
|
4154
4606
|
}
|
|
4155
4607
|
),
|
|
4156
4608
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-center gap-2", children: [
|
|
@@ -4179,6 +4631,26 @@ function StudioGuestLobby({
|
|
|
4179
4631
|
}
|
|
4180
4632
|
)
|
|
4181
4633
|
] }),
|
|
4634
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4635
|
+
StudioDeviceSelectGroup,
|
|
4636
|
+
{
|
|
4637
|
+
devices,
|
|
4638
|
+
selected: effective,
|
|
4639
|
+
labels: deviceLabels,
|
|
4640
|
+
onChange: changeDevice,
|
|
4641
|
+
speakerAction: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4642
|
+
Button,
|
|
4643
|
+
{
|
|
4644
|
+
type: "button",
|
|
4645
|
+
size: "sm",
|
|
4646
|
+
variant: "outline",
|
|
4647
|
+
disabled: !(effective.speaker || devices.speaker[0]?.deviceId) || testingSpeaker,
|
|
4648
|
+
onClick: () => void onTestSpeaker(),
|
|
4649
|
+
children: deviceLabels.testSpeaker
|
|
4650
|
+
}
|
|
4651
|
+
)
|
|
4652
|
+
}
|
|
4653
|
+
),
|
|
4182
4654
|
showName ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
|
|
4183
4655
|
/* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "studio-guest-name", children: labels.name }),
|
|
4184
4656
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -4224,16 +4696,16 @@ function usePrefersReducedMotion() {
|
|
|
4224
4696
|
// src/studio/lib/guest-identity.ts
|
|
4225
4697
|
var NAME_KEY = "livo-studio-display-name";
|
|
4226
4698
|
var ID_KEY = "livo-studio-guest-id";
|
|
4227
|
-
function readStudioGuestIdentity(storage =
|
|
4699
|
+
function readStudioGuestIdentity(storage = defaultStorage2()) {
|
|
4228
4700
|
const guestId = storage?.getItem(ID_KEY)?.trim() || crypto.randomUUID();
|
|
4229
4701
|
storage?.setItem(ID_KEY, guestId);
|
|
4230
4702
|
const displayName = storage?.getItem(NAME_KEY)?.trim() ?? "";
|
|
4231
4703
|
return { displayName, guestId };
|
|
4232
4704
|
}
|
|
4233
|
-
function persistStudioGuestName(displayName, storage =
|
|
4705
|
+
function persistStudioGuestName(displayName, storage = defaultStorage2()) {
|
|
4234
4706
|
storage?.setItem(NAME_KEY, displayName.trim());
|
|
4235
4707
|
}
|
|
4236
|
-
function
|
|
4708
|
+
function defaultStorage2() {
|
|
4237
4709
|
if (typeof window === "undefined") return null;
|
|
4238
4710
|
try {
|
|
4239
4711
|
return window.localStorage;
|
|
@@ -4817,7 +5289,15 @@ function studioMeetingFromRtk(raw) {
|
|
|
4817
5289
|
notifyStage();
|
|
4818
5290
|
return;
|
|
4819
5291
|
}
|
|
4820
|
-
|
|
5292
|
+
try {
|
|
5293
|
+
await meeting.stage?.join?.();
|
|
5294
|
+
} catch {
|
|
5295
|
+
const userId = meeting.self?.userId;
|
|
5296
|
+
if (userId) {
|
|
5297
|
+
await meeting.stage?.grantAccess?.([userId]);
|
|
5298
|
+
}
|
|
5299
|
+
await meeting.stage?.join?.();
|
|
5300
|
+
}
|
|
4821
5301
|
joinedStageLocally = true;
|
|
4822
5302
|
await republishCameraIfOn();
|
|
4823
5303
|
notifyStage();
|
|
@@ -5107,7 +5587,11 @@ function useRealtimeKitMeeting(authToken, generation = 0) {
|
|
|
5107
5587
|
const [meeting, setMeeting] = react.useState(null);
|
|
5108
5588
|
const [error, setError] = react.useState(null);
|
|
5109
5589
|
react.useEffect(() => {
|
|
5110
|
-
if (!authToken)
|
|
5590
|
+
if (!authToken) {
|
|
5591
|
+
setMeeting(null);
|
|
5592
|
+
setError(null);
|
|
5593
|
+
return;
|
|
5594
|
+
}
|
|
5111
5595
|
let cancelled = false;
|
|
5112
5596
|
let handle = null;
|
|
5113
5597
|
setError(null);
|
|
@@ -5394,6 +5878,9 @@ function LivoGuestStudio({
|
|
|
5394
5878
|
const [waitingForHost, setWaitingForHost] = react.useState(false);
|
|
5395
5879
|
const [cameraOn, setCameraOn] = react.useState(true);
|
|
5396
5880
|
const [muted, setMuted] = react.useState(false);
|
|
5881
|
+
const [deviceSelection, setDeviceSelection] = react.useState(
|
|
5882
|
+
() => readStudioDeviceSelection()
|
|
5883
|
+
);
|
|
5397
5884
|
const [retryAfterMs, setRetryAfterMs] = react.useState(null);
|
|
5398
5885
|
const [sessionGen, setSessionGen] = react.useState(0);
|
|
5399
5886
|
const joinInFlight = react.useRef(false);
|
|
@@ -5408,7 +5895,15 @@ function LivoGuestStudio({
|
|
|
5408
5895
|
const identity = readStudioGuestIdentity();
|
|
5409
5896
|
setDisplayName(identity.displayName);
|
|
5410
5897
|
setGuestId(identity.guestId);
|
|
5898
|
+
setDeviceSelection(readStudioDeviceSelection());
|
|
5411
5899
|
}, []);
|
|
5900
|
+
const onDeviceChange = react.useCallback(
|
|
5901
|
+
(kind, deviceId) => {
|
|
5902
|
+
persistStudioDeviceSelection(kind, deviceId);
|
|
5903
|
+
setDeviceSelection((prev) => ({ ...prev, [kind]: deviceId }));
|
|
5904
|
+
},
|
|
5905
|
+
[]
|
|
5906
|
+
);
|
|
5412
5907
|
const join = react.useCallback(async () => {
|
|
5413
5908
|
if (joinInFlight.current) return;
|
|
5414
5909
|
const name = displayName.trim();
|
|
@@ -5441,9 +5936,9 @@ function LivoGuestStudio({
|
|
|
5441
5936
|
setError(err instanceof Error ? err.message : lobbyLabels.joinError);
|
|
5442
5937
|
setWaitingForHost(false);
|
|
5443
5938
|
}
|
|
5444
|
-
setJoining(false);
|
|
5445
5939
|
} finally {
|
|
5446
5940
|
joinInFlight.current = false;
|
|
5941
|
+
setJoining(false);
|
|
5447
5942
|
}
|
|
5448
5943
|
}, [apiUrl, displayName, guestId, lobbyLabels, token]);
|
|
5449
5944
|
react.useEffect(() => {
|
|
@@ -5497,9 +5992,11 @@ function LivoGuestStudio({
|
|
|
5497
5992
|
waitingForHost,
|
|
5498
5993
|
error,
|
|
5499
5994
|
labels: lobbyLabels,
|
|
5995
|
+
savedDevices: deviceSelection,
|
|
5500
5996
|
onCameraChange: setCameraOn,
|
|
5501
5997
|
onMutedChange: setMuted,
|
|
5502
5998
|
onDisplayNameChange: setDisplayName,
|
|
5999
|
+
onDeviceChange,
|
|
5503
6000
|
onJoin: () => void join()
|
|
5504
6001
|
}
|
|
5505
6002
|
);
|
|
@@ -5517,13 +6014,18 @@ function LivoGuestStudio({
|
|
|
5517
6014
|
onRejoin: () => {
|
|
5518
6015
|
setSession(null);
|
|
5519
6016
|
setSessionGen((n) => n + 1);
|
|
6017
|
+
setJoining(false);
|
|
6018
|
+
setWaitingForHost(false);
|
|
6019
|
+
setError(null);
|
|
6020
|
+
joinInFlight.current = false;
|
|
5520
6021
|
},
|
|
5521
6022
|
reducedMotion,
|
|
5522
6023
|
labels,
|
|
5523
6024
|
embed,
|
|
5524
6025
|
playerOrigin,
|
|
5525
6026
|
initialCameraOn: cameraOn,
|
|
5526
|
-
initialMuted: muted
|
|
6027
|
+
initialMuted: muted,
|
|
6028
|
+
initialDevices: deviceSelection
|
|
5527
6029
|
}
|
|
5528
6030
|
);
|
|
5529
6031
|
}
|