@ai-matrx/meet 0.7.84 → 0.8.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.
@@ -0,0 +1,4636 @@
1
+ "use client";
2
+ "use strict";
3
+ "use client";
4
+ var __create = Object.create;
5
+ var __defProp = Object.defineProperty;
6
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __getProtoOf = Object.getPrototypeOf;
9
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
10
+ var __export = (target, all) => {
11
+ for (var name in all)
12
+ __defProp(target, name, { get: all[name], enumerable: true });
13
+ };
14
+ var __copyProps = (to, from, except, desc) => {
15
+ if (from && typeof from === "object" || typeof from === "function") {
16
+ for (let key of __getOwnPropNames(from))
17
+ if (!__hasOwnProp.call(to, key) && key !== except)
18
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
19
+ }
20
+ return to;
21
+ };
22
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
23
+ // If the importer is in node compatibility mode or this is not an ESM
24
+ // file that has been converted to a CommonJS file using a Babel-
25
+ // compatible transform (i.e. "__esModule" has not been set), then set
26
+ // "default" to the CommonJS "module.exports" for node compatibility.
27
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
28
+ mod
29
+ ));
30
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
31
+
32
+ // src/skins/meet.ts
33
+ var meet_exports = {};
34
+ __export(meet_exports, {
35
+ ActivitiesControl: () => ActivitiesControl,
36
+ ActivitiesIcon: () => ActivitiesIcon,
37
+ AiPanel: () => AiPanel,
38
+ AttendanceNotice: () => AttendanceNotice,
39
+ Avatar: () => Avatar,
40
+ BreakoutBanner: () => BreakoutBanner,
41
+ BreakoutIcon: () => BreakoutIcon,
42
+ BreakoutsPanel: () => BreakoutsPanel,
43
+ CameraIcon: () => CameraIcon,
44
+ CameraOffIcon: () => CameraOffIcon,
45
+ CaptionLanguageSettings: () => CaptionLanguageSettings,
46
+ Captions: () => Captions,
47
+ ChatIcon: () => ChatIcon,
48
+ ChatPanel: () => ChatPanel,
49
+ CheckIcon: () => CheckIcon,
50
+ CloseIcon: () => CloseIcon,
51
+ ConnectionDot: () => ConnectionDot,
52
+ ConsentNotice: () => ConsentNotice,
53
+ ControlBar: () => ControlBar,
54
+ ControlButton: () => ControlButton,
55
+ EraserIcon: () => EraserIcon,
56
+ FlagIcon: () => FlagIcon,
57
+ HandIcon: () => HandIcon,
58
+ HighlighterIcon: () => HighlighterIcon,
59
+ HostMenu: () => HostMenu,
60
+ KeyboardIcon: () => KeyboardIcon,
61
+ LayoutIcon: () => LayoutIcon,
62
+ LobbyPanel: () => LobbyPanel,
63
+ LockIcon: () => LockIcon,
64
+ MeetSkinRoom: () => MeetSkinRoom,
65
+ MeetingOverlays: () => MeetingOverlays,
66
+ MenuItem: () => MenuItem,
67
+ MicIcon: () => MicIcon,
68
+ MicOffIcon: () => MicOffIcon,
69
+ MoreIcon: () => MoreIcon,
70
+ PanelShell: () => PanelShell,
71
+ ParticipantTile: () => ParticipantTile,
72
+ PencilIcon: () => PencilIcon,
73
+ PeopleIcon: () => PeopleIcon,
74
+ PeoplePanel: () => PeoplePanel,
75
+ PersonMenu: () => PersonMenu,
76
+ PhoneIcon: () => PhoneIcon,
77
+ PhoneOffIcon: () => PhoneOffIcon,
78
+ PinIcon: () => PinIcon,
79
+ PollIcon: () => PollIcon,
80
+ PollsPanel: () => PollsPanel,
81
+ Popover: () => Popover,
82
+ PreJoin: () => PreJoin,
83
+ QaPanel: () => QaPanel,
84
+ QualityBars: () => QualityBars,
85
+ QuestionIcon: () => QuestionIcon,
86
+ ReactionOverlay: () => ReactionOverlay,
87
+ RecordIcon: () => RecordIcon,
88
+ RecordingIndicator: () => RecordingIndicator,
89
+ ScreenShareIcon: () => ScreenShareIcon,
90
+ SearchIcon: () => SearchIcon,
91
+ SettingsIcon: () => SettingsIcon,
92
+ SettingsPanel: () => SettingsPanel,
93
+ ShortcutsSheet: () => ShortcutsSheet,
94
+ SignalIcon: () => SignalIcon,
95
+ SmileIcon: () => SmileIcon,
96
+ SparklesIcon: () => SparklesIcon,
97
+ SpeakerIcon: () => SpeakerIcon,
98
+ SpotlightIcon: () => SpotlightIcon,
99
+ Stage: () => Stage,
100
+ Toast: () => Toast,
101
+ UndoIcon: () => UndoIcon,
102
+ UpvoteIcon: () => UpvoteIcon,
103
+ WhiteboardIcon: () => WhiteboardIcon,
104
+ WhiteboardPanel: () => WhiteboardPanel,
105
+ WifiLowIcon: () => WifiLowIcon
106
+ });
107
+ module.exports = __toCommonJS(meet_exports);
108
+
109
+ // src/skins/meet/components.tsx
110
+ var import_react9 = require("react");
111
+
112
+ // src/react/facade.ts
113
+ var import_react7 = require("react");
114
+
115
+ // src/react/root.tsx
116
+ var import_react3 = require("react");
117
+
118
+ // src/core/names.ts
119
+ function isGuestIdentity(identity) {
120
+ return String(identity).startsWith("guest:");
121
+ }
122
+ function isAgentIdentity(identity) {
123
+ return String(identity).startsWith("agent:");
124
+ }
125
+ var NOTE_TAKER_PRODUCT_NAME = "Matrx note-taker";
126
+ var AGENT_PRODUCT_NAME = "Matrx agent";
127
+ var UNNAMED_GUEST = "Unnamed guest";
128
+ var UNNAMED_PARTICIPANT = "Unnamed participant";
129
+ var PLACEHOLDER_NAMES = [
130
+ NOTE_TAKER_PRODUCT_NAME,
131
+ AGENT_PRODUCT_NAME,
132
+ UNNAMED_GUEST,
133
+ UNNAMED_PARTICIPANT
134
+ ];
135
+ function isPlaceholderName(name) {
136
+ return typeof name === "string" && PLACEHOLDER_NAMES.includes(name.trim());
137
+ }
138
+ function placeholderNameFor(identity) {
139
+ const key = String(identity);
140
+ if (isAgentIdentity(key)) {
141
+ return key.startsWith("agent:note_taker:") || key.startsWith("agent:note-taker") ? NOTE_TAKER_PRODUCT_NAME : AGENT_PRODUCT_NAME;
142
+ }
143
+ return isGuestIdentity(key) ? UNNAMED_GUEST : UNNAMED_PARTICIPANT;
144
+ }
145
+ function isRealName(identity, name) {
146
+ if (typeof name !== "string") return false;
147
+ const trimmed = name.trim();
148
+ if (trimmed.length === 0) return false;
149
+ if (trimmed === String(identity)) return false;
150
+ if (isPlaceholderName(trimmed)) return false;
151
+ return true;
152
+ }
153
+ function displayNameFor(book, identity, preferred) {
154
+ const key = String(identity);
155
+ if (isRealName(key, preferred)) return preferred.trim();
156
+ const known = book[key];
157
+ if (known !== void 0) return known;
158
+ return placeholderNameFor(key);
159
+ }
160
+
161
+ // src/core/errors.ts
162
+ var MeetError = class extends Error {
163
+ code;
164
+ /** What to DO. Rendered to the user for the device codes. */
165
+ remedy;
166
+ cause;
167
+ constructor(code, message, remedy, cause) {
168
+ super(message);
169
+ this.name = "MeetError";
170
+ this.code = code;
171
+ this.remedy = remedy;
172
+ if (cause !== void 0) this.cause = cause;
173
+ }
174
+ /** True when retrying after the host re-establishes a session is the fix. */
175
+ get isRetryable() {
176
+ return this.code === "session-unavailable" || this.code === "transport" || this.code === "token";
177
+ }
178
+ /** True when the user, not the code, must act — the UI shows `remedy`. */
179
+ get isUserActionable() {
180
+ return this.code === "permission-denied" || this.code === "device-not-found" || this.code === "device-in-use" || this.code === "insecure-context";
181
+ }
182
+ };
183
+ function normalizeDeviceError(error, operation) {
184
+ if (error instanceof MeetError) return error;
185
+ const raw = error ?? {};
186
+ const name = raw.name ?? "";
187
+ const message = typeof raw.message === "string" ? raw.message : String(error);
188
+ switch (name) {
189
+ case "NotAllowedError":
190
+ case "SecurityError":
191
+ return new MeetError(
192
+ "permission-denied",
193
+ `${operation}: camera/microphone permission denied`,
194
+ "Allow camera and microphone access for this site, then rejoin. On macOS also check System Settings \u203A Privacy & Security \u2014 a browser denied there shows the same error.",
195
+ error
196
+ );
197
+ case "NotFoundError":
198
+ case "OverconstrainedError":
199
+ return new MeetError(
200
+ "device-not-found",
201
+ `${operation}: no matching camera/microphone (${message})`,
202
+ "The selected device is gone \u2014 usually an unplugged headset. Pick another device in the pre-join panel; the package falls back to the system default automatically.",
203
+ error
204
+ );
205
+ case "NotReadableError":
206
+ case "AbortError":
207
+ return new MeetError(
208
+ "device-in-use",
209
+ `${operation}: the device is already in use (${message})`,
210
+ "Another application (or another tab) holds the camera. Close it and try again \u2014 the hardware, not the meeting, is what refused.",
211
+ error
212
+ );
213
+ case "TypeError":
214
+ return new MeetError(
215
+ "insecure-context",
216
+ `${operation}: media devices are unavailable in this context (${message})`,
217
+ "getUserMedia only exists on https or localhost. Serve the app over https.",
218
+ error
219
+ );
220
+ default:
221
+ return new MeetError(
222
+ "media-unsupported",
223
+ `${operation}: ${message}`,
224
+ "This browser refused the media request for a reason the package does not recognize. Report it with this message so the class gets a named remedy rather than a shrug.",
225
+ error
226
+ );
227
+ }
228
+ }
229
+
230
+ // src/core/tokens.ts
231
+ var MEET_ROUTES = {
232
+ token: "/api/v1/meet/token",
233
+ recordingStart: "/api/v1/meet/recording/start",
234
+ recordingStop: "/api/v1/meet/recording/stop",
235
+ noteTakerStart: "/api/v1/meet/agent/start",
236
+ noteTakerStop: "/api/v1/meet/agent/stop",
237
+ /** In-meeting Q&A. Streams NDJSON; body is `{meeting_id, question}` only. */
238
+ intelligenceAsk: "/api/v1/meet/intelligence/ask",
239
+ /** A host ends the meeting for everyone. Served by MRI-A6. */
240
+ meetingEnd: "/api/v1/meet/end",
241
+ /**
242
+ * THE POST-MEETING RECORD FOR A READER WITH NO DATABASE (MRF-1). A `GET`,
243
+ * authorized by the room token this device already holds, carried in the
244
+ * `x-meet-room-token` header — never a query parameter, because a bearer
245
+ * credential in a URL lands in access logs, referrers and history.
246
+ */
247
+ record: "/api/v1/meet/record",
248
+ /** A host chooses what guests may read of ONE meeting (MRF-1). */
249
+ recordAccess: "/api/v1/meet/record/access",
250
+ /**
251
+ * KEEP YOUR NOTES (2026-10-01): a guest who just created an account presents
252
+ * their kept pass in `x-meet-room-token` with their new session, and the
253
+ * meeting they attended becomes theirs to read. Idempotent; never a grant.
254
+ */
255
+ claim: "/api/v1/meet/claim",
256
+ /**
257
+ * Meet wave 1 (0.8): bring every invitee up to date after a write — sends the
258
+ * invitation / update / cancellation emails with their `.ics` and re-queues
259
+ * reminders. Idempotent. `{meeting_id}` is substituted by `meetingPath`.
260
+ */
261
+ meetingAnnounce: "/api/v1/meet/meetings/{meeting_id}/announce",
262
+ /** Put a one-off meeting on the caller's own Google Calendar (gated by the server). */
263
+ meetingGoogleCalendar: "/api/v1/meet/meetings/{meeting_id}/google-calendar",
264
+ /**
265
+ * Meet wave 3: the recap email after the meeting. `GET` = the draft (subject,
266
+ * body from the wrap-up, who it can go to); `POST` = send what the host
267
+ * reviewed (`dry_run` renders and queues nothing). Never sent automatically.
268
+ */
269
+ meetingFollowUp: "/api/v1/meet/meetings/{meeting_id}/follow-up",
270
+ /**
271
+ * Meet wave 2: EVERY in-meeting host power (mute, stop video, mute all, ask to
272
+ * unmute, spotlight, lower hands, co-host, hand over, rename, remove, report).
273
+ * Decided by the server from the meeting itself; a guest's pass rides
274
+ * `x-meet-room-token`.
275
+ */
276
+ moderate: "/api/v1/meet/moderate",
277
+ /** Meet wave 2: the SAVED chat — `POST` sends to everyone, `GET ?meeting_id=` reads history. */
278
+ chat: "/api/v1/meet/chat",
279
+ /**
280
+ * Meet wave 5: polls, Q&A, the shared whiteboard and breakout rooms. `POST` acts
281
+ * (`{meeting_id, action, …}`), `GET ?meeting_id=` reads what this viewer may see.
282
+ */
283
+ collab: "/api/v1/meet/collab",
284
+ /** Meet wave 5: translate finished caption lines into this viewer's language. */
285
+ captionsTranslate: "/api/v1/meet/captions/translate"
286
+ };
287
+
288
+ // src/core/recording.ts
289
+ var RECORDING_IN_FLIGHT = [
290
+ "starting",
291
+ "recording",
292
+ "stopping"
293
+ ];
294
+ function isRecordingInFlight(state) {
295
+ return RECORDING_IN_FLIGHT.includes(state);
296
+ }
297
+ function recordingLabel(state) {
298
+ switch (state) {
299
+ case "starting":
300
+ return "Starting recording\u2026";
301
+ case "recording":
302
+ return "Recording";
303
+ case "stopping":
304
+ return "Stopping recording\u2026";
305
+ case "processing":
306
+ return "Processing recording\u2026";
307
+ case "available":
308
+ return "Recording ready";
309
+ case "expired":
310
+ return "Recording expired";
311
+ case "failed":
312
+ return "Recording failed";
313
+ default:
314
+ return null;
315
+ }
316
+ }
317
+
318
+ // src/core/attendance.ts
319
+ var IDLE_TRACK = { enabled: false, unavailable: null };
320
+ var IDLE_MEDIA = {
321
+ microphone: IDLE_TRACK,
322
+ camera: IDLE_TRACK
323
+ };
324
+ var LISTEN_ONLY_NOTICE = "You're attending without a microphone \u2014 others can't hear you";
325
+ function isListenOnly(media) {
326
+ return !media.microphone.enabled && media.microphone.unavailable !== null;
327
+ }
328
+ function listenOnlyNotice(media) {
329
+ return isListenOnly(media) ? LISTEN_ONLY_NOTICE : null;
330
+ }
331
+
332
+ // src/core/types.ts
333
+ var ROLE_RANK = {
334
+ host: 3,
335
+ cohost: 2,
336
+ participant: 1,
337
+ guest: 0
338
+ };
339
+ function isHostRole(role) {
340
+ return role === "host" || role === "cohost";
341
+ }
342
+
343
+ // src/core/collab.ts
344
+ function assignedRoom(plan, identity) {
345
+ if (plan === null || plan.state !== "open" || identity === null) return null;
346
+ return plan.rooms.find((room) => room.identities.includes(identity)) ?? null;
347
+ }
348
+ function autoAssign(identities, rooms, exclude = []) {
349
+ const count = Math.max(1, Math.floor(rooms));
350
+ const out = Array.from({ length: count }, () => []);
351
+ const skip = new Set(exclude);
352
+ let index = 0;
353
+ for (const identity of identities) {
354
+ if (skip.has(identity)) continue;
355
+ out[index % count].push(identity);
356
+ index += 1;
357
+ }
358
+ return out;
359
+ }
360
+ function breakoutRemainingMs(plan, now) {
361
+ if (plan === null || plan.state !== "open" || plan.endsAt === null) return null;
362
+ const end = Date.parse(plan.endsAt);
363
+ return Number.isNaN(end) ? null : Math.max(0, end - now);
364
+ }
365
+ function defaultCaptionLanguage(settings, viewerLanguage) {
366
+ const configured = settings?.captionTranslationLanguage.trim() ?? "";
367
+ if (configured.length > 0) return configured;
368
+ const own = (viewerLanguage ?? "").trim();
369
+ return own.length > 0 ? own : "en";
370
+ }
371
+ var CAPTION_LANGUAGES = [
372
+ { tag: "en", name: "English" },
373
+ { tag: "es", name: "Spanish" },
374
+ { tag: "fr", name: "French" },
375
+ { tag: "de", name: "German" },
376
+ { tag: "it", name: "Italian" },
377
+ { tag: "pt", name: "Portuguese" },
378
+ { tag: "nl", name: "Dutch" },
379
+ { tag: "pl", name: "Polish" },
380
+ { tag: "ru", name: "Russian" },
381
+ { tag: "uk", name: "Ukrainian" },
382
+ { tag: "tr", name: "Turkish" },
383
+ { tag: "ar", name: "Arabic" },
384
+ { tag: "he", name: "Hebrew" },
385
+ { tag: "fa", name: "Persian" },
386
+ { tag: "hi", name: "Hindi" },
387
+ { tag: "bn", name: "Bengali" },
388
+ { tag: "zh", name: "Chinese" },
389
+ { tag: "ja", name: "Japanese" },
390
+ { tag: "ko", name: "Korean" },
391
+ { tag: "vi", name: "Vietnamese" },
392
+ { tag: "th", name: "Thai" },
393
+ { tag: "id", name: "Indonesian" },
394
+ { tag: "sv", name: "Swedish" }
395
+ ];
396
+
397
+ // src/core/transcript.ts
398
+ function readableTranscript(list) {
399
+ return [...list].filter((entry) => entry.text.trim().length > 0).sort((left, right) => left.startedAt.localeCompare(right.startedAt));
400
+ }
401
+
402
+ // src/core/store.ts
403
+ var DEFAULT_EFFECTS = {
404
+ noiseSuppression: true,
405
+ background: "none",
406
+ backgroundImage: null
407
+ };
408
+ function effectiveRole(snapshot) {
409
+ const me = snapshot.participants.find(
410
+ (participant) => participant.identity === snapshot.localIdentity
411
+ );
412
+ const fromRoster = me?.role ?? "participant";
413
+ const fromToken = snapshot.grants.role;
414
+ return ROLE_RANK[fromRoster] >= ROLE_RANK[fromToken] ? fromRoster : fromToken;
415
+ }
416
+ var REACTION_TTL_MS = 5e3;
417
+
418
+ // src/core/observation.ts
419
+ function observedDevice(track) {
420
+ if (track.enabled) return "on";
421
+ const refused = track.unavailable;
422
+ if (refused === null) return "muted_by_me";
423
+ switch (refused.code) {
424
+ case "permission-denied":
425
+ return "unavailable:denied";
426
+ case "device-not-found":
427
+ return "unavailable:none";
428
+ case "device-in-use":
429
+ return "unavailable:busy";
430
+ case "insecure-context":
431
+ return "unavailable:blocked_site";
432
+ default:
433
+ return "unavailable:failed";
434
+ }
435
+ }
436
+ function endedRecord(snapshot, given) {
437
+ const record = snapshot?.meeting ?? given;
438
+ return record !== null && record.endedAt !== null;
439
+ }
440
+ function observedPhase(input) {
441
+ const { snapshot } = input;
442
+ if (snapshot === null) return "resolving";
443
+ const raw = snapshot.phase;
444
+ const live = raw === "connected" || raw === "reconnecting";
445
+ if (endedRecord(snapshot, input.meeting ?? null) && !live) return "ended";
446
+ switch (raw) {
447
+ case "idle":
448
+ case "failed":
449
+ case "left":
450
+ return "prejoin";
451
+ case "lobby":
452
+ return "waiting:knocking";
453
+ case "requesting-token":
454
+ case "connecting":
455
+ return "joining";
456
+ case "connected":
457
+ case "reconnecting":
458
+ return "in_call";
459
+ }
460
+ }
461
+ var ADMITTER_ROLES = /* @__PURE__ */ new Set(["host", "cohost"]);
462
+ function observe(input) {
463
+ const { snapshot } = input;
464
+ const phase = observedPhase(input);
465
+ const inCall = phase === "in_call";
466
+ const role = inCall && snapshot !== null ? effectiveRole(snapshot) : "none";
467
+ const canAdmit = role !== "none" && ADMITTER_ROLES.has(role);
468
+ const deviceInPlay = phase === "joining" || inCall;
469
+ return {
470
+ phase,
471
+ reason: null,
472
+ role,
473
+ connection: snapshot?.phase === "reconnecting" ? "reconnecting" : "stable",
474
+ hostPresent: inCall && snapshot !== null ? snapshot.participants.some((p) => ADMITTER_ROLES.has(p.role)) : null,
475
+ lobbyCount: canAdmit && snapshot !== null ? snapshot.lobby.filter((entry) => entry.state === "knocking").length : 0,
476
+ recording: snapshot?.recording === "recording" || snapshot?.recording === "stopping",
477
+ audioBlocked: null,
478
+ camera: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.camera) : null,
479
+ microphone: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.microphone) : null
480
+ };
481
+ }
482
+ function staticObservation(phase, reason = null) {
483
+ return {
484
+ phase,
485
+ reason,
486
+ role: "none",
487
+ connection: phase === "disconnected" ? "server_unreachable" : "stable",
488
+ hostPresent: null,
489
+ lobbyCount: 0,
490
+ recording: false,
491
+ audioBlocked: null,
492
+ camera: null,
493
+ microphone: null
494
+ };
495
+ }
496
+ function observedQuality(quality) {
497
+ switch (quality) {
498
+ case "excellent":
499
+ case "good":
500
+ return "good";
501
+ case "poor":
502
+ return "poor";
503
+ case "lost":
504
+ return "lost";
505
+ default:
506
+ return null;
507
+ }
508
+ }
509
+ function participantAttributes(p) {
510
+ const attrs = {
511
+ "data-meet-participant": "",
512
+ "data-meet-participant-identity": p.identity,
513
+ "data-meet-participant-name": p.displayName,
514
+ "data-meet-participant-role": p.role,
515
+ "data-meet-participant-self": String(p.isLocal)
516
+ };
517
+ const quality = observedQuality(p.quality);
518
+ if (quality !== null) attrs["data-meet-participant-connection"] = quality;
519
+ return attrs;
520
+ }
521
+ function microphoneNoticeCode(track) {
522
+ const refused = track.unavailable;
523
+ if (refused === null) return null;
524
+ return refused.code === "permission-denied" || refused.code === "insecure-context" ? "mic-blocked" : "device-lost";
525
+ }
526
+
527
+ // src/react/provider.tsx
528
+ var import_global_slot = require("@ai-matrx/kit/global-slot");
529
+ var import_react = require("react");
530
+ var import_react2 = require("@ai-matrx/realtime/react");
531
+
532
+ // src/core/meetings.ts
533
+ function canModerateParticipant(actor, target) {
534
+ if (actor === void 0 || target === void 0) return false;
535
+ if (actor.role === "participant") return false;
536
+ if (actor.identity === target.identity) return false;
537
+ return ROLE_RANK[actor.role] > ROLE_RANK[target.role];
538
+ }
539
+ var END_MEETING_NOT_SERVED = "Ending a meeting is not available on this server yet";
540
+
541
+ // src/core/note-taker.ts
542
+ var NOTE_TAKER_IDENTITY_PREFIX = "agent:note_taker:";
543
+ function noteTakerIdentity(meetingId) {
544
+ return `${NOTE_TAKER_IDENTITY_PREFIX}${meetingId}`;
545
+ }
546
+ var CONSENT_NOTICE_ATTRIBUTE = "matrx.agent.consent_notice";
547
+ var NOTE_TAKER_METADATA_KEY = "note_taker";
548
+ var STATES = [
549
+ "absent",
550
+ "joining",
551
+ "transcribing",
552
+ "handoff_pending",
553
+ "ended"
554
+ ];
555
+ function readState(value) {
556
+ return typeof value === "string" && STATES.includes(value) ? value : null;
557
+ }
558
+ function isNoteTaker(participant) {
559
+ return participant.identity.startsWith(NOTE_TAKER_IDENTITY_PREFIX) || participant.attributes[CONSENT_NOTICE_ATTRIBUTE] !== void 0 || participant.isAgent;
560
+ }
561
+ function noteTakerParticipant(participants) {
562
+ return participants.find(isNoteTaker) ?? null;
563
+ }
564
+ function exactNoteTakerParticipant(meeting, participants) {
565
+ if (meeting === null) return null;
566
+ const identity = noteTakerIdentity(meeting.id);
567
+ return participants.find((participant) => participant.identity === identity) ?? null;
568
+ }
569
+ function noteTakerStatus(args) {
570
+ const present = exactNoteTakerParticipant(args.meeting, args.participants) !== null;
571
+ const raw = args.meeting?.metadata[NOTE_TAKER_METADATA_KEY];
572
+ const row = typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : {};
573
+ const recorded = readState(row["state"]);
574
+ const detail = typeof row["reason"] === "string" && row["reason"].length > 0 ? row["reason"] : null;
575
+ const refusal = args.refusal ?? null;
576
+ if (recorded === null) {
577
+ return { state: "absent", detail, present, refusal };
578
+ }
579
+ if (recorded === "transcribing" && !present) {
580
+ return { state: "handoff_pending", detail, present, refusal };
581
+ }
582
+ return { state: recorded, detail, present, refusal };
583
+ }
584
+ function noteTakerLabel(status) {
585
+ switch (status.state) {
586
+ case "joining":
587
+ return "The note-taker is joining this meeting";
588
+ case "transcribing":
589
+ return "The note-taker is connected and listening for speech";
590
+ case "handoff_pending":
591
+ return "The note-taker's transcription is interrupted; reconnection is pending";
592
+ case "ended":
593
+ return "The note-taker has stopped";
594
+ case "absent":
595
+ default:
596
+ return status.present ? "A meeting assistant is connected. Transcription is not confirmed" : "No note-taker is in this meeting";
597
+ }
598
+ }
599
+
600
+ // src/core/livekit-driver.ts
601
+ function videoEffectsSupported() {
602
+ const scope = globalThis;
603
+ if (typeof scope.OffscreenCanvas === "undefined" || scope.document === void 0) return false;
604
+ try {
605
+ return scope.document.createElement("canvas").getContext("webgl2") !== null;
606
+ } catch {
607
+ return false;
608
+ }
609
+ }
610
+ var encoder = new TextEncoder();
611
+ var decoder = new TextDecoder();
612
+ async function createCameraPreview(deviceId, effect) {
613
+ let module2;
614
+ try {
615
+ module2 = await import("livekit-client");
616
+ } catch (error) {
617
+ throw new MeetError(
618
+ "misconfigured",
619
+ "livekit-client could not be loaded for the camera preview",
620
+ "The bundler excluded `livekit-client`; remove it from any `external` list.",
621
+ error
622
+ );
623
+ }
624
+ let track;
625
+ try {
626
+ track = await module2.createLocalVideoTrack(deviceId === null ? {} : { deviceId });
627
+ } catch (error) {
628
+ throw normalizeDeviceError(error, "meet.cameraPreview");
629
+ }
630
+ const target = track;
631
+ async function apply(next) {
632
+ if (next.kind === "none") {
633
+ await target.stopProcessor();
634
+ return;
635
+ }
636
+ const processors = await import("@livekit/track-processors");
637
+ await target.setProcessor(
638
+ next.kind === "blur" ? processors.BackgroundBlur(next.radius ?? 12) : processors.VirtualBackground(next.imageUrl)
639
+ );
640
+ }
641
+ if (effect.kind !== "none" && videoEffectsSupported()) {
642
+ try {
643
+ await apply(effect);
644
+ } catch {
645
+ }
646
+ }
647
+ return {
648
+ attach(element) {
649
+ track.attach(element);
650
+ },
651
+ setEffect: apply,
652
+ stop() {
653
+ track.stop();
654
+ track.detach();
655
+ }
656
+ };
657
+ }
658
+
659
+ // src/react/provider.tsx
660
+ var import_jsx_runtime = require("react/jsx-runtime");
661
+ var MeetContext = (0, import_global_slot.globalSlot)("ai-matrx.meet.react.context", () => (0, import_react.createContext)(null));
662
+ function useMeetHost() {
663
+ return (0, import_react.useContext)(MeetContext);
664
+ }
665
+ function useRequiredMeetHost() {
666
+ const host = useMeetHost();
667
+ if (host === null) {
668
+ throw new Error(
669
+ "[@ai-matrx/meet] No meet host. Wrap this tree in <MeetProvider client={supabase} baseUrl={aidreamUrl} organizationId={orgId} accessToken={fn}> and render it only once auth has resolved."
670
+ );
671
+ }
672
+ return host;
673
+ }
674
+ function useMeetSnapshot() {
675
+ const host = useMeetHost();
676
+ const store = host?.store ?? null;
677
+ const empty = (0, import_react.useRef)(null);
678
+ return (0, import_react.useSyncExternalStore)(
679
+ (listener) => store === null ? () => void 0 : store.subscribe(listener),
680
+ () => store === null ? empty.current : store.snapshot(),
681
+ () => store === null ? empty.current : store.snapshot()
682
+ );
683
+ }
684
+ function useDeviceSnapshot() {
685
+ const host = useMeetHost();
686
+ const devices = host?.devices ?? null;
687
+ return (0, import_react.useSyncExternalStore)(
688
+ (listener) => devices === null ? () => void 0 : devices.subscribe(() => listener()),
689
+ () => devices?.snapshot() ?? null,
690
+ () => devices?.snapshot() ?? null
691
+ );
692
+ }
693
+ function useRole() {
694
+ const snapshot = useMeetSnapshot();
695
+ return snapshot === null ? "participant" : effectiveRole(snapshot);
696
+ }
697
+ function useIsHost() {
698
+ return isHostRole(useRole());
699
+ }
700
+ function useCanRecord() {
701
+ const snapshot = useMeetSnapshot();
702
+ if (snapshot === null) return false;
703
+ const me = snapshot.participants.find(
704
+ (participant) => participant.identity === snapshot.localIdentity
705
+ );
706
+ return snapshot.grants.canRecord || me?.canRecord === true;
707
+ }
708
+
709
+ // src/react/root.tsx
710
+ var import_jsx_runtime2 = require("react/jsx-runtime");
711
+ function useCoreObservation(options = {}) {
712
+ const snapshot = useMeetSnapshot();
713
+ const { meeting, phase, reason } = options;
714
+ return (0, import_react3.useMemo)(
715
+ () => phase !== void 0 ? staticObservation(phase, reason ?? null) : observe({ snapshot, meeting: meeting ?? null }),
716
+ [snapshot, meeting, phase, reason]
717
+ );
718
+ }
719
+
720
+ // src/react/facade.ts
721
+ var import_format2 = require("@ai-matrx/kit/format");
722
+
723
+ // src/core/format.ts
724
+ function participantSummary(participants) {
725
+ const names = participants.filter((p) => !p.isLocal).map((p) => p.displayName);
726
+ if (names.length === 0) return "Just you";
727
+ if (names.length === 1) return names[0] ?? "";
728
+ if (names.length === 2) return `${names[0]} and ${names[1]}`;
729
+ return `${names[0]}, ${names[1]} and ${names.length - 2} ${names.length - 2 === 1 ? "other" : "others"}`;
730
+ }
731
+
732
+ // src/core/layout.ts
733
+ var DEFAULT_PAGE_SIZE = 12;
734
+ function orderParticipants(participants, activeSpeakers) {
735
+ const speakerRank = /* @__PURE__ */ new Map();
736
+ activeSpeakers.forEach((identity, index) => speakerRank.set(identity, index));
737
+ return [...participants].sort((a, b) => {
738
+ const speakerA = speakerRank.get(a.identity) ?? Number.MAX_SAFE_INTEGER;
739
+ const speakerB = speakerRank.get(b.identity) ?? Number.MAX_SAFE_INTEGER;
740
+ if (speakerA !== speakerB) return speakerA - speakerB;
741
+ if (a.handRaised !== b.handRaised) return a.handRaised ? -1 : 1;
742
+ if (a.cameraEnabled !== b.cameraEnabled) return a.cameraEnabled ? -1 : 1;
743
+ if (a.joinedAt !== b.joinedAt) return a.joinedAt < b.joinedAt ? -1 : 1;
744
+ return a.identity < b.identity ? -1 : 1;
745
+ });
746
+ }
747
+ function computeStage(args) {
748
+ const pageSize = Math.max(1, args.pageSize ?? DEFAULT_PAGE_SIZE);
749
+ const ordered = orderParticipants(args.participants, args.activeSpeakers);
750
+ const sharer = ordered.find((participant) => participant.screenSharing) ?? null;
751
+ const pinnedParticipant = args.pinned === null ? null : ordered.find((participant) => participant.identity === args.pinned) ?? null;
752
+ const spotlit = args.spotlight == null ? null : ordered.find((participant) => participant.identity === args.spotlight) ?? null;
753
+ const feature = sharer !== null ? sharer : pinnedParticipant !== null && args.layout !== "grid" ? pinnedParticipant : spotlit !== null ? spotlit : args.layout === "grid" ? null : ordered[0] ?? null;
754
+ const rest = feature === null ? ordered : ordered.filter((participant) => participant.identity !== feature.identity);
755
+ const pageCount = Math.max(1, Math.ceil(rest.length / pageSize));
756
+ const page = Math.min(Math.max(0, args.page ?? 0), pageCount - 1);
757
+ const tiles = rest.slice(page * pageSize, page * pageSize + pageSize);
758
+ return {
759
+ feature,
760
+ tiles,
761
+ overflow: rest.length - tiles.length,
762
+ page,
763
+ pageCount,
764
+ presenting: sharer !== null
765
+ };
766
+ }
767
+ function gridColumns(count) {
768
+ if (count <= 1) return 1;
769
+ if (count <= 4) return 2;
770
+ if (count <= 9) return 3;
771
+ if (count <= 16) return 4;
772
+ return 5;
773
+ }
774
+
775
+ // src/react/hooks.ts
776
+ var import_react4 = require("react");
777
+ var import_react5 = require("@ai-matrx/realtime/react");
778
+
779
+ // src/core/record.ts
780
+ var import_format = require("@ai-matrx/kit/format");
781
+ function activeWrapUpSummary(notes) {
782
+ return notes.find((note) => note.role === "summary" && note.supersededBy === null) ?? null;
783
+ }
784
+ var GUEST_RECORD_ACCESS_LEVELS = [
785
+ "none",
786
+ "summary",
787
+ "full"
788
+ ];
789
+ var GUEST_RECORD_ACCESS_CHOICES = [
790
+ "default",
791
+ "summary",
792
+ "full",
793
+ "none"
794
+ ];
795
+ var GUEST_RECORD_ACCESS_METADATA_KEY = "guest_record_access";
796
+ function guestRecordAccessSetting(meeting) {
797
+ const raw = meeting?.metadata?.[GUEST_RECORD_ACCESS_METADATA_KEY];
798
+ return GUEST_RECORD_ACCESS_LEVELS.includes(raw) ? raw : "default";
799
+ }
800
+ function guestRecordAccessLabel(setting) {
801
+ switch (setting) {
802
+ case "default":
803
+ return "Use my organization's setting";
804
+ case "summary":
805
+ return "Summary, decisions and action items";
806
+ case "full":
807
+ return "Everything, including the transcript";
808
+ case "none":
809
+ return "Nothing \u2014 keep the record internal";
810
+ }
811
+ }
812
+ function guestRecordAccessExplanation(setting) {
813
+ switch (setting) {
814
+ case "default":
815
+ return "Guests who attended read whatever your organization shares by default. An administrator sets that once, for every meeting nobody thinks about.";
816
+ case "summary":
817
+ return "Guests who attended read the wrap-up summary, the decisions, the action items, the notes taken as it happened, and who was here. They do not get the word-for-word transcript or the recording.";
818
+ case "full":
819
+ return "Guests who attended read everything this meeting produced, including the word-for-word transcript of what each person said.";
820
+ case "none":
821
+ return "Guests who attended are told plainly that this meeting's record is kept inside your organization. They see nothing of it.";
822
+ }
823
+ }
824
+ async function readRefusal(response) {
825
+ try {
826
+ const body = await response.json();
827
+ const detail = typeof body["detail"] === "object" && body["detail"] !== null ? body["detail"] : body;
828
+ const message = detail["message"];
829
+ const remedy = detail["remedy"];
830
+ if (typeof message === "string" && message.length > 0) {
831
+ return {
832
+ message,
833
+ remedy: typeof remedy === "string" && remedy.length > 0 ? remedy : "Ask whoever hosted the meeting for what you need."
834
+ };
835
+ }
836
+ } catch {
837
+ }
838
+ return {
839
+ message: `This meeting's record could not be read (the server answered ${response.status}).`,
840
+ remedy: "This is not a decision about you \u2014 it is a failure to reach the record. Reload the page; if it repeats, tell whoever hosted the meeting."
841
+ };
842
+ }
843
+ async function setGuestRecordAccess(args) {
844
+ const response = await args.api.request({
845
+ path: MEET_ROUTES.recordAccess,
846
+ method: "POST",
847
+ requireAuth: true,
848
+ operation: "meet.record.access",
849
+ body: {
850
+ meeting_id: args.meetingId,
851
+ organization_id: args.organizationId,
852
+ access: args.access
853
+ }
854
+ });
855
+ if (!response.ok) {
856
+ const refusal = await readRefusal(response);
857
+ throw new MeetError("forbidden", refusal.message, refusal.remedy);
858
+ }
859
+ const body = await response.json();
860
+ const payload = typeof body["data"] === "object" && body["data"] !== null ? body["data"] : body;
861
+ return {
862
+ setting: payload.setting ?? args.access,
863
+ effective: payload.effective ?? { level: "summary", source: "organization" }
864
+ };
865
+ }
866
+
867
+ // src/react/hooks.ts
868
+ var EMPTY_NAMES = {};
869
+ var NO_RUNTIME_REMEDY = "Render this inside a <MeetProvider> that has a Supabase client, an organization id and an identity (userId or guestName). The provider stays inert until all three are real, and while it is inert this package's components render nothing rather than a dead control.";
870
+ function noRuntime(action) {
871
+ return new MeetError(
872
+ "misconfigured",
873
+ `[@ai-matrx/meet] ${action}: there is no meet runtime on this tree.`,
874
+ NO_RUNTIME_REMEDY
875
+ );
876
+ }
877
+ function requireHost(host, action) {
878
+ if (host === null) throw noRuntime(action);
879
+ return host;
880
+ }
881
+ function requireEngine(engine, action) {
882
+ if (engine === null) throw noRuntime(action);
883
+ return engine;
884
+ }
885
+ function requireSession(session, action) {
886
+ if (session === null) throw noRuntime(action);
887
+ return session;
888
+ }
889
+ function useRoom() {
890
+ const host = useMeetHost();
891
+ const snapshot = useMeetSnapshot();
892
+ const engine = host?.engine ?? null;
893
+ const local = snapshot?.participants.find(
894
+ (participant) => participant.identity === snapshot.localIdentity
895
+ ) ?? null;
896
+ return {
897
+ phase: snapshot?.phase ?? "idle",
898
+ participants: snapshot?.participants ?? [],
899
+ failure: snapshot?.failure ?? null,
900
+ local,
901
+ canShareScreen: engine?.supportsScreenShare() ?? true,
902
+ join: (0, import_react4.useCallback)(
903
+ async (request) => requireEngine(engine, "join").join(request),
904
+ [engine]
905
+ ),
906
+ leave: (0, import_react4.useCallback)(async () => requireEngine(engine, "leave").leave(), [engine]),
907
+ toggleMicrophone: (0, import_react4.useCallback)(
908
+ async () => requireEngine(engine, "toggleMicrophone").setMicrophoneEnabled(
909
+ !(local?.micEnabled ?? false)
910
+ ),
911
+ [engine, local]
912
+ ),
913
+ toggleCamera: (0, import_react4.useCallback)(
914
+ async () => requireEngine(engine, "toggleCamera").setCameraEnabled(!(local?.cameraEnabled ?? false)),
915
+ [engine, local]
916
+ ),
917
+ toggleScreenShare: (0, import_react4.useCallback)(
918
+ async () => requireEngine(engine, "toggleScreenShare").setScreenShareEnabled(
919
+ !(local?.screenSharing ?? false)
920
+ ),
921
+ [engine, local]
922
+ ),
923
+ toggleHand: (0, import_react4.useCallback)(
924
+ async () => requireEngine(engine, "toggleHand").raiseHand(!(local?.handRaised ?? false)),
925
+ [engine, local]
926
+ ),
927
+ react: (0, import_react4.useCallback)(
928
+ async (emoji) => requireEngine(engine, "react").react(emoji),
929
+ [engine]
930
+ ),
931
+ sendChat: (0, import_react4.useCallback)(
932
+ async (text) => {
933
+ if (host === null) throw noRuntime("sendChat");
934
+ await host.inRoom.sendChat(text);
935
+ },
936
+ [host]
937
+ ),
938
+ sendPrivateChat: (0, import_react4.useCallback)(
939
+ async (to, text) => requireEngine(engine, "sendPrivateChat").sendPrivateChat(to, text),
940
+ [engine]
941
+ ),
942
+ pin: (0, import_react4.useCallback)(
943
+ (identity) => requireEngine(engine, "pin").pin(identity),
944
+ [engine]
945
+ ),
946
+ setLayout: (0, import_react4.useCallback)(
947
+ (layout) => requireEngine(engine, "setLayout").setLayout(layout),
948
+ [engine]
949
+ )
950
+ };
951
+ }
952
+ function useStage(pageSize = DEFAULT_PAGE_SIZE) {
953
+ const snapshot = useMeetSnapshot();
954
+ const [page, setPage] = (0, import_react4.useState)(0);
955
+ const stage = (0, import_react4.useMemo)(
956
+ () => computeStage({
957
+ participants: snapshot?.participants ?? [],
958
+ layout: snapshot?.layout ?? "grid",
959
+ pinned: snapshot?.pinned ?? null,
960
+ spotlight: snapshot?.spotlight ?? null,
961
+ activeSpeakers: snapshot?.activeSpeakers ?? [],
962
+ pageSize,
963
+ page
964
+ }),
965
+ [snapshot, pageSize, page]
966
+ );
967
+ (0, import_react4.useEffect)(() => {
968
+ if (page > stage.pageCount - 1) setPage(Math.max(0, stage.pageCount - 1));
969
+ }, [page, stage.pageCount]);
970
+ return stage;
971
+ }
972
+ function useTrack(identity, kind) {
973
+ const host = useMeetHost();
974
+ const detachRef = (0, import_react4.useRef)(null);
975
+ return (0, import_react4.useCallback)(
976
+ (element) => {
977
+ detachRef.current?.();
978
+ detachRef.current = null;
979
+ if (element === null || identity === null || host === null) return;
980
+ detachRef.current = host.engine.attach(identity, kind, element);
981
+ },
982
+ [host, identity, kind]
983
+ );
984
+ }
985
+ function useLobby() {
986
+ const host = useMeetHost();
987
+ const snapshot = useMeetSnapshot();
988
+ const isHost = useIsHost();
989
+ const session = host?.meetings ?? null;
990
+ return {
991
+ waiting: (snapshot?.lobby ?? []).filter((entry) => entry.state === "knocking"),
992
+ // No runtime means no host powers — `<LobbyPanel>` renders nothing rather
993
+ // than an admit button that would throw.
994
+ canAdmit: session !== null && isHost,
995
+ admit: (0, import_react4.useCallback)(
996
+ async (entry) => requireSession(session, "admit").admit(entry, true),
997
+ [session]
998
+ ),
999
+ deny: (0, import_react4.useCallback)(
1000
+ async (entry) => requireSession(session, "deny").admit(entry, false),
1001
+ [session]
1002
+ ),
1003
+ admitAll: (0, import_react4.useCallback)(
1004
+ async () => requireSession(session, "admitAll").admitAll(),
1005
+ [session]
1006
+ ),
1007
+ knock: (0, import_react4.useCallback)(
1008
+ (roomName) => requireSession(session, "knock").knock(roomName),
1009
+ [session]
1010
+ )
1011
+ };
1012
+ }
1013
+ function useRecording() {
1014
+ const host = useMeetHost();
1015
+ const snapshot = useMeetSnapshot();
1016
+ const canRecord = useCanRecord();
1017
+ const meeting = snapshot?.meeting ?? null;
1018
+ const state = snapshot?.recording ?? "idle";
1019
+ const canStart = host !== null && canRecord && host.recording.canStart(meeting, true) && !isRecordingInFlight(state);
1020
+ return {
1021
+ state,
1022
+ label: recordingLabel(state),
1023
+ canStart,
1024
+ // `stopping` is already stopping; the other in-flight states can be.
1025
+ canStop: host !== null && canRecord && (state === "recording" || state === "starting"),
1026
+ consentNotice: host?.recording.consentNotice(meeting) ?? null,
1027
+ start: (0, import_react4.useCallback)(async () => {
1028
+ const live = requireHost(host, "startRecording");
1029
+ if (meeting === null || live.identity.userId === null) return;
1030
+ await live.recording.start({
1031
+ meetingId: meeting.id,
1032
+ roomName: meeting.roomName,
1033
+ by: live.identity.userId
1034
+ });
1035
+ }, [host, meeting]),
1036
+ stop: (0, import_react4.useCallback)(async () => {
1037
+ const live = requireHost(host, "stopRecording");
1038
+ if (meeting === null || live.identity.userId === null) return;
1039
+ await live.recording.stop({ meetingId: meeting.id, by: live.identity.userId });
1040
+ }, [host, meeting])
1041
+ };
1042
+ }
1043
+ function useMeetAi() {
1044
+ const host = useMeetHost();
1045
+ const snapshot = useMeetSnapshot();
1046
+ const isHost = useIsHost();
1047
+ const [asking, setAsking] = (0, import_react4.useState)(false);
1048
+ const [answer, setAnswer] = (0, import_react4.useState)(null);
1049
+ const [coverage, setCoverage] = (0, import_react4.useState)(null);
1050
+ const [phase, setPhase] = (0, import_react4.useState)(null);
1051
+ const [error, setError] = (0, import_react4.useState)(null);
1052
+ const ai = host?.ai ?? null;
1053
+ const meeting = snapshot?.meeting ?? null;
1054
+ const participants = snapshot?.participants ?? [];
1055
+ const notes = snapshot?.notes ?? [];
1056
+ const wrapUp = snapshot?.wrapUp ?? null;
1057
+ const status = (0, import_react4.useMemo)(
1058
+ () => noteTakerStatus({
1059
+ meeting,
1060
+ participants,
1061
+ refusal: snapshot?.noteTakerRefusal ?? null
1062
+ }),
1063
+ [meeting, participants, snapshot?.noteTakerRefusal]
1064
+ );
1065
+ const names = snapshot?.names ?? EMPTY_NAMES;
1066
+ const transcript = (0, import_react4.useMemo)(
1067
+ () => readableTranscript(snapshot?.transcript ?? []).map((line) => ({
1068
+ id: line.id,
1069
+ identity: line.identity,
1070
+ // 🚨 RESOLVED, not rendered raw (MRI-A16). A line stamped before its
1071
+ // speaker's name was known — a durable row that beat the roster, a
1072
+ // caption from someone who has since left — is re-attributed the moment
1073
+ // anything learns the name, in BOTH lanes, so the host and the guest
1074
+ // never read two different names for one speaker again (MRI-C8).
1075
+ speaker: displayNameFor(names, line.identity, line.speaker),
1076
+ text: line.text,
1077
+ final: line.final,
1078
+ source: line.source
1079
+ })),
1080
+ [snapshot?.transcript, names]
1081
+ );
1082
+ const dispatchNoteTaker = (0, import_react4.useCallback)(
1083
+ async (run) => {
1084
+ setError(null);
1085
+ try {
1086
+ await run();
1087
+ } catch (thrown) {
1088
+ setError({
1089
+ message: thrown.message,
1090
+ remedy: thrown.remedy ?? "The note-taker action could not be confirmed. Check its current state and try again."
1091
+ });
1092
+ }
1093
+ },
1094
+ []
1095
+ );
1096
+ return {
1097
+ noteTaker: status,
1098
+ noteTakerLabel: noteTakerLabel(status),
1099
+ // A guest cannot dispatch an agent, and neither can anyone in a meeting
1100
+ // whose AI is off. The control is ABSENT for them, never disabled-looking.
1101
+ canControlNoteTaker: ai !== null && meeting !== null && meeting.aiEnabled && isHost && host?.identity.userId !== null,
1102
+ notes: notes.filter((note) => note.kind === "note" && note.role === null),
1103
+ // D20: the wrap-up is the authoritative post-meeting set. A window-written
1104
+ // decision the wrap-up superseded stays in `notes` — the timeline — and is
1105
+ // not listed a second time as a decision that was made.
1106
+ decisions: notes.filter((note) => note.kind === "decision" && note.supersededBy === null),
1107
+ actionItems: notes.filter(
1108
+ (note) => note.kind === "action-item" && note.supersededBy === null
1109
+ ),
1110
+ summary: activeWrapUpSummary(notes),
1111
+ wrapUp,
1112
+ transcript,
1113
+ asking,
1114
+ answer,
1115
+ coverage,
1116
+ phase,
1117
+ error,
1118
+ ask: (0, import_react4.useCallback)(
1119
+ async (question) => {
1120
+ if (ai === null || meeting === null) return;
1121
+ setAsking(true);
1122
+ setError(null);
1123
+ setAnswer(null);
1124
+ setCoverage(null);
1125
+ setPhase(null);
1126
+ try {
1127
+ const result = await ai.ask({
1128
+ meetingId: meeting.id,
1129
+ question,
1130
+ // THE ANSWER RENDERS AS IT ARRIVES. Waiting for the last token to
1131
+ // show the first is a spinner wearing an answer's clothes.
1132
+ onText: (text) => setAnswer(text),
1133
+ onPhase: (value) => setPhase(value)
1134
+ });
1135
+ setAnswer(result.text);
1136
+ setCoverage(result.coverage);
1137
+ } catch (thrown) {
1138
+ setError({
1139
+ message: thrown.message,
1140
+ remedy: thrown.remedy ?? "The assistant could not answer. Nothing was changed by the attempt."
1141
+ });
1142
+ } finally {
1143
+ setAsking(false);
1144
+ setPhase(null);
1145
+ }
1146
+ },
1147
+ [ai, meeting]
1148
+ ),
1149
+ startNoteTaker: (0, import_react4.useCallback)(async () => {
1150
+ if (ai === null || meeting === null) return;
1151
+ await dispatchNoteTaker(
1152
+ () => ai.startNoteTaker({ meetingId: meeting.id, roomName: meeting.roomName })
1153
+ );
1154
+ }, [ai, meeting, dispatchNoteTaker]),
1155
+ stopNoteTaker: (0, import_react4.useCallback)(async () => {
1156
+ if (ai === null || meeting === null) return;
1157
+ await dispatchNoteTaker(() => ai.stopNoteTaker({ meetingId: meeting.id }));
1158
+ }, [ai, meeting, dispatchNoteTaker])
1159
+ };
1160
+ }
1161
+ function useEndMeeting() {
1162
+ const host = useMeetHost();
1163
+ const snapshot = useMeetSnapshot();
1164
+ const isHost = useIsHost();
1165
+ const [ending, setEnding] = (0, import_react4.useState)(false);
1166
+ const [notice, setNotice] = (0, import_react4.useState)(null);
1167
+ const meeting = snapshot?.meeting ?? null;
1168
+ const session = host?.meetings ?? null;
1169
+ return {
1170
+ canEnd: session !== null && isHost && meeting !== null && meeting.endedAt === null,
1171
+ ending,
1172
+ ended: meeting?.endedAt !== null && meeting?.endedAt !== void 0,
1173
+ notice,
1174
+ end: (0, import_react4.useCallback)(async () => {
1175
+ const live = requireSession(session, "endForEveryone");
1176
+ setEnding(true);
1177
+ setNotice(null);
1178
+ try {
1179
+ const result = await live.endForEveryone();
1180
+ if (!result.served) {
1181
+ setNotice(END_MEETING_NOT_SERVED);
1182
+ }
1183
+ } catch (thrown) {
1184
+ const remedy = thrown.remedy;
1185
+ setNotice(
1186
+ remedy !== void 0 ? `${thrown.message} ${remedy}` : thrown.message
1187
+ );
1188
+ } finally {
1189
+ setEnding(false);
1190
+ }
1191
+ }, [session])
1192
+ };
1193
+ }
1194
+ function useConsentNotice() {
1195
+ const snapshot = useMeetSnapshot();
1196
+ const agent = noteTakerParticipant(snapshot?.participants ?? []);
1197
+ if (agent === null) return null;
1198
+ return `${agent.displayName} is in this meeting: it is transcribing what is said and writing notes everyone here can read.`;
1199
+ }
1200
+ function useLocalMedia() {
1201
+ const snapshot = useMeetSnapshot();
1202
+ return snapshot?.media ?? IDLE_MEDIA;
1203
+ }
1204
+ function useReactions() {
1205
+ const host = useMeetHost();
1206
+ const snapshot = useMeetSnapshot();
1207
+ const store = host?.store ?? null;
1208
+ (0, import_react4.useEffect)(() => {
1209
+ if (store === null) return void 0;
1210
+ const timer = setInterval(() => store.expireReactions(Date.now()), REACTION_TTL_MS / 2);
1211
+ return () => clearInterval(timer);
1212
+ }, [store]);
1213
+ return snapshot?.reactions ?? [];
1214
+ }
1215
+ function useElapsed(startedAt) {
1216
+ const [now, setNow] = (0, import_react4.useState)(() => Date.now());
1217
+ (0, import_react4.useEffect)(() => {
1218
+ if (startedAt === null) return void 0;
1219
+ const timer = setInterval(() => setNow(Date.now()), 1e3);
1220
+ return () => clearInterval(timer);
1221
+ }, [startedAt]);
1222
+ if (startedAt === null) return 0;
1223
+ return Math.max(0, now - Date.parse(startedAt));
1224
+ }
1225
+ function useHostControls() {
1226
+ const host = useMeetHost();
1227
+ const snapshot = useMeetSnapshot();
1228
+ const role = useRole();
1229
+ const me = snapshot?.participants.find(
1230
+ (participant) => participant.identity === snapshot.localIdentity
1231
+ );
1232
+ const actor = me === void 0 ? void 0 : { ...me, role };
1233
+ const [busy, setBusy] = (0, import_react4.useState)(false);
1234
+ const inRoom = host?.inRoom ?? null;
1235
+ const store = host?.store ?? null;
1236
+ const notice = snapshot?.actionNotice ?? null;
1237
+ const setNotice = (0, import_react4.useCallback)(
1238
+ (next) => store?.patch({ actionNotice: next === null ? null : { ...next, at: Date.now() } }),
1239
+ [store]
1240
+ );
1241
+ const run = (0, import_react4.useCallback)(
1242
+ async (request) => {
1243
+ if (inRoom === null) throw noRuntime(`moderate(${request.action})`);
1244
+ setBusy(true);
1245
+ try {
1246
+ const result = await inRoom.moderate(request);
1247
+ setNotice({ text: result.message, failed: false });
1248
+ return result;
1249
+ } catch (thrown) {
1250
+ const error = thrown;
1251
+ setNotice({
1252
+ text: [error.message, error.remedy].filter(Boolean).join(" "),
1253
+ failed: true
1254
+ });
1255
+ return null;
1256
+ } finally {
1257
+ setBusy(false);
1258
+ }
1259
+ },
1260
+ [inRoom, setNotice]
1261
+ );
1262
+ return {
1263
+ isHost: isHostRole(role),
1264
+ isOwner: role === "host",
1265
+ notice,
1266
+ busy,
1267
+ clearNotice: (0, import_react4.useCallback)(() => setNotice(null), [setNotice]),
1268
+ canModerate: (0, import_react4.useCallback)(
1269
+ (target) => canModerateParticipant(actor, target),
1270
+ [actor]
1271
+ ),
1272
+ run,
1273
+ mute: (0, import_react4.useCallback)(
1274
+ async (target) => {
1275
+ await run({ action: "mute", identity: target.identity });
1276
+ },
1277
+ [run]
1278
+ ),
1279
+ remove: (0, import_react4.useCallback)(
1280
+ async (target) => {
1281
+ await run({ action: "remove", identity: target.identity });
1282
+ },
1283
+ [run]
1284
+ )
1285
+ };
1286
+ }
1287
+ function useHandQueue() {
1288
+ const snapshot = useMeetSnapshot();
1289
+ return (0, import_react4.useMemo)(
1290
+ () => (snapshot?.participants ?? []).filter((participant) => participant.handRaised).sort((a, b) => (a.handRaisedAt ?? 0) - (b.handRaisedAt ?? 0)),
1291
+ [snapshot?.participants]
1292
+ );
1293
+ }
1294
+ function useMediaEffects() {
1295
+ const host = useMeetHost();
1296
+ const snapshot = useMeetSnapshot();
1297
+ const engine = host?.engine ?? null;
1298
+ return {
1299
+ effects: snapshot?.effects ?? DEFAULT_EFFECTS,
1300
+ backgroundsSupported: engine?.supportsVideoEffects() ?? false,
1301
+ set: (0, import_react4.useCallback)(
1302
+ async (next) => requireEngine(engine, "setEffects").setEffects(next),
1303
+ [engine]
1304
+ )
1305
+ };
1306
+ }
1307
+ function useModerationNotice() {
1308
+ const host = useMeetHost();
1309
+ const snapshot = useMeetSnapshot();
1310
+ return {
1311
+ notice: snapshot?.moderationNotice ?? null,
1312
+ dismiss: (0, import_react4.useCallback)(() => host?.store.patch({ moderationNotice: null }), [host])
1313
+ };
1314
+ }
1315
+ function useAudioLevel(stream) {
1316
+ const [level, setLevel] = (0, import_react4.useState)(0);
1317
+ (0, import_react4.useEffect)(() => {
1318
+ if (stream === null || stream.getAudioTracks().length === 0) {
1319
+ setLevel(0);
1320
+ return void 0;
1321
+ }
1322
+ const Context = globalThis.AudioContext ?? globalThis.webkitAudioContext;
1323
+ if (Context === void 0) return void 0;
1324
+ const context = new Context();
1325
+ const source = context.createMediaStreamSource(stream);
1326
+ const analyser = context.createAnalyser();
1327
+ analyser.fftSize = 512;
1328
+ source.connect(analyser);
1329
+ const samples = new Uint8Array(analyser.fftSize);
1330
+ let frame = 0;
1331
+ const tick = () => {
1332
+ analyser.getByteTimeDomainData(samples);
1333
+ let peak = 0;
1334
+ for (const sample of samples) peak = Math.max(peak, Math.abs(sample - 128) / 128);
1335
+ setLevel((previous) => Math.abs(previous - peak) > 0.02 ? peak : previous);
1336
+ frame = requestAnimationFrame(tick);
1337
+ };
1338
+ frame = requestAnimationFrame(tick);
1339
+ return () => {
1340
+ cancelAnimationFrame(frame);
1341
+ source.disconnect();
1342
+ void context.close();
1343
+ };
1344
+ }, [stream]);
1345
+ return level;
1346
+ }
1347
+ var MEET_SHORTCUTS = [
1348
+ { id: "mic", keys: "\u2318/Ctrl + D", label: "Mute or unmute your microphone" },
1349
+ { id: "camera", keys: "\u2318/Ctrl + E", label: "Turn your camera on or off" },
1350
+ { id: "hand", keys: "\u2318/Ctrl + Alt + H", label: "Raise or lower your hand" },
1351
+ { id: "chat", keys: "\u2318/Ctrl + Alt + C", label: "Open or close chat" },
1352
+ { id: "people", keys: "\u2318/Ctrl + Alt + P", label: "Open or close people" },
1353
+ { id: "help", keys: "Shift + ?", label: "Show keyboard shortcuts" }
1354
+ ];
1355
+ function useMeetShortcuts(handlers) {
1356
+ const ref = (0, import_react4.useRef)(handlers);
1357
+ ref.current = handlers;
1358
+ (0, import_react4.useEffect)(() => {
1359
+ const target = globalThis.window;
1360
+ if (target === void 0) return void 0;
1361
+ const onKey = (event) => {
1362
+ const element = event.target;
1363
+ const typing = element !== null && (element.isContentEditable || element.tagName === "INPUT" || element.tagName === "TEXTAREA" || element.tagName === "SELECT");
1364
+ const mod = event.metaKey || event.ctrlKey;
1365
+ const key = event.key.toLowerCase();
1366
+ let id = null;
1367
+ if (mod && !event.altKey && !event.shiftKey && key === "d") id = "mic";
1368
+ else if (mod && !event.altKey && !event.shiftKey && key === "e") id = "camera";
1369
+ else if (mod && event.altKey && (event.code === "KeyH" || key === "h")) id = "hand";
1370
+ else if (mod && event.altKey && (event.code === "KeyC" || key === "c")) id = "chat";
1371
+ else if (mod && event.altKey && (event.code === "KeyP" || key === "p")) id = "people";
1372
+ else if (!mod && !typing && event.key === "?") id = "help";
1373
+ if (id === null) return;
1374
+ const handler = ref.current[id];
1375
+ if (handler === void 0) return;
1376
+ event.preventDefault();
1377
+ handler();
1378
+ };
1379
+ target.addEventListener("keydown", onKey);
1380
+ return () => target.removeEventListener("keydown", onKey);
1381
+ }, []);
1382
+ }
1383
+ function useGuestRecordAccess(meeting) {
1384
+ const host = useMeetHost();
1385
+ const api = host?.api ?? null;
1386
+ const stored = guestRecordAccessSetting(meeting);
1387
+ const [chosen, setChosen] = (0, import_react4.useState)(null);
1388
+ const [effective, setEffective] = (0, import_react4.useState)(null);
1389
+ const [saving, setSaving] = (0, import_react4.useState)(false);
1390
+ const [error, setError] = (0, import_react4.useState)(null);
1391
+ (0, import_react4.useEffect)(() => {
1392
+ setChosen(null);
1393
+ }, [stored, meeting?.id]);
1394
+ return {
1395
+ setting: chosen ?? stored,
1396
+ effective,
1397
+ saving,
1398
+ error,
1399
+ choose: (0, import_react4.useCallback)(
1400
+ async (next) => {
1401
+ if (api === null || meeting === null) return;
1402
+ setSaving(true);
1403
+ setError(null);
1404
+ try {
1405
+ const result = await setGuestRecordAccess({
1406
+ api,
1407
+ meetingId: meeting.id,
1408
+ organizationId: meeting.organizationId,
1409
+ access: next
1410
+ });
1411
+ setChosen(result.setting);
1412
+ setEffective(result.effective);
1413
+ } catch (thrown) {
1414
+ setError({
1415
+ message: thrown.message,
1416
+ remedy: thrown.remedy ?? "Nothing was changed. Reload the page and try again."
1417
+ });
1418
+ } finally {
1419
+ setSaving(false);
1420
+ }
1421
+ },
1422
+ [api, meeting]
1423
+ )
1424
+ };
1425
+ }
1426
+
1427
+ // src/core/collab-state.ts
1428
+ function availableActivities(collab, isHost) {
1429
+ const settings = collab?.settings ?? null;
1430
+ if (settings === null) return [];
1431
+ const out = [];
1432
+ if (settings.whiteboardEnabled) out.push("whiteboard");
1433
+ if (settings.pollsEnabled && (isHost || (collab?.polls.length ?? 0) > 0)) out.push("polls");
1434
+ if (settings.qaEnabled) out.push("qa");
1435
+ if (settings.breakoutsEnabled && (isHost || collab?.breakout?.state === "open")) out.push("breakouts");
1436
+ return out;
1437
+ }
1438
+ function activityAttention(collab, isHost) {
1439
+ if (collab === null) return false;
1440
+ if (collab.polls.some((poll) => poll.state === "live" && poll.myVote.length === 0 && !isHost)) return true;
1441
+ if (isHost && collab.questions.some((q) => q.state === "open")) return true;
1442
+ return false;
1443
+ }
1444
+ async function performCollab(runtime, request) {
1445
+ if (runtime === null) return { ok: false, message: "This meeting is not connected." };
1446
+ try {
1447
+ const result = await runtime.inRoom.collab(request);
1448
+ return { ok: true, message: result.message, itemId: result.itemId };
1449
+ } catch (error) {
1450
+ return { ok: false, message: error.message ?? "That did not work." };
1451
+ }
1452
+ }
1453
+ async function moveToRoom(runtime, roomId) {
1454
+ if (runtime === null) return { ok: false, message: "This meeting is not connected." };
1455
+ try {
1456
+ await runtime.engine.moveToBreakout(roomId);
1457
+ return { ok: true, message: roomId === null ? "Back in the main room." : "Joined the breakout room." };
1458
+ } catch (error) {
1459
+ return { ok: false, message: error.message ?? "That room could not be joined." };
1460
+ }
1461
+ }
1462
+ function untranslatedLines(lines, translations) {
1463
+ return lines.filter((line) => line.final && translations[line.id] === void 0).map(({ id, text }) => ({ id, text }));
1464
+ }
1465
+ function nextTranslationBatch(pending, language, asked) {
1466
+ const batch = pending.filter((line) => !asked.has(`${language}:${line.id}`)).slice(-20);
1467
+ for (const line of batch) asked.add(`${language}:${line.id}`);
1468
+ return batch;
1469
+ }
1470
+ function setCaptionLanguage(store, next) {
1471
+ store.patch({
1472
+ collab: {
1473
+ ...store.snapshot().collab,
1474
+ captionLanguage: next,
1475
+ captionTranslations: {},
1476
+ captionTranslationError: null
1477
+ }
1478
+ });
1479
+ }
1480
+ function takePanelRequest(store) {
1481
+ const request = store.snapshot().collab.panelRequest;
1482
+ if (request === null) return null;
1483
+ store.patch({ collab: { ...store.snapshot().collab, panelRequest: null } });
1484
+ return request.panel;
1485
+ }
1486
+ function takeMoveRequest(store) {
1487
+ const request = store.snapshot().collab.moveRequest;
1488
+ if (request === null) return null;
1489
+ store.patch({ collab: { ...store.snapshot().collab, moveRequest: null } });
1490
+ return { roomId: request.roomId };
1491
+ }
1492
+
1493
+ // src/react/collab-hooks.ts
1494
+ var import_react6 = require("react");
1495
+ function useCollab() {
1496
+ const host = useMeetHost();
1497
+ const snapshot = useMeetSnapshot();
1498
+ const isHost = useIsHost();
1499
+ return {
1500
+ collab: snapshot?.collab ?? null,
1501
+ isHost,
1502
+ localIdentity: snapshot?.localIdentity ?? null,
1503
+ breakoutRoomId: snapshot?.breakoutRoomId ?? null,
1504
+ act: (request) => performCollab(host, request),
1505
+ moveTo: (roomId) => moveToRoom(host, roomId)
1506
+ };
1507
+ }
1508
+ function useAvailableActivities() {
1509
+ const { collab, isHost } = useCollab();
1510
+ return availableActivities(collab, isHost);
1511
+ }
1512
+ function useActivityAttention() {
1513
+ const { collab, isHost } = useCollab();
1514
+ return activityAttention(collab, isHost);
1515
+ }
1516
+ function useCaptionTranslation(lines) {
1517
+ const host = useMeetHost();
1518
+ const snapshot = useMeetSnapshot();
1519
+ const collab = snapshot?.collab ?? null;
1520
+ const language = collab?.captionLanguage ?? null;
1521
+ const asked = (0, import_react6.useRef)(/* @__PURE__ */ new Set());
1522
+ const translations = collab?.captionTranslations ?? {};
1523
+ const missing = untranslatedLines(lines, translations);
1524
+ const key = `${language ?? ""}|${missing.map((l) => l.id).join(",")}`;
1525
+ const pending = (0, import_react6.useRef)(missing);
1526
+ pending.current = missing;
1527
+ (0, import_react6.useEffect)(() => {
1528
+ if (host === null || language === null) return;
1529
+ const batch = nextTranslationBatch(pending.current, language, asked.current);
1530
+ if (batch.length === 0) return;
1531
+ void host.inRoom.translateCaptions(language, batch).catch(() => void 0);
1532
+ }, [host, language, key]);
1533
+ return { language, translations, error: collab?.captionTranslationError ?? null };
1534
+ }
1535
+ function useCollabPanelRequest(onRequest) {
1536
+ const host = useMeetHost();
1537
+ const at = useMeetSnapshot()?.collab.panelRequest?.at ?? null;
1538
+ const callback = (0, import_react6.useRef)(onRequest);
1539
+ callback.current = onRequest;
1540
+ (0, import_react6.useEffect)(() => {
1541
+ if (host === null) return;
1542
+ const panel = takePanelRequest(host.store);
1543
+ if (panel === null) return;
1544
+ callback.current(panel);
1545
+ }, [at, host]);
1546
+ }
1547
+
1548
+ // src/core/reactions.ts
1549
+ var MEET_REACTIONS = ["\u{1F496}", "\u{1F44D}", "\u{1F389}", "\u{1F44F}", "\u{1F602}", "\u{1F62E}", "\u{1F622}", "\u{1F914}", "\u{1F44E}"];
1550
+
1551
+ // src/react/facade.ts
1552
+ function useMeetCore() {
1553
+ const host = useMeetHost();
1554
+ const snapshot = useMeetSnapshot();
1555
+ const devices = useDeviceSnapshot();
1556
+ const role = useRole();
1557
+ const isHost = useIsHost();
1558
+ const observation = useCoreObservation();
1559
+ return (0, import_react7.useMemo)(
1560
+ () => ({ host, snapshot, devices, role, isHost, observation }),
1561
+ [host, snapshot, devices, role, isHost, observation]
1562
+ );
1563
+ }
1564
+
1565
+ // src/skins/meet/components.tsx
1566
+ var import_design_system = require("@ai-matrx/design-system");
1567
+
1568
+ // src/skins/meet/collab.tsx
1569
+ var import_react8 = require("react");
1570
+
1571
+ // src/skins/meet/icons.tsx
1572
+ var import_jsx_runtime3 = require("react/jsx-runtime");
1573
+ function Icon({ children, ...props }) {
1574
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1575
+ "svg",
1576
+ {
1577
+ width: "20",
1578
+ height: "20",
1579
+ viewBox: "0 0 24 24",
1580
+ fill: "none",
1581
+ stroke: "currentColor",
1582
+ strokeWidth: "1.75",
1583
+ strokeLinecap: "round",
1584
+ strokeLinejoin: "round",
1585
+ "aria-hidden": "true",
1586
+ focusable: "false",
1587
+ ...props,
1588
+ children
1589
+ }
1590
+ );
1591
+ }
1592
+ function MicIcon(props) {
1593
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Icon, { ...props, children: [
1594
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z" }),
1595
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }),
1596
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M12 19v3" })
1597
+ ] });
1598
+ }
1599
+ function MicOffIcon(props) {
1600
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Icon, { ...props, children: [
1601
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M2 2l20 20" }),
1602
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M9 9v3a3 3 0 0 0 5.12 2.12" }),
1603
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M15 9.34V5a3 3 0 0 0-5.94-.6" }),
1604
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M19 10v2a7 7 0 0 1-.11 1.23M5 10v2a7 7 0 0 0 11 5.66" }),
1605
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M12 19v3" })
1606
+ ] });
1607
+ }
1608
+ function CameraIcon(props) {
1609
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Icon, { ...props, children: [
1610
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "m23 7-7 5 7 5V7Z" }),
1611
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { x: "1", y: "5", width: "15", height: "14", rx: "2" })
1612
+ ] });
1613
+ }
1614
+ function CameraOffIcon(props) {
1615
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Icon, { ...props, children: [
1616
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M2 2l20 20" }),
1617
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M10.66 5H14a2 2 0 0 1 2 2v3.34l1 1L23 7v10" }),
1618
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M16 16a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h2" })
1619
+ ] });
1620
+ }
1621
+ function ScreenShareIcon(props) {
1622
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Icon, { ...props, children: [
1623
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { x: "2", y: "3", width: "20", height: "14", rx: "2" }),
1624
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M8 21h8M12 17v4" }),
1625
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M12 13V8m0 0-2.5 2.5M12 8l2.5 2.5" })
1626
+ ] });
1627
+ }
1628
+ function PhoneIcon(props) {
1629
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Icon, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6A19.79 19.79 0 0 1 2.12 4.18 2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.96.36 1.9.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.9.34 1.85.57 2.81.7A2 2 0 0 1 22 16.92Z" }) });
1630
+ }
1631
+ function PhoneOffIcon(props) {
1632
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Icon, { fill: "currentColor", stroke: "none", ...props, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M12 9c-1.6 0-3.15.25-4.6.72v3.1c0 .39-.23.74-.56.9-.98.49-1.87 1.12-2.66 1.85-.18.18-.43.28-.7.28-.28 0-.53-.11-.71-.29L.29 13.08a.99.99 0 0 1-.29-.7c0-.28.11-.53.29-.71C3.34 8.78 7.46 7 12 7s8.66 1.78 11.71 4.67c.18.18.29.43.29.71 0 .28-.11.53-.29.71l-2.48 2.48c-.18.18-.43.29-.71.29-.27 0-.52-.11-.7-.28a11.27 11.27 0 0 0-2.67-1.85.996.996 0 0 1-.56-.9v-3.1C15.15 9.25 13.6 9 12 9Z" }) });
1633
+ }
1634
+ function HandIcon(props) {
1635
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Icon, { ...props, children: [
1636
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M18 11V6a2 2 0 0 0-4 0v5" }),
1637
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M14 10V4a2 2 0 0 0-4 0v7" }),
1638
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M10 10.5V6a2 2 0 0 0-4 0v8" }),
1639
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M18 8a2 2 0 1 1 4 0v6a8 8 0 0 1-8 8h-2a8 8 0 0 1-8-8v-1a2 2 0 1 1 4 0" })
1640
+ ] });
1641
+ }
1642
+ function ChatIcon(props) {
1643
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Icon, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2Z" }) });
1644
+ }
1645
+ function PeopleIcon(props) {
1646
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Icon, { ...props, children: [
1647
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" }),
1648
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "9", cy: "7", r: "4" }),
1649
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M22 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75" })
1650
+ ] });
1651
+ }
1652
+ function RecordIcon(props) {
1653
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Icon, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "12", cy: "12", r: "8", fill: "currentColor", stroke: "none" }) });
1654
+ }
1655
+ function SparklesIcon(props) {
1656
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Icon, { ...props, children: [
1657
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M12 3l1.9 4.6L18.5 9.5l-4.6 1.9L12 16l-1.9-4.6L5.5 9.5l4.6-1.9L12 3Z" }),
1658
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M19 15l.9 2.1 2.1.9-2.1.9-.9 2.1-.9-2.1-2.1-.9 2.1-.9L19 15Z" })
1659
+ ] });
1660
+ }
1661
+ function LockIcon(props) {
1662
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Icon, { ...props, children: [
1663
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { x: "4", y: "10", width: "16", height: "11", rx: "2" }),
1664
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M8 10V7a4 4 0 0 1 8 0v3" })
1665
+ ] });
1666
+ }
1667
+ function LayoutIcon(props) {
1668
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Icon, { ...props, children: [
1669
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
1670
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M3 9h18M9 21V9" })
1671
+ ] });
1672
+ }
1673
+ function SettingsIcon(props) {
1674
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Icon, { ...props, children: [
1675
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "12", cy: "12", r: "3" }),
1676
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.6a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1Z" })
1677
+ ] });
1678
+ }
1679
+ function CheckIcon(props) {
1680
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Icon, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M20 6 9 17l-5-5" }) });
1681
+ }
1682
+ function CloseIcon(props) {
1683
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Icon, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M18 6 6 18M6 6l12 12" }) });
1684
+ }
1685
+ function PinIcon(props) {
1686
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Icon, { ...props, children: [
1687
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M12 17v5" }),
1688
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M9 2h6l-1 6 3.5 3.5V14H6.5v-2.5L10 8 9 2Z" })
1689
+ ] });
1690
+ }
1691
+ function WifiLowIcon(props) {
1692
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Icon, { ...props, children: [
1693
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M12 20h.01" }),
1694
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M8.5 16.5a5 5 0 0 1 7 0" })
1695
+ ] });
1696
+ }
1697
+ function SmileIcon(props) {
1698
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Icon, { ...props, children: [
1699
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
1700
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M8 14s1.5 2 4 2 4-2 4-2" }),
1701
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "9", x2: "9.01", y1: "9", y2: "9" }),
1702
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "15", x2: "15.01", y1: "9", y2: "9" })
1703
+ ] });
1704
+ }
1705
+ function MoreIcon(props) {
1706
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Icon, { ...props, children: [
1707
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "12", cy: "12", r: "1" }),
1708
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "19", cy: "12", r: "1" }),
1709
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "5", cy: "12", r: "1" })
1710
+ ] });
1711
+ }
1712
+ function SearchIcon(props) {
1713
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Icon, { ...props, children: [
1714
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "11", cy: "11", r: "8" }),
1715
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "m21 21-4.3-4.3" })
1716
+ ] });
1717
+ }
1718
+ function KeyboardIcon(props) {
1719
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Icon, { ...props, children: [
1720
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M10 8h.01M12 12h.01M14 8h.01M16 12h.01M18 8h.01M6 8h.01M7 16h10M8 12h.01" }),
1721
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { width: "20", height: "16", x: "2", y: "4", rx: "2" })
1722
+ ] });
1723
+ }
1724
+ function SpotlightIcon(props) {
1725
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Icon, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M12 2 15 8.5 22 9.3 17 14 18.2 21 12 17.6 5.8 21 7 14 2 9.3 9 8.5Z" }) });
1726
+ }
1727
+ function SignalIcon({ bars, ...props }) {
1728
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Icon, { ...props, children: [
1729
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M4 20v-2", opacity: bars >= 1 ? 1 : 0.3 }),
1730
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M10 20v-6", opacity: bars >= 2 ? 1 : 0.3 }),
1731
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M16 20V9", opacity: bars >= 3 ? 1 : 0.3 }),
1732
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M22 20V4", opacity: bars >= 3 ? 1 : 0.3 })
1733
+ ] });
1734
+ }
1735
+ function SpeakerIcon(props) {
1736
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Icon, { ...props, children: [
1737
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M11 5 6 9H2v6h4l5 4V5Z" }),
1738
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M15.54 8.46a5 5 0 0 1 0 7.07" }),
1739
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M19.07 4.93a10 10 0 0 1 0 14.14" })
1740
+ ] });
1741
+ }
1742
+ function FlagIcon(props) {
1743
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Icon, { ...props, children: [
1744
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z" }),
1745
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "4", x2: "4", y1: "22", y2: "15" })
1746
+ ] });
1747
+ }
1748
+ function PencilIcon(props) {
1749
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Icon, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" }) });
1750
+ }
1751
+ function ActivitiesIcon(props) {
1752
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Icon, { ...props, children: [
1753
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M8.3 10a.7.7 0 0 1-.626-1.079L11.4 3a.7.7 0 0 1 1.198-.043L16.3 8.9a.7.7 0 0 1-.572 1.1Z" }),
1754
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { x: "3", y: "14", width: "7", height: "7", rx: "1" }),
1755
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "17.5", cy: "17.5", r: "3.5" })
1756
+ ] });
1757
+ }
1758
+ function PollIcon(props) {
1759
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Icon, { ...props, children: [
1760
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "18", x2: "18", y1: "20", y2: "10" }),
1761
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "12", x2: "12", y1: "20", y2: "4" }),
1762
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "6", x2: "6", y1: "20", y2: "14" })
1763
+ ] });
1764
+ }
1765
+ function QuestionIcon(props) {
1766
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Icon, { ...props, children: [
1767
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M7.9 20A9 9 0 1 0 4 16.1L2 22Z" }),
1768
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" }),
1769
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M12 17h.01" })
1770
+ ] });
1771
+ }
1772
+ function WhiteboardIcon(props) {
1773
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Icon, { ...props, children: [
1774
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M2 3h20" }),
1775
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M21 3v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V3" }),
1776
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "m7 21 5-5 5 5" })
1777
+ ] });
1778
+ }
1779
+ function BreakoutIcon(props) {
1780
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Icon, { ...props, children: [
1781
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { width: "7", height: "18", x: "3", y: "3", rx: "1" }),
1782
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { width: "7", height: "7", x: "14", y: "3", rx: "1" }),
1783
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { width: "7", height: "7", x: "14", y: "14", rx: "1" })
1784
+ ] });
1785
+ }
1786
+ function UpvoteIcon(props) {
1787
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Icon, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M9 18v-6H5l7-7 7 7h-4v6H9z" }) });
1788
+ }
1789
+ function EraserIcon(props) {
1790
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Icon, { ...props, children: [
1791
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "m7 21-4.3-4.3c-1-1-1-2.5 0-3.4l9.6-9.6c1-1 2.5-1 3.4 0l5.6 5.6c1 1 1 2.5 0 3.4L13 21" }),
1792
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M22 21H7" }),
1793
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "m5 11 9 9" })
1794
+ ] });
1795
+ }
1796
+ function HighlighterIcon(props) {
1797
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Icon, { ...props, children: [
1798
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "m9 11-6 6v3h9l3-3" }),
1799
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "m22 12-4.6 4.6a2 2 0 0 1-2.8 0l-5.2-5.2a2 2 0 0 1 0-2.8L14 4" })
1800
+ ] });
1801
+ }
1802
+ function UndoIcon(props) {
1803
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Icon, { ...props, children: [
1804
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M9 14 4 9l5-5" }),
1805
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11" })
1806
+ ] });
1807
+ }
1808
+
1809
+ // src/skins/meet/collab.tsx
1810
+ var import_jsx_runtime4 = require("react/jsx-runtime");
1811
+ function useNotice() {
1812
+ const [notice, setNotice] = (0, import_react8.useState)(null);
1813
+ (0, import_react8.useEffect)(() => {
1814
+ if (notice === null) return void 0;
1815
+ const timer = setTimeout(() => setNotice(null), notice.failed ? 7e3 : 3e3);
1816
+ return () => clearTimeout(timer);
1817
+ }, [notice]);
1818
+ return [notice, (result) => setNotice({ text: result.message, failed: !result.ok })];
1819
+ }
1820
+ function Notice({ notice }) {
1821
+ if (notice === null) return null;
1822
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: notice.failed ? "mx-meet__panel-note mx-meet__collab-error" : "mx-meet__panel-note", role: "status", children: notice.text });
1823
+ }
1824
+ var ACTIVITY_LABELS = {
1825
+ polls: "Polls",
1826
+ qa: "Q&A",
1827
+ whiteboard: "Whiteboard",
1828
+ breakouts: "Breakout rooms"
1829
+ };
1830
+ function ActivityIcon({ panel }) {
1831
+ const props = { width: 16, height: 16 };
1832
+ if (panel === "polls") return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PollIcon, { ...props });
1833
+ if (panel === "qa") return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(QuestionIcon, { ...props });
1834
+ if (panel === "whiteboard") return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(WhiteboardIcon, { ...props });
1835
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(BreakoutIcon, { ...props });
1836
+ }
1837
+ function ActivitiesControl({
1838
+ active,
1839
+ onOpen
1840
+ }) {
1841
+ const [open, setOpen] = (0, import_react8.useState)(false);
1842
+ const available = useAvailableActivities();
1843
+ const attention = useActivityAttention();
1844
+ if (available.length === 0) return null;
1845
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "mx-meet__anchor", children: [
1846
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(ControlButton, { label: "Activities", active: open || active !== null, onClick: () => setOpen(!open), children: [
1847
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ActivitiesIcon, {}),
1848
+ attention ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "mx-meet__dot", "aria-hidden": "true" }) : null
1849
+ ] }),
1850
+ open ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Popover, { label: "Activities", onClose: () => setOpen(false), children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "mx-meet__menu", role: "menu", children: available.map((panel) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1851
+ MenuItem,
1852
+ {
1853
+ onSelect: () => {
1854
+ setOpen(false);
1855
+ onOpen(panel);
1856
+ },
1857
+ children: [
1858
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ActivityIcon, { panel }),
1859
+ " ",
1860
+ ACTIVITY_LABELS[panel]
1861
+ ]
1862
+ },
1863
+ panel
1864
+ )) }) }) : null
1865
+ ] });
1866
+ }
1867
+ function PollComposer({ maxOptions, onDone }) {
1868
+ const { act } = useCollab();
1869
+ const [question, setQuestion] = (0, import_react8.useState)("");
1870
+ const [options, setOptions] = (0, import_react8.useState)(["", ""]);
1871
+ const [multiple, setMultiple] = (0, import_react8.useState)(false);
1872
+ const [anonymous, setAnonymous] = (0, import_react8.useState)(false);
1873
+ const [busy, setBusy] = (0, import_react8.useState)(false);
1874
+ const [notice, show] = useNotice();
1875
+ const filled = options.map((o) => o.trim()).filter(Boolean);
1876
+ const ready = question.trim().length > 0 && filled.length >= 2;
1877
+ const save = async (launch) => {
1878
+ setBusy(true);
1879
+ const created = await act({ action: "poll_create", question, options: filled, multiple, anonymous });
1880
+ if (!created.ok) {
1881
+ show(created);
1882
+ setBusy(false);
1883
+ return;
1884
+ }
1885
+ if (launch && created.itemId != null) {
1886
+ const launched = await act({ action: "poll_launch", itemId: created.itemId });
1887
+ if (!launched.ok) {
1888
+ show(launched);
1889
+ setBusy(false);
1890
+ return;
1891
+ }
1892
+ }
1893
+ setBusy(false);
1894
+ onDone();
1895
+ };
1896
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1897
+ "form",
1898
+ {
1899
+ className: "mx-meet__collab-card",
1900
+ onSubmit: (event) => {
1901
+ event.preventDefault();
1902
+ if (ready) void save(true);
1903
+ },
1904
+ children: [
1905
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "mx-meet__field", children: [
1906
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "mx-meet__field-label", children: "Question" }),
1907
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1908
+ "input",
1909
+ {
1910
+ className: "mx-meet__input",
1911
+ value: question,
1912
+ maxLength: 1e3,
1913
+ onChange: (event) => setQuestion(event.target.value),
1914
+ placeholder: "Ask the room something",
1915
+ autoFocus: true
1916
+ }
1917
+ )
1918
+ ] }),
1919
+ options.map((option, index) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mx-meet__composer-row", children: [
1920
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1921
+ "input",
1922
+ {
1923
+ className: "mx-meet__input",
1924
+ value: option,
1925
+ maxLength: 200,
1926
+ "aria-label": `Answer ${index + 1}`,
1927
+ placeholder: `Answer ${index + 1}`,
1928
+ onChange: (event) => setOptions(options.map((o, i) => i === index ? event.target.value : o))
1929
+ }
1930
+ ),
1931
+ options.length > 2 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ControlButton, { label: `Remove answer ${index + 1}`, onClick: () => setOptions(options.filter((_, i) => i !== index)), children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CloseIcon, { width: 14, height: 14 }) }) : null
1932
+ ] }, index)),
1933
+ options.length < maxOptions ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: "mx-meet__link", onClick: () => setOptions([...options, ""]), children: "Add an answer" }) : null,
1934
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "mx-meet__toggle", children: [
1935
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "checkbox", checked: multiple, onChange: (event) => setMultiple(event.target.checked) }),
1936
+ "People can pick more than one"
1937
+ ] }),
1938
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "mx-meet__toggle", children: [
1939
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "checkbox", checked: anonymous, onChange: (event) => setAnonymous(event.target.checked) }),
1940
+ "Anonymous \u2014 nobody sees who picked what"
1941
+ ] }),
1942
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Notice, { notice }),
1943
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mx-meet__collab-actions", children: [
1944
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "submit", className: "mx-meet__primary mx-meet__primary--small", disabled: !ready || busy, children: "Launch" }),
1945
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: "mx-meet__chip", disabled: !ready || busy, onClick: () => void save(false), children: "Save draft" }),
1946
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: "mx-meet__link", onClick: onDone, children: "Cancel" })
1947
+ ] })
1948
+ ]
1949
+ }
1950
+ );
1951
+ }
1952
+ function PollCard({ poll }) {
1953
+ const { act, isHost } = useCollab();
1954
+ const [picked, setPicked] = (0, import_react8.useState)(poll.myVote.slice());
1955
+ const [notice, show] = useNotice();
1956
+ (0, import_react8.useEffect)(() => setPicked(poll.myVote.slice()), [poll.myVote]);
1957
+ const counts = poll.options.some((o) => o.votes !== void 0);
1958
+ const total = poll.options.reduce((sum, o) => sum + (o.votes ?? 0), 0);
1959
+ const canVote = poll.state === "live";
1960
+ const voted = poll.myVote.length > 0;
1961
+ const stateWord = poll.state === "draft" ? "Draft" : poll.state === "live" ? "Open" : "Closed";
1962
+ const toggle = (id) => {
1963
+ if (!canVote) return;
1964
+ setPicked(poll.multiple ? picked.includes(id) ? picked.filter((p) => p !== id) : [...picked, id] : [id]);
1965
+ };
1966
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("article", { className: "mx-meet__collab-card", "aria-label": poll.question, children: [
1967
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("header", { className: "mx-meet__collab-head", children: [
1968
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { className: "mx-meet__collab-title", children: poll.question }),
1969
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "mx-meet__collab-state", children: stateWord })
1970
+ ] }),
1971
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { className: "mx-meet__panel-note", children: [
1972
+ poll.multiple ? "Pick any." : "Pick one.",
1973
+ poll.anonymous ? " Anonymous." : "",
1974
+ counts && poll.voterCount !== null ? ` ${poll.voterCount} voted.` : "",
1975
+ isHost && poll.state !== "draft" && !poll.resultsShared ? " Results are visible only to hosts." : ""
1976
+ ] }),
1977
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { className: "mx-meet__poll-options", children: poll.options.map((option) => {
1978
+ const share = counts && total > 0 ? Math.round((option.votes ?? 0) / total * 100) : 0;
1979
+ const chosen = picked.includes(option.id);
1980
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("li", { children: [
1981
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1982
+ "button",
1983
+ {
1984
+ type: "button",
1985
+ className: chosen ? "mx-meet__poll-option mx-meet__poll-option--chosen" : "mx-meet__poll-option",
1986
+ "aria-pressed": chosen,
1987
+ disabled: !canVote,
1988
+ onClick: () => toggle(option.id),
1989
+ children: [
1990
+ counts ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "mx-meet__poll-bar", style: { width: `${share}%` }, "aria-hidden": "true" }) : null,
1991
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "mx-meet__poll-text", children: option.text }),
1992
+ counts ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "mx-meet__poll-count", children: [
1993
+ option.votes ?? 0,
1994
+ " \xB7 ",
1995
+ share,
1996
+ "%"
1997
+ ] }) : null
1998
+ ]
1999
+ }
2000
+ ),
2001
+ option.voters !== void 0 && option.voters.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "mx-meet__poll-voters", children: option.voters.join(", ") }) : null
2002
+ ] }, option.id);
2003
+ }) }),
2004
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Notice, { notice }),
2005
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mx-meet__collab-actions", children: [
2006
+ canVote ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2007
+ "button",
2008
+ {
2009
+ type: "button",
2010
+ className: "mx-meet__primary mx-meet__primary--small",
2011
+ disabled: picked.length === 0 || voted && picked.join() === poll.myVote.join(),
2012
+ onClick: () => void act({ action: "poll_vote", itemId: poll.id, optionIds: picked }).then(show),
2013
+ children: voted ? "Change vote" : "Vote"
2014
+ }
2015
+ ) : null,
2016
+ isHost && poll.state === "draft" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: "mx-meet__primary mx-meet__primary--small", onClick: () => void act({ action: "poll_launch", itemId: poll.id }).then(show), children: "Launch" }) : null,
2017
+ isHost && poll.state === "live" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: "mx-meet__chip", onClick: () => void act({ action: "poll_close", itemId: poll.id }).then(show), children: "End poll" }) : null,
2018
+ isHost && poll.state !== "draft" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: "mx-meet__chip", onClick: () => void act({ action: "poll_share", itemId: poll.id }).then(show), children: poll.resultsShared ? "Hide results" : "Share results" }) : null,
2019
+ isHost && poll.state === "live" ? null : isHost ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: "mx-meet__link", onClick: () => void act({ action: "poll_delete", itemId: poll.id }).then(show), children: "Delete" }) : null
2020
+ ] })
2021
+ ] });
2022
+ }
2023
+ function PollsPanel({ onClose }) {
2024
+ const { collab, isHost } = useCollab();
2025
+ const [composing, setComposing] = (0, import_react8.useState)(false);
2026
+ const polls = collab?.polls ?? [];
2027
+ const maxOptions = collab?.settings?.pollMaxOptions ?? 10;
2028
+ const ordered = [...polls].reverse();
2029
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(PanelShell, { title: "Polls", onClose, children: [
2030
+ collab?.error != null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "mx-meet__panel-note mx-meet__collab-error", children: collab.error }) : null,
2031
+ isHost && !composing ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: "mx-meet__primary mx-meet__primary--small", onClick: () => setComposing(true), children: "New poll" }) : null,
2032
+ isHost && composing ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PollComposer, { maxOptions, onDone: () => setComposing(false) }) : null,
2033
+ ordered.length === 0 && !composing ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "mx-meet__panel-note", children: isHost ? "No polls yet. Polls and their results are kept with the meeting." : "No polls yet." }) : null,
2034
+ ordered.map((poll) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PollCard, { poll }, poll.id))
2035
+ ] });
2036
+ }
2037
+ function QuestionCard({ question }) {
2038
+ const { act, isHost } = useCollab();
2039
+ const [answering, setAnswering] = (0, import_react8.useState)(false);
2040
+ const [reply, setReply] = (0, import_react8.useState)("");
2041
+ const [notice, show] = useNotice();
2042
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("li", { className: `mx-meet__collab-card mx-meet__question mx-meet__question--${question.state}`, children: [
2043
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mx-meet__question-row", children: [
2044
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2045
+ "button",
2046
+ {
2047
+ type: "button",
2048
+ className: question.upvotedByMe ? "mx-meet__upvote mx-meet__upvote--on" : "mx-meet__upvote",
2049
+ "aria-pressed": question.upvotedByMe,
2050
+ "aria-label": question.upvotedByMe ? "Remove your upvote" : "Upvote",
2051
+ disabled: question.state === "dismissed",
2052
+ onClick: () => void act({ action: "question_upvote", itemId: question.id }).then((r) => r.ok ? void 0 : show(r)),
2053
+ children: [
2054
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(UpvoteIcon, { width: 16, height: 16 }),
2055
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: question.upvotes })
2056
+ ]
2057
+ }
2058
+ ),
2059
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mx-meet__question-body", children: [
2060
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "mx-meet__chat-meta", children: [
2061
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { children: question.mine ? "You" : question.askerName }),
2062
+ question.mine && question.anonymous ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "mx-meet__chat-private", children: "anonymous" }) : null,
2063
+ question.state !== "open" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "mx-meet__collab-state", children: question.state === "answered" ? "Answered" : "Dismissed" }) : null
2064
+ ] }),
2065
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "mx-meet__chat-text", children: question.text }),
2066
+ question.answer !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "mx-meet__question-answer", children: [
2067
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("strong", { children: [
2068
+ question.answeredByName ?? "Host",
2069
+ ":"
2070
+ ] }),
2071
+ " ",
2072
+ question.answer
2073
+ ] }) : null
2074
+ ] })
2075
+ ] }),
2076
+ answering ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2077
+ "form",
2078
+ {
2079
+ className: "mx-meet__composer-row",
2080
+ onSubmit: (event) => {
2081
+ event.preventDefault();
2082
+ void act({ action: "question_answer", itemId: question.id, text: reply }).then((r) => {
2083
+ show(r);
2084
+ if (r.ok) {
2085
+ setAnswering(false);
2086
+ setReply("");
2087
+ }
2088
+ });
2089
+ },
2090
+ children: [
2091
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2092
+ "input",
2093
+ {
2094
+ className: "mx-meet__input",
2095
+ value: reply,
2096
+ maxLength: 2e3,
2097
+ onChange: (event) => setReply(event.target.value),
2098
+ placeholder: "Type an answer, or leave empty if you answered out loud",
2099
+ "aria-label": "Answer",
2100
+ autoFocus: true
2101
+ }
2102
+ ),
2103
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "submit", className: "mx-meet__send", children: "Done" })
2104
+ ]
2105
+ }
2106
+ ) : null,
2107
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Notice, { notice }),
2108
+ isHost || question.mine ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mx-meet__collab-actions", children: [
2109
+ isHost && question.state === "open" && !answering ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: "mx-meet__chip", onClick: () => setAnswering(true), children: "Answer" }) : null,
2110
+ isHost && question.state === "open" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: "mx-meet__chip", onClick: () => void act({ action: "question_dismiss", itemId: question.id }).then(show), children: "Dismiss" }) : null,
2111
+ isHost && question.state !== "open" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: "mx-meet__chip", onClick: () => void act({ action: "question_reopen", itemId: question.id }).then(show), children: "Reopen" }) : null,
2112
+ question.mine || isHost ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: "mx-meet__link", onClick: () => void act({ action: "question_delete", itemId: question.id }).then(show), children: "Delete" }) : null
2113
+ ] }) : null
2114
+ ] });
2115
+ }
2116
+ function QaPanel({ onClose }) {
2117
+ const { collab, act } = useCollab();
2118
+ const [draft, setDraft] = (0, import_react8.useState)("");
2119
+ const [anonymous, setAnonymous] = (0, import_react8.useState)(false);
2120
+ const [order, setOrder] = (0, import_react8.useState)("popular");
2121
+ const [notice, show] = useNotice();
2122
+ const allowAnonymous = collab?.settings?.qaAnonymousAllowed === true;
2123
+ const questions = [...collab?.questions ?? []];
2124
+ const rank = (q) => q.state === "open" ? 0 : q.state === "answered" ? 1 : 2;
2125
+ questions.sort(
2126
+ (a, b) => rank(a) !== rank(b) ? rank(a) - rank(b) : order === "popular" ? b.upvotes - a.upvotes || (b.createdAt ?? "").localeCompare(a.createdAt ?? "") : (b.createdAt ?? "").localeCompare(a.createdAt ?? "")
2127
+ );
2128
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(PanelShell, { title: "Q&A", onClose, children: [
2129
+ collab?.error != null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "mx-meet__panel-note mx-meet__collab-error", children: collab.error }) : null,
2130
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2131
+ "form",
2132
+ {
2133
+ className: "mx-meet__composer mx-meet__composer--chat",
2134
+ onSubmit: (event) => {
2135
+ event.preventDefault();
2136
+ const text = draft.trim();
2137
+ if (text.length === 0) return;
2138
+ void act({ action: "question_ask", text, anonymous: allowAnonymous && anonymous }).then((r) => {
2139
+ if (r.ok) setDraft("");
2140
+ else show(r);
2141
+ });
2142
+ },
2143
+ children: [
2144
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mx-meet__composer-row", children: [
2145
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2146
+ "input",
2147
+ {
2148
+ className: "mx-meet__input",
2149
+ value: draft,
2150
+ maxLength: 1e3,
2151
+ onChange: (event) => setDraft(event.target.value),
2152
+ placeholder: "Ask the host a question",
2153
+ "aria-label": "Ask the host a question"
2154
+ }
2155
+ ),
2156
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "submit", className: "mx-meet__send", disabled: draft.trim().length === 0, children: "Ask" })
2157
+ ] }),
2158
+ allowAnonymous ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "mx-meet__toggle", children: [
2159
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "checkbox", checked: anonymous, onChange: (event) => setAnonymous(event.target.checked) }),
2160
+ "Ask anonymously"
2161
+ ] }) : null
2162
+ ]
2163
+ }
2164
+ ),
2165
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Notice, { notice }),
2166
+ questions.length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "mx-meet__pills", role: "group", "aria-label": "Order questions", children: ["popular", "recent"].map((value) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2167
+ "button",
2168
+ {
2169
+ type: "button",
2170
+ className: order === value ? "mx-meet__pill mx-meet__pill--on" : "mx-meet__pill",
2171
+ "aria-pressed": order === value,
2172
+ onClick: () => setOrder(value),
2173
+ children: value === "popular" ? "Popular" : "Recent"
2174
+ },
2175
+ value
2176
+ )) }) : null,
2177
+ questions.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "mx-meet__panel-note", children: "No questions yet. Questions are kept with the meeting." }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { className: "mx-meet__chat", children: questions.map((question) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(QuestionCard, { question }, question.id)) })
2178
+ ] });
2179
+ }
2180
+ var BOARD_W = 1600;
2181
+ var BOARD_H = 1e3;
2182
+ var INK = ["#111827", "#dc2626", "#2563eb", "#16a34a", "#f59e0b", "#7c3aed"];
2183
+ function strokePath(points) {
2184
+ if (points.length === 0) return "";
2185
+ const [first, ...rest] = points;
2186
+ const move = `M${(first[0] * BOARD_W).toFixed(1)} ${(first[1] * BOARD_H).toFixed(1)}`;
2187
+ if (rest.length === 0) return `${move} l0.1 0`;
2188
+ return `${move} ${rest.map(([x, y]) => `L${(x * BOARD_W).toFixed(1)} ${(y * BOARD_H).toFixed(1)}`).join(" ")}`;
2189
+ }
2190
+ function StrokeView({ stroke, onPick }) {
2191
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2192
+ "path",
2193
+ {
2194
+ d: strokePath(stroke.points),
2195
+ fill: "none",
2196
+ stroke: stroke.color,
2197
+ strokeWidth: stroke.tool === "highlighter" ? stroke.width * 4 : stroke.width,
2198
+ strokeOpacity: stroke.tool === "highlighter" ? 0.35 : stroke.pending === true ? 0.6 : 1,
2199
+ strokeLinecap: "round",
2200
+ strokeLinejoin: "round",
2201
+ onPointerDown: onPick,
2202
+ style: onPick !== void 0 ? { cursor: "pointer" } : void 0,
2203
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("title", { children: stroke.byName })
2204
+ }
2205
+ );
2206
+ }
2207
+ function WhiteboardPanel({ onClose }) {
2208
+ const { collab, act, isHost, localIdentity } = useCollab();
2209
+ const [tool, setTool] = (0, import_react8.useState)("pen");
2210
+ const [color, setColor] = (0, import_react8.useState)(INK[0]);
2211
+ const [drawing, setDrawing] = (0, import_react8.useState)(null);
2212
+ const [pending, setPending] = (0, import_react8.useState)([]);
2213
+ const [notice, show] = useNotice();
2214
+ const svgRef = (0, import_react8.useRef)(null);
2215
+ const strokes = collab?.strokes ?? [];
2216
+ const mine = strokes.filter((s) => s.by === localIdentity);
2217
+ const point = (event) => {
2218
+ const svg = svgRef.current;
2219
+ if (svg === null) return null;
2220
+ const rect = svg.getBoundingClientRect();
2221
+ const scale = Math.min(rect.width / BOARD_W, rect.height / BOARD_H);
2222
+ const offsetX = (rect.width - BOARD_W * scale) / 2;
2223
+ const offsetY = (rect.height - BOARD_H * scale) / 2;
2224
+ const x = (event.clientX - rect.left - offsetX) / (BOARD_W * scale);
2225
+ const y = (event.clientY - rect.top - offsetY) / (BOARD_H * scale);
2226
+ return [Math.min(1, Math.max(0, x)), Math.min(1, Math.max(0, y))];
2227
+ };
2228
+ const finish = () => {
2229
+ const points = drawing;
2230
+ setDrawing(null);
2231
+ if (points === null || points.length === 0 || tool === "eraser") return;
2232
+ const thinned = points.filter((p, i) => i === 0 || i === points.length - 1 || i % 2 === 0).slice(0, 2e3);
2233
+ const stroke = {
2234
+ points: thinned.map(([x, y]) => [Number(x.toFixed(4)), Number(y.toFixed(4))]),
2235
+ color,
2236
+ width: tool === "highlighter" ? 4 : 3,
2237
+ tool: tool === "highlighter" ? "highlighter" : "pen"
2238
+ };
2239
+ const localId = `pending-${Date.now()}`;
2240
+ setPending((list) => [...list, { id: localId, ...stroke, by: localIdentity ?? "", byName: "You", pending: true }]);
2241
+ void act({ action: "stroke_add", stroke }).then((result) => {
2242
+ setPending((list) => list.filter((s) => s.id !== localId));
2243
+ if (!result.ok) show(result);
2244
+ });
2245
+ };
2246
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("section", { className: "mx-meet__whiteboard", "aria-label": "Whiteboard", children: [
2247
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("header", { className: "mx-meet__whiteboard-bar", children: [
2248
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { className: "mx-meet__panel-title", children: "Whiteboard" }),
2249
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "mx-meet__pills", role: "group", "aria-label": "Tool", children: [
2250
+ ["pen", "Pen", /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PencilIcon, { width: 16, height: 16 }, "p")],
2251
+ ["highlighter", "Highlighter", /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(HighlighterIcon, { width: 16, height: 16 }, "h")],
2252
+ ["eraser", "Eraser \u2014 click a stroke", /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(EraserIcon, { width: 16, height: 16 }, "e")]
2253
+ ].map(([value, label, icon]) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2254
+ "button",
2255
+ {
2256
+ type: "button",
2257
+ className: tool === value ? "mx-meet__pill mx-meet__pill--on" : "mx-meet__pill",
2258
+ "aria-pressed": tool === value,
2259
+ "aria-label": label,
2260
+ title: label,
2261
+ onClick: () => setTool(value),
2262
+ children: icon
2263
+ },
2264
+ value
2265
+ )) }),
2266
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "mx-meet__swatches", role: "group", "aria-label": "Color", children: INK.map((ink) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2267
+ "button",
2268
+ {
2269
+ type: "button",
2270
+ className: ink === color ? "mx-meet__swatch mx-meet__swatch--on" : "mx-meet__swatch",
2271
+ style: { background: ink },
2272
+ "aria-label": `Color ${ink}`,
2273
+ "aria-pressed": ink === color,
2274
+ onClick: () => {
2275
+ setColor(ink);
2276
+ if (tool === "eraser") setTool("pen");
2277
+ }
2278
+ },
2279
+ ink
2280
+ )) }),
2281
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2282
+ ControlButton,
2283
+ {
2284
+ label: "Undo my last stroke",
2285
+ onClick: () => {
2286
+ const last = mine[mine.length - 1];
2287
+ if (last !== void 0) void act({ action: "stroke_erase", itemId: last.id }).then((r) => r.ok ? void 0 : show(r));
2288
+ },
2289
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(UndoIcon, { width: 16, height: 16 })
2290
+ }
2291
+ ),
2292
+ isHost && strokes.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: "mx-meet__chip", onClick: () => void act({ action: "whiteboard_clear" }).then(show), children: "Clear board" }) : null,
2293
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "mx-meet__whiteboard-note", children: "Saved with the meeting" }),
2294
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ControlButton, { label: "Close whiteboard", onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CloseIcon, { width: 16, height: 16 }) })
2295
+ ] }),
2296
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Notice, { notice }),
2297
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2298
+ "svg",
2299
+ {
2300
+ ref: svgRef,
2301
+ className: tool === "eraser" ? "mx-meet__board mx-meet__board--erase" : "mx-meet__board",
2302
+ viewBox: `0 0 ${BOARD_W} ${BOARD_H}`,
2303
+ preserveAspectRatio: "xMidYMid meet",
2304
+ role: "img",
2305
+ "aria-label": `Shared whiteboard, ${strokes.length} strokes`,
2306
+ onPointerDown: (event) => {
2307
+ if (tool === "eraser") return;
2308
+ const p = point(event);
2309
+ if (p === null) return;
2310
+ event.target.setPointerCapture?.(event.pointerId);
2311
+ setDrawing([p]);
2312
+ },
2313
+ onPointerMove: (event) => {
2314
+ if (drawing === null) return;
2315
+ const p = point(event);
2316
+ if (p !== null) setDrawing([...drawing, p]);
2317
+ },
2318
+ onPointerUp: finish,
2319
+ onPointerCancel: finish,
2320
+ children: [
2321
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { x: 0, y: 0, width: BOARD_W, height: BOARD_H, className: "mx-meet__board-paper" }),
2322
+ strokes.map((stroke) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2323
+ StrokeView,
2324
+ {
2325
+ stroke,
2326
+ onPick: tool === "eraser" && (isHost || stroke.by === localIdentity) ? () => void act({ action: "stroke_erase", itemId: stroke.id }).then((r) => r.ok ? void 0 : show(r)) : void 0
2327
+ },
2328
+ stroke.id
2329
+ )),
2330
+ pending.map((stroke) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(StrokeView, { stroke }, stroke.id)),
2331
+ drawing !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2332
+ StrokeView,
2333
+ {
2334
+ stroke: {
2335
+ id: "drawing",
2336
+ points: drawing,
2337
+ color,
2338
+ width: tool === "highlighter" ? 4 : 3,
2339
+ tool: tool === "highlighter" ? "highlighter" : "pen",
2340
+ by: "",
2341
+ byName: "You"
2342
+ }
2343
+ }
2344
+ ) : null
2345
+ ]
2346
+ }
2347
+ )
2348
+ ] });
2349
+ }
2350
+ function clock(ms) {
2351
+ return (0, import_format2.formatDurationMs)(Math.ceil(ms / 1e3) * 1e3, { style: "clock" });
2352
+ }
2353
+ var BREAKOUT_EXPIRE_RETRY_MS = 5e3;
2354
+ function useNow(active) {
2355
+ const [now, setNow] = (0, import_react8.useState)(() => Date.now());
2356
+ (0, import_react8.useEffect)(() => {
2357
+ if (!active) return void 0;
2358
+ const timer = setInterval(() => setNow(Date.now()), 1e3);
2359
+ return () => clearInterval(timer);
2360
+ }, [active]);
2361
+ return now;
2362
+ }
2363
+ function BreakoutSetup() {
2364
+ const { collab, act } = useCollab();
2365
+ const snapshot = useMeetSnapshot();
2366
+ const settings = collab?.settings ?? null;
2367
+ const people = (snapshot?.participants ?? []).filter(
2368
+ (p) => !p.isAgent && p.role !== "host" && p.role !== "cohost"
2369
+ );
2370
+ const [count, setCount] = (0, import_react8.useState)(Math.min(Math.max(2, Math.ceil(people.length / 4)), settings?.breakoutMaxRooms ?? 20));
2371
+ const [mode, setMode] = (0, import_react8.useState)("auto");
2372
+ const [minutes, setMinutes] = (0, import_react8.useState)(settings?.breakoutDefaultMinutes ?? 15);
2373
+ const [manual, setManual] = (0, import_react8.useState)({});
2374
+ const [busy, setBusy] = (0, import_react8.useState)(false);
2375
+ const [notice, show] = useNotice();
2376
+ const max = settings?.breakoutMaxRooms ?? 20;
2377
+ const rooms = Math.min(Math.max(1, count), max);
2378
+ const assignment = mode === "auto" ? autoAssign(
2379
+ people.map((p) => p.identity),
2380
+ rooms
2381
+ ) : mode === "manual" ? Array.from(
2382
+ { length: rooms },
2383
+ (_, index) => people.filter((p) => (manual[p.identity] ?? -1) === index).map((p) => p.identity)
2384
+ ) : Array.from({ length: rooms }, () => []);
2385
+ const nameOf = (identity) => people.find((p) => p.identity === identity)?.displayName ?? identity;
2386
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mx-meet__collab-card", children: [
2387
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "mx-meet__field", children: [
2388
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "mx-meet__field-label", children: "Rooms" }),
2389
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2390
+ "input",
2391
+ {
2392
+ className: "mx-meet__input",
2393
+ type: "number",
2394
+ min: 1,
2395
+ max,
2396
+ value: count,
2397
+ onChange: (event) => setCount(Number(event.target.value) || 1)
2398
+ }
2399
+ )
2400
+ ] }),
2401
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "mx-meet__pills", role: "group", "aria-label": "How people are placed", children: [
2402
+ ["auto", "Automatically"],
2403
+ ["manual", "Manually"],
2404
+ ["choose", "Let people choose"]
2405
+ ].map(([value, label]) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2406
+ "button",
2407
+ {
2408
+ type: "button",
2409
+ className: mode === value ? "mx-meet__pill mx-meet__pill--on" : "mx-meet__pill",
2410
+ "aria-pressed": mode === value,
2411
+ onClick: () => setMode(value),
2412
+ children: label
2413
+ },
2414
+ value
2415
+ )) }),
2416
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "mx-meet__field", children: [
2417
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "mx-meet__field-label", children: "Timer (minutes, 0 for none)" }),
2418
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2419
+ "input",
2420
+ {
2421
+ className: "mx-meet__input",
2422
+ type: "number",
2423
+ min: 0,
2424
+ max: 720,
2425
+ value: minutes,
2426
+ onChange: (event) => setMinutes(Math.max(0, Number(event.target.value) || 0))
2427
+ }
2428
+ )
2429
+ ] }),
2430
+ mode === "manual" ? people.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "mx-meet__panel-note", children: "Nobody else is in the meeting yet." }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { className: "mx-meet__people", children: people.map((person) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("li", { className: "mx-meet__person", children: [
2431
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "mx-meet__person-name", children: person.displayName }),
2432
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2433
+ "select",
2434
+ {
2435
+ className: "mx-meet__select mx-meet__select--compact",
2436
+ "aria-label": `Room for ${person.displayName}`,
2437
+ value: manual[person.identity] ?? -1,
2438
+ onChange: (event) => setManual({ ...manual, [person.identity]: Number(event.target.value) }),
2439
+ children: [
2440
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: -1, children: "Main room" }),
2441
+ Array.from({ length: rooms }, (_, index) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("option", { value: index, children: [
2442
+ "Room ",
2443
+ index + 1
2444
+ ] }, index))
2445
+ ]
2446
+ }
2447
+ )
2448
+ ] }, person.identity)) }) : mode === "auto" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { className: "mx-meet__breakout-rooms", children: assignment.map((members, index) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("li", { children: [
2449
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("strong", { children: [
2450
+ "Room ",
2451
+ index + 1
2452
+ ] }),
2453
+ " ",
2454
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "mx-meet__panel-note", children: members.length === 0 ? "empty" : members.map(nameOf).join(", ") })
2455
+ ] }, index)) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "mx-meet__panel-note", children: "Everyone picks a room from their own screen." }),
2456
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Notice, { notice }),
2457
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2458
+ "button",
2459
+ {
2460
+ type: "button",
2461
+ className: "mx-meet__primary mx-meet__primary--small",
2462
+ disabled: busy,
2463
+ onClick: () => {
2464
+ setBusy(true);
2465
+ void act({
2466
+ action: "breakout_open",
2467
+ rooms: assignment.map((identities, index) => ({ name: `Room ${index + 1}`, identities })),
2468
+ allowChoose: mode === "choose",
2469
+ minutes
2470
+ }).then((result) => {
2471
+ setBusy(false);
2472
+ show(result);
2473
+ });
2474
+ },
2475
+ children: [
2476
+ "Open ",
2477
+ rooms,
2478
+ " room",
2479
+ rooms === 1 ? "" : "s"
2480
+ ]
2481
+ }
2482
+ )
2483
+ ] });
2484
+ }
2485
+ function BreakoutRun({ plan }) {
2486
+ const { act, moveTo, isHost, breakoutRoomId, localIdentity } = useCollab();
2487
+ const snapshot = useMeetSnapshot();
2488
+ const now = useNow(plan.endsAt !== null);
2489
+ const remaining = breakoutRemainingMs(plan, now);
2490
+ const [broadcast, setBroadcast] = (0, import_react8.useState)("");
2491
+ const [notice, show] = useNotice();
2492
+ const nameOf = (identity) => displayNameFor(snapshot?.names ?? {}, identity, snapshot?.participants.find((p) => p.identity === identity)?.displayName);
2493
+ const mine = assignedRoom(plan, localIdentity);
2494
+ const join = async (roomId) => {
2495
+ const said = await act({ action: "breakout_join", roomId });
2496
+ if (!said.ok) {
2497
+ show(said);
2498
+ return;
2499
+ }
2500
+ show(await moveTo(roomId));
2501
+ };
2502
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mx-meet__collab-card", children: [
2503
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { className: "mx-meet__panel-note", children: [
2504
+ plan.rooms.length,
2505
+ " room",
2506
+ plan.rooms.length === 1 ? "" : "s",
2507
+ " open",
2508
+ remaining !== null ? ` \xB7 ${clock(remaining)} left` : "",
2509
+ plan.allowChoose ? " \xB7 people choose their room" : ""
2510
+ ] }),
2511
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { className: "mx-meet__breakout-rooms", children: plan.rooms.map((room) => {
2512
+ const here = breakoutRoomId === room.id;
2513
+ const mayJoin = isHost || plan.allowChoose || mine?.id === room.id;
2514
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("li", { className: here ? "mx-meet__breakout-room mx-meet__breakout-room--here" : "mx-meet__breakout-room", children: [
2515
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mx-meet__collab-head", children: [
2516
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { children: room.name }),
2517
+ here ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "mx-meet__collab-state", children: "You are here" }) : mayJoin ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: "mx-meet__chip", onClick: () => void join(room.id), children: "Join" }) : null
2518
+ ] }),
2519
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "mx-meet__panel-note", children: room.identities.length === 0 ? "Nobody assigned" : room.identities.map(nameOf).join(", ") }),
2520
+ isHost && room.identities.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { className: "mx-meet__people", children: room.identities.map((identity) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("li", { className: "mx-meet__person", children: [
2521
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "mx-meet__person-name", children: nameOf(identity) }),
2522
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2523
+ "select",
2524
+ {
2525
+ className: "mx-meet__select mx-meet__select--compact",
2526
+ "aria-label": `Move ${nameOf(identity)}`,
2527
+ value: room.id,
2528
+ onChange: (event) => void act({
2529
+ action: "breakout_move",
2530
+ identity,
2531
+ roomId: event.target.value === "" ? null : event.target.value
2532
+ }).then(show),
2533
+ children: [
2534
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "", children: "Main room" }),
2535
+ plan.rooms.map((option) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: option.id, children: option.name }, option.id))
2536
+ ]
2537
+ }
2538
+ )
2539
+ ] }, identity)) }) : null
2540
+ ] }, room.id);
2541
+ }) }),
2542
+ breakoutRoomId !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: "mx-meet__chip", onClick: () => void join(null), children: "Return to the main room" }) : null,
2543
+ isHost ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2544
+ "form",
2545
+ {
2546
+ className: "mx-meet__composer-row",
2547
+ onSubmit: (event) => {
2548
+ event.preventDefault();
2549
+ const text = broadcast.trim();
2550
+ if (text.length === 0) return;
2551
+ void act({ action: "breakout_broadcast", text }).then((result) => {
2552
+ show(result);
2553
+ if (result.ok) setBroadcast("");
2554
+ });
2555
+ },
2556
+ children: [
2557
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2558
+ "input",
2559
+ {
2560
+ className: "mx-meet__input",
2561
+ value: broadcast,
2562
+ maxLength: 500,
2563
+ onChange: (event) => setBroadcast(event.target.value),
2564
+ placeholder: "Message every room",
2565
+ "aria-label": "Message every room"
2566
+ }
2567
+ ),
2568
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "submit", className: "mx-meet__send", disabled: broadcast.trim().length === 0, children: "Send" })
2569
+ ]
2570
+ }
2571
+ ) : null,
2572
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Notice, { notice }),
2573
+ isHost ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2574
+ "button",
2575
+ {
2576
+ type: "button",
2577
+ className: "mx-meet__primary mx-meet__primary--small",
2578
+ onClick: () => void act({ action: "breakout_close" }).then(show),
2579
+ children: "Bring everyone back"
2580
+ }
2581
+ ) : null
2582
+ ] });
2583
+ }
2584
+ function BreakoutsPanel({ onClose }) {
2585
+ const { collab, isHost } = useCollab();
2586
+ const plan = collab?.breakout?.state === "open" ? collab.breakout : null;
2587
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(PanelShell, { title: "Breakout rooms", onClose, children: [
2588
+ collab?.error != null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "mx-meet__panel-note mx-meet__collab-error", children: collab.error }) : null,
2589
+ plan !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(BreakoutRun, { plan }) : isHost ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(BreakoutSetup, {}) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "mx-meet__panel-note", children: "Breakout rooms are closed." })
2590
+ ] });
2591
+ }
2592
+ function BreakoutBanner() {
2593
+ const host = useMeetHost();
2594
+ const { collab, act, moveTo, localIdentity, breakoutRoomId } = useCollab();
2595
+ const plan = collab?.breakout ?? null;
2596
+ const open = plan?.state === "open" ? plan : null;
2597
+ const now = useNow(open?.endsAt != null);
2598
+ const remaining = breakoutRemainingMs(open, now);
2599
+ const assigned = assignedRoom(open, localIdentity);
2600
+ const [dismissedBroadcast, setDismissedBroadcast] = (0, import_react8.useState)(null);
2601
+ const [failure, setFailure] = (0, import_react8.useState)(null);
2602
+ const expireAsked = (0, import_react8.useRef)(null);
2603
+ const moveAt = collab?.moveRequest?.at ?? null;
2604
+ (0, import_react8.useEffect)(() => {
2605
+ if (host === null) return;
2606
+ const request = takeMoveRequest(host.store);
2607
+ if (request === null) return;
2608
+ const here = host.store.snapshot().breakoutRoomId;
2609
+ if ((here ?? null) === request.roomId) return;
2610
+ void moveTo(request.roomId).then((result) => setFailure(result.ok ? null : result.message));
2611
+ }, [moveAt, host, moveTo]);
2612
+ (0, import_react8.useEffect)(() => {
2613
+ if (open === null || remaining === null || remaining > 0) return;
2614
+ const asked = expireAsked.current;
2615
+ if (asked !== null && asked.id === open.id && now - asked.at < BREAKOUT_EXPIRE_RETRY_MS) return;
2616
+ expireAsked.current = { id: open.id, at: now };
2617
+ void act({ action: "breakout_expire" });
2618
+ }, [open, remaining, act, now]);
2619
+ const broadcast = collab?.broadcast ?? null;
2620
+ const showBroadcast = broadcast !== null && broadcast.at !== dismissedBroadcast && open !== null;
2621
+ const room = open?.rooms.find((r) => r.id === breakoutRoomId) ?? null;
2622
+ if (open === null && failure === null) return null;
2623
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mx-meet__breakout-banner", role: "status", children: [
2624
+ failure !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "mx-meet__collab-error", children: failure }) : null,
2625
+ room !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { children: [
2626
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { children: room.name }),
2627
+ remaining !== null ? ` \xB7 ${clock(remaining)} left` : "",
2628
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: "mx-meet__chip", onClick: () => void moveTo(null), children: "Return to main room" })
2629
+ ] }) : assigned !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { children: [
2630
+ "You are assigned to ",
2631
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { children: assigned.name }),
2632
+ remaining !== null ? ` \xB7 ${clock(remaining)} left` : "",
2633
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2634
+ "button",
2635
+ {
2636
+ type: "button",
2637
+ className: "mx-meet__primary mx-meet__primary--small",
2638
+ onClick: () => void act({ action: "breakout_join", roomId: assigned.id }).then(async (said) => {
2639
+ const result = said.ok ? await moveTo(assigned.id) : said;
2640
+ setFailure(result.ok ? null : result.message);
2641
+ }),
2642
+ children: "Join"
2643
+ }
2644
+ )
2645
+ ] }) : open?.allowChoose ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "Breakout rooms are open \u2014 pick one under Activities." }) : null,
2646
+ showBroadcast ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "mx-meet__breakout-broadcast", children: [
2647
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("strong", { children: [
2648
+ broadcast.by,
2649
+ ":"
2650
+ ] }),
2651
+ " ",
2652
+ broadcast.text,
2653
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: "mx-meet__link", onClick: () => setDismissedBroadcast(broadcast.at), children: "Dismiss" })
2654
+ ] }) : null
2655
+ ] });
2656
+ }
2657
+ function CaptionLanguageSettings() {
2658
+ const host = useMeetHost();
2659
+ const snapshot = useMeetSnapshot();
2660
+ const collab = snapshot?.collab ?? null;
2661
+ const settings = collab?.settings ?? null;
2662
+ if (host === null || settings === null || !settings.captionTranslationEnabled) return null;
2663
+ const viewer = typeof navigator !== "undefined" && typeof navigator.language === "string" ? navigator.language.slice(0, 2) : null;
2664
+ const preferred = defaultCaptionLanguage(settings, viewer);
2665
+ const current = collab?.captionLanguage ?? "";
2666
+ const choices = CAPTION_LANGUAGES.some((l) => l.tag === preferred) ? CAPTION_LANGUAGES : [{ tag: preferred, name: preferred }, ...CAPTION_LANGUAGES];
2667
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("section", { className: "mx-meet__section", "aria-label": "Captions", children: [
2668
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "mx-meet__field", children: [
2669
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "mx-meet__field-label", children: "Translate captions to" }),
2670
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2671
+ "select",
2672
+ {
2673
+ className: "mx-meet__select",
2674
+ value: current,
2675
+ onChange: (event) => {
2676
+ const next = event.target.value === "" ? null : event.target.value;
2677
+ setCaptionLanguage(host.store, next);
2678
+ },
2679
+ children: [
2680
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "", children: "Off \u2014 as spoken" }),
2681
+ current === "" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("option", { value: preferred, children: [
2682
+ choices.find((c) => c.tag === preferred)?.name ?? preferred,
2683
+ " (yours)"
2684
+ ] }) : null,
2685
+ choices.filter((choice) => current !== "" || choice.tag !== preferred).map((choice) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: choice.tag, children: choice.name }, choice.tag))
2686
+ ]
2687
+ }
2688
+ )
2689
+ ] }),
2690
+ collab?.captionTranslationError != null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "mx-meet__panel-note mx-meet__collab-error", children: collab.captionTranslationError }) : null
2691
+ ] });
2692
+ }
2693
+
2694
+ // src/skins/meet/components.tsx
2695
+ var import_jsx_runtime5 = require("react/jsx-runtime");
2696
+ function InertNotice({ what }) {
2697
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mx-meet__empty", role: "status", children: [
2698
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { className: "mx-meet__empty-title", children: [
2699
+ what,
2700
+ " is not ready yet"
2701
+ ] }),
2702
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mx-meet__empty-body", children: "Calls and meetings need a signed-in identity and an active organization. This will appear as soon as they resolve." })
2703
+ ] });
2704
+ }
2705
+ function Avatar({
2706
+ name,
2707
+ url,
2708
+ size = 40
2709
+ }) {
2710
+ const tint = (0, import_format2.avatarPaletteIndex)(name);
2711
+ if (url != null && url.length > 0) {
2712
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2713
+ "img",
2714
+ {
2715
+ className: "mx-meet__avatar",
2716
+ src: url,
2717
+ alt: "",
2718
+ width: size,
2719
+ height: size,
2720
+ style: { width: size, height: size }
2721
+ }
2722
+ );
2723
+ }
2724
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2725
+ "span",
2726
+ {
2727
+ className: "mx-meet__avatar mx-meet__avatar--initials",
2728
+ style: {
2729
+ width: size,
2730
+ height: size,
2731
+ fontSize: Math.round(size * 0.38),
2732
+ background: `var(--mx-meet-avatar-${tint})`
2733
+ },
2734
+ "aria-hidden": "true",
2735
+ children: (0, import_format2.getInitials)(name)
2736
+ }
2737
+ );
2738
+ }
2739
+ function ControlButton({
2740
+ label,
2741
+ active = false,
2742
+ danger = false,
2743
+ onClick,
2744
+ children
2745
+ }) {
2746
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2747
+ "button",
2748
+ {
2749
+ type: "button",
2750
+ className: [
2751
+ "mx-meet__control",
2752
+ active ? "mx-meet__control--active" : "",
2753
+ danger ? "mx-meet__control--danger" : ""
2754
+ ].filter(Boolean).join(" "),
2755
+ "aria-label": label,
2756
+ title: label,
2757
+ onClick,
2758
+ children
2759
+ }
2760
+ );
2761
+ }
2762
+ function RecordingIndicator() {
2763
+ const { state, label } = useRecording();
2764
+ if (label === null) return null;
2765
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2766
+ "span",
2767
+ {
2768
+ className: `mx-meet__recording mx-meet__recording--${state}`,
2769
+ role: "status",
2770
+ "aria-live": "polite",
2771
+ children: [
2772
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RecordIcon, { width: 10, height: 10 }),
2773
+ label
2774
+ ]
2775
+ }
2776
+ );
2777
+ }
2778
+ function ConnectionDot({ quality }) {
2779
+ if (quality === "excellent" || quality === "good" || quality === "unknown") return null;
2780
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: `mx-meet__quality mx-meet__quality--${quality}`, title: `Connection ${quality}`, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(WifiLowIcon, { width: 14, height: 14 }) });
2781
+ }
2782
+ function RoleBadge({ role }) {
2783
+ if (role === "host") return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__badge", children: "Host" });
2784
+ if (role === "cohost") return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__badge", children: "Co-host" });
2785
+ if (role === "guest") return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__badge", children: "Guest" });
2786
+ return null;
2787
+ }
2788
+ function ParticipantTile({
2789
+ participant,
2790
+ featured = false
2791
+ }) {
2792
+ const host = useMeetHost();
2793
+ const snapshot = useMeetSnapshot();
2794
+ const queue = useHandQueue();
2795
+ const [menu, setMenu] = (0, import_react9.useState)(false);
2796
+ const videoRef = useTrack(
2797
+ participant.identity,
2798
+ participant.screenSharing ? "screen" : "camera"
2799
+ );
2800
+ const showVideo = participant.cameraEnabled || participant.screenSharing;
2801
+ const pinnedHere = snapshot?.pinned === participant.identity;
2802
+ const spotlit = snapshot?.spotlight === participant.identity;
2803
+ const handPosition = participant.handRaised ? queue.findIndex((entry) => entry.identity === participant.identity) + 1 : 0;
2804
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2805
+ "div",
2806
+ {
2807
+ className: [
2808
+ "mx-meet__tile",
2809
+ featured ? "mx-meet__tile--featured" : "",
2810
+ participant.speaking ? "mx-meet__tile--speaking" : ""
2811
+ ].filter(Boolean).join(" "),
2812
+ "data-identity": participant.identity,
2813
+ ...participantAttributes(participant),
2814
+ children: [
2815
+ showVideo ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2816
+ "video",
2817
+ {
2818
+ ref: videoRef,
2819
+ className: "mx-meet__video",
2820
+ autoPlay: true,
2821
+ playsInline: true,
2822
+ muted: participant.isLocal
2823
+ }
2824
+ ) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mx-meet__tile-placeholder", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Avatar, { name: participant.displayName, url: participant.avatarUrl, size: featured ? 96 : 56 }) }),
2825
+ participant.handRaised ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "mx-meet__tile-hand", title: `Hand raised \xB7 ${ordinal(handPosition)} in line`, children: [
2826
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(HandIcon, { width: 14, height: 14 }),
2827
+ handPosition > 0 ? handPosition : null
2828
+ ] }) : null,
2829
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mx-meet__tile-bar", children: [
2830
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "mx-meet__tile-name", children: [
2831
+ spotlit ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { title: "Spotlighted for everyone", className: "mx-meet__tile-flag", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SpotlightIcon, { width: 12, height: 12 }) }) : null,
2832
+ participant.displayName,
2833
+ participant.isLocal ? " (you)" : "",
2834
+ participant.isAgent ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__badge", children: "AI" }) : null,
2835
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RoleBadge, { role: participant.role })
2836
+ ] }),
2837
+ !participant.micEnabled ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MicOffIcon, { width: 14, height: 14 }) : null,
2838
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(QualityBars, { quality: participant.quality })
2839
+ ] }),
2840
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: menu ? "mx-meet__tile-actions mx-meet__tile-actions--open" : "mx-meet__tile-actions", children: [
2841
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2842
+ ControlButton,
2843
+ {
2844
+ label: pinnedHere ? "Unpin" : `Pin ${participant.displayName} for me`,
2845
+ active: pinnedHere,
2846
+ onClick: () => host?.engine.pin(pinnedHere ? null : participant.identity),
2847
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PinIcon, { width: 16, height: 16 })
2848
+ }
2849
+ ),
2850
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "mx-meet__anchor", children: [
2851
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2852
+ ControlButton,
2853
+ {
2854
+ label: `More for ${participant.displayName}`,
2855
+ active: menu,
2856
+ onClick: () => setMenu(!menu),
2857
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MoreIcon, { width: 16, height: 16 })
2858
+ }
2859
+ ),
2860
+ menu ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PersonMenu, { participant, onClose: () => setMenu(false), placement: "down" }) : null
2861
+ ] })
2862
+ ] })
2863
+ ]
2864
+ }
2865
+ );
2866
+ }
2867
+ function ordinal(position) {
2868
+ if (position <= 0) return "";
2869
+ const suffix = position % 100 >= 11 && position % 100 <= 13 ? "th" : { 1: "st", 2: "nd", 3: "rd" }[position % 10] ?? "th";
2870
+ return `${position}${suffix}`;
2871
+ }
2872
+ function QualityBars({ quality }) {
2873
+ if (quality === "unknown") return null;
2874
+ const bars = quality === "excellent" ? 3 : quality === "good" ? 2 : quality === "poor" ? 1 : 0;
2875
+ const label = quality === "lost" ? "Connection lost" : quality === "poor" ? "Poor connection" : `Connection ${quality}`;
2876
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: `mx-meet__signal mx-meet__signal--${quality}`, title: label, "aria-label": label, role: "img", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SignalIcon, { bars, width: 14, height: 14 }) });
2877
+ }
2878
+ function Stage({ pageSize }) {
2879
+ const stage = useStage(pageSize);
2880
+ const columns = gridColumns(stage.tiles.length);
2881
+ if (stage.feature === null && stage.tiles.length === 0) {
2882
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mx-meet__stage mx-meet__stage--empty", children: [
2883
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mx-meet__empty-title", children: "Waiting for others to join" }),
2884
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mx-meet__empty-body", children: "Share the meeting link and people can join from anywhere \u2014 no account needed." })
2885
+ ] });
2886
+ }
2887
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: `mx-meet__stage${stage.feature !== null ? " mx-meet__stage--speaker" : ""}`, children: [
2888
+ stage.feature !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ParticipantTile, { participant: stage.feature, featured: true }) : null,
2889
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2890
+ "div",
2891
+ {
2892
+ className: "mx-meet__grid",
2893
+ style: { "--mx-meet-columns": columns },
2894
+ children: stage.tiles.map((participant) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ParticipantTile, { participant }, participant.identity))
2895
+ }
2896
+ ),
2897
+ stage.overflow > 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { className: "mx-meet__overflow", children: [
2898
+ "+",
2899
+ stage.overflow,
2900
+ " more ",
2901
+ stage.overflow === 1 ? "person" : "people",
2902
+ " in this meeting"
2903
+ ] }) : null
2904
+ ] });
2905
+ }
2906
+ function ControlBar({ onLeave }) {
2907
+ const room = useRoom();
2908
+ const recording = useRecording();
2909
+ const [panel, setPanel] = (0, import_react9.useState)("none");
2910
+ const [chatTo, setChatTo] = (0, import_react9.useState)(null);
2911
+ const [popover, setPopover] = (0, import_react9.useState)("none");
2912
+ const [shortcuts, setShortcuts] = (0, import_react9.useState)(false);
2913
+ const snapshot = useMeetSnapshot();
2914
+ const lobby = useLobby();
2915
+ const controls = useHostControls();
2916
+ const queue = useHandQueue();
2917
+ const micUnavailable = useLocalMedia().microphone.unavailable;
2918
+ const unread = useUnreadChat(panel === "chat");
2919
+ const [whiteboard, setWhiteboard] = (0, import_react9.useState)(false);
2920
+ const toggle = (next) => setPanel(panel === next ? "none" : next);
2921
+ const openActivity = (activity) => {
2922
+ if (activity === "whiteboard") setWhiteboard(true);
2923
+ else setPanel(activity);
2924
+ };
2925
+ useCollabPanelRequest((activity) => {
2926
+ if (!controls.isHost) openActivity(activity);
2927
+ });
2928
+ const privateChatAt = snapshot?.privateChatRequest?.at ?? null;
2929
+ const meetStore = useMeetHost()?.store ?? null;
2930
+ (0, import_react9.useEffect)(() => {
2931
+ const request = meetStore?.snapshot().privateChatRequest ?? null;
2932
+ if (request === null) return;
2933
+ setChatTo(request.to);
2934
+ setPanel("chat");
2935
+ meetStore?.patch({ privateChatRequest: null });
2936
+ }, [privateChatAt, meetStore]);
2937
+ useMeetShortcuts({
2938
+ mic: () => void room.toggleMicrophone(),
2939
+ camera: () => void room.toggleCamera(),
2940
+ hand: () => void room.toggleHand(),
2941
+ chat: () => toggle("chat"),
2942
+ people: () => toggle("people"),
2943
+ help: () => setShortcuts(true)
2944
+ });
2945
+ const hasSpotlight = snapshot?.spotlight !== null && snapshot?.spotlight !== void 0;
2946
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
2947
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MeetingOverlays, {}),
2948
+ shortcuts ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ShortcutsSheet, { onClose: () => setShortcuts(false) }) : null,
2949
+ panel === "chat" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ChatPanel, { onClose: () => setPanel("none"), to: chatTo, onTo: setChatTo }) : null,
2950
+ panel === "people" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2951
+ PeoplePanel,
2952
+ {
2953
+ onClose: () => setPanel("none"),
2954
+ onMessage: (identity) => {
2955
+ setChatTo(identity);
2956
+ setPanel("chat");
2957
+ }
2958
+ }
2959
+ ) : null,
2960
+ panel === "ai" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(AiPanel, { onClose: () => setPanel("none") }) : null,
2961
+ panel === "settings" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SettingsPanel, { onClose: () => setPanel("none") }) : null,
2962
+ panel === "polls" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PollsPanel, { onClose: () => setPanel("none") }) : null,
2963
+ panel === "qa" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(QaPanel, { onClose: () => setPanel("none") }) : null,
2964
+ panel === "breakouts" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(BreakoutsPanel, { onClose: () => setPanel("none") }) : null,
2965
+ whiteboard ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(WhiteboardPanel, { onClose: () => setWhiteboard(false) }) : null,
2966
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(BreakoutBanner, {}),
2967
+ controls.notice !== null && panel !== "people" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Toast, { text: controls.notice.text, failed: controls.notice.failed, onDone: controls.clearNotice }) : null,
2968
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mx-meet__controls", children: [
2969
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2970
+ ControlButton,
2971
+ {
2972
+ label: micUnavailable !== null ? `Unmute \u2014 ${micUnavailable.remedy}` : room.local?.micEnabled === true ? "Mute (\u2318D)" : "Unmute (\u2318D)",
2973
+ active: room.local?.micEnabled === true,
2974
+ onClick: () => void room.toggleMicrophone(),
2975
+ children: room.local?.micEnabled === true ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MicIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MicOffIcon, {})
2976
+ }
2977
+ ),
2978
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2979
+ ControlButton,
2980
+ {
2981
+ label: room.local?.cameraEnabled === true ? "Stop video (\u2318E)" : "Start video (\u2318E)",
2982
+ active: room.local?.cameraEnabled === true,
2983
+ onClick: () => void room.toggleCamera(),
2984
+ children: room.local?.cameraEnabled === true ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CameraIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CameraOffIcon, {})
2985
+ }
2986
+ ),
2987
+ room.canShareScreen || room.local?.screenSharing === true ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2988
+ ControlButton,
2989
+ {
2990
+ label: room.local?.screenSharing === true ? "Stop sharing" : "Share screen",
2991
+ active: room.local?.screenSharing === true,
2992
+ onClick: () => void room.toggleScreenShare(),
2993
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ScreenShareIcon, {})
2994
+ }
2995
+ ) : null,
2996
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "mx-meet__anchor", children: [
2997
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2998
+ ControlButton,
2999
+ {
3000
+ label: "Send a reaction",
3001
+ active: popover === "reactions",
3002
+ onClick: () => setPopover(popover === "reactions" ? "none" : "reactions"),
3003
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SmileIcon, {})
3004
+ }
3005
+ ),
3006
+ popover === "reactions" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Popover, { label: "Reactions", onClose: () => setPopover("none"), children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mx-meet__emoji-bar", children: MEET_REACTIONS.map((emoji) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3007
+ "button",
3008
+ {
3009
+ type: "button",
3010
+ className: "mx-meet__emoji",
3011
+ "aria-label": `React ${emoji}`,
3012
+ onClick: () => void room.react(emoji),
3013
+ children: emoji
3014
+ },
3015
+ emoji
3016
+ )) }) }) : null
3017
+ ] }),
3018
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3019
+ ControlButton,
3020
+ {
3021
+ label: room.local?.handRaised === true ? "Lower hand (\u2318\u2325H)" : "Raise hand (\u2318\u2325H)",
3022
+ active: room.local?.handRaised === true,
3023
+ onClick: () => void room.toggleHand(),
3024
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(HandIcon, {})
3025
+ }
3026
+ ),
3027
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(ControlButton, { label: "Chat (\u2318\u2325C)", active: panel === "chat", onClick: () => toggle("chat"), children: [
3028
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ChatIcon, {}),
3029
+ unread > 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__dot", "aria-hidden": "true" }) : null
3030
+ ] }),
3031
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3032
+ ControlButton,
3033
+ {
3034
+ label: lobby.waiting.length > 0 ? `People \u2014 ${lobby.waiting.length} waiting` : `People \u2014 ${(snapshot?.participants ?? []).length} in the meeting`,
3035
+ active: panel === "people",
3036
+ onClick: () => toggle("people"),
3037
+ children: [
3038
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PeopleIcon, {}),
3039
+ lobby.waiting.length > 0 || controls.isHost && queue.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__dot", "aria-hidden": "true" }) : null
3040
+ ]
3041
+ }
3042
+ ),
3043
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3044
+ ActivitiesControl,
3045
+ {
3046
+ active: whiteboard ? "whiteboard" : panel === "polls" || panel === "qa" || panel === "breakouts" ? panel : null,
3047
+ onOpen: openActivity
3048
+ }
3049
+ ),
3050
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(AiControl, { active: panel === "ai", onToggle: () => toggle("ai") }),
3051
+ recording.canStart ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ControlButton, { label: "Start recording", onClick: () => void recording.start(), children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RecordIcon, {}) }) : null,
3052
+ recording.canStop ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ControlButton, { label: "Stop recording", active: true, onClick: () => void recording.stop(), children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RecordIcon, {}) }) : null,
3053
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "mx-meet__anchor", children: [
3054
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3055
+ ControlButton,
3056
+ {
3057
+ label: "More options",
3058
+ active: popover === "more" || panel === "settings",
3059
+ onClick: () => setPopover(popover === "more" ? "none" : "more"),
3060
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MoreIcon, {})
3061
+ }
3062
+ ),
3063
+ popover === "more" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Popover, { label: "More options", onClose: () => setPopover("none"), children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mx-meet__menu", role: "menu", children: [
3064
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3065
+ MenuItem,
3066
+ {
3067
+ onSelect: () => {
3068
+ setPopover("none");
3069
+ setPanel("settings");
3070
+ },
3071
+ children: [
3072
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SettingsIcon, { width: 16, height: 16 }),
3073
+ " Settings"
3074
+ ]
3075
+ }
3076
+ ),
3077
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3078
+ MenuItem,
3079
+ {
3080
+ onSelect: () => {
3081
+ setPopover("none");
3082
+ setShortcuts(true);
3083
+ },
3084
+ children: [
3085
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(KeyboardIcon, { width: 16, height: 16 }),
3086
+ " Keyboard shortcuts"
3087
+ ]
3088
+ }
3089
+ ),
3090
+ controls.isHost ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
3091
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mx-meet__menu-sep", role: "separator" }),
3092
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3093
+ MenuItem,
3094
+ {
3095
+ onSelect: () => {
3096
+ setPopover("none");
3097
+ void controls.run({ action: "mute_all" });
3098
+ },
3099
+ children: [
3100
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MicOffIcon, { width: 16, height: 16 }),
3101
+ " Mute everyone"
3102
+ ]
3103
+ }
3104
+ ),
3105
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3106
+ MenuItem,
3107
+ {
3108
+ onSelect: () => {
3109
+ setPopover("none");
3110
+ void controls.run({ action: "ask_all_unmute" });
3111
+ },
3112
+ children: [
3113
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MicIcon, { width: 16, height: 16 }),
3114
+ " Ask everyone to unmute"
3115
+ ]
3116
+ }
3117
+ ),
3118
+ queue.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3119
+ MenuItem,
3120
+ {
3121
+ onSelect: () => {
3122
+ setPopover("none");
3123
+ void controls.run({ action: "lower_all_hands" });
3124
+ },
3125
+ children: [
3126
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(HandIcon, { width: 16, height: 16 }),
3127
+ " Lower all hands (",
3128
+ queue.length,
3129
+ ")"
3130
+ ]
3131
+ }
3132
+ ) : null,
3133
+ hasSpotlight ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3134
+ MenuItem,
3135
+ {
3136
+ onSelect: () => {
3137
+ setPopover("none");
3138
+ void controls.run({ action: "spotlight", identity: null });
3139
+ },
3140
+ children: [
3141
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SpotlightIcon, { width: 16, height: 16 }),
3142
+ " Remove spotlight"
3143
+ ]
3144
+ }
3145
+ ) : null
3146
+ ] }) : null
3147
+ ] }) }) : null
3148
+ ] }),
3149
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3150
+ ControlButton,
3151
+ {
3152
+ label: "Leave",
3153
+ danger: true,
3154
+ onClick: () => {
3155
+ void room.leave();
3156
+ onLeave?.();
3157
+ },
3158
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PhoneOffIcon, {})
3159
+ }
3160
+ )
3161
+ ] })
3162
+ ] });
3163
+ }
3164
+ function useUnreadChat(open) {
3165
+ const snapshot = useMeetSnapshot();
3166
+ const chat = snapshot?.chat ?? [];
3167
+ const local = snapshot?.localIdentity ?? null;
3168
+ const [seen, setSeen] = (0, import_react9.useState)(0);
3169
+ (0, import_react9.useEffect)(() => {
3170
+ if (open) setSeen(chat.length);
3171
+ }, [open, chat.length]);
3172
+ const loaded = snapshot?.chatLoaded === true;
3173
+ const [baseline, setBaseline] = (0, import_react9.useState)(null);
3174
+ (0, import_react9.useEffect)(() => {
3175
+ if (loaded && baseline === null) setBaseline(chat.length);
3176
+ }, [loaded, baseline, chat.length]);
3177
+ if (open) return 0;
3178
+ const from = Math.max(seen, baseline ?? chat.length);
3179
+ return chat.slice(from).filter((message) => message.identity !== local).length;
3180
+ }
3181
+ function Popover({
3182
+ label,
3183
+ onClose,
3184
+ children,
3185
+ placement = "up"
3186
+ }) {
3187
+ const ref = (0, import_react9.useRef)(null);
3188
+ const [style, setStyle] = (0, import_react9.useState)({
3189
+ position: "fixed",
3190
+ top: 0,
3191
+ left: 0,
3192
+ visibility: "hidden"
3193
+ });
3194
+ (0, import_react9.useLayoutEffect)(() => {
3195
+ const element = ref.current;
3196
+ const anchor = element?.parentElement ?? null;
3197
+ if (element === null || anchor === null) return;
3198
+ const place = () => {
3199
+ const rect = anchor.getBoundingClientRect();
3200
+ const width = element.offsetWidth;
3201
+ const height = element.offsetHeight;
3202
+ const gutter = 8;
3203
+ const left = Math.min(
3204
+ Math.max(gutter, rect.left + rect.width / 2 - width / 2),
3205
+ window.innerWidth - width - gutter
3206
+ );
3207
+ const fitsBelow = rect.bottom + gutter + height <= window.innerHeight;
3208
+ const fitsAbove = rect.top - gutter - height >= 0;
3209
+ if (!fitsBelow && !fitsAbove) {
3210
+ const top = Math.max(gutter, window.innerHeight - height - gutter);
3211
+ setStyle({
3212
+ position: "fixed",
3213
+ left,
3214
+ top,
3215
+ maxHeight: window.innerHeight - top - gutter,
3216
+ overflowY: "auto"
3217
+ });
3218
+ return;
3219
+ }
3220
+ const down = placement === "down" ? fitsBelow || !fitsAbove : !fitsAbove && fitsBelow;
3221
+ setStyle(
3222
+ down ? { position: "fixed", left, top: rect.bottom + gutter } : { position: "fixed", left, bottom: window.innerHeight - rect.top + gutter }
3223
+ );
3224
+ };
3225
+ place();
3226
+ const frame = requestAnimationFrame(place);
3227
+ const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(() => place());
3228
+ observer?.observe(document.body);
3229
+ observer?.observe(element);
3230
+ window.addEventListener("resize", place);
3231
+ window.addEventListener("scroll", place, true);
3232
+ return () => {
3233
+ cancelAnimationFrame(frame);
3234
+ observer?.disconnect();
3235
+ window.removeEventListener("resize", place);
3236
+ window.removeEventListener("scroll", place, true);
3237
+ };
3238
+ }, [placement]);
3239
+ (0, import_react9.useEffect)(() => {
3240
+ const onKey = (event) => {
3241
+ if (event.key === "Escape") onClose();
3242
+ };
3243
+ const onPointer = (event) => {
3244
+ const anchor = ref.current?.parentElement ?? null;
3245
+ if (anchor !== null && !anchor.contains(event.target)) onClose();
3246
+ };
3247
+ window.addEventListener("keydown", onKey);
3248
+ window.addEventListener("pointerdown", onPointer);
3249
+ return () => {
3250
+ window.removeEventListener("keydown", onKey);
3251
+ window.removeEventListener("pointerdown", onPointer);
3252
+ };
3253
+ }, [onClose]);
3254
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { ref, className: "mx-meet__popover", style, role: "dialog", "aria-label": label, children });
3255
+ }
3256
+ function MenuItem({
3257
+ onSelect,
3258
+ danger = false,
3259
+ children
3260
+ }) {
3261
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3262
+ "button",
3263
+ {
3264
+ type: "button",
3265
+ role: "menuitem",
3266
+ className: danger ? "mx-meet__menu-item mx-meet__menu-item--danger" : "mx-meet__menu-item",
3267
+ onClick: onSelect,
3268
+ children
3269
+ }
3270
+ );
3271
+ }
3272
+ function Toast({ text, failed, onDone }) {
3273
+ (0, import_react9.useEffect)(() => {
3274
+ const timer = setTimeout(onDone, failed ? 7e3 : 4e3);
3275
+ return () => clearTimeout(timer);
3276
+ }, [text, failed, onDone]);
3277
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: failed ? "mx-meet__toast mx-meet__toast--failed" : "mx-meet__toast", role: "status", children: [
3278
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: text }),
3279
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "mx-meet__toast-close", "aria-label": "Dismiss", onClick: onDone, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CloseIcon, { width: 14, height: 14 }) })
3280
+ ] });
3281
+ }
3282
+ function MeetingOverlays() {
3283
+ const reactions = useReactions();
3284
+ const { notice, dismiss } = useModerationNotice();
3285
+ const room = useRoom();
3286
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
3287
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ReactionOverlay, { reactions }),
3288
+ notice !== null && notice.kind !== "removed" ? (
3289
+ // A host's request ("the host is asking you to unmute"), not an error:
3290
+ // an assertive status, so it is announced at once without being filed
3291
+ // as a failure (design-system error-box census).
3292
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mx-meet__notice-bar", role: "status", "aria-live": "assertive", children: [
3293
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: notice.text }),
3294
+ notice.kind === "ask-unmute" && room.local?.micEnabled !== true ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3295
+ "button",
3296
+ {
3297
+ type: "button",
3298
+ className: "mx-meet__primary mx-meet__primary--small",
3299
+ onClick: () => {
3300
+ dismiss();
3301
+ void room.toggleMicrophone();
3302
+ },
3303
+ children: "Unmute"
3304
+ }
3305
+ ) : null,
3306
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "mx-meet__toast-close", "aria-label": "Dismiss", onClick: dismiss, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CloseIcon, { width: 14, height: 14 }) })
3307
+ ] })
3308
+ ) : null
3309
+ ] });
3310
+ }
3311
+ function ShortcutsSheet({ onClose }) {
3312
+ (0, import_react9.useEffect)(() => {
3313
+ const onKey = (event) => {
3314
+ if (event.key === "Escape") onClose();
3315
+ };
3316
+ window.addEventListener("keydown", onKey);
3317
+ return () => window.removeEventListener("keydown", onKey);
3318
+ }, [onClose]);
3319
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mx-meet__sheet-backdrop", onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3320
+ "div",
3321
+ {
3322
+ className: "mx-meet__sheet",
3323
+ role: "dialog",
3324
+ "aria-label": "Keyboard shortcuts",
3325
+ onClick: (event) => event.stopPropagation(),
3326
+ children: [
3327
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("header", { className: "mx-meet__panel-head", children: [
3328
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h2", { className: "mx-meet__panel-title", children: "Keyboard shortcuts" }),
3329
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ControlButton, { label: "Close", onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CloseIcon, { width: 16, height: 16 }) })
3330
+ ] }),
3331
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("dl", { className: "mx-meet__shortcuts", children: MEET_SHORTCUTS.map((shortcut) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mx-meet__shortcut", children: [
3332
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("dt", { children: shortcut.label }),
3333
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("dd", { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("kbd", { className: "mx-meet__kbd", children: shortcut.keys }) })
3334
+ ] }, shortcut.id)) })
3335
+ ]
3336
+ }
3337
+ ) });
3338
+ }
3339
+ function AiControl({ active, onToggle }) {
3340
+ const snapshot = useMeetSnapshot();
3341
+ const meeting = snapshot?.meeting ?? null;
3342
+ if (meeting === null || !meeting.aiEnabled) return null;
3343
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ControlButton, { label: "Meeting assistant", active, onClick: onToggle, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SparklesIcon, {}) });
3344
+ }
3345
+ function PanelShell({
3346
+ title,
3347
+ onClose,
3348
+ children
3349
+ }) {
3350
+ const ref = (0, import_react9.useRef)(null);
3351
+ (0, import_react9.useLayoutEffect)(() => {
3352
+ const panel = ref.current;
3353
+ const root = panel?.closest(".mx-meet");
3354
+ const header = root?.querySelector(":scope > .mx-meet__header") ?? null;
3355
+ if (!panel || !header) return void 0;
3356
+ const place = () => panel.style.setProperty("--mx-meet-panel-top", `${header.offsetHeight}px`);
3357
+ place();
3358
+ if (typeof ResizeObserver === "undefined") return void 0;
3359
+ const observer = new ResizeObserver(place);
3360
+ observer.observe(header);
3361
+ return () => observer.disconnect();
3362
+ }, []);
3363
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("aside", { ref, className: "mx-meet__panel", "aria-label": title, children: [
3364
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("header", { className: "mx-meet__panel-head", children: [
3365
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h2", { className: "mx-meet__panel-title", children: title }),
3366
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ControlButton, { label: "Close panel", onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CloseIcon, { width: 16, height: 16 }) })
3367
+ ] }),
3368
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mx-meet__panel-body", children })
3369
+ ] });
3370
+ }
3371
+ function ChatPanel({
3372
+ onClose,
3373
+ to = null,
3374
+ onTo
3375
+ }) {
3376
+ const snapshot = useMeetSnapshot();
3377
+ const room = useRoom();
3378
+ const [draft, setDraft] = (0, import_react9.useState)("");
3379
+ const [recipient, setRecipient] = (0, import_react9.useState)(to);
3380
+ const listRef = (0, import_react9.useRef)(null);
3381
+ const messages = snapshot?.chat ?? [];
3382
+ const local = snapshot?.localIdentity ?? null;
3383
+ const people = (snapshot?.participants ?? []).filter(
3384
+ (participant) => !participant.isLocal && !participant.isAgent
3385
+ );
3386
+ const recipientName = recipient === null ? null : people.find((p) => p.identity === recipient)?.displayName ?? null;
3387
+ (0, import_react9.useEffect)(() => setRecipient(to), [to]);
3388
+ (0, import_react9.useEffect)(() => {
3389
+ const list = listRef.current;
3390
+ if (list !== null) list.scrollTop = list.scrollHeight;
3391
+ }, [messages.length]);
3392
+ const choose = (next) => {
3393
+ setRecipient(next);
3394
+ onTo?.(next);
3395
+ };
3396
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(PanelShell, { title: "Chat", onClose, children: [
3397
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("ul", { className: "mx-meet__chat", ref: listRef, children: [
3398
+ snapshot?.chatHistoryError != null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("li", { className: "mx-meet__panel-note", children: snapshot.chatHistoryError }) : null,
3399
+ snapshot?.chatLoaded !== true && messages.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("li", { className: "mx-meet__panel-note", children: "Reading earlier messages\u2026" }) : messages.length === 0 && snapshot?.chatHistoryError == null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("li", { className: "mx-meet__panel-note", children: "No messages yet. Messages to everyone stay with the meeting, so people who join later see them." }) : null,
3400
+ messages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ChatLine, { message, local, people }, message.id))
3401
+ ] }),
3402
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3403
+ "form",
3404
+ {
3405
+ className: "mx-meet__composer mx-meet__composer--chat",
3406
+ onSubmit: (event) => {
3407
+ event.preventDefault();
3408
+ const text = draft;
3409
+ if (text.trim().length === 0) return;
3410
+ setDraft("");
3411
+ if (recipient === null) void room.sendChat(text).catch(() => void 0);
3412
+ else void room.sendPrivateChat(recipient, text);
3413
+ },
3414
+ children: [
3415
+ people.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3416
+ "select",
3417
+ {
3418
+ className: "mx-meet__select mx-meet__select--compact",
3419
+ "aria-label": "Send to",
3420
+ value: recipient ?? "",
3421
+ onChange: (event) => choose(event.target.value === "" ? null : event.target.value),
3422
+ children: [
3423
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: "", children: "Everyone" }),
3424
+ people.map((participant) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("option", { value: participant.identity, children: [
3425
+ participant.displayName,
3426
+ " (private)"
3427
+ ] }, participant.identity))
3428
+ ]
3429
+ }
3430
+ ) : null,
3431
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mx-meet__composer-row", children: [
3432
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3433
+ "input",
3434
+ {
3435
+ className: "mx-meet__input",
3436
+ value: draft,
3437
+ onChange: (event) => setDraft(event.target.value),
3438
+ placeholder: recipientName === null ? "Message everyone" : `Message ${recipientName} privately`,
3439
+ "aria-label": recipientName === null ? "Message everyone" : `Message ${recipientName} privately`,
3440
+ maxLength: 4e3
3441
+ }
3442
+ ),
3443
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "submit", className: "mx-meet__send", disabled: draft.trim().length === 0, children: "Send" })
3444
+ ] }),
3445
+ recipientName !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { className: "mx-meet__panel-note", children: [
3446
+ "Only ",
3447
+ recipientName,
3448
+ " sees this. Private messages are not saved."
3449
+ ] }) : null
3450
+ ]
3451
+ }
3452
+ )
3453
+ ] });
3454
+ }
3455
+ function ChatLine({
3456
+ message,
3457
+ local,
3458
+ people
3459
+ }) {
3460
+ const mine = message.identity === local;
3461
+ const privateTo = message.to ?? null;
3462
+ const counterpart = privateTo === null ? null : mine ? people.find((p) => p.identity === privateTo)?.displayName ?? "them" : "you";
3463
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3464
+ "li",
3465
+ {
3466
+ className: [
3467
+ "mx-meet__chat-line",
3468
+ privateTo !== null ? "mx-meet__chat-line--private" : "",
3469
+ message.failed !== void 0 ? "mx-meet__chat-line--failed" : ""
3470
+ ].filter(Boolean).join(" "),
3471
+ children: [
3472
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "mx-meet__chat-meta", children: [
3473
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("strong", { children: mine ? "You" : message.displayName }),
3474
+ counterpart !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "mx-meet__chat-private", children: [
3475
+ "to ",
3476
+ counterpart,
3477
+ " \xB7 private"
3478
+ ] }) : null,
3479
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("time", { dateTime: message.at, children: clockOf(message.at) })
3480
+ ] }),
3481
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__chat-text", children: message.text }),
3482
+ message.failed !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__chat-failed", children: message.failed }) : privateTo === null && message.saved === false ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__chat-pending", children: message.id.startsWith("pending-") ? "Sending\u2026" : "Not saved \u2014 people who join later will not see this" }) : null
3483
+ ]
3484
+ }
3485
+ );
3486
+ }
3487
+ function PeoplePanel({
3488
+ onClose,
3489
+ onMessage
3490
+ }) {
3491
+ const snapshot = useMeetSnapshot();
3492
+ const controls = useHostControls();
3493
+ const queue = useHandQueue();
3494
+ const [query, setQuery] = (0, import_react9.useState)("");
3495
+ const [menuFor, setMenuFor] = (0, import_react9.useState)(null);
3496
+ const myRole = useRole();
3497
+ const local = snapshot?.localIdentity ?? null;
3498
+ const everyone = snapshot?.participants ?? [];
3499
+ const needle = query.trim().toLowerCase();
3500
+ const shown = needle.length === 0 ? everyone : everyone.filter((participant) => participant.displayName.toLowerCase().includes(needle));
3501
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(PanelShell, { title: `People (${everyone.length})`, onClose, children: [
3502
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(LobbyPanel, {}),
3503
+ queue.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("section", { className: "mx-meet__section", "aria-label": "Raised hands", children: [
3504
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("header", { className: "mx-meet__section-head", children: [
3505
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("h3", { className: "mx-meet__section-title", children: [
3506
+ "Raised hands \xB7 ",
3507
+ queue.length
3508
+ ] }),
3509
+ controls.isHost ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3510
+ "button",
3511
+ {
3512
+ type: "button",
3513
+ className: "mx-meet__link",
3514
+ onClick: () => void controls.run({ action: "lower_all_hands" }),
3515
+ children: "Lower all"
3516
+ }
3517
+ ) : null
3518
+ ] }),
3519
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("ol", { className: "mx-meet__queue", children: queue.map((participant) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("li", { className: "mx-meet__person", children: [
3520
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Avatar, { name: participant.displayName, url: participant.avatarUrl, size: 28 }),
3521
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "mx-meet__person-name", children: [
3522
+ participant.displayName,
3523
+ participant.identity === local ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__you", children: " (you)" }) : null
3524
+ ] }),
3525
+ controls.isHost || participant.identity === local ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3526
+ "button",
3527
+ {
3528
+ type: "button",
3529
+ className: "mx-meet__link",
3530
+ onClick: () => void controls.run({ action: "lower_hand", identity: participant.identity }),
3531
+ children: "Lower"
3532
+ }
3533
+ ) : null
3534
+ ] }, participant.identity)) })
3535
+ ] }) : null,
3536
+ controls.isHost && everyone.length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mx-meet__bulk", children: [
3537
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("button", { type: "button", className: "mx-meet__chip", onClick: () => void controls.run({ action: "mute_all" }), children: [
3538
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MicOffIcon, { width: 14, height: 14 }),
3539
+ " Mute all"
3540
+ ] }),
3541
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3542
+ "button",
3543
+ {
3544
+ type: "button",
3545
+ className: "mx-meet__chip",
3546
+ onClick: () => void controls.run({ action: "ask_all_unmute" }),
3547
+ children: [
3548
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MicIcon, { width: 14, height: 14 }),
3549
+ " Ask all to unmute"
3550
+ ]
3551
+ }
3552
+ )
3553
+ ] }) : null,
3554
+ everyone.length > 4 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "mx-meet__search", children: [
3555
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SearchIcon, { width: 16, height: 16 }),
3556
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3557
+ "input",
3558
+ {
3559
+ className: "mx-meet__input mx-meet__input--bare",
3560
+ value: query,
3561
+ onChange: (event) => setQuery(event.target.value),
3562
+ placeholder: "Search people",
3563
+ "aria-label": "Search people"
3564
+ }
3565
+ )
3566
+ ] }) : null,
3567
+ controls.notice !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: controls.notice.failed ? "mx-meet__error" : "mx-meet__notice", role: "status", children: controls.notice.text }) : null,
3568
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("ul", { className: "mx-meet__people", children: [
3569
+ shown.map((participant) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("li", { className: "mx-meet__person", children: [
3570
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Avatar, { name: participant.displayName, url: participant.avatarUrl, size: 32 }),
3571
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "mx-meet__person-name", children: [
3572
+ participant.displayName,
3573
+ participant.identity === local ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__you", children: " (you)" }) : null,
3574
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RoleBadge, { role: participant.identity === local ? myRole : participant.role }),
3575
+ isNoteTaker(participant) ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__badge", children: "Meeting assistant" }) : null,
3576
+ snapshot?.spotlight === participant.identity ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__badge", children: "Spotlight" }) : null
3577
+ ] }),
3578
+ participant.handRaised ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(HandIcon, { width: 14, height: 14 }) : null,
3579
+ !participant.micEnabled && !isNoteTaker(participant) ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MicOffIcon, { width: 14, height: 14 }) : null,
3580
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(QualityBars, { quality: participant.quality }),
3581
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "mx-meet__anchor", children: [
3582
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3583
+ ControlButton,
3584
+ {
3585
+ label: `More for ${participant.displayName}`,
3586
+ active: menuFor === participant.identity,
3587
+ onClick: () => setMenuFor(menuFor === participant.identity ? null : participant.identity),
3588
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MoreIcon, { width: 16, height: 16 })
3589
+ }
3590
+ ),
3591
+ menuFor === participant.identity ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3592
+ PersonMenu,
3593
+ {
3594
+ participant,
3595
+ placement: "down",
3596
+ onClose: () => setMenuFor(null),
3597
+ ...onMessage !== void 0 ? { onMessage } : {}
3598
+ }
3599
+ ) : null
3600
+ ] })
3601
+ ] }, participant.identity)),
3602
+ shown.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("li", { className: "mx-meet__panel-note", children: [
3603
+ "Nobody matches \u201C",
3604
+ query,
3605
+ "\u201D."
3606
+ ] }) : null
3607
+ ] }),
3608
+ snapshot?.meeting !== null && snapshot?.meeting !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(GuestRecordAccessControl, { meeting: snapshot.meeting }) : null
3609
+ ] });
3610
+ }
3611
+ function PersonMenu({
3612
+ participant,
3613
+ onClose,
3614
+ onMessage,
3615
+ placement = "down"
3616
+ }) {
3617
+ const host = useMeetHost();
3618
+ const snapshot = useMeetSnapshot();
3619
+ const controls = useHostControls();
3620
+ const [mode, setMode] = (0, import_react9.useState)("menu");
3621
+ const [text, setText] = (0, import_react9.useState)("");
3622
+ const self = participant.identity === snapshot?.localIdentity;
3623
+ const moderate = controls.canModerate(participant);
3624
+ const pinned = snapshot?.pinned === participant.identity;
3625
+ const spotlit = snapshot?.spotlight === participant.identity;
3626
+ const hasAccount = participant.userId !== null;
3627
+ const [refused, setRefused] = (0, import_react9.useState)(false);
3628
+ const act = (request) => {
3629
+ setRefused(false);
3630
+ void controls.run(request).then((result) => {
3631
+ if (result !== null) onClose();
3632
+ else setRefused(true);
3633
+ });
3634
+ };
3635
+ const refusal = refused && controls.notice?.failed === true ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system.ErrorBox, { as: "p", className: "mx-meet__error", message: controls.notice.text, operation: "Change this participant", origin: "meet/components", children: controls.notice.text }) : null;
3636
+ if (mode === "rename" || mode === "report") {
3637
+ const renaming = mode === "rename";
3638
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Popover, { label: renaming ? "Rename" : "Report", onClose, placement, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3639
+ "form",
3640
+ {
3641
+ className: "mx-meet__menu-form",
3642
+ onSubmit: (event) => {
3643
+ event.preventDefault();
3644
+ if (text.trim().length === 0) return;
3645
+ act(
3646
+ renaming ? { action: "rename", identity: participant.identity, name: text.trim() } : { action: "report", identity: participant.identity, reason: text.trim() }
3647
+ );
3648
+ },
3649
+ children: [
3650
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "mx-meet__field", children: [
3651
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__field-label", children: renaming ? "Name shown in this meeting" : `What did ${participant.displayName} do?` }),
3652
+ renaming ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3653
+ "input",
3654
+ {
3655
+ className: "mx-meet__input",
3656
+ autoFocus: true,
3657
+ maxLength: 80,
3658
+ value: text,
3659
+ onChange: (event) => setText(event.target.value)
3660
+ }
3661
+ ) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3662
+ "textarea",
3663
+ {
3664
+ className: "mx-meet__input",
3665
+ autoFocus: true,
3666
+ rows: 3,
3667
+ maxLength: 1e3,
3668
+ value: text,
3669
+ onChange: (event) => setText(event.target.value)
3670
+ }
3671
+ )
3672
+ ] }),
3673
+ refusal,
3674
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mx-meet__menu-actions", children: [
3675
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "mx-meet__link", onClick: () => setMode("menu"), children: "Back" }),
3676
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "submit", className: "mx-meet__send", disabled: text.trim().length === 0 || controls.busy, children: renaming ? "Rename" : "Report" })
3677
+ ] })
3678
+ ]
3679
+ }
3680
+ ) });
3681
+ }
3682
+ if (mode === "remove" || mode === "transfer") {
3683
+ const removing = mode === "remove";
3684
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Popover, { label: removing ? "Remove" : "Make host", onClose, placement, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mx-meet__menu-form", children: [
3685
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mx-meet__confirm-text", children: removing ? `Remove ${participant.displayName}? They leave immediately and cannot rejoin this meeting.` : `Make ${participant.displayName} the host? They get every host control and own this meeting. You stay on as a co-host.` }),
3686
+ refusal,
3687
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mx-meet__menu-actions", children: [
3688
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "mx-meet__link", onClick: () => setMode("menu"), children: "Cancel" }),
3689
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3690
+ "button",
3691
+ {
3692
+ type: "button",
3693
+ className: removing ? "mx-meet__send mx-meet__send--danger" : "mx-meet__send",
3694
+ disabled: controls.busy,
3695
+ onClick: () => act({ action: removing ? "remove" : "transfer_host", identity: participant.identity }),
3696
+ children: removing ? "Remove" : "Make host"
3697
+ }
3698
+ )
3699
+ ] })
3700
+ ] }) });
3701
+ }
3702
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Popover, { label: `Options for ${participant.displayName}`, onClose, placement, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mx-meet__menu", role: "menu", children: [
3703
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3704
+ MenuItem,
3705
+ {
3706
+ onSelect: () => {
3707
+ host?.engine.pin(pinned ? null : participant.identity);
3708
+ onClose();
3709
+ },
3710
+ children: [
3711
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PinIcon, { width: 16, height: 16 }),
3712
+ " ",
3713
+ pinned ? "Unpin" : "Pin for me"
3714
+ ]
3715
+ }
3716
+ ),
3717
+ !self && !participant.isAgent && (onMessage !== void 0 || host !== null) ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3718
+ MenuItem,
3719
+ {
3720
+ onSelect: () => {
3721
+ if (onMessage !== void 0) onMessage(participant.identity);
3722
+ else
3723
+ host?.store.patch({
3724
+ privateChatRequest: { to: participant.identity, at: Date.now() }
3725
+ });
3726
+ onClose();
3727
+ },
3728
+ children: [
3729
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ChatIcon, { width: 16, height: 16 }),
3730
+ " Message privately"
3731
+ ]
3732
+ }
3733
+ ) : null,
3734
+ controls.isHost ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(MenuItem, { onSelect: () => act({ action: "spotlight", identity: spotlit ? null : participant.identity }), children: [
3735
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SpotlightIcon, { width: 16, height: 16 }),
3736
+ " ",
3737
+ spotlit ? "Remove spotlight" : "Spotlight for everyone"
3738
+ ] }) : null,
3739
+ moderate ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
3740
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mx-meet__menu-sep", role: "separator" }),
3741
+ participant.micEnabled ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(MenuItem, { onSelect: () => act({ action: "mute", identity: participant.identity }), children: [
3742
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MicOffIcon, { width: 16, height: 16 }),
3743
+ " Mute"
3744
+ ] }) : !participant.isAgent ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(MenuItem, { onSelect: () => act({ action: "ask_unmute", identity: participant.identity }), children: [
3745
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MicIcon, { width: 16, height: 16 }),
3746
+ " Ask to unmute"
3747
+ ] }) : null,
3748
+ participant.cameraEnabled ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(MenuItem, { onSelect: () => act({ action: "stop_video", identity: participant.identity }), children: [
3749
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CameraOffIcon, { width: 16, height: 16 }),
3750
+ " Stop video"
3751
+ ] }) : null,
3752
+ participant.screenSharing ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(MenuItem, { onSelect: () => act({ action: "stop_screen", identity: participant.identity }), children: [
3753
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ScreenShareIcon, { width: 16, height: 16 }),
3754
+ " Stop screen share"
3755
+ ] }) : null,
3756
+ participant.handRaised ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(MenuItem, { onSelect: () => act({ action: "lower_hand", identity: participant.identity }), children: [
3757
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(HandIcon, { width: 16, height: 16 }),
3758
+ " Lower hand"
3759
+ ] }) : null,
3760
+ hasAccount && !participant.isAgent && participant.role !== "cohost" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(MenuItem, { onSelect: () => act({ action: "make_cohost", identity: participant.identity }), children: [
3761
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PeopleIcon, { width: 16, height: 16 }),
3762
+ " Make co-host"
3763
+ ] }) : null,
3764
+ !participant.isAgent ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3765
+ MenuItem,
3766
+ {
3767
+ onSelect: () => {
3768
+ setText(participant.displayName);
3769
+ setMode("rename");
3770
+ },
3771
+ children: [
3772
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PencilIcon, { width: 16, height: 16 }),
3773
+ " Rename"
3774
+ ]
3775
+ }
3776
+ ) : null
3777
+ ] }) : null,
3778
+ controls.isOwner && participant.role === "cohost" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(MenuItem, { onSelect: () => act({ action: "revoke_cohost", identity: participant.identity }), children: [
3779
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PeopleIcon, { width: 16, height: 16 }),
3780
+ " Remove co-host"
3781
+ ] }) : null,
3782
+ controls.isOwner && !self && hasAccount && !participant.isAgent ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(MenuItem, { onSelect: () => setMode("transfer"), children: [
3783
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SpotlightIcon, { width: 16, height: 16 }),
3784
+ " Make host"
3785
+ ] }) : null,
3786
+ self ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3787
+ MenuItem,
3788
+ {
3789
+ onSelect: () => {
3790
+ setText(participant.displayName);
3791
+ setMode("rename");
3792
+ },
3793
+ children: [
3794
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PencilIcon, { width: 16, height: 16 }),
3795
+ " Rename yourself"
3796
+ ]
3797
+ }
3798
+ ) : null,
3799
+ !self && !participant.isAgent ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3800
+ MenuItem,
3801
+ {
3802
+ onSelect: () => {
3803
+ setText("");
3804
+ setMode("report");
3805
+ },
3806
+ children: [
3807
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(FlagIcon, { width: 16, height: 16 }),
3808
+ " Report"
3809
+ ]
3810
+ }
3811
+ ) : null,
3812
+ moderate && !participant.isAgent ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(MenuItem, { danger: true, onSelect: () => setMode("remove"), children: [
3813
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CloseIcon, { width: 16, height: 16 }),
3814
+ " Remove from meeting"
3815
+ ] }) : null
3816
+ ] }) });
3817
+ }
3818
+ function SettingsPanel({ onClose }) {
3819
+ const host = useRequiredMeetHost();
3820
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(PanelShell, { title: "Settings", onClose, children: [
3821
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3822
+ DeviceSettings,
3823
+ {
3824
+ onPick: (kind, deviceId) => {
3825
+ host.devices.select(kind, deviceId);
3826
+ void host.engine.switchDevice(kind, deviceId);
3827
+ }
3828
+ }
3829
+ ),
3830
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(EffectsSettings, {}),
3831
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CaptionLanguageSettings, {})
3832
+ ] });
3833
+ }
3834
+ function DeviceSettings({
3835
+ onPick
3836
+ }) {
3837
+ const host = useRequiredMeetHost();
3838
+ const devices = useDeviceSnapshot();
3839
+ const microphone = devices?.selected.audioinput ?? host.devices.resolve("audioinput");
3840
+ const speaker = devices?.selected.audiooutput ?? host.devices.resolve("audiooutput");
3841
+ const stream = useMicrophoneStream(microphone);
3842
+ const level = useAudioLevel(stream);
3843
+ const [testing, setTesting] = (0, import_react9.useState)(false);
3844
+ (0, import_react9.useEffect)(() => {
3845
+ void host.devices.refresh();
3846
+ }, [host]);
3847
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("section", { className: "mx-meet__section", "aria-label": "Devices", children: [
3848
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(DevicePicker, { kind: "audioinput", label: "Microphone", onPick }),
3849
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mx-meet__meter", role: "meter", "aria-label": "Microphone level", "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": Math.round(level * 100), children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: { width: `${Math.min(100, Math.round(level * 160))}%` } }) }),
3850
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(DevicePicker, { kind: "audiooutput", label: "Speaker", onPick }),
3851
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3852
+ "button",
3853
+ {
3854
+ type: "button",
3855
+ className: "mx-meet__chip",
3856
+ disabled: testing,
3857
+ onClick: () => {
3858
+ setTesting(true);
3859
+ void playTestSound(speaker).finally(() => setTesting(false));
3860
+ },
3861
+ children: [
3862
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SpeakerIcon, { width: 14, height: 14 }),
3863
+ " ",
3864
+ testing ? "Playing\u2026" : "Test speaker"
3865
+ ]
3866
+ }
3867
+ ),
3868
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(DevicePicker, { kind: "videoinput", label: "Camera", onPick })
3869
+ ] });
3870
+ }
3871
+ function EffectsSettings({ preview }) {
3872
+ const effects = useMediaEffects();
3873
+ const [error, setError] = (0, import_react9.useState)(null);
3874
+ const fileRef = (0, import_react9.useRef)(null);
3875
+ const supported = effects.backgroundsSupported || preview != null || videoEffectsSupported();
3876
+ const apply = (next) => {
3877
+ setError(null);
3878
+ void effects.set(next);
3879
+ if (preview != null) {
3880
+ const merged = { ...effects.effects, ...next };
3881
+ void preview.setEffect(toVideoEffect(merged)).catch(
3882
+ () => setError("This browser could not run the background effect.")
3883
+ );
3884
+ }
3885
+ };
3886
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("section", { className: "mx-meet__section", "aria-label": "Effects", children: [
3887
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mx-meet__check", children: [
3888
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3889
+ import_design_system.Checkbox,
3890
+ {
3891
+ id: "mx-meet-noise",
3892
+ checked: effects.effects.noiseSuppression,
3893
+ onCheckedChange: (checked) => apply({ noiseSuppression: checked === true })
3894
+ }
3895
+ ),
3896
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mx-meet__check-text", children: [
3897
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system.Label, { htmlFor: "mx-meet-noise", children: "Noise suppression" }),
3898
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__hint", children: "Filters keyboards, fans and background chatter." })
3899
+ ] })
3900
+ ] }),
3901
+ supported ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mx-meet__field", children: [
3902
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system.Label, { children: "Background" }),
3903
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3904
+ import_design_system.SegmentedControl,
3905
+ {
3906
+ fullWidth: true,
3907
+ value: effects.effects.background,
3908
+ onValueChange: (kind) => {
3909
+ if (kind === "image") {
3910
+ fileRef.current?.click();
3911
+ return;
3912
+ }
3913
+ apply({ background: kind });
3914
+ },
3915
+ data: [
3916
+ { value: "none", label: "None" },
3917
+ { value: "blur", label: "Blur" },
3918
+ { value: "image", label: "Picture\u2026" }
3919
+ ]
3920
+ }
3921
+ ),
3922
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3923
+ "input",
3924
+ {
3925
+ ref: fileRef,
3926
+ type: "file",
3927
+ accept: "image/*",
3928
+ hidden: true,
3929
+ onChange: (event) => {
3930
+ const file = event.target.files?.[0];
3931
+ event.target.value = "";
3932
+ if (file === void 0) return;
3933
+ void shrinkImage(file).then((url) => apply({ background: "image", backgroundImage: url })).catch(() => setError("That picture could not be read. Try a JPEG or PNG."));
3934
+ }
3935
+ }
3936
+ ),
3937
+ error !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mx-meet__error", children: error }) : null
3938
+ ] }) : null
3939
+ ] });
3940
+ }
3941
+ function toVideoEffect(effects) {
3942
+ if (effects.background === "blur") return { kind: "blur" };
3943
+ if (effects.background === "image" && effects.backgroundImage !== null) {
3944
+ return { kind: "image", imageUrl: effects.backgroundImage };
3945
+ }
3946
+ return { kind: "none" };
3947
+ }
3948
+ async function shrinkImage(file) {
3949
+ const url = URL.createObjectURL(file);
3950
+ try {
3951
+ const image = new Image();
3952
+ image.src = url;
3953
+ await image.decode();
3954
+ const scale = Math.min(1, 1280 / image.naturalWidth);
3955
+ const canvas = document.createElement("canvas");
3956
+ canvas.width = Math.round(image.naturalWidth * scale);
3957
+ canvas.height = Math.round(image.naturalHeight * scale);
3958
+ const context = canvas.getContext("2d");
3959
+ if (context === null) throw new Error("no 2d context");
3960
+ context.drawImage(image, 0, 0, canvas.width, canvas.height);
3961
+ return canvas.toDataURL("image/jpeg", 0.85);
3962
+ } finally {
3963
+ URL.revokeObjectURL(url);
3964
+ }
3965
+ }
3966
+ async function playTestSound(sinkId) {
3967
+ const Context = globalThis.AudioContext;
3968
+ if (Context === void 0) return;
3969
+ const context = new Context();
3970
+ const destination = context.createMediaStreamDestination();
3971
+ const gain = context.createGain();
3972
+ gain.gain.value = 0.2;
3973
+ gain.connect(destination);
3974
+ for (const [frequency, start] of [
3975
+ [660, 0],
3976
+ [880, 0.25]
3977
+ ]) {
3978
+ const oscillator = context.createOscillator();
3979
+ oscillator.frequency.value = frequency;
3980
+ oscillator.connect(gain);
3981
+ oscillator.start(context.currentTime + start);
3982
+ oscillator.stop(context.currentTime + start + 0.22);
3983
+ }
3984
+ const audio = new Audio();
3985
+ audio.srcObject = destination.stream;
3986
+ const sinkable = audio;
3987
+ if (sinkId !== null && sinkId !== "default" && sinkable.setSinkId !== void 0) {
3988
+ await sinkable.setSinkId(sinkId).catch(() => void 0);
3989
+ }
3990
+ await audio.play().catch(() => void 0);
3991
+ await new Promise((resolve) => setTimeout(resolve, 700));
3992
+ audio.pause();
3993
+ await context.close();
3994
+ }
3995
+ function useMicrophoneStream(deviceId) {
3996
+ const [stream, setStream] = (0, import_react9.useState)(null);
3997
+ (0, import_react9.useEffect)(() => {
3998
+ if (deviceId === "off") return void 0;
3999
+ let live = true;
4000
+ let opened = null;
4001
+ const media = globalThis.navigator?.mediaDevices;
4002
+ if (media === void 0) return void 0;
4003
+ void media.getUserMedia({ audio: deviceId === null ? true : { deviceId: { exact: deviceId } } }).then((next) => {
4004
+ if (!live) {
4005
+ next.getTracks().forEach((track) => track.stop());
4006
+ return;
4007
+ }
4008
+ opened = next;
4009
+ setStream(next);
4010
+ }).catch(() => setStream(null));
4011
+ return () => {
4012
+ live = false;
4013
+ opened?.getTracks().forEach((track) => track.stop());
4014
+ setStream(null);
4015
+ };
4016
+ }, [deviceId]);
4017
+ return stream;
4018
+ }
4019
+ function LobbyPanel() {
4020
+ const lobby = useLobby();
4021
+ if (!lobby.canAdmit || lobby.waiting.length === 0) return null;
4022
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("section", { className: "mx-meet__lobby", "aria-label": "Waiting room", children: [
4023
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("header", { className: "mx-meet__lobby-head", children: [
4024
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h3", { className: "mx-meet__lobby-title", children: lobby.waiting.length === 1 ? "1 person is waiting" : `${lobby.waiting.length} people are waiting` }),
4025
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "mx-meet__link", onClick: () => void lobby.admitAll(), children: "Admit all" })
4026
+ ] }),
4027
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("ul", { className: "mx-meet__lobby-list", children: lobby.waiting.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
4028
+ "li",
4029
+ {
4030
+ className: "mx-meet__lobby-row",
4031
+ "data-meet-lobby-entry": "",
4032
+ "data-meet-participant-name": entry.displayName,
4033
+ children: [
4034
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Avatar, { name: entry.displayName, url: entry.avatarUrl, size: 32 }),
4035
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "mx-meet__person-name", children: [
4036
+ entry.displayName,
4037
+ entry.userId === null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__badge", children: "Guest" }) : null
4038
+ ] }),
4039
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ControlButton, { label: `Admit ${entry.displayName}`, onClick: () => void lobby.admit(entry), children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CheckIcon, { width: 16, height: 16 }) }),
4040
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4041
+ ControlButton,
4042
+ {
4043
+ label: `Deny ${entry.displayName}`,
4044
+ danger: true,
4045
+ onClick: () => void lobby.deny(entry),
4046
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CloseIcon, { width: 16, height: 16 })
4047
+ }
4048
+ )
4049
+ ]
4050
+ },
4051
+ entry.identity
4052
+ )) })
4053
+ ] });
4054
+ }
4055
+ function NoteTakerRow() {
4056
+ const ai = useMeetAi();
4057
+ const status = ai.noteTaker;
4058
+ const duplicateError = status.refusal !== null && ai.error !== null && status.refusal.reason === ai.error.message && status.refusal.remedy === ai.error.remedy;
4059
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("section", { className: "mx-meet__note-taker", "aria-label": "Note-taker", children: [
4060
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { className: `mx-meet__note-taker-state mx-meet__note-taker-state--${status.state}`, children: [
4061
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__note-taker-dot", "aria-hidden": "true" }),
4062
+ ai.noteTakerLabel
4063
+ ] }),
4064
+ status.detail !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mx-meet__panel-note", children: status.detail }) : null,
4065
+ status.refusal !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_design_system.ErrorBox, { as: "div", className: "mx-meet__refusal", error: status.refusal, operation: "Join this meeting", origin: "meet/components", children: [
4066
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mx-meet__refusal-reason", children: status.refusal.reason }),
4067
+ status.refusal.remedy.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mx-meet__refusal-remedy", children: status.refusal.remedy }) : null
4068
+ ] }) : null,
4069
+ ai.error !== null && !duplicateError ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_design_system.ErrorBox, { as: "div", className: "mx-meet__refusal", error: ai.error, operation: "Run the meeting assistant", origin: "meet/components", children: [
4070
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mx-meet__refusal-reason", children: ai.error.message }),
4071
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mx-meet__refusal-remedy", children: ai.error.remedy })
4072
+ ] }) : null,
4073
+ ai.canControlNoteTaker ? status.state === "transcribing" || status.state === "joining" || status.state === "handoff_pending" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4074
+ "button",
4075
+ {
4076
+ type: "button",
4077
+ className: "mx-meet__link",
4078
+ onClick: () => void ai.stopNoteTaker(),
4079
+ children: "Dismiss the note-taker"
4080
+ }
4081
+ ) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4082
+ "button",
4083
+ {
4084
+ type: "button",
4085
+ className: "mx-meet__primary",
4086
+ onClick: () => void ai.startNoteTaker(),
4087
+ children: "Start note-taker"
4088
+ }
4089
+ ) : null
4090
+ ] });
4091
+ }
4092
+ function NoteList({
4093
+ title,
4094
+ notes,
4095
+ empty
4096
+ }) {
4097
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("section", { className: "mx-meet__note-group", "aria-label": title, children: [
4098
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h3", { className: "mx-meet__note-group-title", children: title }),
4099
+ notes.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mx-meet__panel-note", children: empty }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("ul", { className: "mx-meet__notes", children: notes.map((note) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("li", { className: "mx-meet__note", children: [
4100
+ note.text,
4101
+ note.assigneeDisplayName !== void 0 && note.assigneeDisplayName !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__assignee", children: note.assigneeDisplayName }) : null
4102
+ ] }, note.id)) })
4103
+ ] });
4104
+ }
4105
+ function AiPanel({ onClose }) {
4106
+ const ai = useMeetAi();
4107
+ const [question, setQuestion] = (0, import_react9.useState)("");
4108
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(PanelShell, { title: "Meeting assistant", onClose, children: [
4109
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(NoteTakerRow, {}),
4110
+ ai.summary !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("section", { className: "mx-meet__note-group", "aria-label": "Summary", children: [
4111
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h3", { className: "mx-meet__note-group-title", children: "Summary" }),
4112
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mx-meet__summary", children: ai.summary.text })
4113
+ ] }) : null,
4114
+ ai.wrapUp !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { className: "mx-meet__wrap-up-notice", role: "status", children: [
4115
+ "The wrap-up is written \u2014 ",
4116
+ ai.wrapUp.noteIds.length,
4117
+ " ",
4118
+ ai.wrapUp.noteIds.length === 1 ? "entry" : "entries",
4119
+ " from",
4120
+ " ",
4121
+ ai.wrapUp.segmentCount,
4122
+ " ",
4123
+ ai.wrapUp.segmentCount === 1 ? "transcript line" : "transcript lines",
4124
+ "."
4125
+ ] }) : null,
4126
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4127
+ NoteList,
4128
+ {
4129
+ title: "Notes",
4130
+ notes: ai.notes,
4131
+ empty: "Notes appear here as the meeting goes on."
4132
+ }
4133
+ ),
4134
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4135
+ NoteList,
4136
+ {
4137
+ title: "Decisions",
4138
+ notes: ai.decisions,
4139
+ empty: "Nothing has been decided yet."
4140
+ }
4141
+ ),
4142
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4143
+ NoteList,
4144
+ {
4145
+ title: "Action items",
4146
+ notes: ai.actionItems,
4147
+ empty: "No action items yet."
4148
+ }
4149
+ ),
4150
+ ai.transcript.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("section", { className: "mx-meet__note-group", "aria-label": "Transcript", children: [
4151
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h3", { className: "mx-meet__note-group-title", children: "Transcript" }),
4152
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("ul", { className: "mx-meet__transcript", children: ai.transcript.map((line) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
4153
+ "li",
4154
+ {
4155
+ className: line.final && line.source === "record" ? "mx-meet__line" : "mx-meet__line mx-meet__line--interim",
4156
+ children: [
4157
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("strong", { children: line.speaker }),
4158
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: line.text })
4159
+ ]
4160
+ },
4161
+ line.id
4162
+ )) })
4163
+ ] }) : null,
4164
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
4165
+ "form",
4166
+ {
4167
+ className: "mx-meet__composer",
4168
+ onSubmit: (event) => {
4169
+ event.preventDefault();
4170
+ const asked = question;
4171
+ setQuestion("");
4172
+ void ai.ask(asked);
4173
+ },
4174
+ children: [
4175
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4176
+ "input",
4177
+ {
4178
+ className: "mx-meet__input",
4179
+ value: question,
4180
+ onChange: (event) => setQuestion(event.target.value),
4181
+ placeholder: "Ask about this meeting",
4182
+ "aria-label": "Ask about this meeting"
4183
+ }
4184
+ ),
4185
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "submit", className: "mx-meet__send", disabled: ai.asking, children: ai.asking ? "Asking\u2026" : "Ask" })
4186
+ ]
4187
+ }
4188
+ ),
4189
+ ai.asking && ai.phase !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mx-meet__panel-note", role: "status", children: ai.phase }) : null,
4190
+ ai.answer !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mx-meet__answer", children: ai.answer }) : null,
4191
+ ai.answer !== null && ai.coverage !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { className: "mx-meet__coverage", children: [
4192
+ "Answered from ",
4193
+ ai.coverage.segmentsUsed,
4194
+ " of ",
4195
+ ai.coverage.segmentsTotal,
4196
+ " transcript lines",
4197
+ ai.coverage.truncated ? " (the oldest were left out)" : "",
4198
+ "."
4199
+ ] }) : null,
4200
+ ai.error !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_design_system.ErrorBox, { as: "div", className: "mx-meet__refusal", error: ai.error, operation: "Run the meeting assistant", origin: "meet/components", children: [
4201
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mx-meet__refusal-reason", children: ai.error.message }),
4202
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mx-meet__refusal-remedy", children: ai.error.remedy })
4203
+ ] }) : null
4204
+ ] });
4205
+ }
4206
+ function Captions({ lines = 3 }) {
4207
+ const ai = useMeetAi();
4208
+ const visible = ai.transcript.slice(-lines);
4209
+ const translation = useCaptionTranslation(visible);
4210
+ if (visible.length === 0) return null;
4211
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mx-meet__captions", "aria-live": "polite", "aria-label": "Live captions", children: [
4212
+ visible.map((line) => {
4213
+ const translated = translation.language === null ? void 0 : translation.translations[line.id];
4214
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
4215
+ "p",
4216
+ {
4217
+ className: line.final ? "mx-meet__caption" : "mx-meet__caption mx-meet__caption--interim",
4218
+ children: [
4219
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("strong", { children: line.speaker }),
4220
+ " ",
4221
+ translated ?? line.text,
4222
+ translated !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__caption-original", children: line.text }) : null
4223
+ ]
4224
+ },
4225
+ line.id
4226
+ );
4227
+ }),
4228
+ translation.language !== null && translation.error !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mx-meet__caption mx-meet__caption--interim", children: translation.error }) : null
4229
+ ] });
4230
+ }
4231
+ function ConsentNotice() {
4232
+ const notice = useConsentNotice();
4233
+ if (notice === null) return null;
4234
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mx-meet__consent", role: "status", children: notice });
4235
+ }
4236
+ function AttendanceNotice() {
4237
+ const media = useLocalMedia();
4238
+ const notice = listenOnlyNotice(media);
4239
+ if (notice === null) return null;
4240
+ const refused = media.microphone.unavailable;
4241
+ const code = microphoneNoticeCode(media.microphone);
4242
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
4243
+ "p",
4244
+ {
4245
+ className: "mx-meet__banner mx-meet__banner--attendance",
4246
+ role: "status",
4247
+ ...code !== null ? { "data-meet-notice": code } : {},
4248
+ children: [
4249
+ notice,
4250
+ refused === null ? null : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__remedy", children: refused.remedy })
4251
+ ]
4252
+ }
4253
+ );
4254
+ }
4255
+ function PreJoin(props) {
4256
+ const host = useMeetHost();
4257
+ if (host === null) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(InertNotice, { what: "This meeting" });
4258
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PreJoinBody, { ...props, host });
4259
+ }
4260
+ function PreJoinBody({
4261
+ roomName,
4262
+ meetingId,
4263
+ slug,
4264
+ onJoined,
4265
+ host
4266
+ }) {
4267
+ const room = useRoom();
4268
+ const devices = useDeviceSnapshot();
4269
+ const recording = useRecording();
4270
+ const effects = useMediaEffects();
4271
+ const { notice } = useModerationNotice();
4272
+ const [mode, setMode] = (0, import_react9.useState)("video");
4273
+ const [microphone, setMicrophone] = (0, import_react9.useState)(true);
4274
+ const [joining, setJoining] = (0, import_react9.useState)(false);
4275
+ const camera = devices?.selected.videoinput ?? host.devices.resolve("videoinput");
4276
+ const mic = devices?.selected.audioinput ?? host.devices.resolve("audioinput");
4277
+ const preview = useCameraPreview(mode === "video" ? camera : void 0, effects.effects);
4278
+ const micStream = useMicrophoneStream(microphone ? mic : "off");
4279
+ const level = useAudioLevel(micStream);
4280
+ const [speakerTesting, setSpeakerTesting] = (0, import_react9.useState)(false);
4281
+ (0, import_react9.useEffect)(() => {
4282
+ void host.devices.refresh();
4283
+ }, [host]);
4284
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mx-meet__prejoin", children: [
4285
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mx-meet__preview", children: [
4286
+ mode === "video" && preview.preview !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4287
+ "video",
4288
+ {
4289
+ className: "mx-meet__video mx-meet__video--mirror",
4290
+ autoPlay: true,
4291
+ playsInline: true,
4292
+ muted: true,
4293
+ ref: (element) => {
4294
+ if (element !== null) preview.preview?.attach(element);
4295
+ }
4296
+ }
4297
+ ) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mx-meet__tile-placeholder", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Avatar, { name: host.identity.displayName, url: host.identity.avatarUrl, size: 96 }) }),
4298
+ mode === "video" && preview.error !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mx-meet__preview-note", children: preview.error }) : null,
4299
+ microphone && micStream !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mx-meet__meter mx-meet__meter--preview", role: "meter", "aria-label": "Microphone level", "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": Math.round(level * 100), children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: { width: `${Math.min(100, Math.round(level * 160))}%` } }) }) : null
4300
+ ] }),
4301
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mx-meet__prejoin-panel", children: [
4302
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h1", { className: "mx-meet__prejoin-title", children: "Ready to join?" }),
4303
+ notice?.kind === "removed" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mx-meet__warning", children: notice.text }) : null,
4304
+ devices?.labelsHidden === true ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { className: "mx-meet__panel-note", children: [
4305
+ "Your browser is hiding device names until you allow camera and microphone access.",
4306
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4307
+ "button",
4308
+ {
4309
+ type: "button",
4310
+ className: "mx-meet__link",
4311
+ onClick: () => {
4312
+ void host.devices.requestPermission({ audio: true, video: true });
4313
+ },
4314
+ children: "Allow access"
4315
+ }
4316
+ )
4317
+ ] }) : null,
4318
+ devices?.fallbackNotice != null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mx-meet__warning", children: devices.fallbackNotice }) : null,
4319
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(DevicePicker, { kind: "audioinput", label: "Microphone" }),
4320
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mx-meet__field-row", children: [
4321
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(DevicePicker, { kind: "audiooutput", label: "Speaker" }),
4322
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
4323
+ import_design_system.Button,
4324
+ {
4325
+ type: "button",
4326
+ variant: "outline",
4327
+ disabled: speakerTesting,
4328
+ onClick: () => {
4329
+ setSpeakerTesting(true);
4330
+ void playTestSound(devices?.selected.audiooutput ?? host.devices.resolve("audiooutput")).finally(
4331
+ () => setSpeakerTesting(false)
4332
+ );
4333
+ },
4334
+ children: [
4335
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SpeakerIcon, {}),
4336
+ " ",
4337
+ speakerTesting ? "Playing\u2026" : "Test"
4338
+ ]
4339
+ }
4340
+ )
4341
+ ] }),
4342
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(DevicePicker, { kind: "videoinput", label: "Camera" }),
4343
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(EffectsSettings, { preview: preview.preview }),
4344
+ recording.consentNotice !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system.Alert, { variant: "info", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system.AlertDescription, { children: recording.consentNotice }) }) : null,
4345
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mx-meet__prejoin-actions", children: [
4346
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mx-meet__check", children: [
4347
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4348
+ import_design_system.Checkbox,
4349
+ {
4350
+ id: "mx-meet-join-video",
4351
+ checked: mode === "video",
4352
+ onCheckedChange: (checked) => setMode(checked === true ? "video" : "audio")
4353
+ }
4354
+ ),
4355
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system.Label, { htmlFor: "mx-meet-join-video", children: "Join with video" })
4356
+ ] }),
4357
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mx-meet__check", children: [
4358
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4359
+ import_design_system.Checkbox,
4360
+ {
4361
+ id: "mx-meet-join-mic",
4362
+ checked: microphone,
4363
+ onCheckedChange: (checked) => setMicrophone(checked === true)
4364
+ }
4365
+ ),
4366
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system.Label, { htmlFor: "mx-meet-join-mic", children: "Join with microphone" })
4367
+ ] }),
4368
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4369
+ import_design_system.Button,
4370
+ {
4371
+ type: "button",
4372
+ className: "mx-meet__join",
4373
+ disabled: joining,
4374
+ onClick: () => {
4375
+ setJoining(true);
4376
+ preview.release();
4377
+ void room.join({
4378
+ roomName,
4379
+ mode,
4380
+ microphone,
4381
+ ...meetingId !== void 0 ? { meetingId } : {},
4382
+ ...slug !== void 0 ? { slug } : {}
4383
+ }).then(() => onJoined?.()).catch(() => void 0).finally(() => setJoining(false));
4384
+ },
4385
+ children: joining ? "Joining\u2026" : "Join now"
4386
+ }
4387
+ )
4388
+ ] }),
4389
+ room.failure !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { className: "mx-meet__error", children: [
4390
+ room.failure.message,
4391
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__remedy", children: room.failure.remedy })
4392
+ ] }) : null
4393
+ ] })
4394
+ ] });
4395
+ }
4396
+ function useCameraPreview(deviceId, effects) {
4397
+ const [preview, setPreview] = (0, import_react9.useState)(null);
4398
+ const [error, setError] = (0, import_react9.useState)(null);
4399
+ const current = (0, import_react9.useRef)(null);
4400
+ const initialEffect = (0, import_react9.useRef)(toVideoEffect(effects));
4401
+ initialEffect.current = toVideoEffect(effects);
4402
+ (0, import_react9.useEffect)(() => {
4403
+ if (deviceId === void 0) return void 0;
4404
+ let live = true;
4405
+ setError(null);
4406
+ void createCameraPreview(deviceId, initialEffect.current).then((created) => {
4407
+ if (!live) {
4408
+ created.stop();
4409
+ return;
4410
+ }
4411
+ current.current = created;
4412
+ setPreview(created);
4413
+ }).catch((thrown) => {
4414
+ if (!live) return;
4415
+ const failure = thrown;
4416
+ const message = (failure.message ?? "").replace(/^meet\.[A-Za-z.()]+:\s*/, "");
4417
+ const sentence = message.length > 0 ? message.charAt(0).toUpperCase() + message.slice(1) : "";
4418
+ setError([sentence, failure.remedy].filter(Boolean).join(". ") || "The camera did not start.");
4419
+ });
4420
+ return () => {
4421
+ live = false;
4422
+ current.current?.stop();
4423
+ current.current = null;
4424
+ setPreview(null);
4425
+ };
4426
+ }, [deviceId]);
4427
+ return {
4428
+ preview,
4429
+ error,
4430
+ release: () => {
4431
+ current.current?.stop();
4432
+ current.current = null;
4433
+ setPreview(null);
4434
+ }
4435
+ };
4436
+ }
4437
+ function DevicePicker({
4438
+ kind,
4439
+ label,
4440
+ onPick
4441
+ }) {
4442
+ const host = useRequiredMeetHost();
4443
+ const devices = useDeviceSnapshot();
4444
+ const list = devices?.devices[kind] ?? [];
4445
+ if (list.length === 0) return null;
4446
+ const triggerId = `mx-meet-device-${kind}`;
4447
+ const valueOf = (deviceId) => deviceId.length > 0 ? deviceId : "default";
4448
+ const current = devices?.selected[kind] ?? host.devices.resolve(kind) ?? "";
4449
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mx-meet__field", children: [
4450
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system.Label, { htmlFor: triggerId, children: label }),
4451
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
4452
+ import_design_system.Select,
4453
+ {
4454
+ ...current.length > 0 ? { value: valueOf(current) } : {},
4455
+ onValueChange: (value) => {
4456
+ const deviceId = list.find((device) => valueOf(device.deviceId) === value)?.deviceId ?? value;
4457
+ if (onPick !== void 0) onPick(kind, deviceId);
4458
+ else host.devices.select(kind, deviceId);
4459
+ },
4460
+ children: [
4461
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system.SelectTrigger, { id: triggerId, "aria-label": label, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system.SelectValue, { placeholder: label }) }),
4462
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system.SelectContent, { children: list.map((device) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system.SelectItem, { value: valueOf(device.deviceId), children: device.label.length > 0 ? device.label : `${label} (name hidden)` }, device.deviceId || "default")) })
4463
+ ]
4464
+ }
4465
+ )
4466
+ ] });
4467
+ }
4468
+ function MeetSkinRoom({
4469
+ roomName,
4470
+ meetingId,
4471
+ slug,
4472
+ onLeave,
4473
+ headerControls
4474
+ }) {
4475
+ const { host, snapshot, isHost } = useMeetCore();
4476
+ const elapsed = useElapsed(snapshot?.meeting?.startedAt ?? null);
4477
+ const phase = snapshot?.phase ?? "idle";
4478
+ if (host === null) return null;
4479
+ if (phase === "idle" || phase === "failed" || phase === "left") {
4480
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mx-meet", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4481
+ PreJoin,
4482
+ {
4483
+ roomName,
4484
+ ...meetingId !== void 0 ? { meetingId } : {},
4485
+ ...slug !== void 0 ? { slug } : {}
4486
+ }
4487
+ ) });
4488
+ }
4489
+ if (phase === "lobby") {
4490
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mx-meet mx-meet--lobby", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(WaitingRoom, { roomName, host }) });
4491
+ }
4492
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mx-meet", children: [
4493
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ConsentNotice, {}),
4494
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(AttendanceNotice, {}),
4495
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("header", { className: "mx-meet__header", children: [
4496
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h1", { className: "mx-meet__title", children: snapshot?.meeting?.title ?? "Meeting" }),
4497
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "mx-meet__meta", children: [
4498
+ participantSummary(snapshot?.participants ?? []),
4499
+ elapsed > 0 ? ` \xB7 ${(0, import_format2.formatDurationMs)(elapsed)}` : ""
4500
+ ] }),
4501
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RecordingIndicator, {}),
4502
+ snapshot?.locked === true ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "mx-meet__badge", children: [
4503
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(LockIcon, { width: 12, height: 12 }),
4504
+ " Locked"
4505
+ ] }) : null,
4506
+ headerControls !== void 0 && headerControls !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__header-controls", "data-meet-header-controls": "", children: headerControls }) : null,
4507
+ isHost ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(HostMenu, {}) : null
4508
+ ] }),
4509
+ phase === "reconnecting" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mx-meet__banner", role: "status", children: "Reconnecting\u2026" }) : null,
4510
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Stage, {}),
4511
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Captions, {}),
4512
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ControlBar, { ...onLeave !== void 0 ? { onLeave } : {} })
4513
+ ] });
4514
+ }
4515
+ function WaitingRoom({ roomName, host }) {
4516
+ const knocked = (0, import_react9.useRef)(false);
4517
+ (0, import_react9.useEffect)(() => {
4518
+ if (knocked.current) return;
4519
+ knocked.current = true;
4520
+ host.meetings.knock(roomName);
4521
+ }, [host, roomName]);
4522
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mx-meet__waiting", children: [
4523
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Avatar, { name: host.identity.displayName, url: host.identity.avatarUrl, size: 72 }),
4524
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h1", { className: "mx-meet__prejoin-title", children: "Waiting for the host to let you in" }),
4525
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { className: "mx-meet__empty-body", children: [
4526
+ "You are in the waiting room as ",
4527
+ host.identity.displayName,
4528
+ ". You will join automatically once you are admitted."
4529
+ ] })
4530
+ ] });
4531
+ }
4532
+ function HostMenu() {
4533
+ const host = useRequiredMeetHost();
4534
+ const snapshot = useMeetSnapshot();
4535
+ const end = useEndMeeting();
4536
+ const [confirmingEnd, setConfirmingEnd] = (0, import_react9.useState)(false);
4537
+ const locked = snapshot?.locked === true;
4538
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mx-meet__host-menu", children: [
4539
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4540
+ "button",
4541
+ {
4542
+ type: "button",
4543
+ className: "mx-meet__link",
4544
+ onClick: () => {
4545
+ void host.meetings.setLock(!locked);
4546
+ },
4547
+ children: locked ? "Unlock meeting" : "Lock meeting"
4548
+ }
4549
+ ),
4550
+ end.canEnd ? confirmingEnd ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "mx-meet__confirm", role: "group", "aria-label": "End this meeting?", children: [
4551
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__confirm-text", children: "End it for everyone? Every participant is disconnected, the note-taker stops, and the wrap-up is written. Nobody can rejoin this meeting afterwards." }),
4552
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4553
+ "button",
4554
+ {
4555
+ type: "button",
4556
+ className: "mx-meet__link mx-meet__link--danger",
4557
+ onClick: () => {
4558
+ setConfirmingEnd(false);
4559
+ void end.end();
4560
+ },
4561
+ children: "End it"
4562
+ }
4563
+ ),
4564
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4565
+ "button",
4566
+ {
4567
+ type: "button",
4568
+ className: "mx-meet__link",
4569
+ onClick: () => setConfirmingEnd(false),
4570
+ children: "Keep it running"
4571
+ }
4572
+ )
4573
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4574
+ "button",
4575
+ {
4576
+ type: "button",
4577
+ className: "mx-meet__link mx-meet__link--danger",
4578
+ onClick: () => setConfirmingEnd(true),
4579
+ children: end.ending ? "Ending\u2026" : "End meeting for everyone"
4580
+ }
4581
+ ) : null,
4582
+ end.notice !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__notice", role: "status", children: end.notice }) : null
4583
+ ] });
4584
+ }
4585
+ function ReactionOverlay({
4586
+ reactions
4587
+ }) {
4588
+ if (reactions.length === 0) return null;
4589
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mx-meet__reactions", "aria-hidden": "true", children: reactions.map((reaction) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__reaction", children: reaction.emoji }, reaction.id)) });
4590
+ }
4591
+ function clockOf(iso) {
4592
+ const at = new Date(iso);
4593
+ if (Number.isNaN(at.getTime())) return "";
4594
+ return at.toLocaleTimeString(void 0, { hour: "numeric", minute: "2-digit" });
4595
+ }
4596
+ function GuestRecordAccessControl({
4597
+ meeting
4598
+ }) {
4599
+ const host = useMeetHost();
4600
+ const inRoomHost = useIsHost();
4601
+ const access = useGuestRecordAccess(meeting);
4602
+ const viewerId = host?.identity.userId ?? null;
4603
+ const isHost = inRoomHost || viewerId !== null && meeting.hostUserId === viewerId;
4604
+ if (host === null || !isHost) return null;
4605
+ const resolved = access.setting === "default" && access.effective !== null ? ` Right now that means: ${guestRecordAccessLabel(access.effective.level).toLowerCase()}.` : "";
4606
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("section", { className: "mx-meet__record-section", "aria-label": "What guests may read", children: [
4607
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h2", { className: "mx-meet__record-section-title", children: "What guests may read" }),
4608
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "mx-meet__field", children: [
4609
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__field-label", children: "People who attended by link, without an account" }),
4610
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4611
+ "select",
4612
+ {
4613
+ className: "mx-meet__select",
4614
+ value: access.setting,
4615
+ disabled: access.saving,
4616
+ onChange: (event) => {
4617
+ void access.choose(
4618
+ event.target.value
4619
+ );
4620
+ },
4621
+ children: GUEST_RECORD_ACCESS_CHOICES.map((choice) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: choice, children: guestRecordAccessLabel(choice) }, choice))
4622
+ }
4623
+ )
4624
+ ] }),
4625
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { className: "mx-meet__panel-note", children: [
4626
+ guestRecordAccessExplanation(access.setting),
4627
+ resolved
4628
+ ] }),
4629
+ access.saving ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mx-meet__panel-note", role: "status", children: "Saving what guests may read\u2026" }) : null,
4630
+ access.error !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_design_system.ErrorBox, { as: "div", className: "mx-meet__refusal", error: access.error, operation: "Change who can join", origin: "meet/components", children: [
4631
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mx-meet__refusal-reason", children: access.error.message }),
4632
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mx-meet__refusal-remedy", children: access.error.remedy })
4633
+ ] }) : null
4634
+ ] });
4635
+ }
4636
+ //# sourceMappingURL=meet.cjs.map