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