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