@ai-matrx/meet 0.7.2 → 0.7.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +88 -0
- package/dist/index.cjs +1002 -69
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +484 -26
- package/dist/index.d.ts +484 -26
- package/dist/index.js +1002 -69
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +2385 -244
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +136 -12
- package/dist/react.d.ts +136 -12
- package/dist/react.js +2386 -245
- package/dist/react.js.map +1 -1
- package/dist/styles.css +522 -0
- package/package.json +5 -4
package/dist/react.cjs
CHANGED
|
@@ -51,19 +51,23 @@ __export(react_exports, {
|
|
|
51
51
|
ControlBar: () => ControlBar,
|
|
52
52
|
ControlButton: () => ControlButton,
|
|
53
53
|
DATA_TOPICS: () => DATA_TOPICS,
|
|
54
|
+
DEFAULT_EFFECTS: () => DEFAULT_EFFECTS,
|
|
54
55
|
DEFAULT_PAGE_SIZE: () => DEFAULT_PAGE_SIZE,
|
|
55
56
|
END_MEETING_NOT_SERVED: () => END_MEETING_NOT_SERVED,
|
|
56
57
|
FILE_SESSION_PATH: () => FILE_SESSION_PATH,
|
|
58
|
+
FlagIcon: () => FlagIcon,
|
|
57
59
|
GUEST_RECORD_ACCESS_CHOICES: () => GUEST_RECORD_ACCESS_CHOICES,
|
|
58
60
|
GUEST_RECORD_ACCESS_LEVELS: () => GUEST_RECORD_ACCESS_LEVELS,
|
|
59
61
|
GUEST_RECORD_ACCESS_METADATA_KEY: () => GUEST_RECORD_ACCESS_METADATA_KEY,
|
|
60
62
|
GUEST_RECORD_NOT_SHARED: () => GUEST_RECORD_NOT_SHARED,
|
|
61
63
|
GuestRecordAccessControl: () => GuestRecordAccessControl,
|
|
62
64
|
HandIcon: () => HandIcon,
|
|
65
|
+
HostMenu: () => HostMenu,
|
|
63
66
|
IDLE_MEDIA: () => IDLE_MEDIA,
|
|
64
67
|
IDLE_TRACK: () => IDLE_TRACK,
|
|
65
68
|
IncomingCallCard: () => IncomingCallCard,
|
|
66
69
|
IncomingCallHost: () => IncomingCallHost,
|
|
70
|
+
KeyboardIcon: () => KeyboardIcon,
|
|
67
71
|
LISTEN_ONLY_NOTICE: () => LISTEN_ONLY_NOTICE,
|
|
68
72
|
LayoutIcon: () => LayoutIcon,
|
|
69
73
|
LobbyPanel: () => LobbyPanel,
|
|
@@ -73,15 +77,20 @@ __export(react_exports, {
|
|
|
73
77
|
MEET_ANSWER_CONTEXT_EVENT: () => MEET_ANSWER_CONTEXT_EVENT,
|
|
74
78
|
MEET_ANSWER_EVENTS: () => MEET_ANSWER_EVENTS,
|
|
75
79
|
MEET_EVENTS: () => MEET_EVENTS,
|
|
80
|
+
MEET_REACTIONS: () => MEET_REACTIONS,
|
|
76
81
|
MEET_ROUTES: () => MEET_ROUTES,
|
|
77
82
|
MEET_SCHEMA: () => MEET_SCHEMA,
|
|
83
|
+
MEET_SHORTCUTS: () => MEET_SHORTCUTS,
|
|
84
|
+
MODERATION_ACTIONS: () => MODERATION_ACTIONS,
|
|
78
85
|
MeetError: () => MeetError,
|
|
79
86
|
MeetProvider: () => MeetProvider,
|
|
80
87
|
MeetingList: () => MeetingList,
|
|
88
|
+
MeetingOverlays: () => MeetingOverlays,
|
|
81
89
|
MeetingRecordView: () => MeetingRecordView,
|
|
82
90
|
MeetingRoom: () => MeetingRoom,
|
|
83
91
|
MicIcon: () => MicIcon,
|
|
84
92
|
MicOffIcon: () => MicOffIcon,
|
|
93
|
+
MoreIcon: () => MoreIcon,
|
|
85
94
|
NOTE_TAKER_IDENTITY_PREFIX: () => NOTE_TAKER_IDENTITY_PREFIX,
|
|
86
95
|
NOTE_TAKER_METADATA_KEY: () => NOTE_TAKER_METADATA_KEY,
|
|
87
96
|
NO_GRANTS: () => NO_GRANTS,
|
|
@@ -89,12 +98,15 @@ __export(react_exports, {
|
|
|
89
98
|
PARTICIPANT_CAN_RECORD_ATTRIBUTE: () => PARTICIPANT_CAN_RECORD_ATTRIBUTE,
|
|
90
99
|
PARTICIPANT_ROLE_ATTRIBUTE: () => PARTICIPANT_ROLE_ATTRIBUTE,
|
|
91
100
|
ParticipantTile: () => ParticipantTile,
|
|
101
|
+
PencilIcon: () => PencilIcon,
|
|
92
102
|
PeopleIcon: () => PeopleIcon,
|
|
93
103
|
PeoplePanel: () => PeoplePanel,
|
|
104
|
+
PersonMenu: () => PersonMenu,
|
|
94
105
|
PhoneIcon: () => PhoneIcon,
|
|
95
106
|
PhoneOffIcon: () => PhoneOffIcon,
|
|
96
107
|
PinIcon: () => PinIcon,
|
|
97
108
|
PreJoin: () => PreJoin,
|
|
109
|
+
QualityBars: () => QualityBars,
|
|
98
110
|
REACTION_TTL_MS: () => REACTION_TTL_MS,
|
|
99
111
|
REFUSED_ABSENCE: () => REFUSED_ABSENCE,
|
|
100
112
|
ROLE_RANK: () => ROLE_RANK,
|
|
@@ -108,14 +120,22 @@ __export(react_exports, {
|
|
|
108
120
|
RecordingPlayer: () => RecordingPlayer,
|
|
109
121
|
ScheduleMeeting: () => ScheduleMeeting,
|
|
110
122
|
ScreenShareIcon: () => ScreenShareIcon,
|
|
123
|
+
SearchIcon: () => SearchIcon,
|
|
111
124
|
SettingsIcon: () => SettingsIcon,
|
|
125
|
+
SettingsPanel: () => SettingsPanel,
|
|
126
|
+
ShortcutsSheet: () => ShortcutsSheet,
|
|
127
|
+
SignalIcon: () => SignalIcon,
|
|
128
|
+
SmileIcon: () => SmileIcon,
|
|
112
129
|
SparklesIcon: () => SparklesIcon,
|
|
130
|
+
SpeakerIcon: () => SpeakerIcon,
|
|
131
|
+
SpotlightIcon: () => SpotlightIcon,
|
|
113
132
|
Stage: () => Stage,
|
|
114
133
|
TABLES: () => TABLES,
|
|
115
134
|
TRANSCRIPTION_ATTRIBUTES: () => TRANSCRIPTION_ATTRIBUTES,
|
|
116
135
|
TRANSCRIPTION_TOPIC: () => TRANSCRIPTION_TOPIC,
|
|
117
136
|
WifiLowIcon: () => WifiLowIcon,
|
|
118
137
|
absenceSentence: () => absenceSentence,
|
|
138
|
+
announceMeeting: () => announceMeeting,
|
|
119
139
|
applyRoomEvent: () => applyRoomEvent,
|
|
120
140
|
asCallId: () => asCallId,
|
|
121
141
|
asMeetingId: () => asMeetingId,
|
|
@@ -130,11 +150,13 @@ __export(react_exports, {
|
|
|
130
150
|
canModerateParticipant: () => canModerateParticipant,
|
|
131
151
|
canTransition: () => canTransition,
|
|
132
152
|
captionSegment: () => captionSegment,
|
|
153
|
+
changesPatch: () => changesPatch,
|
|
133
154
|
computeStage: () => computeStage,
|
|
134
155
|
countLabel: () => countLabel,
|
|
135
156
|
createCallCenter: () => createCallCenter,
|
|
136
157
|
createCallInviteHandler: () => createCallInviteHandler,
|
|
137
158
|
createDeviceManager: () => createDeviceManager,
|
|
159
|
+
createInRoomClient: () => createInRoomClient,
|
|
138
160
|
createLiveKitDriver: () => createLiveKitDriver,
|
|
139
161
|
createMeetAi: () => createMeetAi,
|
|
140
162
|
createMeetApi: () => createMeetApi,
|
|
@@ -147,6 +169,7 @@ __export(react_exports, {
|
|
|
147
169
|
createRoomEngine: () => createRoomEngine,
|
|
148
170
|
createTokenClient: () => createTokenClient,
|
|
149
171
|
createWebDeviceStorage: () => createWebDeviceStorage,
|
|
172
|
+
createWebEffectsStorage: () => createWebEffectsStorage,
|
|
150
173
|
createWebRoomTokenStorage: () => createWebRoomTokenStorage,
|
|
151
174
|
displayNameFor: () => displayNameFor,
|
|
152
175
|
fileDownloadPath: () => fileDownloadPath,
|
|
@@ -174,6 +197,7 @@ __export(react_exports, {
|
|
|
174
197
|
mapQuality: () => mapQuality,
|
|
175
198
|
meetingInviteAction: () => meetingInviteAction,
|
|
176
199
|
meetingLink: () => meetingLink,
|
|
200
|
+
meetingPath: () => meetingPath,
|
|
177
201
|
meetingSlug: () => meetingSlug,
|
|
178
202
|
meetingTopic: () => meetingTopic,
|
|
179
203
|
meetingWhen: () => meetingWhen,
|
|
@@ -190,9 +214,15 @@ __export(react_exports, {
|
|
|
190
214
|
parseRoomToken: () => parseRoomToken,
|
|
191
215
|
participantSummary: () => participantSummary,
|
|
192
216
|
playableRecordingFileId: () => playableRecordingFileId,
|
|
217
|
+
projectInvitee: () => projectInvitee,
|
|
193
218
|
projectNote: () => projectNote,
|
|
219
|
+
projectOccurrence: () => projectOccurrence,
|
|
220
|
+
projectOverride: () => projectOverride,
|
|
194
221
|
projectParticipant: () => projectParticipant,
|
|
222
|
+
projectRsvpInvitation: () => projectRsvpInvitation,
|
|
195
223
|
projectSegment: () => projectSegment,
|
|
224
|
+
projectUpcoming: () => projectUpcoming,
|
|
225
|
+
pushMeetingToGoogleCalendar: () => pushMeetingToGoogleCalendar,
|
|
196
226
|
readServerRefusal: () => readServerRefusal,
|
|
197
227
|
readableTranscript: () => readableTranscript,
|
|
198
228
|
recordingLabel: () => recordingLabel,
|
|
@@ -200,8 +230,10 @@ __export(react_exports, {
|
|
|
200
230
|
roleOf: () => roleOf,
|
|
201
231
|
roomTopic: () => roomTopic,
|
|
202
232
|
setGuestRecordAccess: () => setGuestRecordAccess,
|
|
233
|
+
settingsPatch: () => settingsPatch,
|
|
203
234
|
snapshotParticipant: () => snapshotParticipant,
|
|
204
235
|
unavailableFrom: () => unavailableFrom,
|
|
236
|
+
useAudioLevel: () => useAudioLevel,
|
|
205
237
|
useCalls: () => useCalls,
|
|
206
238
|
useCanModerate: () => useCanModerate,
|
|
207
239
|
useCanRecord: () => useCanRecord,
|
|
@@ -210,19 +242,23 @@ __export(react_exports, {
|
|
|
210
242
|
useElapsed: () => useElapsed,
|
|
211
243
|
useEndMeeting: () => useEndMeeting,
|
|
212
244
|
useGuestRecordAccess: () => useGuestRecordAccess,
|
|
245
|
+
useHandQueue: () => useHandQueue,
|
|
213
246
|
useHostControls: () => useHostControls,
|
|
214
247
|
useIsHost: () => useIsHost,
|
|
215
248
|
useListenOnlyNotice: () => useListenOnlyNotice,
|
|
216
249
|
useLobby: () => useLobby,
|
|
217
250
|
useLocalMedia: () => useLocalMedia,
|
|
218
251
|
useLocalUserId: () => useLocalUserId,
|
|
252
|
+
useMediaEffects: () => useMediaEffects,
|
|
219
253
|
useMeetAi: () => useMeetAi,
|
|
220
254
|
useMeetHost: () => useMeetHost,
|
|
255
|
+
useMeetShortcuts: () => useMeetShortcuts,
|
|
221
256
|
useMeetSnapshot: () => useMeetSnapshot,
|
|
222
257
|
useMeeting: () => useMeeting,
|
|
223
258
|
useMeetingId: () => useMeetingId,
|
|
224
259
|
useMeetingRecord: () => useMeetingRecord,
|
|
225
260
|
useMeetings: () => useMeetings,
|
|
261
|
+
useModerationNotice: () => useModerationNotice,
|
|
226
262
|
useReactions: () => useReactions,
|
|
227
263
|
useRecording: () => useRecording,
|
|
228
264
|
useRequiredMeetHost: () => useRequiredMeetHost,
|
|
@@ -266,7 +302,7 @@ var MeetError = class extends Error {
|
|
|
266
302
|
};
|
|
267
303
|
var FORBIDDEN_CODES = /* @__PURE__ */ new Set(["42501", "PGRST301", "PGRST302"]);
|
|
268
304
|
var NOT_FOUND_CODES = /* @__PURE__ */ new Set(["PGRST116", "PGRST205"]);
|
|
269
|
-
var CONFLICT_CODES = /* @__PURE__ */ new Set(["23505"]);
|
|
305
|
+
var CONFLICT_CODES = /* @__PURE__ */ new Set(["23505", "40001", "55000"]);
|
|
270
306
|
var SESSION_MARKERS = [
|
|
271
307
|
"auth session missing",
|
|
272
308
|
"jwt expired",
|
|
@@ -424,8 +460,28 @@ var MEET_ROUTES = {
|
|
|
424
460
|
*/
|
|
425
461
|
record: "/api/v1/meet/record",
|
|
426
462
|
/** A host chooses what guests may read of ONE meeting (MRF-1). */
|
|
427
|
-
recordAccess: "/api/v1/meet/record/access"
|
|
463
|
+
recordAccess: "/api/v1/meet/record/access",
|
|
464
|
+
/**
|
|
465
|
+
* Meet wave 1 (0.8): bring every invitee up to date after a write — sends the
|
|
466
|
+
* invitation / update / cancellation emails with their `.ics` and re-queues
|
|
467
|
+
* reminders. Idempotent. `{meeting_id}` is substituted by `meetingPath`.
|
|
468
|
+
*/
|
|
469
|
+
meetingAnnounce: "/api/v1/meet/meetings/{meeting_id}/announce",
|
|
470
|
+
/** Put a one-off meeting on the caller's own Google Calendar (gated by the server). */
|
|
471
|
+
meetingGoogleCalendar: "/api/v1/meet/meetings/{meeting_id}/google-calendar",
|
|
472
|
+
/**
|
|
473
|
+
* Meet wave 2: EVERY in-meeting host power (mute, stop video, mute all, ask to
|
|
474
|
+
* unmute, spotlight, lower hands, co-host, hand over, rename, remove, report).
|
|
475
|
+
* Decided by the server from the meeting itself; a guest's pass rides
|
|
476
|
+
* `x-meet-room-token`.
|
|
477
|
+
*/
|
|
478
|
+
moderate: "/api/v1/meet/moderate",
|
|
479
|
+
/** Meet wave 2: the SAVED chat — `POST` sends to everyone, `GET ?meeting_id=` reads history. */
|
|
480
|
+
chat: "/api/v1/meet/chat"
|
|
428
481
|
};
|
|
482
|
+
function meetingPath(template, meetingId) {
|
|
483
|
+
return template.replace("{meeting_id}", encodeURIComponent(meetingId));
|
|
484
|
+
}
|
|
429
485
|
var ROOM_TOKEN_HEADER = "x-meet-room-token";
|
|
430
486
|
var REFRESH_RATIO = 0.8;
|
|
431
487
|
function readString(source, key) {
|
|
@@ -555,6 +611,19 @@ function createTokenClient(options) {
|
|
|
555
611
|
"The link is wrong or the meeting was deleted. Show the 'meeting not found' screen; a durable link that 404s is never a retry."
|
|
556
612
|
);
|
|
557
613
|
}
|
|
614
|
+
if (response.status === 410) {
|
|
615
|
+
let reason = "This meeting was cancelled.";
|
|
616
|
+
try {
|
|
617
|
+
const body = await response.json();
|
|
618
|
+
if (typeof body?.detail?.message === "string") reason = body.detail.message;
|
|
619
|
+
} catch {
|
|
620
|
+
}
|
|
621
|
+
throw new MeetError(
|
|
622
|
+
"cancelled",
|
|
623
|
+
reason,
|
|
624
|
+
"Nobody can join a cancelled meeting. Show the cancelled screen with this sentence; ask the host to schedule a new one."
|
|
625
|
+
);
|
|
626
|
+
}
|
|
558
627
|
if (response.status === 423) {
|
|
559
628
|
throw new MeetError(
|
|
560
629
|
"room-locked",
|
|
@@ -756,12 +825,12 @@ function createMeetAi(options) {
|
|
|
756
825
|
async function dispatch(route, body, operation) {
|
|
757
826
|
const response = await send({ path: route, body, operation, requireAuth: true });
|
|
758
827
|
if (!response.ok) {
|
|
759
|
-
const
|
|
760
|
-
options.store.patch({ noteTakerRefusal:
|
|
828
|
+
const refusal2 = await refusalFor(response, operation);
|
|
829
|
+
options.store.patch({ noteTakerRefusal: refusal2 });
|
|
761
830
|
throw new MeetError(
|
|
762
831
|
response.status === 403 ? "forbidden" : "transport",
|
|
763
|
-
|
|
764
|
-
|
|
832
|
+
refusal2.reason,
|
|
833
|
+
refusal2.remedy
|
|
765
834
|
);
|
|
766
835
|
}
|
|
767
836
|
options.store.patch({ noteTakerRefusal: null });
|
|
@@ -799,11 +868,11 @@ function createMeetAi(options) {
|
|
|
799
868
|
...signal !== void 0 ? { signal } : {}
|
|
800
869
|
});
|
|
801
870
|
if (!response.ok) {
|
|
802
|
-
const
|
|
871
|
+
const refusal2 = await refusalFor(response, "meet.ai.ask");
|
|
803
872
|
throw new MeetError(
|
|
804
873
|
response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : "transport",
|
|
805
|
-
|
|
806
|
-
|
|
874
|
+
refusal2.reason,
|
|
875
|
+
refusal2.remedy
|
|
807
876
|
);
|
|
808
877
|
}
|
|
809
878
|
const body = response.body;
|
|
@@ -1713,6 +1782,11 @@ function captionSegment(args) {
|
|
|
1713
1782
|
}
|
|
1714
1783
|
|
|
1715
1784
|
// src/core/store.ts
|
|
1785
|
+
var DEFAULT_EFFECTS = {
|
|
1786
|
+
noiseSuppression: true,
|
|
1787
|
+
background: "none",
|
|
1788
|
+
backgroundImage: null
|
|
1789
|
+
};
|
|
1716
1790
|
var NO_GRANTS = {
|
|
1717
1791
|
role: "participant",
|
|
1718
1792
|
canRecord: false,
|
|
@@ -1735,7 +1809,12 @@ var EMPTY = {
|
|
|
1735
1809
|
recordingSource: "local",
|
|
1736
1810
|
recordingId: null,
|
|
1737
1811
|
chat: [],
|
|
1812
|
+
chatLoaded: false,
|
|
1738
1813
|
reactions: [],
|
|
1814
|
+
spotlight: null,
|
|
1815
|
+
moderationNotice: null,
|
|
1816
|
+
actionNotice: null,
|
|
1817
|
+
effects: DEFAULT_EFFECTS,
|
|
1739
1818
|
transcript: [],
|
|
1740
1819
|
notes: [],
|
|
1741
1820
|
names: {},
|
|
@@ -1774,6 +1853,7 @@ function createMeetStore(initial = {}) {
|
|
|
1774
1853
|
setParticipants(participants) {
|
|
1775
1854
|
const names = learnNames(state.names, participants);
|
|
1776
1855
|
const hands = new Map(state.participants.map((p) => [p.identity, p.handRaised]));
|
|
1856
|
+
const handTimes = new Map(state.participants.map((p) => [p.identity, p.handRaisedAt ?? null]));
|
|
1777
1857
|
const quality = new Map(state.participants.map((p) => [p.identity, p.quality]));
|
|
1778
1858
|
commit({
|
|
1779
1859
|
...state,
|
|
@@ -1787,6 +1867,7 @@ function createMeetStore(initial = {}) {
|
|
|
1787
1867
|
// name book. `roster-names.test.ts` fails if a token ever gets past it.
|
|
1788
1868
|
displayName: displayNameFor(names, participant.identity, participant.displayName),
|
|
1789
1869
|
handRaised: hands.get(participant.identity) ?? participant.handRaised,
|
|
1870
|
+
handRaisedAt: handTimes.get(participant.identity) ?? participant.handRaisedAt ?? null,
|
|
1790
1871
|
quality: participant.quality === "unknown" ? quality.get(participant.identity) ?? "unknown" : participant.quality
|
|
1791
1872
|
}))
|
|
1792
1873
|
});
|
|
@@ -1809,11 +1890,17 @@ function createMeetStore(initial = {}) {
|
|
|
1809
1890
|
)
|
|
1810
1891
|
});
|
|
1811
1892
|
},
|
|
1812
|
-
setHand(identity, raised) {
|
|
1893
|
+
setHand(identity, raised, at) {
|
|
1813
1894
|
commit({
|
|
1814
1895
|
...state,
|
|
1815
1896
|
participants: state.participants.map(
|
|
1816
|
-
(participant) => participant.identity === identity ? {
|
|
1897
|
+
(participant) => participant.identity === identity ? {
|
|
1898
|
+
...participant,
|
|
1899
|
+
handRaised: raised,
|
|
1900
|
+
// A hand that is ALREADY up keeps its place in the queue when
|
|
1901
|
+
// it is re-announced (to a late joiner): the first time wins.
|
|
1902
|
+
handRaisedAt: raised ? participant.handRaised && participant.handRaisedAt != null ? participant.handRaisedAt : at ?? Date.now() : null
|
|
1903
|
+
} : participant
|
|
1817
1904
|
)
|
|
1818
1905
|
});
|
|
1819
1906
|
},
|
|
@@ -1823,6 +1910,31 @@ function createMeetStore(initial = {}) {
|
|
|
1823
1910
|
const names = learnName(state.names, message.identity, message.displayName);
|
|
1824
1911
|
commit({ ...state, chat: chat.slice(-CHAT_LIMIT), names });
|
|
1825
1912
|
},
|
|
1913
|
+
loadChat(messages) {
|
|
1914
|
+
const known = new Set(state.chat.map((message) => message.id));
|
|
1915
|
+
const merged = [...messages.filter((message) => !known.has(message.id)), ...state.chat].sort(
|
|
1916
|
+
(a, b) => a.at < b.at ? -1 : a.at > b.at ? 1 : 0
|
|
1917
|
+
);
|
|
1918
|
+
const names = learnNames(
|
|
1919
|
+
state.names,
|
|
1920
|
+
messages.map((message) => ({ identity: message.identity, displayName: message.displayName }))
|
|
1921
|
+
);
|
|
1922
|
+
commit({ ...state, chat: merged.slice(-CHAT_LIMIT), chatLoaded: true, names });
|
|
1923
|
+
},
|
|
1924
|
+
settleChat(localId, next) {
|
|
1925
|
+
const index = state.chat.findIndex((message) => message.id === localId);
|
|
1926
|
+
if (index === -1) return;
|
|
1927
|
+
const current = state.chat[index];
|
|
1928
|
+
let replacement;
|
|
1929
|
+
if ("failed" in next && typeof next.failed === "string" && !("id" in next)) {
|
|
1930
|
+
replacement = { ...current, failed: next.failed };
|
|
1931
|
+
} else {
|
|
1932
|
+
replacement = next;
|
|
1933
|
+
}
|
|
1934
|
+
const duplicate = replacement.id !== localId && state.chat.some((message) => message.id === replacement.id);
|
|
1935
|
+
const chat = duplicate ? state.chat.filter((message) => message.id !== localId) : state.chat.map((message, position) => position === index ? replacement : message);
|
|
1936
|
+
commit({ ...state, chat });
|
|
1937
|
+
},
|
|
1826
1938
|
addReaction(reaction) {
|
|
1827
1939
|
commit({ ...state, reactions: [...state.reactions, reaction] });
|
|
1828
1940
|
},
|
|
@@ -1913,7 +2025,7 @@ function createMeetStore(initial = {}) {
|
|
|
1913
2025
|
commit({ ...state, media: { ...state.media, [kind]: next } });
|
|
1914
2026
|
},
|
|
1915
2027
|
reset() {
|
|
1916
|
-
commit({ ...EMPTY });
|
|
2028
|
+
commit({ ...EMPTY, effects: state.effects });
|
|
1917
2029
|
}
|
|
1918
2030
|
};
|
|
1919
2031
|
}
|
|
@@ -2243,11 +2355,11 @@ function createMeetingSession(options) {
|
|
|
2243
2355
|
} catch {
|
|
2244
2356
|
body = null;
|
|
2245
2357
|
}
|
|
2246
|
-
const
|
|
2358
|
+
const refusal2 = readServerRefusal(body);
|
|
2247
2359
|
throw new MeetError(
|
|
2248
2360
|
response.status === 403 ? "forbidden" : "transport",
|
|
2249
|
-
|
|
2250
|
-
|
|
2361
|
+
refusal2?.reason ?? `meet.endForEveryone: the server answered ${response.status}`,
|
|
2362
|
+
refusal2?.remedy !== void 0 && refusal2.remedy.length > 0 ? refusal2.remedy : "The meeting is still running. Only a host may end it, and the server re-checks that \u2014 being a host on this screen is not the decision."
|
|
2251
2363
|
);
|
|
2252
2364
|
}
|
|
2253
2365
|
let payload = null;
|
|
@@ -2290,7 +2402,11 @@ var TABLES = {
|
|
|
2290
2402
|
invites: "meet_call_invites",
|
|
2291
2403
|
recordings: "meet_recordings",
|
|
2292
2404
|
transcripts: "meet_transcript_segments",
|
|
2293
|
-
notes: "meet_notes"
|
|
2405
|
+
notes: "meet_notes",
|
|
2406
|
+
/** Wave 1 (0.8): who a meeting was sent to, and what they answered. */
|
|
2407
|
+
invitees: "meet_invitees",
|
|
2408
|
+
/** Wave 1 (0.8): one occurrence of a series cancelled or moved. */
|
|
2409
|
+
occurrenceOverrides: "meet_occurrence_overrides"
|
|
2294
2410
|
};
|
|
2295
2411
|
var RPCS = {
|
|
2296
2412
|
/** Atomic: create the invite row and stamp its expiry in one statement. */
|
|
@@ -2306,10 +2422,41 @@ var RPCS = {
|
|
|
2306
2422
|
admit: "meet_admit_participant",
|
|
2307
2423
|
setLock: "meet_set_lock",
|
|
2308
2424
|
endMeeting: "meet_end_meeting",
|
|
2309
|
-
recordConsent: "meet_record_consent"
|
|
2425
|
+
recordConsent: "meet_record_consent",
|
|
2426
|
+
/*
|
|
2427
|
+
* ── MEET WAVE 1 — management (0.8). Every write is a SECURITY DEFINER door
|
|
2428
|
+
* authorized by the meeting's host or a real iam grant (editor to change,
|
|
2429
|
+
* admin = co-host to archive or name a co-host) — never an organization
|
|
2430
|
+
* match. After any of these writes, call `announceMeeting` so the server
|
|
2431
|
+
* sends invitations / updates / cancellations and re-queues reminders.
|
|
2432
|
+
*/
|
|
2433
|
+
/** Schedule with every setting at create; unset settings come from the knobs. */
|
|
2434
|
+
scheduleMeeting: "meet_schedule_meeting",
|
|
2435
|
+
/** Edit / reschedule — a JSON patch with a version compare-and-swap. */
|
|
2436
|
+
updateMeeting: "meet_update_meeting",
|
|
2437
|
+
/** Cancel is a state (`cancelled_at`); the link keeps resolving. */
|
|
2438
|
+
cancelMeeting: "meet_cancel_meeting",
|
|
2439
|
+
/** Archive (soft delete) — the invitations and exceptions cascade. */
|
|
2440
|
+
archiveMeeting: "meet_archive_meeting",
|
|
2441
|
+
restoreMeeting: "meet_restore_meeting",
|
|
2442
|
+
/** Cancel, move or restore ONE occurrence of a series by its original start. */
|
|
2443
|
+
setOccurrence: "meet_set_occurrence",
|
|
2444
|
+
addInvitees: "meet_add_invitees",
|
|
2445
|
+
setInviteeRole: "meet_set_invitee_role",
|
|
2446
|
+
removeInvitee: "meet_remove_invitee",
|
|
2447
|
+
/** A signed-in invitee answers for themselves. */
|
|
2448
|
+
respond: "meet_respond",
|
|
2449
|
+
/** The emailed RSVP link (no account): read and answer. */
|
|
2450
|
+
invitationByToken: "meet_invitation_by_token",
|
|
2451
|
+
respondByToken: "meet_respond_by_token",
|
|
2452
|
+
/** One meeting's occurrences between two instants (INVOKER — the reader's RLS). */
|
|
2453
|
+
meetingOccurrences: "meet_meeting_occurrences",
|
|
2454
|
+
/** Every occurrence the reader can see between two instants — the Upcoming list. */
|
|
2455
|
+
occurrencesBetween: "meet_occurrences_between"
|
|
2310
2456
|
};
|
|
2311
2457
|
var VOID_RPCS = [RPCS.admit, RPCS.recordConsent];
|
|
2312
|
-
var MEETING_COLUMNS = "id,organization_id,room_name,slug,title,kind,host_user_id,scheduled_for,scheduled_duration_minutes,started_at,ended_at,locked,lobby_enabled,recording_policy,ai_enabled,metadata";
|
|
2458
|
+
var MEETING_COLUMNS = "id,organization_id,room_name,slug,title,kind,host_user_id,scheduled_for,scheduled_duration_minutes,started_at,ended_at,locked,lobby_enabled,recording_policy,ai_enabled,metadata,time_zone,agenda,recurrence_rule,join_before_host,cancelled_at,cancellation_reason,deleted_at,calendar_sequence,version";
|
|
2459
|
+
var INVITEE_COLUMNS = "id,meeting_id,invitee_user_id,email,display_name,role,rsvp_state,responded_at,rsvp_note,last_sent_sequence,last_sent_at";
|
|
2313
2460
|
var RECORDING_COLUMNS = "id,meeting_id,state,started_at,ended_at,started_by,file_id,failure_reason";
|
|
2314
2461
|
var SEGMENT_COLUMNS = "id,meeting_id,identity,speaker,text,started_at,is_final,metadata";
|
|
2315
2462
|
var NOTE_COLUMNS = "id,meeting_id,text,kind,assignee_user_id,created_at,metadata";
|
|
@@ -2407,6 +2554,138 @@ function projectNote(row) {
|
|
|
2407
2554
|
source: "record"
|
|
2408
2555
|
};
|
|
2409
2556
|
}
|
|
2557
|
+
var RSVP_STATES = ["needs_action", "accepted", "declined", "tentative"];
|
|
2558
|
+
var OCCURRENCE_STATES = ["scheduled", "moved", "cancelled"];
|
|
2559
|
+
function rsvpState(value) {
|
|
2560
|
+
return RSVP_STATES.includes(value) ? value : "needs_action";
|
|
2561
|
+
}
|
|
2562
|
+
function num(row, key) {
|
|
2563
|
+
const value = row[key];
|
|
2564
|
+
return typeof value === "number" ? value : null;
|
|
2565
|
+
}
|
|
2566
|
+
function when(row, key) {
|
|
2567
|
+
const value = row[key];
|
|
2568
|
+
if (value instanceof Date) return value.toISOString();
|
|
2569
|
+
return text(row, key);
|
|
2570
|
+
}
|
|
2571
|
+
function projectInvitee(row) {
|
|
2572
|
+
const operation = "meet.projectInvitee";
|
|
2573
|
+
const userId = text(row, "invitee_user_id");
|
|
2574
|
+
return {
|
|
2575
|
+
id: requireText(row, "id", operation),
|
|
2576
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
2577
|
+
userId: userId === null ? null : asUserId(userId),
|
|
2578
|
+
email: text(row, "email"),
|
|
2579
|
+
displayName: text(row, "display_name"),
|
|
2580
|
+
role: text(row, "role") === "cohost" ? "cohost" : "invitee",
|
|
2581
|
+
rsvpState: rsvpState(text(row, "rsvp_state")),
|
|
2582
|
+
respondedAt: when(row, "responded_at"),
|
|
2583
|
+
rsvpNote: text(row, "rsvp_note"),
|
|
2584
|
+
lastSentSequence: num(row, "last_sent_sequence"),
|
|
2585
|
+
lastSentAt: when(row, "last_sent_at")
|
|
2586
|
+
};
|
|
2587
|
+
}
|
|
2588
|
+
function projectOccurrence(row) {
|
|
2589
|
+
const operation = "meet.projectOccurrence";
|
|
2590
|
+
const state = text(row, "state");
|
|
2591
|
+
return {
|
|
2592
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
2593
|
+
originalStart: when(row, "original_start") ?? requireText(row, "original_start", operation),
|
|
2594
|
+
occurrenceStart: when(row, "occurrence_start") ?? requireText(row, "occurrence_start", operation),
|
|
2595
|
+
durationMinutes: num(row, "duration_minutes"),
|
|
2596
|
+
// An unknown state is shown as scheduled, never dropped: the row is real.
|
|
2597
|
+
state: OCCURRENCE_STATES.includes(state) ? state : "scheduled",
|
|
2598
|
+
sequence: num(row, "sequence") ?? 0
|
|
2599
|
+
};
|
|
2600
|
+
}
|
|
2601
|
+
function projectUpcoming(row) {
|
|
2602
|
+
const operation = "meet.projectUpcoming";
|
|
2603
|
+
const kind = text(row, "kind");
|
|
2604
|
+
const myRole = text(row, "my_role");
|
|
2605
|
+
const myRsvp = text(row, "my_rsvp");
|
|
2606
|
+
return {
|
|
2607
|
+
...projectOccurrence(row),
|
|
2608
|
+
organizationId: asOrganizationId(requireText(row, "organization_id", operation)),
|
|
2609
|
+
title: text(row, "title") ?? "Meeting",
|
|
2610
|
+
slug: requireText(row, "slug", operation),
|
|
2611
|
+
kind: kind === "scheduled" || kind === "recurring" ? kind : "instant",
|
|
2612
|
+
hostUserId: asUserId(requireText(row, "host_user_id", operation)),
|
|
2613
|
+
timeZone: text(row, "time_zone") ?? "UTC",
|
|
2614
|
+
meetingCancelled: bool(row, "meeting_cancelled", false),
|
|
2615
|
+
myRsvp: myRsvp === null ? null : rsvpState(myRsvp),
|
|
2616
|
+
myRole: myRole === "host" || myRole === "cohost" || myRole === "invitee" ? myRole : null
|
|
2617
|
+
};
|
|
2618
|
+
}
|
|
2619
|
+
function projectOverride(row) {
|
|
2620
|
+
const operation = "meet.projectOverride";
|
|
2621
|
+
return {
|
|
2622
|
+
id: requireText(row, "id", operation),
|
|
2623
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
2624
|
+
originalStart: when(row, "original_start") ?? requireText(row, "original_start", operation),
|
|
2625
|
+
state: text(row, "state") === "moved" ? "moved" : "cancelled",
|
|
2626
|
+
newStart: when(row, "new_start"),
|
|
2627
|
+
newDurationMinutes: num(row, "new_duration_minutes"),
|
|
2628
|
+
reason: text(row, "reason"),
|
|
2629
|
+
calendarSequence: num(row, "calendar_sequence") ?? 0,
|
|
2630
|
+
deletedAt: when(row, "deleted_at")
|
|
2631
|
+
};
|
|
2632
|
+
}
|
|
2633
|
+
function projectRsvpInvitation(body) {
|
|
2634
|
+
const operation = "meet.projectRsvpInvitation";
|
|
2635
|
+
if (typeof body !== "object" || body === null) {
|
|
2636
|
+
throw invalidResponse(operation, "the RSVP door returned no object");
|
|
2637
|
+
}
|
|
2638
|
+
const row = body;
|
|
2639
|
+
if (row["ok"] !== true) {
|
|
2640
|
+
return {
|
|
2641
|
+
ok: false,
|
|
2642
|
+
message: text(row, "message") ?? "This invitation link is no longer valid \u2014 ask the host to send it again."
|
|
2643
|
+
};
|
|
2644
|
+
}
|
|
2645
|
+
const meeting = row["meeting"] ?? {};
|
|
2646
|
+
const invitee = row["invitee"] ?? {};
|
|
2647
|
+
return {
|
|
2648
|
+
ok: true,
|
|
2649
|
+
meeting: {
|
|
2650
|
+
title: text(meeting, "title") ?? "Meeting",
|
|
2651
|
+
slug: requireText(meeting, "slug", operation),
|
|
2652
|
+
scheduledFor: when(meeting, "scheduled_for"),
|
|
2653
|
+
durationMinutes: num(meeting, "duration_minutes"),
|
|
2654
|
+
timeZone: text(meeting, "time_zone") ?? "UTC",
|
|
2655
|
+
agenda: text(meeting, "agenda"),
|
|
2656
|
+
recurrenceRule: text(meeting, "recurrence_rule"),
|
|
2657
|
+
cancelledAt: when(meeting, "cancelled_at"),
|
|
2658
|
+
cancellationReason: text(meeting, "cancellation_reason"),
|
|
2659
|
+
endedAt: when(meeting, "ended_at")
|
|
2660
|
+
},
|
|
2661
|
+
invitee: {
|
|
2662
|
+
displayName: text(invitee, "display_name"),
|
|
2663
|
+
email: text(invitee, "email"),
|
|
2664
|
+
rsvpState: rsvpState(text(invitee, "rsvp_state")),
|
|
2665
|
+
respondedAt: when(invitee, "responded_at")
|
|
2666
|
+
}
|
|
2667
|
+
};
|
|
2668
|
+
}
|
|
2669
|
+
function settingsPatch(settings) {
|
|
2670
|
+
const patch = {};
|
|
2671
|
+
if (settings.lobbyEnabled !== void 0) patch["lobby_enabled"] = settings.lobbyEnabled;
|
|
2672
|
+
if (settings.recordingPolicy !== void 0) patch["recording_policy"] = settings.recordingPolicy;
|
|
2673
|
+
if (settings.aiEnabled !== void 0) patch["ai_enabled"] = settings.aiEnabled;
|
|
2674
|
+
if (settings.joinBeforeHost !== void 0) patch["join_before_host"] = settings.joinBeforeHost;
|
|
2675
|
+
return patch;
|
|
2676
|
+
}
|
|
2677
|
+
function changesPatch(changes) {
|
|
2678
|
+
const patch = settingsPatch(changes);
|
|
2679
|
+
if (changes.title !== void 0) patch["title"] = changes.title;
|
|
2680
|
+
if (changes.agenda !== void 0) patch["agenda"] = changes.agenda;
|
|
2681
|
+
if (changes.timeZone !== void 0) patch["time_zone"] = changes.timeZone;
|
|
2682
|
+
if (changes.scheduledFor !== void 0) patch["scheduled_for"] = changes.scheduledFor;
|
|
2683
|
+
if (changes.scheduledDurationMinutes !== void 0) {
|
|
2684
|
+
patch["scheduled_duration_minutes"] = changes.scheduledDurationMinutes;
|
|
2685
|
+
}
|
|
2686
|
+
if (changes.recurrenceRule !== void 0) patch["recurrence_rule"] = changes.recurrenceRule;
|
|
2687
|
+
return patch;
|
|
2688
|
+
}
|
|
2410
2689
|
function createMeetRepository(options) {
|
|
2411
2690
|
const db = options.client.schema(MEET_SCHEMA);
|
|
2412
2691
|
async function guardVoid(operation, run) {
|
|
@@ -2476,7 +2755,16 @@ function createMeetRepository(options) {
|
|
|
2476
2755
|
lobbyEnabled: bool(row, "lobby_enabled", true),
|
|
2477
2756
|
recordingPolicy: policy === "disabled" || policy === "always-on" ? policy : "host-controlled",
|
|
2478
2757
|
aiEnabled: bool(row, "ai_enabled", true),
|
|
2479
|
-
metadata: typeof metadata === "object" && metadata !== null && !Array.isArray(metadata) ? metadata : {}
|
|
2758
|
+
metadata: typeof metadata === "object" && metadata !== null && !Array.isArray(metadata) ? metadata : {},
|
|
2759
|
+
timeZone: text(row, "time_zone") ?? "UTC",
|
|
2760
|
+
agenda: text(row, "agenda"),
|
|
2761
|
+
recurrenceRule: text(row, "recurrence_rule"),
|
|
2762
|
+
joinBeforeHost: bool(row, "join_before_host", true),
|
|
2763
|
+
cancelledAt: text(row, "cancelled_at"),
|
|
2764
|
+
cancellationReason: text(row, "cancellation_reason"),
|
|
2765
|
+
deletedAt: text(row, "deleted_at"),
|
|
2766
|
+
calendarSequence: typeof row["calendar_sequence"] === "number" ? row["calendar_sequence"] : 0,
|
|
2767
|
+
version: typeof row["version"] === "number" ? row["version"] : 1
|
|
2480
2768
|
};
|
|
2481
2769
|
}
|
|
2482
2770
|
function projectRecording(row) {
|
|
@@ -2563,6 +2851,170 @@ function createMeetRepository(options) {
|
|
|
2563
2851
|
projectNote,
|
|
2564
2852
|
projectRecording,
|
|
2565
2853
|
projectAttendee,
|
|
2854
|
+
projectInvitee,
|
|
2855
|
+
async scheduleMeeting(input) {
|
|
2856
|
+
requireOrg(input.organizationId, "meet.scheduleMeeting");
|
|
2857
|
+
const row = await guard(
|
|
2858
|
+
"meet.scheduleMeeting",
|
|
2859
|
+
() => db.rpc(RPCS.scheduleMeeting, {
|
|
2860
|
+
p_organization_id: input.organizationId,
|
|
2861
|
+
p_host_user_id: input.hostUserId,
|
|
2862
|
+
p_title: input.title,
|
|
2863
|
+
p_scheduled_for: input.scheduledFor,
|
|
2864
|
+
p_time_zone: input.timeZone,
|
|
2865
|
+
p_duration_minutes: input.durationMinutes ?? null,
|
|
2866
|
+
p_agenda: input.agenda ?? null,
|
|
2867
|
+
p_recurrence_rule: input.recurrenceRule ?? null,
|
|
2868
|
+
p_settings: settingsPatch(input.settings ?? {})
|
|
2869
|
+
})
|
|
2870
|
+
);
|
|
2871
|
+
return projectMeeting(row);
|
|
2872
|
+
},
|
|
2873
|
+
async updateMeeting(meetingId, changes, options2 = {}) {
|
|
2874
|
+
const row = await guard(
|
|
2875
|
+
"meet.updateMeeting",
|
|
2876
|
+
() => db.rpc(RPCS.updateMeeting, {
|
|
2877
|
+
p_meeting_id: meetingId,
|
|
2878
|
+
p_changes: changesPatch(changes),
|
|
2879
|
+
p_expected_version: options2.expectedVersion ?? null,
|
|
2880
|
+
p_by_user_id: options2.byUserId ?? null
|
|
2881
|
+
})
|
|
2882
|
+
);
|
|
2883
|
+
return projectMeeting(row);
|
|
2884
|
+
},
|
|
2885
|
+
async cancelMeeting(meetingId, reason, byUserId = null) {
|
|
2886
|
+
const row = await guard(
|
|
2887
|
+
"meet.cancelMeeting",
|
|
2888
|
+
() => db.rpc(RPCS.cancelMeeting, {
|
|
2889
|
+
p_meeting_id: meetingId,
|
|
2890
|
+
p_reason: reason,
|
|
2891
|
+
p_by_user_id: byUserId
|
|
2892
|
+
})
|
|
2893
|
+
);
|
|
2894
|
+
return projectMeeting(row);
|
|
2895
|
+
},
|
|
2896
|
+
async archiveMeeting(meetingId, byUserId = null) {
|
|
2897
|
+
const row = await guard(
|
|
2898
|
+
"meet.archiveMeeting",
|
|
2899
|
+
() => db.rpc(RPCS.archiveMeeting, { p_meeting_id: meetingId, p_by_user_id: byUserId })
|
|
2900
|
+
);
|
|
2901
|
+
return projectMeeting(row);
|
|
2902
|
+
},
|
|
2903
|
+
async restoreMeeting(meetingId, byUserId = null) {
|
|
2904
|
+
const row = await guard(
|
|
2905
|
+
"meet.restoreMeeting",
|
|
2906
|
+
() => db.rpc(RPCS.restoreMeeting, { p_meeting_id: meetingId, p_by_user_id: byUserId })
|
|
2907
|
+
);
|
|
2908
|
+
return projectMeeting(row);
|
|
2909
|
+
},
|
|
2910
|
+
async setOccurrence(args) {
|
|
2911
|
+
const row = await guard(
|
|
2912
|
+
"meet.setOccurrence",
|
|
2913
|
+
() => db.rpc(RPCS.setOccurrence, {
|
|
2914
|
+
p_meeting_id: args.meetingId,
|
|
2915
|
+
p_original_start: args.originalStart,
|
|
2916
|
+
p_action: args.action,
|
|
2917
|
+
p_new_start: args.newStart ?? null,
|
|
2918
|
+
p_new_duration_minutes: args.newDurationMinutes ?? null,
|
|
2919
|
+
p_reason: args.reason ?? null,
|
|
2920
|
+
p_by_user_id: args.byUserId ?? null
|
|
2921
|
+
})
|
|
2922
|
+
);
|
|
2923
|
+
return projectOverride(row);
|
|
2924
|
+
},
|
|
2925
|
+
async meetingOccurrences(meetingId, { from, to, limit = 200 }) {
|
|
2926
|
+
const rows = await guard(
|
|
2927
|
+
"meet.meetingOccurrences",
|
|
2928
|
+
() => db.rpc(RPCS.meetingOccurrences, {
|
|
2929
|
+
p_meeting_id: meetingId,
|
|
2930
|
+
p_from: from,
|
|
2931
|
+
p_to: to,
|
|
2932
|
+
p_limit: limit
|
|
2933
|
+
})
|
|
2934
|
+
);
|
|
2935
|
+
return rows.map(projectOccurrence);
|
|
2936
|
+
},
|
|
2937
|
+
async occurrencesBetween({ from, to, organizationId = null, limit = 500 }) {
|
|
2938
|
+
const rows = await guard(
|
|
2939
|
+
"meet.occurrencesBetween",
|
|
2940
|
+
() => db.rpc(RPCS.occurrencesBetween, {
|
|
2941
|
+
p_from: from,
|
|
2942
|
+
p_to: to,
|
|
2943
|
+
p_organization_id: organizationId,
|
|
2944
|
+
p_limit: limit
|
|
2945
|
+
})
|
|
2946
|
+
);
|
|
2947
|
+
return rows.map(projectUpcoming);
|
|
2948
|
+
},
|
|
2949
|
+
async invitees(meetingId) {
|
|
2950
|
+
const response = await db.from(TABLES.invitees).select(INVITEE_COLUMNS).eq("meeting_id", meetingId).is("deleted_at", null).order("created_at", { ascending: true }).order("id", { ascending: true });
|
|
2951
|
+
if (response.error !== null) throw normalizeMeetError(response.error, "meet.invitees");
|
|
2952
|
+
return (response.data ?? []).map(projectInvitee);
|
|
2953
|
+
},
|
|
2954
|
+
async addInvitees(meetingId, people, byUserId = null) {
|
|
2955
|
+
const rows = await guard(
|
|
2956
|
+
"meet.addInvitees",
|
|
2957
|
+
() => db.rpc(RPCS.addInvitees, {
|
|
2958
|
+
p_meeting_id: meetingId,
|
|
2959
|
+
p_invitees: people.map((person) => ({
|
|
2960
|
+
...person.userId !== void 0 ? { user_id: person.userId } : {},
|
|
2961
|
+
...person.email !== void 0 ? { email: person.email } : {},
|
|
2962
|
+
...person.displayName !== void 0 ? { display_name: person.displayName } : {},
|
|
2963
|
+
...person.role !== void 0 ? { role: person.role } : {}
|
|
2964
|
+
})),
|
|
2965
|
+
p_by_user_id: byUserId
|
|
2966
|
+
})
|
|
2967
|
+
);
|
|
2968
|
+
return rows.map(projectInvitee);
|
|
2969
|
+
},
|
|
2970
|
+
async setInviteeRole(inviteeId, role, byUserId = null) {
|
|
2971
|
+
const row = await guard(
|
|
2972
|
+
"meet.setInviteeRole",
|
|
2973
|
+
() => db.rpc(RPCS.setInviteeRole, {
|
|
2974
|
+
p_invitee_id: inviteeId,
|
|
2975
|
+
p_role: role,
|
|
2976
|
+
p_by_user_id: byUserId
|
|
2977
|
+
})
|
|
2978
|
+
);
|
|
2979
|
+
return projectInvitee(row);
|
|
2980
|
+
},
|
|
2981
|
+
async removeInvitee(inviteeId, byUserId = null) {
|
|
2982
|
+
const row = await guard(
|
|
2983
|
+
"meet.removeInvitee",
|
|
2984
|
+
() => db.rpc(RPCS.removeInvitee, { p_invitee_id: inviteeId, p_by_user_id: byUserId })
|
|
2985
|
+
);
|
|
2986
|
+
return projectInvitee(row);
|
|
2987
|
+
},
|
|
2988
|
+
async respond(meetingId, answer, note = null) {
|
|
2989
|
+
const row = await guard(
|
|
2990
|
+
"meet.respond",
|
|
2991
|
+
() => db.rpc(RPCS.respond, {
|
|
2992
|
+
p_meeting_id: meetingId,
|
|
2993
|
+
p_response: answer,
|
|
2994
|
+
p_note: note,
|
|
2995
|
+
p_by_user_id: null
|
|
2996
|
+
})
|
|
2997
|
+
);
|
|
2998
|
+
return projectInvitee(row);
|
|
2999
|
+
},
|
|
3000
|
+
async invitationByToken(secret) {
|
|
3001
|
+
const body = await guard(
|
|
3002
|
+
"meet.invitationByToken",
|
|
3003
|
+
() => db.rpc(RPCS.invitationByToken, { p_secret: secret })
|
|
3004
|
+
);
|
|
3005
|
+
return projectRsvpInvitation(body);
|
|
3006
|
+
},
|
|
3007
|
+
async respondByToken(secret, answer, note = null) {
|
|
3008
|
+
const body = await guard(
|
|
3009
|
+
"meet.respondByToken",
|
|
3010
|
+
() => db.rpc(RPCS.respondByToken, {
|
|
3011
|
+
p_secret: secret,
|
|
3012
|
+
p_response: answer,
|
|
3013
|
+
p_note: note
|
|
3014
|
+
})
|
|
3015
|
+
);
|
|
3016
|
+
return projectRsvpInvitation(body);
|
|
3017
|
+
},
|
|
2566
3018
|
async attendees(meetingId) {
|
|
2567
3019
|
const response = await db.from(TABLES.participants).select(ATTENDEE_COLUMNS).eq("meeting_id", meetingId).order("joined_at", { ascending: true }).order("id", { ascending: true });
|
|
2568
3020
|
if (response.error !== null) throw normalizeMeetError(response.error, "meet.attendees");
|
|
@@ -2828,7 +3280,19 @@ var ROOM_EVENTS = {
|
|
|
2828
3280
|
* production 2026-09-09 the guest lane showed no indicator at all while the
|
|
2829
3281
|
* host recorded (MRI-C8).
|
|
2830
3282
|
*/
|
|
2831
|
-
recording: "matrx.meet.recording"
|
|
3283
|
+
recording: "matrx.meet.recording",
|
|
3284
|
+
/**
|
|
3285
|
+
* A chat message to everyone was SAVED (Meet wave 2). Payload
|
|
3286
|
+
* `{ message: { id, meeting_id, sender_identity, sender_name, body, created_at } }`.
|
|
3287
|
+
*/
|
|
3288
|
+
chat: "matrx.meet.chat",
|
|
3289
|
+
/**
|
|
3290
|
+
* A host or co-host acted (Meet wave 2). Payload `{ meeting_id, kind, … }`:
|
|
3291
|
+
* `muted {source, by}` · `ask-unmute {by}` · `removed {by}` — addressed to the
|
|
3292
|
+
* person concerned; `lower-hands {identities: [...] | "*"}` · `role {identity,
|
|
3293
|
+
* role}` — to everyone.
|
|
3294
|
+
*/
|
|
3295
|
+
moderation: "matrx.meet.moderation"
|
|
2832
3296
|
};
|
|
2833
3297
|
function isRoomEventTopic(topic) {
|
|
2834
3298
|
return topic.startsWith(ROOM_EVENT_PREFIX);
|
|
@@ -2864,6 +3328,10 @@ function applyRoomEvent(args) {
|
|
|
2864
3328
|
return applyEnded(store, payload);
|
|
2865
3329
|
case ROOM_EVENTS.recording:
|
|
2866
3330
|
return applyRecording(store, payload);
|
|
3331
|
+
case ROOM_EVENTS.chat:
|
|
3332
|
+
return applyChat(store, payload);
|
|
3333
|
+
case ROOM_EVENTS.moderation:
|
|
3334
|
+
return applyModeration(store, payload, now);
|
|
2867
3335
|
default:
|
|
2868
3336
|
return ignored(`${topic} is not known to this version of @ai-matrx/meet`);
|
|
2869
3337
|
}
|
|
@@ -2955,6 +3423,101 @@ function applyEnded(store, payload) {
|
|
|
2955
3423
|
store.patch({ meeting: { ...meeting, endedAt } });
|
|
2956
3424
|
return HANDLED;
|
|
2957
3425
|
}
|
|
3426
|
+
function chatFromRow(row) {
|
|
3427
|
+
const id = text2(row["id"]);
|
|
3428
|
+
const identity = text2(row["sender_identity"]);
|
|
3429
|
+
const body = text2(row["body"]);
|
|
3430
|
+
if (id === null || identity === null || body === null) return null;
|
|
3431
|
+
return {
|
|
3432
|
+
id,
|
|
3433
|
+
identity: asParticipantIdentity(identity),
|
|
3434
|
+
displayName: text2(row["sender_name"]) ?? "Participant",
|
|
3435
|
+
text: body,
|
|
3436
|
+
at: text2(row["created_at"]) ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
3437
|
+
// `false` only from a server whose chat table has not landed: the room heard
|
|
3438
|
+
// it, nobody will see it later — and the line says so.
|
|
3439
|
+
saved: row["saved"] !== false
|
|
3440
|
+
};
|
|
3441
|
+
}
|
|
3442
|
+
function applyChat(store, payload) {
|
|
3443
|
+
const row = object(payload["message"]);
|
|
3444
|
+
if (row === null) return ignored("a chat event with no `message` row");
|
|
3445
|
+
const meeting = store.snapshot().meeting;
|
|
3446
|
+
if (meeting !== null && text2(row["meeting_id"]) !== null && row["meeting_id"] !== meeting.id) {
|
|
3447
|
+
return ignored("a chat message for a different meeting");
|
|
3448
|
+
}
|
|
3449
|
+
const message = chatFromRow(row);
|
|
3450
|
+
if (message === null) return ignored("a chat row with no id, sender or body");
|
|
3451
|
+
store.addChat(message);
|
|
3452
|
+
return HANDLED;
|
|
3453
|
+
}
|
|
3454
|
+
function moderationText(kind, payload) {
|
|
3455
|
+
const by = text2(payload["by"]) ?? "The host";
|
|
3456
|
+
switch (kind) {
|
|
3457
|
+
case "muted": {
|
|
3458
|
+
const source = text2(payload["source"]) ?? "microphone";
|
|
3459
|
+
return source === "microphone" ? `${by} muted you.` : source === "camera" ? `${by} turned off your camera.` : `${by} stopped your screen share.`;
|
|
3460
|
+
}
|
|
3461
|
+
case "ask-unmute":
|
|
3462
|
+
return `${by} is asking you to unmute.`;
|
|
3463
|
+
case "removed":
|
|
3464
|
+
return `${by} removed you from this meeting.`;
|
|
3465
|
+
default:
|
|
3466
|
+
return null;
|
|
3467
|
+
}
|
|
3468
|
+
}
|
|
3469
|
+
function applyModeration(store, payload, now) {
|
|
3470
|
+
const snapshot = store.snapshot();
|
|
3471
|
+
const meetingId = text2(payload["meeting_id"]);
|
|
3472
|
+
if (snapshot.meeting !== null && meetingId !== null && meetingId !== snapshot.meeting.id) {
|
|
3473
|
+
return ignored("a moderation event for a different meeting");
|
|
3474
|
+
}
|
|
3475
|
+
const kind = text2(payload["kind"]);
|
|
3476
|
+
switch (kind) {
|
|
3477
|
+
case "lower-hands": {
|
|
3478
|
+
const raw = payload["identities"];
|
|
3479
|
+
const everyone = raw === "*";
|
|
3480
|
+
const named = new Set(
|
|
3481
|
+
Array.isArray(raw) ? raw.filter((value) => typeof value === "string") : []
|
|
3482
|
+
);
|
|
3483
|
+
for (const participant of snapshot.participants) {
|
|
3484
|
+
if (participant.handRaised && (everyone || named.has(participant.identity))) {
|
|
3485
|
+
store.setHand(participant.identity, false);
|
|
3486
|
+
}
|
|
3487
|
+
}
|
|
3488
|
+
return HANDLED;
|
|
3489
|
+
}
|
|
3490
|
+
case "role": {
|
|
3491
|
+
const identity = text2(payload["identity"]);
|
|
3492
|
+
const role = text2(payload["role"]);
|
|
3493
|
+
if (identity === null || role === null || !ROLES.includes(role)) {
|
|
3494
|
+
return ignored("a role event with no identity or an unknown role");
|
|
3495
|
+
}
|
|
3496
|
+
if (identity === snapshot.localIdentity) {
|
|
3497
|
+
store.patch({
|
|
3498
|
+
grants: { ...snapshot.grants, role },
|
|
3499
|
+
moderationNotice: {
|
|
3500
|
+
kind: "role",
|
|
3501
|
+
text: role === "host" ? "You are now the host." : role === "cohost" ? "You are now a co-host." : "You are no longer a co-host.",
|
|
3502
|
+
at: now()
|
|
3503
|
+
}
|
|
3504
|
+
});
|
|
3505
|
+
}
|
|
3506
|
+
return HANDLED;
|
|
3507
|
+
}
|
|
3508
|
+
case "muted":
|
|
3509
|
+
case "ask-unmute":
|
|
3510
|
+
case "removed": {
|
|
3511
|
+
const sentence = moderationText(kind, payload);
|
|
3512
|
+
if (sentence === null) return ignored("a moderation event with no sentence");
|
|
3513
|
+
store.patch({ moderationNotice: { kind, text: sentence, at: now() } });
|
|
3514
|
+
return HANDLED;
|
|
3515
|
+
}
|
|
3516
|
+
default:
|
|
3517
|
+
return ignored(`a moderation kind this version does not know: ${String(kind)}`);
|
|
3518
|
+
}
|
|
3519
|
+
}
|
|
3520
|
+
var ROLES = ["host", "cohost", "participant", "guest"];
|
|
2958
3521
|
|
|
2959
3522
|
// src/core/note-taker.ts
|
|
2960
3523
|
var NOTE_TAKER_IDENTITY_PREFIX = "agent:note_taker:";
|
|
@@ -2990,14 +3553,14 @@ function noteTakerStatus(args) {
|
|
|
2990
3553
|
const row = typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : {};
|
|
2991
3554
|
const recorded = readState(row["state"]);
|
|
2992
3555
|
const detail = typeof row["reason"] === "string" && row["reason"].length > 0 ? row["reason"] : null;
|
|
2993
|
-
const
|
|
3556
|
+
const refusal2 = args.refusal ?? null;
|
|
2994
3557
|
if (recorded === null) {
|
|
2995
|
-
return { state: "absent", detail, present, refusal };
|
|
3558
|
+
return { state: "absent", detail, present, refusal: refusal2 };
|
|
2996
3559
|
}
|
|
2997
3560
|
if (recorded === "transcribing" && !present) {
|
|
2998
|
-
return { state: "handoff_pending", detail, present, refusal };
|
|
3561
|
+
return { state: "handoff_pending", detail, present, refusal: refusal2 };
|
|
2999
3562
|
}
|
|
3000
|
-
return { state: recorded, detail, present, refusal };
|
|
3563
|
+
return { state: recorded, detail, present, refusal: refusal2 };
|
|
3001
3564
|
}
|
|
3002
3565
|
function noteTakerLabel(status) {
|
|
3003
3566
|
switch (status.state) {
|
|
@@ -3019,8 +3582,7 @@ function noteTakerLabel(status) {
|
|
|
3019
3582
|
var DATA_TOPICS = {
|
|
3020
3583
|
hand: "mx.hand",
|
|
3021
3584
|
reaction: "mx.reaction",
|
|
3022
|
-
chat: "mx.chat"
|
|
3023
|
-
hostAction: "mx.host"
|
|
3585
|
+
chat: "mx.chat"
|
|
3024
3586
|
};
|
|
3025
3587
|
var TRANSCRIPTION_TOPIC = "lk.transcription";
|
|
3026
3588
|
var TRANSCRIPTION_ATTRIBUTES = {
|
|
@@ -3156,7 +3718,37 @@ function createRoomEngine(options) {
|
|
|
3156
3718
|
const handlers = {
|
|
3157
3719
|
participants(list) {
|
|
3158
3720
|
const joinedAt = new Date(now()).toISOString();
|
|
3721
|
+
const before = new Set(store.snapshot().participants.map((p) => p.identity));
|
|
3159
3722
|
store.setParticipants(list.map((entry) => projectParticipant(entry, joinedAt)));
|
|
3723
|
+
const snapshot = store.snapshot();
|
|
3724
|
+
const newcomer = snapshot.participants.some(
|
|
3725
|
+
(participant) => !participant.isLocal && !before.has(participant.identity)
|
|
3726
|
+
);
|
|
3727
|
+
const me = snapshot.participants.find((p) => p.identity === snapshot.localIdentity);
|
|
3728
|
+
if (newcomer && before.size > 0 && me?.handRaised === true) {
|
|
3729
|
+
void publish(DATA_TOPICS.hand, { raised: true, raisedAt: me.handRaisedAt ?? now() }).catch(
|
|
3730
|
+
() => void 0
|
|
3731
|
+
);
|
|
3732
|
+
}
|
|
3733
|
+
},
|
|
3734
|
+
roomMetadata(metadata) {
|
|
3735
|
+
let spotlight = null;
|
|
3736
|
+
if (metadata.length > 0) {
|
|
3737
|
+
try {
|
|
3738
|
+
const parsed = JSON.parse(metadata);
|
|
3739
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
3740
|
+
const value = parsed.spotlight;
|
|
3741
|
+
if (typeof value === "string" && value.length > 0) spotlight = asParticipantIdentity(value);
|
|
3742
|
+
}
|
|
3743
|
+
} catch {
|
|
3744
|
+
report(
|
|
3745
|
+
"warn",
|
|
3746
|
+
"meet: the room's metadata is not JSON, so no spotlight was read from it",
|
|
3747
|
+
"Only aidream writes room metadata; check services/meet/moderation.py."
|
|
3748
|
+
);
|
|
3749
|
+
}
|
|
3750
|
+
}
|
|
3751
|
+
if (store.snapshot().spotlight !== spotlight) store.patch({ spotlight });
|
|
3160
3752
|
},
|
|
3161
3753
|
activeSpeakers(identities) {
|
|
3162
3754
|
store.setSpeaking(identities.map(asParticipantIdentity));
|
|
@@ -3260,7 +3852,11 @@ function createRoomEngine(options) {
|
|
|
3260
3852
|
const sender = asParticipantIdentity(from ?? "");
|
|
3261
3853
|
switch (topic) {
|
|
3262
3854
|
case DATA_TOPICS.hand:
|
|
3263
|
-
store.setHand(
|
|
3855
|
+
store.setHand(
|
|
3856
|
+
sender,
|
|
3857
|
+
envelope.raised === true,
|
|
3858
|
+
typeof envelope.raisedAt === "number" ? envelope.raisedAt : now()
|
|
3859
|
+
);
|
|
3264
3860
|
return;
|
|
3265
3861
|
case DATA_TOPICS.reaction:
|
|
3266
3862
|
if (typeof envelope.emoji === "string") {
|
|
@@ -3274,12 +3870,14 @@ function createRoomEngine(options) {
|
|
|
3274
3870
|
return;
|
|
3275
3871
|
case DATA_TOPICS.chat:
|
|
3276
3872
|
if (typeof envelope.text === "string") {
|
|
3873
|
+
const local = store.snapshot().localIdentity;
|
|
3277
3874
|
store.addChat({
|
|
3278
3875
|
id: envelope.id ?? `${sender}-${now()}`,
|
|
3279
3876
|
identity: sender,
|
|
3280
3877
|
displayName: envelope.name ?? sender,
|
|
3281
3878
|
text: envelope.text,
|
|
3282
|
-
at: envelope.at ?? new Date(now()).toISOString()
|
|
3879
|
+
at: envelope.at ?? new Date(now()).toISOString(),
|
|
3880
|
+
...typeof envelope.to === "string" && local !== null ? { to: local } : {}
|
|
3283
3881
|
});
|
|
3284
3882
|
}
|
|
3285
3883
|
return;
|
|
@@ -3325,6 +3923,24 @@ function createRoomEngine(options) {
|
|
|
3325
3923
|
store.patch({ phase: "connected", failure: null });
|
|
3326
3924
|
store.setLocalTrack("microphone", IDLE_TRACK);
|
|
3327
3925
|
store.setLocalTrack("camera", IDLE_TRACK);
|
|
3926
|
+
const effects = options.effectsStorage?.load() ?? store.snapshot().effects;
|
|
3927
|
+
store.patch({ effects });
|
|
3928
|
+
if (!effects.noiseSuppression || effects.background !== "none") {
|
|
3929
|
+
try {
|
|
3930
|
+
await applyEffects(created, effects);
|
|
3931
|
+
} catch {
|
|
3932
|
+
}
|
|
3933
|
+
}
|
|
3934
|
+
for (const kind of ["audioinput", "videoinput", "audiooutput"]) {
|
|
3935
|
+
const chosen = options.resolveDevice?.(kind) ?? null;
|
|
3936
|
+
if (chosen === null || chosen === "default") continue;
|
|
3937
|
+
try {
|
|
3938
|
+
await created.switchDevice(kind, chosen);
|
|
3939
|
+
} catch (error) {
|
|
3940
|
+
const normalized = normalizeDeviceError(error, `meet.switchDevice(${kind})`);
|
|
3941
|
+
report("warn", normalized.message, normalized.remedy);
|
|
3942
|
+
}
|
|
3943
|
+
}
|
|
3328
3944
|
if (request.mode === "video") {
|
|
3329
3945
|
await enableTrack(created, "camera", true);
|
|
3330
3946
|
}
|
|
@@ -3347,9 +3963,30 @@ function createRoomEngine(options) {
|
|
|
3347
3963
|
return state;
|
|
3348
3964
|
}
|
|
3349
3965
|
}
|
|
3350
|
-
async function publish(topic, body) {
|
|
3966
|
+
async function publish(topic, body, to) {
|
|
3351
3967
|
if (driver === null) return;
|
|
3352
|
-
await driver.publishData(topic, JSON.stringify(body));
|
|
3968
|
+
await driver.publishData(topic, JSON.stringify(body), to);
|
|
3969
|
+
}
|
|
3970
|
+
async function applyEffects(target, effects) {
|
|
3971
|
+
try {
|
|
3972
|
+
await target.setAudioProcessing?.({
|
|
3973
|
+
noiseSuppression: effects.noiseSuppression,
|
|
3974
|
+
echoCancellation: true,
|
|
3975
|
+
autoGainControl: true
|
|
3976
|
+
});
|
|
3977
|
+
} catch (error) {
|
|
3978
|
+
report("warn", `meet: noise suppression could not be applied: ${String(error)}`, "The microphone keeps working without it.");
|
|
3979
|
+
}
|
|
3980
|
+
if (target.setVideoEffect === void 0) return;
|
|
3981
|
+
const effect = effects.background === "blur" ? { kind: "blur" } : effects.background === "image" && effects.backgroundImage !== null ? { kind: "image", imageUrl: effects.backgroundImage } : { kind: "none" };
|
|
3982
|
+
if (effect.kind !== "none" && target.supportsVideoEffects?.() === false) return;
|
|
3983
|
+
try {
|
|
3984
|
+
await target.setVideoEffect(effect);
|
|
3985
|
+
} catch (error) {
|
|
3986
|
+
const normalized = normalizeMeetError(error, "meet.setVideoEffect");
|
|
3987
|
+
report("warn", normalized.message, normalized.remedy);
|
|
3988
|
+
throw normalized;
|
|
3989
|
+
}
|
|
3353
3990
|
}
|
|
3354
3991
|
return {
|
|
3355
3992
|
join(request) {
|
|
@@ -3405,7 +4042,7 @@ function createRoomEngine(options) {
|
|
|
3405
4042
|
async raiseHand(raised) {
|
|
3406
4043
|
const local = store.snapshot().localIdentity;
|
|
3407
4044
|
if (local !== null) store.setHand(local, raised);
|
|
3408
|
-
await publish(DATA_TOPICS.hand, { raised });
|
|
4045
|
+
await publish(DATA_TOPICS.hand, { raised, raisedAt: now() });
|
|
3409
4046
|
},
|
|
3410
4047
|
async react(emoji) {
|
|
3411
4048
|
const local = store.snapshot().localIdentity;
|
|
@@ -3415,32 +4052,41 @@ function createRoomEngine(options) {
|
|
|
3415
4052
|
}
|
|
3416
4053
|
await publish(DATA_TOPICS.reaction, { id, emoji });
|
|
3417
4054
|
},
|
|
3418
|
-
async
|
|
4055
|
+
async sendPrivateChat(to, text3) {
|
|
3419
4056
|
const trimmed = text3.trim();
|
|
3420
4057
|
if (trimmed.length === 0) return;
|
|
3421
4058
|
const local = store.snapshot().localIdentity;
|
|
3422
4059
|
const id = `${local ?? "local"}-${now()}`;
|
|
3423
4060
|
const at = new Date(now()).toISOString();
|
|
3424
4061
|
if (local !== null) {
|
|
3425
|
-
store.addChat({
|
|
3426
|
-
id,
|
|
3427
|
-
identity: local,
|
|
3428
|
-
displayName: identity.displayName,
|
|
3429
|
-
text: trimmed,
|
|
3430
|
-
at
|
|
3431
|
-
});
|
|
4062
|
+
store.addChat({ id, identity: local, displayName: identity.displayName, text: trimmed, at, to });
|
|
3432
4063
|
}
|
|
3433
|
-
await publish(
|
|
4064
|
+
await publish(
|
|
4065
|
+
DATA_TOPICS.chat,
|
|
4066
|
+
{ id, text: trimmed, at, name: identity.displayName, to },
|
|
4067
|
+
[to]
|
|
4068
|
+
);
|
|
3434
4069
|
},
|
|
3435
|
-
async
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
4070
|
+
async setEffects(next) {
|
|
4071
|
+
const previous = store.snapshot().effects;
|
|
4072
|
+
const merged = { ...previous, ...next };
|
|
4073
|
+
store.patch({ effects: merged });
|
|
4074
|
+
options.effectsStorage?.save(merged);
|
|
4075
|
+
if (driver === null) return;
|
|
4076
|
+
try {
|
|
4077
|
+
await applyEffects(driver, merged);
|
|
4078
|
+
} catch {
|
|
4079
|
+
const reverted = { ...merged, background: previous.background, backgroundImage: previous.backgroundImage };
|
|
4080
|
+
store.patch({ effects: reverted });
|
|
4081
|
+
options.effectsStorage?.save(reverted);
|
|
3442
4082
|
}
|
|
3443
|
-
|
|
4083
|
+
},
|
|
4084
|
+
supportsVideoEffects() {
|
|
4085
|
+
return driver?.supportsVideoEffects?.() ?? false;
|
|
4086
|
+
},
|
|
4087
|
+
async switchDevice(kind, deviceId2) {
|
|
4088
|
+
if (driver === null) return;
|
|
4089
|
+
await driver.switchDevice(kind, deviceId2);
|
|
3444
4090
|
},
|
|
3445
4091
|
pin(who) {
|
|
3446
4092
|
store.patch({ pinned: who, layout: who === null ? "grid" : "speaker" });
|
|
@@ -3471,58 +4117,281 @@ function hostUserIds(participants) {
|
|
|
3471
4117
|
return participants.filter((participant) => participant.role === "host" && participant.userId !== null).map((participant) => participant.userId);
|
|
3472
4118
|
}
|
|
3473
4119
|
|
|
3474
|
-
// src/core/
|
|
3475
|
-
function
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
default:
|
|
3486
|
-
return "unknown";
|
|
3487
|
-
}
|
|
3488
|
-
}
|
|
3489
|
-
function mapConnectionState(state) {
|
|
3490
|
-
switch (state) {
|
|
3491
|
-
case "connected":
|
|
3492
|
-
return "connected";
|
|
3493
|
-
case "connecting":
|
|
3494
|
-
return "connecting";
|
|
3495
|
-
case "reconnecting":
|
|
3496
|
-
case "signalReconnecting":
|
|
3497
|
-
return "reconnecting";
|
|
3498
|
-
default:
|
|
3499
|
-
return "disconnected";
|
|
3500
|
-
}
|
|
4120
|
+
// src/core/effects.ts
|
|
4121
|
+
function coerce(value) {
|
|
4122
|
+
if (typeof value !== "object" || value === null) return DEFAULT_EFFECTS;
|
|
4123
|
+
const row = value;
|
|
4124
|
+
const background = row["background"] === "blur" || row["background"] === "image" ? row["background"] : "none";
|
|
4125
|
+
const backgroundImage = typeof row["backgroundImage"] === "string" ? row["backgroundImage"] : null;
|
|
4126
|
+
return {
|
|
4127
|
+
noiseSuppression: row["noiseSuppression"] !== false,
|
|
4128
|
+
background: background === "image" && backgroundImage === null ? "none" : background,
|
|
4129
|
+
backgroundImage
|
|
4130
|
+
};
|
|
3501
4131
|
}
|
|
3502
|
-
function
|
|
3503
|
-
|
|
4132
|
+
function createWebEffectsStorage(key = "mx.meet.effects") {
|
|
4133
|
+
let memory = DEFAULT_EFFECTS;
|
|
4134
|
+
const storage = globalThis.localStorage;
|
|
3504
4135
|
return {
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
4136
|
+
load() {
|
|
4137
|
+
if (storage === void 0) return memory;
|
|
4138
|
+
try {
|
|
4139
|
+
const raw = storage.getItem(key);
|
|
4140
|
+
return raw === null ? memory : coerce(JSON.parse(raw));
|
|
4141
|
+
} catch {
|
|
4142
|
+
return memory;
|
|
4143
|
+
}
|
|
4144
|
+
},
|
|
4145
|
+
save(effects) {
|
|
4146
|
+
memory = effects;
|
|
4147
|
+
try {
|
|
4148
|
+
storage?.setItem(key, JSON.stringify(effects));
|
|
4149
|
+
} catch {
|
|
4150
|
+
}
|
|
4151
|
+
}
|
|
3516
4152
|
};
|
|
3517
4153
|
}
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
4154
|
+
|
|
4155
|
+
// src/core/in-room.ts
|
|
4156
|
+
var MODERATION_ACTIONS = [
|
|
4157
|
+
"mute",
|
|
4158
|
+
"stop_video",
|
|
4159
|
+
"stop_screen",
|
|
4160
|
+
"mute_all",
|
|
4161
|
+
"ask_unmute",
|
|
4162
|
+
"ask_all_unmute",
|
|
4163
|
+
"spotlight",
|
|
4164
|
+
"lower_hand",
|
|
4165
|
+
"lower_all_hands",
|
|
4166
|
+
"make_cohost",
|
|
4167
|
+
"revoke_cohost",
|
|
4168
|
+
"transfer_host",
|
|
4169
|
+
"rename",
|
|
4170
|
+
"remove",
|
|
4171
|
+
"report"
|
|
4172
|
+
];
|
|
4173
|
+
function createInRoomClient(options) {
|
|
4174
|
+
const { api, store } = options;
|
|
4175
|
+
const now = options.now ?? (() => Date.now());
|
|
4176
|
+
function meetingId(operation) {
|
|
4177
|
+
const meeting = store.snapshot().meeting;
|
|
4178
|
+
if (meeting === null) {
|
|
4179
|
+
throw new MeetError(
|
|
4180
|
+
"not-found",
|
|
4181
|
+
`${operation}: this room has no meeting record`,
|
|
4182
|
+
"Moderation and saved chat belong to a meeting. An ad-hoc call has neither."
|
|
4183
|
+
);
|
|
4184
|
+
}
|
|
4185
|
+
return meeting.id;
|
|
4186
|
+
}
|
|
4187
|
+
function headers() {
|
|
4188
|
+
const token = options.resolveRoomToken();
|
|
4189
|
+
return token === null ? void 0 : { [ROOM_TOKEN_HEADER]: token };
|
|
4190
|
+
}
|
|
4191
|
+
async function refusal2(response, operation, remedy) {
|
|
4192
|
+
let body = null;
|
|
4193
|
+
try {
|
|
4194
|
+
body = await response.json();
|
|
4195
|
+
} catch {
|
|
4196
|
+
body = null;
|
|
4197
|
+
}
|
|
4198
|
+
const said = readServerRefusal(body);
|
|
4199
|
+
return new MeetError(
|
|
4200
|
+
response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport",
|
|
4201
|
+
said?.reason ?? `${operation}: the server answered ${response.status}`,
|
|
4202
|
+
said?.remedy !== void 0 && said.remedy.length > 0 ? said.remedy : remedy
|
|
4203
|
+
);
|
|
4204
|
+
}
|
|
4205
|
+
return {
|
|
4206
|
+
async moderate(request) {
|
|
4207
|
+
const operation = `meet.moderate(${request.action})`;
|
|
4208
|
+
const hdrs = headers();
|
|
4209
|
+
const response = await api.request({
|
|
4210
|
+
path: MEET_ROUTES.moderate,
|
|
4211
|
+
method: "POST",
|
|
4212
|
+
operation,
|
|
4213
|
+
body: {
|
|
4214
|
+
meeting_id: meetingId(operation),
|
|
4215
|
+
action: request.action,
|
|
4216
|
+
...request.identity != null ? { identity: request.identity } : {},
|
|
4217
|
+
...request.name !== void 0 ? { name: request.name } : {},
|
|
4218
|
+
...request.reason !== void 0 ? { reason: request.reason } : {}
|
|
4219
|
+
},
|
|
4220
|
+
...hdrs !== void 0 ? { headers: hdrs } : {}
|
|
4221
|
+
});
|
|
4222
|
+
if (!response.ok) {
|
|
4223
|
+
throw await refusal2(
|
|
4224
|
+
response,
|
|
4225
|
+
operation,
|
|
4226
|
+
"Nothing was changed. The server decides host powers from the meeting itself."
|
|
4227
|
+
);
|
|
4228
|
+
}
|
|
4229
|
+
const payload = await response.json();
|
|
4230
|
+
return {
|
|
4231
|
+
action: request.action,
|
|
4232
|
+
affected: Array.isArray(payload["affected"]) ? payload["affected"].filter((value) => typeof value === "string") : [],
|
|
4233
|
+
message: typeof payload["message"] === "string" ? payload["message"] : "Done."
|
|
4234
|
+
};
|
|
4235
|
+
},
|
|
4236
|
+
async sendChat(text3) {
|
|
4237
|
+
const body = text3.trim();
|
|
4238
|
+
if (body.length === 0) return;
|
|
4239
|
+
const operation = "meet.sendChat";
|
|
4240
|
+
const id = meetingId(operation);
|
|
4241
|
+
const snapshot = store.snapshot();
|
|
4242
|
+
const me = snapshot.participants.find((p) => p.identity === snapshot.localIdentity);
|
|
4243
|
+
const localId = `pending-${now()}`;
|
|
4244
|
+
const pending = {
|
|
4245
|
+
id: localId,
|
|
4246
|
+
identity: snapshot.localIdentity ?? "local",
|
|
4247
|
+
displayName: me?.displayName ?? "You",
|
|
4248
|
+
text: body,
|
|
4249
|
+
at: new Date(now()).toISOString(),
|
|
4250
|
+
saved: false
|
|
4251
|
+
};
|
|
4252
|
+
store.addChat(pending);
|
|
4253
|
+
const hdrs = headers();
|
|
4254
|
+
let response;
|
|
4255
|
+
try {
|
|
4256
|
+
response = await api.request({
|
|
4257
|
+
path: MEET_ROUTES.chat,
|
|
4258
|
+
method: "POST",
|
|
4259
|
+
operation,
|
|
4260
|
+
body: { meeting_id: id, body },
|
|
4261
|
+
...hdrs !== void 0 ? { headers: hdrs } : {}
|
|
4262
|
+
});
|
|
4263
|
+
} catch (error) {
|
|
4264
|
+
store.settleChat(localId, { failed: "Not sent \u2014 the server could not be reached." });
|
|
4265
|
+
throw error;
|
|
4266
|
+
}
|
|
4267
|
+
if (!response.ok) {
|
|
4268
|
+
const error = await refusal2(response, operation, "Your message was not sent.");
|
|
4269
|
+
store.settleChat(localId, { failed: error.message });
|
|
4270
|
+
throw error;
|
|
4271
|
+
}
|
|
4272
|
+
const saved = chatFromRow(await response.json());
|
|
4273
|
+
if (saved === null) {
|
|
4274
|
+
store.settleChat(localId, { failed: "The server's answer could not be read." });
|
|
4275
|
+
return;
|
|
4276
|
+
}
|
|
4277
|
+
store.settleChat(localId, saved);
|
|
4278
|
+
},
|
|
4279
|
+
async loadChat() {
|
|
4280
|
+
const operation = "meet.loadChat";
|
|
4281
|
+
const id = meetingId(operation);
|
|
4282
|
+
const hdrs = headers();
|
|
4283
|
+
const response = await api.request({
|
|
4284
|
+
path: `${MEET_ROUTES.chat}?meeting_id=${encodeURIComponent(id)}`,
|
|
4285
|
+
method: "GET",
|
|
4286
|
+
operation,
|
|
4287
|
+
...hdrs !== void 0 ? { headers: hdrs } : {}
|
|
4288
|
+
});
|
|
4289
|
+
if (!response.ok) {
|
|
4290
|
+
throw await refusal2(
|
|
4291
|
+
response,
|
|
4292
|
+
operation,
|
|
4293
|
+
"Messages sent before you joined could not be read. New messages still arrive."
|
|
4294
|
+
);
|
|
4295
|
+
}
|
|
4296
|
+
const payload = await response.json();
|
|
4297
|
+
const rows = Array.isArray(payload.messages) ? payload.messages : [];
|
|
4298
|
+
const messages = rows.map(
|
|
4299
|
+
(row) => typeof row === "object" && row !== null ? chatFromRow(row) : null
|
|
4300
|
+
).filter((message) => message !== null);
|
|
4301
|
+
store.loadChat(messages);
|
|
4302
|
+
}
|
|
4303
|
+
};
|
|
4304
|
+
}
|
|
4305
|
+
|
|
4306
|
+
// src/core/livekit-driver.ts
|
|
4307
|
+
function mapQuality(quality) {
|
|
4308
|
+
switch (quality) {
|
|
4309
|
+
case "excellent":
|
|
4310
|
+
return "excellent";
|
|
4311
|
+
case "good":
|
|
4312
|
+
return "good";
|
|
4313
|
+
case "poor":
|
|
4314
|
+
return "poor";
|
|
4315
|
+
case "lost":
|
|
4316
|
+
return "lost";
|
|
4317
|
+
default:
|
|
4318
|
+
return "unknown";
|
|
4319
|
+
}
|
|
4320
|
+
}
|
|
4321
|
+
function mapConnectionState(state) {
|
|
4322
|
+
switch (state) {
|
|
4323
|
+
case "connected":
|
|
4324
|
+
return "connected";
|
|
4325
|
+
case "connecting":
|
|
4326
|
+
return "connecting";
|
|
4327
|
+
case "reconnecting":
|
|
4328
|
+
case "signalReconnecting":
|
|
4329
|
+
return "reconnecting";
|
|
4330
|
+
default:
|
|
4331
|
+
return "disconnected";
|
|
4332
|
+
}
|
|
4333
|
+
}
|
|
4334
|
+
function snapshotParticipant(participant, isLocal) {
|
|
4335
|
+
const name = participant.name;
|
|
4336
|
+
return {
|
|
4337
|
+
identity: participant.identity,
|
|
4338
|
+
name: name ?? "",
|
|
4339
|
+
metadata: participant.metadata ?? null,
|
|
4340
|
+
// ATTRIBUTES ARE PART OF THE SNAPSHOT, not read ad hoc later: the note-taker
|
|
4341
|
+
// announces the consent notice here (D10) and the roster is the only place
|
|
4342
|
+
// this package looks at a participant.
|
|
4343
|
+
attributes: participant.attributes ?? {},
|
|
4344
|
+
isLocal,
|
|
4345
|
+
micEnabled: participant.isMicrophoneEnabled,
|
|
4346
|
+
cameraEnabled: participant.isCameraEnabled,
|
|
4347
|
+
screenSharing: participant.isScreenShareEnabled
|
|
4348
|
+
};
|
|
4349
|
+
}
|
|
4350
|
+
var DEFAULT_AUDIO_PROCESSING = {
|
|
4351
|
+
noiseSuppression: true,
|
|
4352
|
+
echoCancellation: true,
|
|
4353
|
+
autoGainControl: true
|
|
4354
|
+
};
|
|
4355
|
+
function videoEffectsSupported() {
|
|
4356
|
+
const scope = globalThis;
|
|
4357
|
+
if (typeof scope.OffscreenCanvas === "undefined" || scope.document === void 0) return false;
|
|
4358
|
+
try {
|
|
4359
|
+
return scope.document.createElement("canvas").getContext("webgl2") !== null;
|
|
4360
|
+
} catch {
|
|
4361
|
+
return false;
|
|
4362
|
+
}
|
|
4363
|
+
}
|
|
4364
|
+
async function applyVideoEffect(target, effect) {
|
|
4365
|
+
const { Track } = await import("livekit-client");
|
|
4366
|
+
const track = target.localParticipant.getTrackPublication(Track.Source.Camera)?.track;
|
|
4367
|
+
if (track === void 0) return;
|
|
4368
|
+
try {
|
|
4369
|
+
if (effect.kind === "none") {
|
|
4370
|
+
await track.stopProcessor();
|
|
4371
|
+
return;
|
|
4372
|
+
}
|
|
4373
|
+
const processors = await import("@livekit/track-processors");
|
|
4374
|
+
const processor = effect.kind === "blur" ? processors.BackgroundBlur(effect.radius ?? 12) : processors.VirtualBackground(effect.imageUrl);
|
|
4375
|
+
await track.setProcessor(processor);
|
|
4376
|
+
} catch (error) {
|
|
4377
|
+
throw new MeetError(
|
|
4378
|
+
"media-unsupported",
|
|
4379
|
+
"The background effect could not start on this camera",
|
|
4380
|
+
"Background effects need a browser with WebGL2 (current Chrome, Edge, Firefox or Safari). Your video continues without the effect.",
|
|
4381
|
+
error
|
|
4382
|
+
);
|
|
4383
|
+
}
|
|
4384
|
+
}
|
|
4385
|
+
var encoder = new TextEncoder();
|
|
4386
|
+
var decoder = new TextDecoder();
|
|
4387
|
+
function createLiveKitDriver(handlers, options = {}) {
|
|
4388
|
+
let room = null;
|
|
4389
|
+
let projectRoster = null;
|
|
4390
|
+
let audioCapture = DEFAULT_AUDIO_PROCESSING;
|
|
4391
|
+
let videoEffect = { kind: "none" };
|
|
4392
|
+
async function load() {
|
|
4393
|
+
if (room !== null) return room;
|
|
4394
|
+
let module2;
|
|
3526
4395
|
try {
|
|
3527
4396
|
module2 = await import("livekit-client");
|
|
3528
4397
|
} catch (error) {
|
|
@@ -3548,7 +4417,9 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
3548
4417
|
handlers.participants([local, ...remotes]);
|
|
3549
4418
|
};
|
|
3550
4419
|
projectRoster = roster;
|
|
3551
|
-
created.on(RoomEvent.ParticipantConnected, roster).on(RoomEvent.ParticipantDisconnected, roster).on(RoomEvent.TrackSubscribed, roster).on(RoomEvent.TrackUnsubscribed, roster).on(RoomEvent.TrackMuted, roster).on(RoomEvent.TrackUnmuted, roster).on(RoomEvent.LocalTrackPublished, roster).on(RoomEvent.LocalTrackUnpublished, roster).on(RoomEvent.
|
|
4420
|
+
created.on(RoomEvent.ParticipantConnected, roster).on(RoomEvent.ParticipantDisconnected, roster).on(RoomEvent.TrackSubscribed, roster).on(RoomEvent.TrackUnsubscribed, roster).on(RoomEvent.TrackMuted, roster).on(RoomEvent.TrackUnmuted, roster).on(RoomEvent.LocalTrackPublished, roster).on(RoomEvent.LocalTrackUnpublished, roster).on(RoomEvent.ParticipantNameChanged, roster).on(RoomEvent.ParticipantAttributesChanged, roster).on(RoomEvent.ParticipantMetadataChanged, roster).on(RoomEvent.RoomMetadataChanged, (metadata) => {
|
|
4421
|
+
handlers.roomMetadata?.(metadata ?? "");
|
|
4422
|
+
}).on(RoomEvent.ActiveSpeakersChanged, (speakers) => {
|
|
3552
4423
|
handlers.activeSpeakers(speakers.map((speaker) => speaker.identity));
|
|
3553
4424
|
}).on(RoomEvent.ConnectionQualityChanged, (quality, participant) => {
|
|
3554
4425
|
handlers.quality(participant.identity, mapQuality(quality));
|
|
@@ -3625,6 +4496,7 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
3625
4496
|
throw normalizeDeviceError(error, "meet.connect");
|
|
3626
4497
|
}
|
|
3627
4498
|
projectRoster?.();
|
|
4499
|
+
handlers.roomMetadata?.(target.metadata ?? "");
|
|
3628
4500
|
},
|
|
3629
4501
|
async disconnect() {
|
|
3630
4502
|
if (room === null) return;
|
|
@@ -3639,14 +4511,19 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
3639
4511
|
},
|
|
3640
4512
|
async setMicrophoneEnabled(enabled) {
|
|
3641
4513
|
try {
|
|
3642
|
-
await requireRoom("setMicrophoneEnabled").localParticipant.setMicrophoneEnabled(
|
|
4514
|
+
await requireRoom("setMicrophoneEnabled").localParticipant.setMicrophoneEnabled(
|
|
4515
|
+
enabled,
|
|
4516
|
+
audioCapture
|
|
4517
|
+
);
|
|
3643
4518
|
} catch (error) {
|
|
3644
4519
|
throw normalizeDeviceError(error, "meet.setMicrophoneEnabled");
|
|
3645
4520
|
}
|
|
3646
4521
|
},
|
|
3647
4522
|
async setCameraEnabled(enabled) {
|
|
3648
4523
|
try {
|
|
3649
|
-
|
|
4524
|
+
const target = requireRoom("setCameraEnabled");
|
|
4525
|
+
await target.localParticipant.setCameraEnabled(enabled);
|
|
4526
|
+
if (enabled && videoEffect.kind !== "none") await applyVideoEffect(target, videoEffect);
|
|
3650
4527
|
} catch (error) {
|
|
3651
4528
|
throw normalizeDeviceError(error, "meet.setCameraEnabled");
|
|
3652
4529
|
}
|
|
@@ -3658,14 +4535,40 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
3658
4535
|
throw normalizeDeviceError(error, "meet.setScreenShareEnabled");
|
|
3659
4536
|
}
|
|
3660
4537
|
},
|
|
3661
|
-
async publishData(topic, payload) {
|
|
4538
|
+
async publishData(topic, payload, to) {
|
|
3662
4539
|
await requireRoom("publishData").localParticipant.publishData(
|
|
3663
4540
|
// `reliable` is not optional for a control plane: a dropped
|
|
3664
4541
|
// "hand lowered" leaves a hand up on every other screen forever.
|
|
3665
4542
|
encoder.encode(payload),
|
|
3666
|
-
{ reliable: true, topic }
|
|
4543
|
+
to === void 0 || to.length === 0 ? { reliable: true, topic } : { reliable: true, topic, destinationIdentities: [...to] }
|
|
3667
4544
|
);
|
|
3668
4545
|
},
|
|
4546
|
+
async setAudioProcessing(processing) {
|
|
4547
|
+
audioCapture = { ...audioCapture, ...processing };
|
|
4548
|
+
const target = room;
|
|
4549
|
+
if (target === null) return;
|
|
4550
|
+
const { Track } = await import("livekit-client");
|
|
4551
|
+
const publication = target.localParticipant.getTrackPublication(Track.Source.Microphone);
|
|
4552
|
+
const track = publication?.track;
|
|
4553
|
+
if (track === void 0 || publication?.isMuted === true) return;
|
|
4554
|
+
try {
|
|
4555
|
+
await track.restartTrack({
|
|
4556
|
+
...audioCapture,
|
|
4557
|
+
...target.getActiveDevice("audioinput") !== void 0 ? { deviceId: target.getActiveDevice("audioinput") } : {}
|
|
4558
|
+
});
|
|
4559
|
+
} catch (error) {
|
|
4560
|
+
throw normalizeDeviceError(error, "meet.setAudioProcessing");
|
|
4561
|
+
}
|
|
4562
|
+
},
|
|
4563
|
+
async setVideoEffect(effect) {
|
|
4564
|
+
videoEffect = effect;
|
|
4565
|
+
const target = room;
|
|
4566
|
+
if (target === null) return;
|
|
4567
|
+
await applyVideoEffect(target, effect);
|
|
4568
|
+
},
|
|
4569
|
+
supportsVideoEffects() {
|
|
4570
|
+
return videoEffectsSupported();
|
|
4571
|
+
},
|
|
3669
4572
|
attach(identity, kind, element) {
|
|
3670
4573
|
const target = requireRoom("attach");
|
|
3671
4574
|
const source = sources?.[kind];
|
|
@@ -3699,6 +4602,52 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
3699
4602
|
}
|
|
3700
4603
|
};
|
|
3701
4604
|
}
|
|
4605
|
+
async function createCameraPreview(deviceId2, effect) {
|
|
4606
|
+
let module2;
|
|
4607
|
+
try {
|
|
4608
|
+
module2 = await import("livekit-client");
|
|
4609
|
+
} catch (error) {
|
|
4610
|
+
throw new MeetError(
|
|
4611
|
+
"misconfigured",
|
|
4612
|
+
"livekit-client could not be loaded for the camera preview",
|
|
4613
|
+
"The bundler excluded `livekit-client`; remove it from any `external` list.",
|
|
4614
|
+
error
|
|
4615
|
+
);
|
|
4616
|
+
}
|
|
4617
|
+
let track;
|
|
4618
|
+
try {
|
|
4619
|
+
track = await module2.createLocalVideoTrack(deviceId2 === null ? {} : { deviceId: deviceId2 });
|
|
4620
|
+
} catch (error) {
|
|
4621
|
+
throw normalizeDeviceError(error, "meet.cameraPreview");
|
|
4622
|
+
}
|
|
4623
|
+
const target = track;
|
|
4624
|
+
async function apply(next) {
|
|
4625
|
+
if (next.kind === "none") {
|
|
4626
|
+
await target.stopProcessor();
|
|
4627
|
+
return;
|
|
4628
|
+
}
|
|
4629
|
+
const processors = await import("@livekit/track-processors");
|
|
4630
|
+
await target.setProcessor(
|
|
4631
|
+
next.kind === "blur" ? processors.BackgroundBlur(next.radius ?? 12) : processors.VirtualBackground(next.imageUrl)
|
|
4632
|
+
);
|
|
4633
|
+
}
|
|
4634
|
+
if (effect.kind !== "none" && videoEffectsSupported()) {
|
|
4635
|
+
try {
|
|
4636
|
+
await apply(effect);
|
|
4637
|
+
} catch {
|
|
4638
|
+
}
|
|
4639
|
+
}
|
|
4640
|
+
return {
|
|
4641
|
+
attach(element) {
|
|
4642
|
+
track.attach(element);
|
|
4643
|
+
},
|
|
4644
|
+
setEffect: apply,
|
|
4645
|
+
stop() {
|
|
4646
|
+
track.stop();
|
|
4647
|
+
track.detach();
|
|
4648
|
+
}
|
|
4649
|
+
};
|
|
4650
|
+
}
|
|
3702
4651
|
|
|
3703
4652
|
// src/react/provider.tsx
|
|
3704
4653
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
@@ -3808,7 +4757,9 @@ function MeetRuntime(props) {
|
|
|
3808
4757
|
avatarUrl: props.avatarUrl ?? null,
|
|
3809
4758
|
deviceId: deviceId()
|
|
3810
4759
|
};
|
|
3811
|
-
const
|
|
4760
|
+
const effectsStorage = createWebEffectsStorage();
|
|
4761
|
+
const store = createMeetStore({ effects: effectsStorage.load() });
|
|
4762
|
+
const devicesHolder = { current: null };
|
|
3812
4763
|
const api = createMeetApi({
|
|
3813
4764
|
baseUrl,
|
|
3814
4765
|
organizationId: identity.organizationId,
|
|
@@ -3827,7 +4778,9 @@ function MeetRuntime(props) {
|
|
|
3827
4778
|
identity,
|
|
3828
4779
|
resolveMeeting: (slug) => repository.meetingBySlug(slug),
|
|
3829
4780
|
driverFactory: (handlers) => (driverRef.current ?? createLiveKitDriver)(handlers),
|
|
3830
|
-
onDiagnostic: report
|
|
4781
|
+
onDiagnostic: report,
|
|
4782
|
+
effectsStorage,
|
|
4783
|
+
resolveDevice: (kind) => devicesHolder.current?.resolve(kind) ?? null
|
|
3831
4784
|
});
|
|
3832
4785
|
const meetings = createMeetingSession({
|
|
3833
4786
|
manager,
|
|
@@ -3842,16 +4795,32 @@ function MeetRuntime(props) {
|
|
|
3842
4795
|
void engine.rejoin();
|
|
3843
4796
|
}
|
|
3844
4797
|
});
|
|
3845
|
-
const
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
4798
|
+
const resolveRoomToken = () => {
|
|
4799
|
+
const roomName = store.snapshot().meeting?.roomName;
|
|
4800
|
+
if (roomName === void 0) return null;
|
|
4801
|
+
const active = engine.token();
|
|
4802
|
+
if (active?.roomName === roomName && active.token.length > 0) return active.token;
|
|
4803
|
+
return tokens.remembered(roomName)?.token ?? null;
|
|
4804
|
+
};
|
|
4805
|
+
const ai = createMeetAi({ api, store, resolveRoomToken });
|
|
4806
|
+
const inRoom = createInRoomClient({ api, store, resolveRoomToken });
|
|
4807
|
+
let chatReadFor = null;
|
|
4808
|
+
store.subscribe(() => {
|
|
4809
|
+
const snapshot = store.snapshot();
|
|
4810
|
+
if (snapshot.phase !== "connected" || snapshot.meeting === null) {
|
|
4811
|
+
if (snapshot.phase === "left" || snapshot.phase === "idle") chatReadFor = null;
|
|
4812
|
+
return;
|
|
3854
4813
|
}
|
|
4814
|
+
if (chatReadFor === snapshot.meeting.id) return;
|
|
4815
|
+
chatReadFor = snapshot.meeting.id;
|
|
4816
|
+
void inRoom.loadChat().catch((error) => {
|
|
4817
|
+
const normalized = error;
|
|
4818
|
+
report({
|
|
4819
|
+
level: "warn",
|
|
4820
|
+
message: normalized.message ?? "meet: the saved chat could not be read",
|
|
4821
|
+
...normalized.remedy !== void 0 ? { remedy: normalized.remedy } : {}
|
|
4822
|
+
});
|
|
4823
|
+
});
|
|
3855
4824
|
});
|
|
3856
4825
|
const recording = createRecordingController({
|
|
3857
4826
|
api,
|
|
@@ -3873,6 +4842,7 @@ function MeetRuntime(props) {
|
|
|
3873
4842
|
},
|
|
3874
4843
|
storage: createWebDeviceStorage()
|
|
3875
4844
|
});
|
|
4845
|
+
devicesHolder.current = devices;
|
|
3876
4846
|
const calls = identity.userId === null ? null : createCallCenter({
|
|
3877
4847
|
manager,
|
|
3878
4848
|
repository,
|
|
@@ -3887,6 +4857,7 @@ function MeetRuntime(props) {
|
|
|
3887
4857
|
engine,
|
|
3888
4858
|
calls,
|
|
3889
4859
|
meetings,
|
|
4860
|
+
inRoom,
|
|
3890
4861
|
recording,
|
|
3891
4862
|
devices,
|
|
3892
4863
|
repository,
|
|
@@ -4008,7 +4979,8 @@ function computeStage(args) {
|
|
|
4008
4979
|
const ordered = orderParticipants(args.participants, args.activeSpeakers);
|
|
4009
4980
|
const sharer = ordered.find((participant) => participant.screenSharing) ?? null;
|
|
4010
4981
|
const pinnedParticipant = args.pinned === null ? null : ordered.find((participant) => participant.identity === args.pinned) ?? null;
|
|
4011
|
-
const
|
|
4982
|
+
const spotlit = args.spotlight == null ? null : ordered.find((participant) => participant.identity === args.spotlight) ?? null;
|
|
4983
|
+
const feature = sharer !== null ? sharer : pinnedParticipant !== null && args.layout !== "grid" ? pinnedParticipant : spotlit !== null ? spotlit : args.layout === "grid" ? null : ordered[0] ?? null;
|
|
4012
4984
|
const rest = feature === null ? ordered : ordered.filter((participant) => participant.identity !== feature.identity);
|
|
4013
4985
|
const pageCount = Math.max(1, Math.ceil(rest.length / pageSize));
|
|
4014
4986
|
const page = Math.min(Math.max(0, args.page ?? 0), pageCount - 1);
|
|
@@ -4022,11 +4994,11 @@ function computeStage(args) {
|
|
|
4022
4994
|
presenting: sharer !== null
|
|
4023
4995
|
};
|
|
4024
4996
|
}
|
|
4025
|
-
function gridColumns(
|
|
4026
|
-
if (
|
|
4027
|
-
if (
|
|
4028
|
-
if (
|
|
4029
|
-
if (
|
|
4997
|
+
function gridColumns(count2) {
|
|
4998
|
+
if (count2 <= 1) return 1;
|
|
4999
|
+
if (count2 <= 4) return 2;
|
|
5000
|
+
if (count2 <= 9) return 3;
|
|
5001
|
+
if (count2 <= 16) return 4;
|
|
4030
5002
|
return 5;
|
|
4031
5003
|
}
|
|
4032
5004
|
|
|
@@ -4200,7 +5172,7 @@ function meetingWhen(meeting, locale) {
|
|
|
4200
5172
|
if (start === null) return "Time not recorded";
|
|
4201
5173
|
const startedAt = new Date(start);
|
|
4202
5174
|
if (Number.isNaN(startedAt.getTime())) return "Time not recorded";
|
|
4203
|
-
const
|
|
5175
|
+
const when2 = startedAt.toLocaleString(locale, {
|
|
4204
5176
|
weekday: "long",
|
|
4205
5177
|
year: "numeric",
|
|
4206
5178
|
month: "long",
|
|
@@ -4208,11 +5180,11 @@ function meetingWhen(meeting, locale) {
|
|
|
4208
5180
|
hour: "numeric",
|
|
4209
5181
|
minute: "2-digit"
|
|
4210
5182
|
});
|
|
4211
|
-
if (meeting.endedAt === null || meeting.startedAt === null) return
|
|
5183
|
+
if (meeting.endedAt === null || meeting.startedAt === null) return when2;
|
|
4212
5184
|
const endedAt = new Date(meeting.endedAt);
|
|
4213
|
-
if (Number.isNaN(endedAt.getTime())) return
|
|
5185
|
+
if (Number.isNaN(endedAt.getTime())) return when2;
|
|
4214
5186
|
const elapsedMs = endedAt.getTime() - startedAt.getTime();
|
|
4215
|
-
return `${
|
|
5187
|
+
return `${when2} \xB7 ${(0, import_format.formatDurationMs)(elapsedMs, { style: "long" })}`;
|
|
4216
5188
|
}
|
|
4217
5189
|
var GUEST_RECORD_ACCESS_LEVELS = [
|
|
4218
5190
|
"none",
|
|
@@ -4319,8 +5291,8 @@ async function loadGuestMeetingRecord(args) {
|
|
|
4319
5291
|
);
|
|
4320
5292
|
}
|
|
4321
5293
|
if (!response.ok) {
|
|
4322
|
-
const
|
|
4323
|
-
return refusedBundle(meeting,
|
|
5294
|
+
const refusal2 = await readRefusal(response);
|
|
5295
|
+
return refusedBundle(meeting, refusal2.message, refusal2.remedy);
|
|
4324
5296
|
}
|
|
4325
5297
|
const body = await response.json();
|
|
4326
5298
|
const payload = typeof body["data"] === "object" && body["data"] !== null ? body["data"] : body;
|
|
@@ -4411,8 +5383,8 @@ async function setGuestRecordAccess(args) {
|
|
|
4411
5383
|
}
|
|
4412
5384
|
});
|
|
4413
5385
|
if (!response.ok) {
|
|
4414
|
-
const
|
|
4415
|
-
throw new MeetError("forbidden",
|
|
5386
|
+
const refusal2 = await readRefusal(response);
|
|
5387
|
+
throw new MeetError("forbidden", refusal2.message, refusal2.remedy);
|
|
4416
5388
|
}
|
|
4417
5389
|
const body = await response.json();
|
|
4418
5390
|
const payload = typeof body["data"] === "object" && body["data"] !== null ? body["data"] : body;
|
|
@@ -4487,7 +5459,14 @@ function useRoom() {
|
|
|
4487
5459
|
[engine]
|
|
4488
5460
|
),
|
|
4489
5461
|
sendChat: (0, import_react3.useCallback)(
|
|
4490
|
-
async (text3) =>
|
|
5462
|
+
async (text3) => {
|
|
5463
|
+
if (host === null) throw noRuntime("sendChat");
|
|
5464
|
+
await host.inRoom.sendChat(text3);
|
|
5465
|
+
},
|
|
5466
|
+
[host]
|
|
5467
|
+
),
|
|
5468
|
+
sendPrivateChat: (0, import_react3.useCallback)(
|
|
5469
|
+
async (to, text3) => requireEngine(engine, "sendPrivateChat").sendPrivateChat(to, text3),
|
|
4491
5470
|
[engine]
|
|
4492
5471
|
),
|
|
4493
5472
|
pin: (0, import_react3.useCallback)(
|
|
@@ -4508,6 +5487,7 @@ function useStage(pageSize = DEFAULT_PAGE_SIZE) {
|
|
|
4508
5487
|
participants: snapshot?.participants ?? [],
|
|
4509
5488
|
layout: snapshot?.layout ?? "grid",
|
|
4510
5489
|
pinned: snapshot?.pinned ?? null,
|
|
5490
|
+
spotlight: snapshot?.spotlight ?? null,
|
|
4511
5491
|
activeSpeakers: snapshot?.activeSpeakers ?? [],
|
|
4512
5492
|
pageSize,
|
|
4513
5493
|
page
|
|
@@ -4840,33 +5820,160 @@ function useHostControls() {
|
|
|
4840
5820
|
const host = useMeetHost();
|
|
4841
5821
|
const snapshot = useMeetSnapshot();
|
|
4842
5822
|
const role = useRole();
|
|
4843
|
-
const engine = host?.engine ?? null;
|
|
4844
5823
|
const me = snapshot?.participants.find(
|
|
4845
5824
|
(participant) => participant.identity === snapshot.localIdentity
|
|
4846
5825
|
);
|
|
4847
5826
|
const actor = me === void 0 ? void 0 : { ...me, role };
|
|
5827
|
+
const [busy, setBusy] = (0, import_react3.useState)(false);
|
|
5828
|
+
const inRoom = host?.inRoom ?? null;
|
|
5829
|
+
const store = host?.store ?? null;
|
|
5830
|
+
const notice = snapshot?.actionNotice ?? null;
|
|
5831
|
+
const setNotice = (0, import_react3.useCallback)(
|
|
5832
|
+
(next) => store?.patch({ actionNotice: next === null ? null : { ...next, at: Date.now() } }),
|
|
5833
|
+
[store]
|
|
5834
|
+
);
|
|
5835
|
+
const run = (0, import_react3.useCallback)(
|
|
5836
|
+
async (request) => {
|
|
5837
|
+
if (inRoom === null) throw noRuntime(`moderate(${request.action})`);
|
|
5838
|
+
setBusy(true);
|
|
5839
|
+
try {
|
|
5840
|
+
const result = await inRoom.moderate(request);
|
|
5841
|
+
setNotice({ text: result.message, failed: false });
|
|
5842
|
+
return result;
|
|
5843
|
+
} catch (thrown) {
|
|
5844
|
+
const error = thrown;
|
|
5845
|
+
setNotice({
|
|
5846
|
+
text: [error.message, error.remedy].filter(Boolean).join(" "),
|
|
5847
|
+
failed: true
|
|
5848
|
+
});
|
|
5849
|
+
return null;
|
|
5850
|
+
} finally {
|
|
5851
|
+
setBusy(false);
|
|
5852
|
+
}
|
|
5853
|
+
},
|
|
5854
|
+
[inRoom, setNotice]
|
|
5855
|
+
);
|
|
4848
5856
|
return {
|
|
4849
5857
|
isHost: isHostRole(role),
|
|
5858
|
+
isOwner: role === "host",
|
|
5859
|
+
notice,
|
|
5860
|
+
busy,
|
|
5861
|
+
clearNotice: (0, import_react3.useCallback)(() => setNotice(null), [setNotice]),
|
|
4850
5862
|
canModerate: (0, import_react3.useCallback)(
|
|
4851
5863
|
(target) => canModerateParticipant(actor, target),
|
|
4852
5864
|
[actor]
|
|
4853
5865
|
),
|
|
5866
|
+
run,
|
|
4854
5867
|
mute: (0, import_react3.useCallback)(
|
|
4855
|
-
async (target) =>
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
[engine]
|
|
5868
|
+
async (target) => {
|
|
5869
|
+
await run({ action: "mute", identity: target.identity });
|
|
5870
|
+
},
|
|
5871
|
+
[run]
|
|
4860
5872
|
),
|
|
4861
5873
|
remove: (0, import_react3.useCallback)(
|
|
4862
|
-
async (target) =>
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
5874
|
+
async (target) => {
|
|
5875
|
+
await run({ action: "remove", identity: target.identity });
|
|
5876
|
+
},
|
|
5877
|
+
[run]
|
|
5878
|
+
)
|
|
5879
|
+
};
|
|
5880
|
+
}
|
|
5881
|
+
function useHandQueue() {
|
|
5882
|
+
const snapshot = useMeetSnapshot();
|
|
5883
|
+
return (0, import_react3.useMemo)(
|
|
5884
|
+
() => (snapshot?.participants ?? []).filter((participant) => participant.handRaised).sort((a, b) => (a.handRaisedAt ?? 0) - (b.handRaisedAt ?? 0)),
|
|
5885
|
+
[snapshot?.participants]
|
|
5886
|
+
);
|
|
5887
|
+
}
|
|
5888
|
+
function useMediaEffects() {
|
|
5889
|
+
const host = useMeetHost();
|
|
5890
|
+
const snapshot = useMeetSnapshot();
|
|
5891
|
+
const engine = host?.engine ?? null;
|
|
5892
|
+
return {
|
|
5893
|
+
effects: snapshot?.effects ?? DEFAULT_EFFECTS,
|
|
5894
|
+
backgroundsSupported: engine?.supportsVideoEffects() ?? false,
|
|
5895
|
+
set: (0, import_react3.useCallback)(
|
|
5896
|
+
async (next) => requireEngine(engine, "setEffects").setEffects(next),
|
|
4866
5897
|
[engine]
|
|
4867
5898
|
)
|
|
4868
5899
|
};
|
|
4869
5900
|
}
|
|
5901
|
+
function useModerationNotice() {
|
|
5902
|
+
const host = useMeetHost();
|
|
5903
|
+
const snapshot = useMeetSnapshot();
|
|
5904
|
+
return {
|
|
5905
|
+
notice: snapshot?.moderationNotice ?? null,
|
|
5906
|
+
dismiss: (0, import_react3.useCallback)(() => host?.store.patch({ moderationNotice: null }), [host])
|
|
5907
|
+
};
|
|
5908
|
+
}
|
|
5909
|
+
function useAudioLevel(stream) {
|
|
5910
|
+
const [level, setLevel] = (0, import_react3.useState)(0);
|
|
5911
|
+
(0, import_react3.useEffect)(() => {
|
|
5912
|
+
if (stream === null || stream.getAudioTracks().length === 0) {
|
|
5913
|
+
setLevel(0);
|
|
5914
|
+
return void 0;
|
|
5915
|
+
}
|
|
5916
|
+
const Context = globalThis.AudioContext ?? globalThis.webkitAudioContext;
|
|
5917
|
+
if (Context === void 0) return void 0;
|
|
5918
|
+
const context = new Context();
|
|
5919
|
+
const source = context.createMediaStreamSource(stream);
|
|
5920
|
+
const analyser = context.createAnalyser();
|
|
5921
|
+
analyser.fftSize = 512;
|
|
5922
|
+
source.connect(analyser);
|
|
5923
|
+
const samples = new Uint8Array(analyser.fftSize);
|
|
5924
|
+
let frame = 0;
|
|
5925
|
+
const tick = () => {
|
|
5926
|
+
analyser.getByteTimeDomainData(samples);
|
|
5927
|
+
let peak = 0;
|
|
5928
|
+
for (const sample of samples) peak = Math.max(peak, Math.abs(sample - 128) / 128);
|
|
5929
|
+
setLevel((previous) => Math.abs(previous - peak) > 0.02 ? peak : previous);
|
|
5930
|
+
frame = requestAnimationFrame(tick);
|
|
5931
|
+
};
|
|
5932
|
+
frame = requestAnimationFrame(tick);
|
|
5933
|
+
return () => {
|
|
5934
|
+
cancelAnimationFrame(frame);
|
|
5935
|
+
source.disconnect();
|
|
5936
|
+
void context.close();
|
|
5937
|
+
};
|
|
5938
|
+
}, [stream]);
|
|
5939
|
+
return level;
|
|
5940
|
+
}
|
|
5941
|
+
var MEET_SHORTCUTS = [
|
|
5942
|
+
{ id: "mic", keys: "\u2318/Ctrl + D", label: "Mute or unmute your microphone" },
|
|
5943
|
+
{ id: "camera", keys: "\u2318/Ctrl + E", label: "Turn your camera on or off" },
|
|
5944
|
+
{ id: "hand", keys: "\u2318/Ctrl + Alt + H", label: "Raise or lower your hand" },
|
|
5945
|
+
{ id: "chat", keys: "\u2318/Ctrl + Alt + C", label: "Open or close chat" },
|
|
5946
|
+
{ id: "people", keys: "\u2318/Ctrl + Alt + P", label: "Open or close people" },
|
|
5947
|
+
{ id: "help", keys: "Shift + ?", label: "Show keyboard shortcuts" }
|
|
5948
|
+
];
|
|
5949
|
+
function useMeetShortcuts(handlers) {
|
|
5950
|
+
const ref = (0, import_react3.useRef)(handlers);
|
|
5951
|
+
ref.current = handlers;
|
|
5952
|
+
(0, import_react3.useEffect)(() => {
|
|
5953
|
+
const target = globalThis.window;
|
|
5954
|
+
if (target === void 0) return void 0;
|
|
5955
|
+
const onKey = (event) => {
|
|
5956
|
+
const element = event.target;
|
|
5957
|
+
const typing = element !== null && (element.isContentEditable || element.tagName === "INPUT" || element.tagName === "TEXTAREA" || element.tagName === "SELECT");
|
|
5958
|
+
const mod = event.metaKey || event.ctrlKey;
|
|
5959
|
+
const key = event.key.toLowerCase();
|
|
5960
|
+
let id = null;
|
|
5961
|
+
if (mod && !event.altKey && !event.shiftKey && key === "d") id = "mic";
|
|
5962
|
+
else if (mod && !event.altKey && !event.shiftKey && key === "e") id = "camera";
|
|
5963
|
+
else if (mod && event.altKey && (event.code === "KeyH" || key === "h")) id = "hand";
|
|
5964
|
+
else if (mod && event.altKey && (event.code === "KeyC" || key === "c")) id = "chat";
|
|
5965
|
+
else if (mod && event.altKey && (event.code === "KeyP" || key === "p")) id = "people";
|
|
5966
|
+
else if (!mod && !typing && event.key === "?") id = "help";
|
|
5967
|
+
if (id === null) return;
|
|
5968
|
+
const handler = ref.current[id];
|
|
5969
|
+
if (handler === void 0) return;
|
|
5970
|
+
event.preventDefault();
|
|
5971
|
+
handler();
|
|
5972
|
+
};
|
|
5973
|
+
target.addEventListener("keydown", onKey);
|
|
5974
|
+
return () => target.removeEventListener("keydown", onKey);
|
|
5975
|
+
}, []);
|
|
5976
|
+
}
|
|
4870
5977
|
function useMeeting() {
|
|
4871
5978
|
return useMeetSnapshot()?.meeting ?? null;
|
|
4872
5979
|
}
|
|
@@ -5079,8 +6186,8 @@ function callStateLabel(state, outgoing) {
|
|
|
5079
6186
|
return "Call";
|
|
5080
6187
|
}
|
|
5081
6188
|
}
|
|
5082
|
-
function countLabel(
|
|
5083
|
-
return `${
|
|
6189
|
+
function countLabel(count2, singular, plural = `${singular}s`) {
|
|
6190
|
+
return `${count2} ${count2 === 1 ? singular : plural}`;
|
|
5084
6191
|
}
|
|
5085
6192
|
|
|
5086
6193
|
// src/react/icons.tsx
|
|
@@ -5213,6 +6320,60 @@ function WifiLowIcon(props) {
|
|
|
5213
6320
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M8.5 16.5a5 5 0 0 1 7 0" })
|
|
5214
6321
|
] });
|
|
5215
6322
|
}
|
|
6323
|
+
function SmileIcon(props) {
|
|
6324
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
|
|
6325
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
|
|
6326
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M8 14s1.5 2 4 2 4-2 4-2" }),
|
|
6327
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "9", x2: "9.01", y1: "9", y2: "9" }),
|
|
6328
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "15", x2: "15.01", y1: "9", y2: "9" })
|
|
6329
|
+
] });
|
|
6330
|
+
}
|
|
6331
|
+
function MoreIcon(props) {
|
|
6332
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
|
|
6333
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "12", cy: "12", r: "1" }),
|
|
6334
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "19", cy: "12", r: "1" }),
|
|
6335
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "5", cy: "12", r: "1" })
|
|
6336
|
+
] });
|
|
6337
|
+
}
|
|
6338
|
+
function SearchIcon(props) {
|
|
6339
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
|
|
6340
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "11", cy: "11", r: "8" }),
|
|
6341
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "m21 21-4.3-4.3" })
|
|
6342
|
+
] });
|
|
6343
|
+
}
|
|
6344
|
+
function KeyboardIcon(props) {
|
|
6345
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
|
|
6346
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M10 8h.01M12 12h.01M14 8h.01M16 12h.01M18 8h.01M6 8h.01M7 16h10M8 12h.01" }),
|
|
6347
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { width: "20", height: "16", x: "2", y: "4", rx: "2" })
|
|
6348
|
+
] });
|
|
6349
|
+
}
|
|
6350
|
+
function SpotlightIcon(props) {
|
|
6351
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Icon, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime2.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" }) });
|
|
6352
|
+
}
|
|
6353
|
+
function SignalIcon({ bars, ...props }) {
|
|
6354
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
|
|
6355
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M4 20v-2", opacity: bars >= 1 ? 1 : 0.3 }),
|
|
6356
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M10 20v-6", opacity: bars >= 2 ? 1 : 0.3 }),
|
|
6357
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M16 20V9", opacity: bars >= 3 ? 1 : 0.3 }),
|
|
6358
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M22 20V4", opacity: bars >= 3 ? 1 : 0.3 })
|
|
6359
|
+
] });
|
|
6360
|
+
}
|
|
6361
|
+
function SpeakerIcon(props) {
|
|
6362
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
|
|
6363
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M11 5 6 9H2v6h4l5 4V5Z" }),
|
|
6364
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M15.54 8.46a5 5 0 0 1 0 7.07" }),
|
|
6365
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M19.07 4.93a10 10 0 0 1 0 14.14" })
|
|
6366
|
+
] });
|
|
6367
|
+
}
|
|
6368
|
+
function FlagIcon(props) {
|
|
6369
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
|
|
6370
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.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" }),
|
|
6371
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "4", x2: "4", y1: "22", y2: "15" })
|
|
6372
|
+
] });
|
|
6373
|
+
}
|
|
6374
|
+
function PencilIcon(props) {
|
|
6375
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Icon, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" }) });
|
|
6376
|
+
}
|
|
5216
6377
|
|
|
5217
6378
|
// src/react/components.tsx
|
|
5218
6379
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
@@ -5313,12 +6474,17 @@ function ParticipantTile({
|
|
|
5313
6474
|
featured = false
|
|
5314
6475
|
}) {
|
|
5315
6476
|
const host = useMeetHost();
|
|
5316
|
-
const
|
|
6477
|
+
const snapshot = useMeetSnapshot();
|
|
6478
|
+
const queue = useHandQueue();
|
|
6479
|
+
const [menu, setMenu] = (0, import_react4.useState)(false);
|
|
5317
6480
|
const videoRef = useTrack(
|
|
5318
6481
|
participant.identity,
|
|
5319
6482
|
participant.screenSharing ? "screen" : "camera"
|
|
5320
6483
|
);
|
|
5321
6484
|
const showVideo = participant.cameraEnabled || participant.screenSharing;
|
|
6485
|
+
const pinnedHere = snapshot?.pinned === participant.identity;
|
|
6486
|
+
const spotlit = snapshot?.spotlight === participant.identity;
|
|
6487
|
+
const handPosition = participant.handRaised ? queue.findIndex((entry) => entry.identity === participant.identity) + 1 : 0;
|
|
5322
6488
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
5323
6489
|
"div",
|
|
5324
6490
|
{
|
|
@@ -5339,44 +6505,59 @@ function ParticipantTile({
|
|
|
5339
6505
|
muted: participant.isLocal
|
|
5340
6506
|
}
|
|
5341
6507
|
) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mx-meet__tile-placeholder", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Avatar, { name: participant.displayName, url: participant.avatarUrl, size: featured ? 96 : 56 }) }),
|
|
6508
|
+
participant.handRaised ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "mx-meet__tile-hand", title: `Hand raised \xB7 ${ordinal(handPosition)} in line`, children: [
|
|
6509
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(HandIcon, { width: 14, height: 14 }),
|
|
6510
|
+
handPosition > 0 ? handPosition : null
|
|
6511
|
+
] }) : null,
|
|
5342
6512
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__tile-bar", children: [
|
|
5343
6513
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "mx-meet__tile-name", children: [
|
|
6514
|
+
spotlit ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { title: "Spotlighted for everyone", className: "mx-meet__tile-flag", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SpotlightIcon, { width: 12, height: 12 }) }) : null,
|
|
5344
6515
|
participant.displayName,
|
|
5345
6516
|
participant.isLocal ? " (you)" : "",
|
|
5346
6517
|
participant.isAgent ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "mx-meet__badge", children: "AI" }) : null,
|
|
5347
6518
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(RoleBadge, { role: participant.role })
|
|
5348
6519
|
] }),
|
|
5349
6520
|
!participant.micEnabled ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MicOffIcon, { width: 14, height: 14 }) : null,
|
|
5350
|
-
|
|
5351
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ConnectionDot, { quality: participant.quality })
|
|
6521
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(QualityBars, { quality: participant.quality })
|
|
5352
6522
|
] }),
|
|
5353
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__tile-actions", children: [
|
|
6523
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: menu ? "mx-meet__tile-actions mx-meet__tile-actions--open" : "mx-meet__tile-actions", children: [
|
|
5354
6524
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
5355
6525
|
ControlButton,
|
|
5356
6526
|
{
|
|
5357
|
-
label:
|
|
5358
|
-
|
|
6527
|
+
label: pinnedHere ? "Unpin" : `Pin ${participant.displayName} for me`,
|
|
6528
|
+
active: pinnedHere,
|
|
6529
|
+
onClick: () => host?.engine.pin(pinnedHere ? null : participant.identity),
|
|
5359
6530
|
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PinIcon, { width: 16, height: 16 })
|
|
5360
6531
|
}
|
|
5361
6532
|
),
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
) : null
|
|
6533
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "mx-meet__anchor", children: [
|
|
6534
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
6535
|
+
ControlButton,
|
|
6536
|
+
{
|
|
6537
|
+
label: `More for ${participant.displayName}`,
|
|
6538
|
+
active: menu,
|
|
6539
|
+
onClick: () => setMenu(!menu),
|
|
6540
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MoreIcon, { width: 16, height: 16 })
|
|
6541
|
+
}
|
|
6542
|
+
),
|
|
6543
|
+
menu ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PersonMenu, { participant, onClose: () => setMenu(false), placement: "down" }) : null
|
|
6544
|
+
] })
|
|
5375
6545
|
] })
|
|
5376
6546
|
]
|
|
5377
6547
|
}
|
|
5378
6548
|
);
|
|
5379
6549
|
}
|
|
6550
|
+
function ordinal(position) {
|
|
6551
|
+
if (position <= 0) return "";
|
|
6552
|
+
const suffix = position % 100 >= 11 && position % 100 <= 13 ? "th" : { 1: "st", 2: "nd", 3: "rd" }[position % 10] ?? "th";
|
|
6553
|
+
return `${position}${suffix}`;
|
|
6554
|
+
}
|
|
6555
|
+
function QualityBars({ quality }) {
|
|
6556
|
+
if (quality === "unknown") return null;
|
|
6557
|
+
const bars = quality === "excellent" ? 3 : quality === "good" ? 2 : quality === "poor" ? 1 : 0;
|
|
6558
|
+
const label = quality === "lost" ? "Connection lost" : quality === "poor" ? "Poor connection" : `Connection ${quality}`;
|
|
6559
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: `mx-meet__signal mx-meet__signal--${quality}`, title: label, "aria-label": label, role: "img", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SignalIcon, { bars, width: 14, height: 14 }) });
|
|
6560
|
+
}
|
|
5380
6561
|
function Stage({ pageSize }) {
|
|
5381
6562
|
const stage = useStage(pageSize);
|
|
5382
6563
|
const columns = gridColumns(stage.tiles.length);
|
|
@@ -5405,22 +6586,52 @@ function Stage({ pageSize }) {
|
|
|
5405
6586
|
] }) : null
|
|
5406
6587
|
] });
|
|
5407
6588
|
}
|
|
6589
|
+
var MEET_REACTIONS = ["\u{1F496}", "\u{1F44D}", "\u{1F389}", "\u{1F44F}", "\u{1F602}", "\u{1F62E}", "\u{1F622}", "\u{1F914}", "\u{1F44E}"];
|
|
5408
6590
|
function ControlBar({ onLeave }) {
|
|
5409
6591
|
const room = useRoom();
|
|
5410
6592
|
const recording = useRecording();
|
|
5411
6593
|
const [panel, setPanel] = (0, import_react4.useState)("none");
|
|
6594
|
+
const [chatTo, setChatTo] = (0, import_react4.useState)(null);
|
|
6595
|
+
const [popover, setPopover] = (0, import_react4.useState)("none");
|
|
6596
|
+
const [shortcuts, setShortcuts] = (0, import_react4.useState)(false);
|
|
5412
6597
|
const snapshot = useMeetSnapshot();
|
|
5413
6598
|
const lobby = useLobby();
|
|
6599
|
+
const controls = useHostControls();
|
|
6600
|
+
const queue = useHandQueue();
|
|
5414
6601
|
const micUnavailable = useLocalMedia().microphone.unavailable;
|
|
6602
|
+
const unread = useUnreadChat(panel === "chat");
|
|
6603
|
+
const toggle = (next) => setPanel(panel === next ? "none" : next);
|
|
6604
|
+
useMeetShortcuts({
|
|
6605
|
+
mic: () => void room.toggleMicrophone(),
|
|
6606
|
+
camera: () => void room.toggleCamera(),
|
|
6607
|
+
hand: () => void room.toggleHand(),
|
|
6608
|
+
chat: () => toggle("chat"),
|
|
6609
|
+
people: () => toggle("people"),
|
|
6610
|
+
help: () => setShortcuts(true)
|
|
6611
|
+
});
|
|
6612
|
+
const hasSpotlight = snapshot?.spotlight !== null && snapshot?.spotlight !== void 0;
|
|
5415
6613
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
5416
|
-
|
|
5417
|
-
|
|
6614
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MeetingOverlays, {}),
|
|
6615
|
+
shortcuts ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ShortcutsSheet, { onClose: () => setShortcuts(false) }) : null,
|
|
6616
|
+
panel === "chat" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChatPanel, { onClose: () => setPanel("none"), to: chatTo, onTo: setChatTo }) : null,
|
|
6617
|
+
panel === "people" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
6618
|
+
PeoplePanel,
|
|
6619
|
+
{
|
|
6620
|
+
onClose: () => setPanel("none"),
|
|
6621
|
+
onMessage: (identity) => {
|
|
6622
|
+
setChatTo(identity);
|
|
6623
|
+
setPanel("chat");
|
|
6624
|
+
}
|
|
6625
|
+
}
|
|
6626
|
+
) : null,
|
|
5418
6627
|
panel === "ai" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AiPanel, { onClose: () => setPanel("none") }) : null,
|
|
6628
|
+
panel === "settings" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SettingsPanel, { onClose: () => setPanel("none") }) : null,
|
|
6629
|
+
controls.notice !== null && panel !== "people" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Toast, { text: controls.notice.text, failed: controls.notice.failed, onDone: controls.clearNotice }) : null,
|
|
5419
6630
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__controls", children: [
|
|
5420
6631
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
5421
6632
|
ControlButton,
|
|
5422
6633
|
{
|
|
5423
|
-
label: micUnavailable !== null ? `Unmute \u2014 ${micUnavailable.remedy}` : room.local?.micEnabled === true ? "Mute" : "Unmute",
|
|
6634
|
+
label: micUnavailable !== null ? `Unmute \u2014 ${micUnavailable.remedy}` : room.local?.micEnabled === true ? "Mute (\u2318D)" : "Unmute (\u2318D)",
|
|
5424
6635
|
active: room.local?.micEnabled === true,
|
|
5425
6636
|
onClick: () => void room.toggleMicrophone(),
|
|
5426
6637
|
children: room.local?.micEnabled === true ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MicIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MicOffIcon, {})
|
|
@@ -5429,7 +6640,7 @@ function ControlBar({ onLeave }) {
|
|
|
5429
6640
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
5430
6641
|
ControlButton,
|
|
5431
6642
|
{
|
|
5432
|
-
label: room.local?.cameraEnabled === true ? "Stop video" : "Start video",
|
|
6643
|
+
label: room.local?.cameraEnabled === true ? "Stop video (\u2318E)" : "Start video (\u2318E)",
|
|
5433
6644
|
active: room.local?.cameraEnabled === true,
|
|
5434
6645
|
onClick: () => void room.toggleCamera(),
|
|
5435
6646
|
children: room.local?.cameraEnabled === true ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CameraIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CameraOffIcon, {})
|
|
@@ -5444,34 +6655,155 @@ function ControlBar({ onLeave }) {
|
|
|
5444
6655
|
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ScreenShareIcon, {})
|
|
5445
6656
|
}
|
|
5446
6657
|
),
|
|
6658
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "mx-meet__anchor", children: [
|
|
6659
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
6660
|
+
ControlButton,
|
|
6661
|
+
{
|
|
6662
|
+
label: "Send a reaction",
|
|
6663
|
+
active: popover === "reactions",
|
|
6664
|
+
onClick: () => setPopover(popover === "reactions" ? "none" : "reactions"),
|
|
6665
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SmileIcon, {})
|
|
6666
|
+
}
|
|
6667
|
+
),
|
|
6668
|
+
popover === "reactions" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Popover, { label: "Reactions", onClose: () => setPopover("none"), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mx-meet__emoji-bar", children: MEET_REACTIONS.map((emoji) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
6669
|
+
"button",
|
|
6670
|
+
{
|
|
6671
|
+
type: "button",
|
|
6672
|
+
className: "mx-meet__emoji",
|
|
6673
|
+
"aria-label": `React ${emoji}`,
|
|
6674
|
+
onClick: () => void room.react(emoji),
|
|
6675
|
+
children: emoji
|
|
6676
|
+
},
|
|
6677
|
+
emoji
|
|
6678
|
+
)) }) }) : null
|
|
6679
|
+
] }),
|
|
5447
6680
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
5448
6681
|
ControlButton,
|
|
5449
6682
|
{
|
|
5450
|
-
label: room.local?.handRaised === true ? "Lower hand" : "Raise hand",
|
|
6683
|
+
label: room.local?.handRaised === true ? "Lower hand (\u2318\u2325H)" : "Raise hand (\u2318\u2325H)",
|
|
5451
6684
|
active: room.local?.handRaised === true,
|
|
5452
6685
|
onClick: () => void room.toggleHand(),
|
|
5453
6686
|
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(HandIcon, {})
|
|
5454
6687
|
}
|
|
5455
6688
|
),
|
|
5456
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.
|
|
6689
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(ControlButton, { label: "Chat (\u2318\u2325C)", active: panel === "chat", onClick: () => toggle("chat"), children: [
|
|
6690
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChatIcon, {}),
|
|
6691
|
+
unread > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "mx-meet__dot", "aria-hidden": "true" }) : null
|
|
6692
|
+
] }),
|
|
5457
6693
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
5458
6694
|
ControlButton,
|
|
5459
6695
|
{
|
|
5460
6696
|
label: lobby.waiting.length > 0 ? `People \u2014 ${lobby.waiting.length} waiting` : `People \u2014 ${(snapshot?.participants ?? []).length} in the meeting`,
|
|
5461
6697
|
active: panel === "people",
|
|
5462
|
-
onClick: () =>
|
|
6698
|
+
onClick: () => toggle("people"),
|
|
5463
6699
|
children: [
|
|
5464
6700
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PeopleIcon, {}),
|
|
5465
|
-
lobby.waiting.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "mx-meet__dot", "aria-hidden": "true" }) : null
|
|
6701
|
+
lobby.waiting.length > 0 || controls.isHost && queue.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "mx-meet__dot", "aria-hidden": "true" }) : null
|
|
5466
6702
|
]
|
|
5467
6703
|
}
|
|
5468
6704
|
),
|
|
5469
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AiControl, { active: panel === "ai", onToggle: () =>
|
|
6705
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AiControl, { active: panel === "ai", onToggle: () => toggle("ai") }),
|
|
5470
6706
|
recording.canStart ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ControlButton, { label: "Start recording", onClick: () => void recording.start(), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(RecordIcon, {}) }) : null,
|
|
5471
6707
|
recording.canStop ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ControlButton, { label: "Stop recording", active: true, onClick: () => void recording.stop(), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(RecordIcon, {}) }) : null,
|
|
5472
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.
|
|
5473
|
-
|
|
5474
|
-
|
|
6708
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "mx-meet__anchor", children: [
|
|
6709
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
6710
|
+
ControlButton,
|
|
6711
|
+
{
|
|
6712
|
+
label: "More options",
|
|
6713
|
+
active: popover === "more" || panel === "settings",
|
|
6714
|
+
onClick: () => setPopover(popover === "more" ? "none" : "more"),
|
|
6715
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MoreIcon, {})
|
|
6716
|
+
}
|
|
6717
|
+
),
|
|
6718
|
+
popover === "more" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Popover, { label: "More options", onClose: () => setPopover("none"), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__menu", role: "menu", children: [
|
|
6719
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
6720
|
+
MenuItem,
|
|
6721
|
+
{
|
|
6722
|
+
onSelect: () => {
|
|
6723
|
+
setPopover("none");
|
|
6724
|
+
setPanel("settings");
|
|
6725
|
+
},
|
|
6726
|
+
children: [
|
|
6727
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SettingsIcon, { width: 16, height: 16 }),
|
|
6728
|
+
" Audio, video and effects"
|
|
6729
|
+
]
|
|
6730
|
+
}
|
|
6731
|
+
),
|
|
6732
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
6733
|
+
MenuItem,
|
|
6734
|
+
{
|
|
6735
|
+
onSelect: () => {
|
|
6736
|
+
setPopover("none");
|
|
6737
|
+
setShortcuts(true);
|
|
6738
|
+
},
|
|
6739
|
+
children: [
|
|
6740
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(KeyboardIcon, { width: 16, height: 16 }),
|
|
6741
|
+
" Keyboard shortcuts"
|
|
6742
|
+
]
|
|
6743
|
+
}
|
|
6744
|
+
),
|
|
6745
|
+
controls.isHost ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
6746
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mx-meet__menu-sep", role: "separator" }),
|
|
6747
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
6748
|
+
MenuItem,
|
|
6749
|
+
{
|
|
6750
|
+
onSelect: () => {
|
|
6751
|
+
setPopover("none");
|
|
6752
|
+
void controls.run({ action: "mute_all" });
|
|
6753
|
+
},
|
|
6754
|
+
children: [
|
|
6755
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MicOffIcon, { width: 16, height: 16 }),
|
|
6756
|
+
" Mute everyone"
|
|
6757
|
+
]
|
|
6758
|
+
}
|
|
6759
|
+
),
|
|
6760
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
6761
|
+
MenuItem,
|
|
6762
|
+
{
|
|
6763
|
+
onSelect: () => {
|
|
6764
|
+
setPopover("none");
|
|
6765
|
+
void controls.run({ action: "ask_all_unmute" });
|
|
6766
|
+
},
|
|
6767
|
+
children: [
|
|
6768
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MicIcon, { width: 16, height: 16 }),
|
|
6769
|
+
" Ask everyone to unmute"
|
|
6770
|
+
]
|
|
6771
|
+
}
|
|
6772
|
+
),
|
|
6773
|
+
queue.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
6774
|
+
MenuItem,
|
|
6775
|
+
{
|
|
6776
|
+
onSelect: () => {
|
|
6777
|
+
setPopover("none");
|
|
6778
|
+
void controls.run({ action: "lower_all_hands" });
|
|
6779
|
+
},
|
|
6780
|
+
children: [
|
|
6781
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(HandIcon, { width: 16, height: 16 }),
|
|
6782
|
+
" Lower all hands (",
|
|
6783
|
+
queue.length,
|
|
6784
|
+
")"
|
|
6785
|
+
]
|
|
6786
|
+
}
|
|
6787
|
+
) : null,
|
|
6788
|
+
hasSpotlight ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
6789
|
+
MenuItem,
|
|
6790
|
+
{
|
|
6791
|
+
onSelect: () => {
|
|
6792
|
+
setPopover("none");
|
|
6793
|
+
void controls.run({ action: "spotlight", identity: null });
|
|
6794
|
+
},
|
|
6795
|
+
children: [
|
|
6796
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SpotlightIcon, { width: 16, height: 16 }),
|
|
6797
|
+
" Remove spotlight"
|
|
6798
|
+
]
|
|
6799
|
+
}
|
|
6800
|
+
) : null
|
|
6801
|
+
] }) : null
|
|
6802
|
+
] }) }) : null
|
|
6803
|
+
] }),
|
|
6804
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
6805
|
+
ControlButton,
|
|
6806
|
+
{
|
|
5475
6807
|
label: "Leave",
|
|
5476
6808
|
danger: true,
|
|
5477
6809
|
onClick: () => {
|
|
@@ -5484,6 +6816,150 @@ function ControlBar({ onLeave }) {
|
|
|
5484
6816
|
] })
|
|
5485
6817
|
] });
|
|
5486
6818
|
}
|
|
6819
|
+
function useUnreadChat(open) {
|
|
6820
|
+
const snapshot = useMeetSnapshot();
|
|
6821
|
+
const chat = snapshot?.chat ?? [];
|
|
6822
|
+
const local = snapshot?.localIdentity ?? null;
|
|
6823
|
+
const [seen, setSeen] = (0, import_react4.useState)(0);
|
|
6824
|
+
(0, import_react4.useEffect)(() => {
|
|
6825
|
+
if (open) setSeen(chat.length);
|
|
6826
|
+
}, [open, chat.length]);
|
|
6827
|
+
const loaded = snapshot?.chatLoaded === true;
|
|
6828
|
+
const [baseline, setBaseline] = (0, import_react4.useState)(null);
|
|
6829
|
+
(0, import_react4.useEffect)(() => {
|
|
6830
|
+
if (loaded && baseline === null) setBaseline(chat.length);
|
|
6831
|
+
}, [loaded, baseline, chat.length]);
|
|
6832
|
+
if (open) return 0;
|
|
6833
|
+
const from = Math.max(seen, baseline ?? chat.length);
|
|
6834
|
+
return chat.slice(from).filter((message) => message.identity !== local).length;
|
|
6835
|
+
}
|
|
6836
|
+
function Popover({
|
|
6837
|
+
label,
|
|
6838
|
+
onClose,
|
|
6839
|
+
children,
|
|
6840
|
+
placement = "up"
|
|
6841
|
+
}) {
|
|
6842
|
+
const ref = (0, import_react4.useRef)(null);
|
|
6843
|
+
const [style, setStyle] = (0, import_react4.useState)({ visibility: "hidden" });
|
|
6844
|
+
(0, import_react4.useLayoutEffect)(() => {
|
|
6845
|
+
const element = ref.current;
|
|
6846
|
+
const anchor = element?.parentElement ?? null;
|
|
6847
|
+
if (element === null || anchor === null) return;
|
|
6848
|
+
const place = () => {
|
|
6849
|
+
const rect = anchor.getBoundingClientRect();
|
|
6850
|
+
const width = element.offsetWidth;
|
|
6851
|
+
const height = element.offsetHeight;
|
|
6852
|
+
const gutter = 8;
|
|
6853
|
+
const left = Math.min(
|
|
6854
|
+
Math.max(gutter, rect.left + rect.width / 2 - width / 2),
|
|
6855
|
+
window.innerWidth - width - gutter
|
|
6856
|
+
);
|
|
6857
|
+
const fitsBelow = rect.bottom + gutter + height <= window.innerHeight;
|
|
6858
|
+
const fitsAbove = rect.top - gutter - height >= 0;
|
|
6859
|
+
const down = placement === "down" ? fitsBelow || !fitsAbove : !fitsAbove && fitsBelow;
|
|
6860
|
+
setStyle(
|
|
6861
|
+
down ? { position: "fixed", left, top: rect.bottom + gutter } : { position: "fixed", left, bottom: window.innerHeight - rect.top + gutter }
|
|
6862
|
+
);
|
|
6863
|
+
};
|
|
6864
|
+
place();
|
|
6865
|
+
window.addEventListener("resize", place);
|
|
6866
|
+
return () => window.removeEventListener("resize", place);
|
|
6867
|
+
}, [placement]);
|
|
6868
|
+
(0, import_react4.useEffect)(() => {
|
|
6869
|
+
const onKey = (event) => {
|
|
6870
|
+
if (event.key === "Escape") onClose();
|
|
6871
|
+
};
|
|
6872
|
+
const onPointer = (event) => {
|
|
6873
|
+
const anchor = ref.current?.parentElement ?? null;
|
|
6874
|
+
if (anchor !== null && !anchor.contains(event.target)) onClose();
|
|
6875
|
+
};
|
|
6876
|
+
window.addEventListener("keydown", onKey);
|
|
6877
|
+
window.addEventListener("pointerdown", onPointer);
|
|
6878
|
+
return () => {
|
|
6879
|
+
window.removeEventListener("keydown", onKey);
|
|
6880
|
+
window.removeEventListener("pointerdown", onPointer);
|
|
6881
|
+
};
|
|
6882
|
+
}, [onClose]);
|
|
6883
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { ref, className: "mx-meet__popover", style, role: "dialog", "aria-label": label, children });
|
|
6884
|
+
}
|
|
6885
|
+
function MenuItem({
|
|
6886
|
+
onSelect,
|
|
6887
|
+
danger = false,
|
|
6888
|
+
children
|
|
6889
|
+
}) {
|
|
6890
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
6891
|
+
"button",
|
|
6892
|
+
{
|
|
6893
|
+
type: "button",
|
|
6894
|
+
role: "menuitem",
|
|
6895
|
+
className: danger ? "mx-meet__menu-item mx-meet__menu-item--danger" : "mx-meet__menu-item",
|
|
6896
|
+
onClick: onSelect,
|
|
6897
|
+
children
|
|
6898
|
+
}
|
|
6899
|
+
);
|
|
6900
|
+
}
|
|
6901
|
+
function Toast({ text: text3, failed, onDone }) {
|
|
6902
|
+
(0, import_react4.useEffect)(() => {
|
|
6903
|
+
const timer = setTimeout(onDone, failed ? 7e3 : 4e3);
|
|
6904
|
+
return () => clearTimeout(timer);
|
|
6905
|
+
}, [text3, failed, onDone]);
|
|
6906
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: failed ? "mx-meet__toast mx-meet__toast--failed" : "mx-meet__toast", role: "status", children: [
|
|
6907
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: text3 }),
|
|
6908
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "mx-meet__toast-close", "aria-label": "Dismiss", onClick: onDone, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CloseIcon, { width: 14, height: 14 }) })
|
|
6909
|
+
] });
|
|
6910
|
+
}
|
|
6911
|
+
function MeetingOverlays() {
|
|
6912
|
+
const reactions = useReactions();
|
|
6913
|
+
const { notice, dismiss } = useModerationNotice();
|
|
6914
|
+
const room = useRoom();
|
|
6915
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
6916
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ReactionOverlay, { reactions }),
|
|
6917
|
+
notice !== null && notice.kind !== "removed" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__notice-bar", role: "alert", children: [
|
|
6918
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: notice.text }),
|
|
6919
|
+
notice.kind === "ask-unmute" && room.local?.micEnabled !== true ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
6920
|
+
"button",
|
|
6921
|
+
{
|
|
6922
|
+
type: "button",
|
|
6923
|
+
className: "mx-meet__primary mx-meet__primary--small",
|
|
6924
|
+
onClick: () => {
|
|
6925
|
+
dismiss();
|
|
6926
|
+
void room.toggleMicrophone();
|
|
6927
|
+
},
|
|
6928
|
+
children: "Unmute"
|
|
6929
|
+
}
|
|
6930
|
+
) : null,
|
|
6931
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "mx-meet__toast-close", "aria-label": "Dismiss", onClick: dismiss, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CloseIcon, { width: 14, height: 14 }) })
|
|
6932
|
+
] }) : null
|
|
6933
|
+
] });
|
|
6934
|
+
}
|
|
6935
|
+
function ShortcutsSheet({ onClose }) {
|
|
6936
|
+
(0, import_react4.useEffect)(() => {
|
|
6937
|
+
const onKey = (event) => {
|
|
6938
|
+
if (event.key === "Escape") onClose();
|
|
6939
|
+
};
|
|
6940
|
+
window.addEventListener("keydown", onKey);
|
|
6941
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
6942
|
+
}, [onClose]);
|
|
6943
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mx-meet__sheet-backdrop", onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
6944
|
+
"div",
|
|
6945
|
+
{
|
|
6946
|
+
className: "mx-meet__sheet",
|
|
6947
|
+
role: "dialog",
|
|
6948
|
+
"aria-label": "Keyboard shortcuts",
|
|
6949
|
+
onClick: (event) => event.stopPropagation(),
|
|
6950
|
+
children: [
|
|
6951
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("header", { className: "mx-meet__panel-head", children: [
|
|
6952
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { className: "mx-meet__panel-title", children: "Keyboard shortcuts" }),
|
|
6953
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ControlButton, { label: "Close", onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CloseIcon, { width: 16, height: 16 }) })
|
|
6954
|
+
] }),
|
|
6955
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("dl", { className: "mx-meet__shortcuts", children: MEET_SHORTCUTS.map((shortcut) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__shortcut", children: [
|
|
6956
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("dt", { children: shortcut.label }),
|
|
6957
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("dd", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("kbd", { className: "mx-meet__kbd", children: shortcut.keys }) })
|
|
6958
|
+
] }, shortcut.id)) })
|
|
6959
|
+
]
|
|
6960
|
+
}
|
|
6961
|
+
) });
|
|
6962
|
+
}
|
|
5487
6963
|
function AiControl({ active, onToggle }) {
|
|
5488
6964
|
const snapshot = useMeetSnapshot();
|
|
5489
6965
|
const meeting = snapshot?.meeting ?? null;
|
|
@@ -5503,117 +6979,640 @@ function PanelShell({
|
|
|
5503
6979
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mx-meet__panel-body", children })
|
|
5504
6980
|
] });
|
|
5505
6981
|
}
|
|
5506
|
-
function ChatPanel({
|
|
6982
|
+
function ChatPanel({
|
|
6983
|
+
onClose,
|
|
6984
|
+
to = null,
|
|
6985
|
+
onTo
|
|
6986
|
+
}) {
|
|
5507
6987
|
const snapshot = useMeetSnapshot();
|
|
5508
6988
|
const room = useRoom();
|
|
5509
6989
|
const [draft, setDraft] = (0, import_react4.useState)("");
|
|
6990
|
+
const [recipient, setRecipient] = (0, import_react4.useState)(to);
|
|
6991
|
+
const listRef = (0, import_react4.useRef)(null);
|
|
5510
6992
|
const messages = snapshot?.chat ?? [];
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
6993
|
+
const local = snapshot?.localIdentity ?? null;
|
|
6994
|
+
const people = (snapshot?.participants ?? []).filter(
|
|
6995
|
+
(participant) => !participant.isLocal && !participant.isAgent
|
|
6996
|
+
);
|
|
6997
|
+
const recipientName = recipient === null ? null : people.find((p) => p.identity === recipient)?.displayName ?? null;
|
|
6998
|
+
(0, import_react4.useEffect)(() => setRecipient(to), [to]);
|
|
6999
|
+
(0, import_react4.useEffect)(() => {
|
|
7000
|
+
const list = listRef.current;
|
|
7001
|
+
if (list !== null) list.scrollTop = list.scrollHeight;
|
|
7002
|
+
}, [messages.length]);
|
|
7003
|
+
const choose = (next) => {
|
|
7004
|
+
setRecipient(next);
|
|
7005
|
+
onTo?.(next);
|
|
7006
|
+
};
|
|
7007
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(PanelShell, { title: "Chat", onClose, children: [
|
|
7008
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("ul", { className: "mx-meet__chat", ref: listRef, children: [
|
|
7009
|
+
snapshot?.chatLoaded !== true && messages.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("li", { className: "mx-meet__panel-note", children: "Reading earlier messages\u2026" }) : messages.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.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,
|
|
7010
|
+
messages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChatLine, { message, local, people }, message.id))
|
|
7011
|
+
] }),
|
|
5517
7012
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
5518
7013
|
"form",
|
|
5519
7014
|
{
|
|
5520
|
-
className: "mx-meet__composer",
|
|
7015
|
+
className: "mx-meet__composer mx-meet__composer--chat",
|
|
5521
7016
|
onSubmit: (event) => {
|
|
5522
7017
|
event.preventDefault();
|
|
5523
7018
|
const text3 = draft;
|
|
7019
|
+
if (text3.trim().length === 0) return;
|
|
5524
7020
|
setDraft("");
|
|
5525
|
-
void room.sendChat(text3);
|
|
7021
|
+
if (recipient === null) void room.sendChat(text3).catch(() => void 0);
|
|
7022
|
+
else void room.sendPrivateChat(recipient, text3);
|
|
5526
7023
|
},
|
|
5527
7024
|
children: [
|
|
5528
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.
|
|
5529
|
-
"
|
|
7025
|
+
people.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
7026
|
+
"select",
|
|
5530
7027
|
{
|
|
5531
|
-
className: "mx-
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
7028
|
+
className: "mx-meet__select mx-meet__select--compact",
|
|
7029
|
+
"aria-label": "Send to",
|
|
7030
|
+
value: recipient ?? "",
|
|
7031
|
+
onChange: (event) => choose(event.target.value === "" ? null : event.target.value),
|
|
7032
|
+
children: [
|
|
7033
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "", children: "Everyone" }),
|
|
7034
|
+
people.map((participant) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("option", { value: participant.identity, children: [
|
|
7035
|
+
participant.displayName,
|
|
7036
|
+
" (private)"
|
|
7037
|
+
] }, participant.identity))
|
|
7038
|
+
]
|
|
5536
7039
|
}
|
|
5537
|
-
),
|
|
5538
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.
|
|
7040
|
+
) : null,
|
|
7041
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__composer-row", children: [
|
|
7042
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
7043
|
+
"input",
|
|
7044
|
+
{
|
|
7045
|
+
className: "mx-meet__input",
|
|
7046
|
+
value: draft,
|
|
7047
|
+
onChange: (event) => setDraft(event.target.value),
|
|
7048
|
+
placeholder: recipientName === null ? "Message everyone" : `Message ${recipientName} privately`,
|
|
7049
|
+
"aria-label": recipientName === null ? "Message everyone" : `Message ${recipientName} privately`,
|
|
7050
|
+
maxLength: 4e3
|
|
7051
|
+
}
|
|
7052
|
+
),
|
|
7053
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "submit", className: "mx-meet__send", disabled: draft.trim().length === 0, children: "Send" })
|
|
7054
|
+
] }),
|
|
7055
|
+
recipientName !== null ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { className: "mx-meet__panel-note", children: [
|
|
7056
|
+
"Only ",
|
|
7057
|
+
recipientName,
|
|
7058
|
+
" sees this. Private messages are not saved."
|
|
7059
|
+
] }) : null
|
|
5539
7060
|
]
|
|
5540
7061
|
}
|
|
5541
7062
|
)
|
|
5542
7063
|
] });
|
|
5543
7064
|
}
|
|
5544
|
-
function
|
|
7065
|
+
function ChatLine({
|
|
7066
|
+
message,
|
|
7067
|
+
local,
|
|
7068
|
+
people
|
|
7069
|
+
}) {
|
|
7070
|
+
const mine = message.identity === local;
|
|
7071
|
+
const privateTo = message.to ?? null;
|
|
7072
|
+
const counterpart = privateTo === null ? null : mine ? people.find((p) => p.identity === privateTo)?.displayName ?? "them" : "you";
|
|
7073
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
7074
|
+
"li",
|
|
7075
|
+
{
|
|
7076
|
+
className: [
|
|
7077
|
+
"mx-meet__chat-line",
|
|
7078
|
+
privateTo !== null ? "mx-meet__chat-line--private" : "",
|
|
7079
|
+
message.failed !== void 0 ? "mx-meet__chat-line--failed" : ""
|
|
7080
|
+
].filter(Boolean).join(" "),
|
|
7081
|
+
children: [
|
|
7082
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "mx-meet__chat-meta", children: [
|
|
7083
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: mine ? "You" : message.displayName }),
|
|
7084
|
+
counterpart !== null ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "mx-meet__chat-private", children: [
|
|
7085
|
+
"to ",
|
|
7086
|
+
counterpart,
|
|
7087
|
+
" \xB7 private"
|
|
7088
|
+
] }) : null,
|
|
7089
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("time", { dateTime: message.at, children: clockOf(message.at) })
|
|
7090
|
+
] }),
|
|
7091
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "mx-meet__chat-text", children: message.text }),
|
|
7092
|
+
message.failed !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "mx-meet__chat-failed", children: message.failed }) : privateTo === null && message.saved === false ? /* @__PURE__ */ (0, import_jsx_runtime3.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
|
|
7093
|
+
]
|
|
7094
|
+
}
|
|
7095
|
+
);
|
|
7096
|
+
}
|
|
7097
|
+
function PeoplePanel({
|
|
7098
|
+
onClose,
|
|
7099
|
+
onMessage
|
|
7100
|
+
}) {
|
|
5545
7101
|
const snapshot = useMeetSnapshot();
|
|
5546
7102
|
const controls = useHostControls();
|
|
7103
|
+
const queue = useHandQueue();
|
|
7104
|
+
const [query, setQuery] = (0, import_react4.useState)("");
|
|
7105
|
+
const [menuFor, setMenuFor] = (0, import_react4.useState)(null);
|
|
5547
7106
|
const myRole = useRole();
|
|
5548
7107
|
const local = snapshot?.localIdentity ?? null;
|
|
5549
|
-
|
|
7108
|
+
const everyone = snapshot?.participants ?? [];
|
|
7109
|
+
const needle = query.trim().toLowerCase();
|
|
7110
|
+
const shown = needle.length === 0 ? everyone : everyone.filter((participant) => participant.displayName.toLowerCase().includes(needle));
|
|
7111
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(PanelShell, { title: `People (${everyone.length})`, onClose, children: [
|
|
5550
7112
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(LobbyPanel, {}),
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
|
|
5557
|
-
|
|
7113
|
+
queue.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { className: "mx-meet__section", "aria-label": "Raised hands", children: [
|
|
7114
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("header", { className: "mx-meet__section-head", children: [
|
|
7115
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("h3", { className: "mx-meet__section-title", children: [
|
|
7116
|
+
"Raised hands \xB7 ",
|
|
7117
|
+
queue.length
|
|
7118
|
+
] }),
|
|
7119
|
+
controls.isHost ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
7120
|
+
"button",
|
|
5558
7121
|
{
|
|
5559
|
-
|
|
7122
|
+
type: "button",
|
|
7123
|
+
className: "mx-meet__link",
|
|
7124
|
+
onClick: () => void controls.run({ action: "lower_all_hands" }),
|
|
7125
|
+
children: "Lower all"
|
|
5560
7126
|
}
|
|
5561
|
-
)
|
|
5562
|
-
isNoteTaker(participant) ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "mx-meet__badge", children: "Meeting assistant" }) : null
|
|
7127
|
+
) : null
|
|
5563
7128
|
] }),
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
7129
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ol", { className: "mx-meet__queue", children: queue.map((participant) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("li", { className: "mx-meet__person", children: [
|
|
7130
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Avatar, { name: participant.displayName, url: participant.avatarUrl, size: 28 }),
|
|
7131
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "mx-meet__person-name", children: [
|
|
7132
|
+
participant.displayName,
|
|
7133
|
+
participant.identity === local ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "mx-meet__you", children: " (you)" }) : null
|
|
7134
|
+
] }),
|
|
7135
|
+
controls.isHost || participant.identity === local ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
7136
|
+
"button",
|
|
7137
|
+
{
|
|
7138
|
+
type: "button",
|
|
7139
|
+
className: "mx-meet__link",
|
|
7140
|
+
onClick: () => void controls.run({ action: "lower_hand", identity: participant.identity }),
|
|
7141
|
+
children: "Lower"
|
|
7142
|
+
}
|
|
7143
|
+
) : null
|
|
7144
|
+
] }, participant.identity)) })
|
|
7145
|
+
] }) : null,
|
|
7146
|
+
controls.isHost && everyone.length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__bulk", children: [
|
|
7147
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("button", { type: "button", className: "mx-meet__chip", onClick: () => void controls.run({ action: "mute_all" }), children: [
|
|
7148
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MicOffIcon, { width: 14, height: 14 }),
|
|
7149
|
+
" Mute all"
|
|
7150
|
+
] }),
|
|
7151
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
7152
|
+
"button",
|
|
7153
|
+
{
|
|
7154
|
+
type: "button",
|
|
7155
|
+
className: "mx-meet__chip",
|
|
7156
|
+
onClick: () => void controls.run({ action: "ask_all_unmute" }),
|
|
7157
|
+
children: [
|
|
7158
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MicIcon, { width: 14, height: 14 }),
|
|
7159
|
+
" Ask all to unmute"
|
|
7160
|
+
]
|
|
7161
|
+
}
|
|
7162
|
+
)
|
|
7163
|
+
] }) : null,
|
|
7164
|
+
everyone.length > 4 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "mx-meet__search", children: [
|
|
7165
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SearchIcon, { width: 16, height: 16 }),
|
|
7166
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
7167
|
+
"input",
|
|
7168
|
+
{
|
|
7169
|
+
className: "mx-meet__input mx-meet__input--bare",
|
|
7170
|
+
value: query,
|
|
7171
|
+
onChange: (event) => setQuery(event.target.value),
|
|
7172
|
+
placeholder: "Search people",
|
|
7173
|
+
"aria-label": "Search people"
|
|
7174
|
+
}
|
|
7175
|
+
)
|
|
7176
|
+
] }) : null,
|
|
7177
|
+
controls.notice !== null ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: controls.notice.failed ? "mx-meet__error" : "mx-meet__notice", role: "status", children: controls.notice.text }) : null,
|
|
7178
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("ul", { className: "mx-meet__people", children: [
|
|
7179
|
+
shown.map((participant) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("li", { className: "mx-meet__person", children: [
|
|
7180
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Avatar, { name: participant.displayName, url: participant.avatarUrl, size: 32 }),
|
|
7181
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "mx-meet__person-name", children: [
|
|
7182
|
+
participant.displayName,
|
|
7183
|
+
participant.identity === local ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "mx-meet__you", children: " (you)" }) : null,
|
|
7184
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(RoleBadge, { role: participant.identity === local ? myRole : participant.role }),
|
|
7185
|
+
isNoteTaker(participant) ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "mx-meet__badge", children: "Meeting assistant" }) : null,
|
|
7186
|
+
snapshot?.spotlight === participant.identity ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "mx-meet__badge", children: "Spotlight" }) : null
|
|
7187
|
+
] }),
|
|
7188
|
+
participant.handRaised ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(HandIcon, { width: 14, height: 14 }) : null,
|
|
7189
|
+
!participant.micEnabled && !isNoteTaker(participant) ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MicOffIcon, { width: 14, height: 14 }) : null,
|
|
7190
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(QualityBars, { quality: participant.quality }),
|
|
7191
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "mx-meet__anchor", children: [
|
|
7192
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
7193
|
+
ControlButton,
|
|
7194
|
+
{
|
|
7195
|
+
label: `More for ${participant.displayName}`,
|
|
7196
|
+
active: menuFor === participant.identity,
|
|
7197
|
+
onClick: () => setMenuFor(menuFor === participant.identity ? null : participant.identity),
|
|
7198
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MoreIcon, { width: 16, height: 16 })
|
|
7199
|
+
}
|
|
7200
|
+
),
|
|
7201
|
+
menuFor === participant.identity ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
7202
|
+
PersonMenu,
|
|
7203
|
+
{
|
|
7204
|
+
participant,
|
|
7205
|
+
placement: "down",
|
|
7206
|
+
onClose: () => setMenuFor(null),
|
|
7207
|
+
...onMessage !== void 0 ? { onMessage } : {}
|
|
7208
|
+
}
|
|
7209
|
+
) : null
|
|
7210
|
+
] })
|
|
7211
|
+
] }, participant.identity)),
|
|
7212
|
+
shown.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("li", { className: "mx-meet__panel-note", children: [
|
|
7213
|
+
"Nobody matches \u201C",
|
|
7214
|
+
query,
|
|
7215
|
+
"\u201D."
|
|
7216
|
+
] }) : null
|
|
7217
|
+
] }),
|
|
7218
|
+
snapshot?.meeting !== null && snapshot?.meeting !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(GuestRecordAccessControl, { meeting: snapshot.meeting }) : null
|
|
5568
7219
|
] });
|
|
5569
7220
|
}
|
|
5570
|
-
function
|
|
7221
|
+
function PersonMenu({
|
|
5571
7222
|
participant,
|
|
5572
|
-
|
|
7223
|
+
onClose,
|
|
7224
|
+
onMessage,
|
|
7225
|
+
placement = "down"
|
|
5573
7226
|
}) {
|
|
5574
|
-
const
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
7227
|
+
const host = useMeetHost();
|
|
7228
|
+
const snapshot = useMeetSnapshot();
|
|
7229
|
+
const controls = useHostControls();
|
|
7230
|
+
const [mode, setMode] = (0, import_react4.useState)("menu");
|
|
7231
|
+
const [text3, setText] = (0, import_react4.useState)("");
|
|
7232
|
+
const self = participant.identity === snapshot?.localIdentity;
|
|
7233
|
+
const moderate = controls.canModerate(participant);
|
|
7234
|
+
const pinned = snapshot?.pinned === participant.identity;
|
|
7235
|
+
const spotlit = snapshot?.spotlight === participant.identity;
|
|
7236
|
+
const hasAccount = participant.userId !== null;
|
|
7237
|
+
const act = (request) => {
|
|
7238
|
+
void controls.run(request).then((result) => {
|
|
7239
|
+
if (result !== null) onClose();
|
|
7240
|
+
});
|
|
7241
|
+
};
|
|
7242
|
+
if (mode === "rename" || mode === "report") {
|
|
7243
|
+
const renaming = mode === "rename";
|
|
7244
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Popover, { label: renaming ? "Rename" : "Report", onClose, placement, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
7245
|
+
"form",
|
|
7246
|
+
{
|
|
7247
|
+
className: "mx-meet__menu-form",
|
|
7248
|
+
onSubmit: (event) => {
|
|
7249
|
+
event.preventDefault();
|
|
7250
|
+
if (text3.trim().length === 0) return;
|
|
7251
|
+
act(
|
|
7252
|
+
renaming ? { action: "rename", identity: participant.identity, name: text3.trim() } : { action: "report", identity: participant.identity, reason: text3.trim() }
|
|
7253
|
+
);
|
|
7254
|
+
},
|
|
7255
|
+
children: [
|
|
7256
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "mx-meet__field", children: [
|
|
7257
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "mx-meet__field-label", children: renaming ? "Name shown in this meeting" : `What did ${participant.displayName} do?` }),
|
|
7258
|
+
renaming ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
7259
|
+
"input",
|
|
7260
|
+
{
|
|
7261
|
+
className: "mx-meet__input",
|
|
7262
|
+
autoFocus: true,
|
|
7263
|
+
maxLength: 80,
|
|
7264
|
+
value: text3,
|
|
7265
|
+
onChange: (event) => setText(event.target.value)
|
|
7266
|
+
}
|
|
7267
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
7268
|
+
"textarea",
|
|
7269
|
+
{
|
|
7270
|
+
className: "mx-meet__input",
|
|
7271
|
+
autoFocus: true,
|
|
7272
|
+
rows: 3,
|
|
7273
|
+
maxLength: 1e3,
|
|
7274
|
+
value: text3,
|
|
7275
|
+
onChange: (event) => setText(event.target.value)
|
|
7276
|
+
}
|
|
7277
|
+
)
|
|
7278
|
+
] }),
|
|
7279
|
+
controls.notice?.failed === true ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mx-meet__error", children: controls.notice.text }) : null,
|
|
7280
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__menu-actions", children: [
|
|
7281
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "mx-meet__link", onClick: () => setMode("menu"), children: "Back" }),
|
|
7282
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "submit", className: "mx-meet__send", disabled: text3.trim().length === 0 || controls.busy, children: renaming ? "Rename" : "Report" })
|
|
7283
|
+
] })
|
|
7284
|
+
]
|
|
7285
|
+
}
|
|
7286
|
+
) });
|
|
7287
|
+
}
|
|
7288
|
+
if (mode === "remove" || mode === "transfer") {
|
|
7289
|
+
const removing = mode === "remove";
|
|
7290
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Popover, { label: removing ? "Remove" : "Make host", onClose, placement, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__menu-form", children: [
|
|
7291
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.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.` }),
|
|
7292
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__menu-actions", children: [
|
|
7293
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "mx-meet__link", onClick: () => setMode("menu"), children: "Cancel" }),
|
|
7294
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
7295
|
+
"button",
|
|
7296
|
+
{
|
|
7297
|
+
type: "button",
|
|
7298
|
+
className: removing ? "mx-meet__send mx-meet__send--danger" : "mx-meet__send",
|
|
7299
|
+
disabled: controls.busy,
|
|
7300
|
+
onClick: () => act({ action: removing ? "remove" : "transfer_host", identity: participant.identity }),
|
|
7301
|
+
children: removing ? "Remove" : "Make host"
|
|
7302
|
+
}
|
|
7303
|
+
)
|
|
7304
|
+
] })
|
|
7305
|
+
] }) });
|
|
7306
|
+
}
|
|
7307
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Popover, { label: `Options for ${participant.displayName}`, onClose, placement, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__menu", role: "menu", children: [
|
|
7308
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
7309
|
+
MenuItem,
|
|
7310
|
+
{
|
|
7311
|
+
onSelect: () => {
|
|
7312
|
+
host?.engine.pin(pinned ? null : participant.identity);
|
|
7313
|
+
onClose();
|
|
7314
|
+
},
|
|
7315
|
+
children: [
|
|
7316
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PinIcon, { width: 16, height: 16 }),
|
|
7317
|
+
" ",
|
|
7318
|
+
pinned ? "Unpin" : "Pin for me"
|
|
7319
|
+
]
|
|
7320
|
+
}
|
|
7321
|
+
),
|
|
7322
|
+
!self && !participant.isAgent && onMessage !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
7323
|
+
MenuItem,
|
|
7324
|
+
{
|
|
7325
|
+
onSelect: () => {
|
|
7326
|
+
onMessage(participant.identity);
|
|
7327
|
+
onClose();
|
|
7328
|
+
},
|
|
7329
|
+
children: [
|
|
7330
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChatIcon, { width: 16, height: 16 }),
|
|
7331
|
+
" Message privately"
|
|
7332
|
+
]
|
|
7333
|
+
}
|
|
7334
|
+
) : null,
|
|
7335
|
+
controls.isHost ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(MenuItem, { onSelect: () => act({ action: "spotlight", identity: spotlit ? null : participant.identity }), children: [
|
|
7336
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SpotlightIcon, { width: 16, height: 16 }),
|
|
7337
|
+
" ",
|
|
7338
|
+
spotlit ? "Remove spotlight" : "Spotlight for everyone"
|
|
7339
|
+
] }) : null,
|
|
7340
|
+
moderate ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
7341
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mx-meet__menu-sep", role: "separator" }),
|
|
7342
|
+
participant.micEnabled ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(MenuItem, { onSelect: () => act({ action: "mute", identity: participant.identity }), children: [
|
|
7343
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MicOffIcon, { width: 16, height: 16 }),
|
|
7344
|
+
" Mute"
|
|
7345
|
+
] }) : !participant.isAgent ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(MenuItem, { onSelect: () => act({ action: "ask_unmute", identity: participant.identity }), children: [
|
|
7346
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MicIcon, { width: 16, height: 16 }),
|
|
7347
|
+
" Ask to unmute"
|
|
7348
|
+
] }) : null,
|
|
7349
|
+
participant.cameraEnabled ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(MenuItem, { onSelect: () => act({ action: "stop_video", identity: participant.identity }), children: [
|
|
7350
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CameraOffIcon, { width: 16, height: 16 }),
|
|
7351
|
+
" Stop video"
|
|
7352
|
+
] }) : null,
|
|
7353
|
+
participant.screenSharing ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(MenuItem, { onSelect: () => act({ action: "stop_screen", identity: participant.identity }), children: [
|
|
7354
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ScreenShareIcon, { width: 16, height: 16 }),
|
|
7355
|
+
" Stop screen share"
|
|
7356
|
+
] }) : null,
|
|
7357
|
+
participant.handRaised ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(MenuItem, { onSelect: () => act({ action: "lower_hand", identity: participant.identity }), children: [
|
|
7358
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(HandIcon, { width: 16, height: 16 }),
|
|
7359
|
+
" Lower hand"
|
|
7360
|
+
] }) : null,
|
|
7361
|
+
hasAccount && !participant.isAgent && participant.role !== "cohost" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(MenuItem, { onSelect: () => act({ action: "make_cohost", identity: participant.identity }), children: [
|
|
7362
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PeopleIcon, { width: 16, height: 16 }),
|
|
7363
|
+
" Make co-host"
|
|
7364
|
+
] }) : null,
|
|
7365
|
+
!participant.isAgent ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
7366
|
+
MenuItem,
|
|
5584
7367
|
{
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
setConfirming(false);
|
|
5589
|
-
void controls.remove(participant);
|
|
7368
|
+
onSelect: () => {
|
|
7369
|
+
setText(participant.displayName);
|
|
7370
|
+
setMode("rename");
|
|
5590
7371
|
},
|
|
5591
|
-
children:
|
|
7372
|
+
children: [
|
|
7373
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PencilIcon, { width: 16, height: 16 }),
|
|
7374
|
+
" Rename"
|
|
7375
|
+
]
|
|
5592
7376
|
}
|
|
5593
|
-
)
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
7377
|
+
) : null
|
|
7378
|
+
] }) : null,
|
|
7379
|
+
controls.isOwner && participant.role === "cohost" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(MenuItem, { onSelect: () => act({ action: "revoke_cohost", identity: participant.identity }), children: [
|
|
7380
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PeopleIcon, { width: 16, height: 16 }),
|
|
7381
|
+
" Remove co-host"
|
|
7382
|
+
] }) : null,
|
|
7383
|
+
controls.isOwner && !self && hasAccount && !participant.isAgent ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(MenuItem, { onSelect: () => setMode("transfer"), children: [
|
|
7384
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SpotlightIcon, { width: 16, height: 16 }),
|
|
7385
|
+
" Make host"
|
|
7386
|
+
] }) : null,
|
|
7387
|
+
self ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
7388
|
+
MenuItem,
|
|
5600
7389
|
{
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
7390
|
+
onSelect: () => {
|
|
7391
|
+
setText(participant.displayName);
|
|
7392
|
+
setMode("rename");
|
|
7393
|
+
},
|
|
7394
|
+
children: [
|
|
7395
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PencilIcon, { width: 16, height: 16 }),
|
|
7396
|
+
" Rename yourself"
|
|
7397
|
+
]
|
|
7398
|
+
}
|
|
7399
|
+
) : null,
|
|
7400
|
+
!self && !participant.isAgent ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
7401
|
+
MenuItem,
|
|
7402
|
+
{
|
|
7403
|
+
onSelect: () => {
|
|
7404
|
+
setText("");
|
|
7405
|
+
setMode("report");
|
|
7406
|
+
},
|
|
7407
|
+
children: [
|
|
7408
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(FlagIcon, { width: 16, height: 16 }),
|
|
7409
|
+
" Report"
|
|
7410
|
+
]
|
|
5604
7411
|
}
|
|
5605
7412
|
) : null,
|
|
7413
|
+
moderate && !participant.isAgent ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(MenuItem, { danger: true, onSelect: () => setMode("remove"), children: [
|
|
7414
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CloseIcon, { width: 16, height: 16 }),
|
|
7415
|
+
" Remove from meeting"
|
|
7416
|
+
] }) : null
|
|
7417
|
+
] }) });
|
|
7418
|
+
}
|
|
7419
|
+
function SettingsPanel({ onClose }) {
|
|
7420
|
+
const host = useRequiredMeetHost();
|
|
7421
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(PanelShell, { title: "Audio, video and effects", onClose, children: [
|
|
5606
7422
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
5607
|
-
|
|
7423
|
+
DeviceSettings,
|
|
5608
7424
|
{
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
7425
|
+
onPick: (kind, deviceId2) => {
|
|
7426
|
+
host.devices.select(kind, deviceId2);
|
|
7427
|
+
void host.engine.switchDevice(kind, deviceId2);
|
|
7428
|
+
}
|
|
5613
7429
|
}
|
|
5614
|
-
)
|
|
7430
|
+
),
|
|
7431
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(EffectsSettings, {})
|
|
7432
|
+
] });
|
|
7433
|
+
}
|
|
7434
|
+
function DeviceSettings({
|
|
7435
|
+
onPick
|
|
7436
|
+
}) {
|
|
7437
|
+
const host = useRequiredMeetHost();
|
|
7438
|
+
const devices = useDeviceSnapshot();
|
|
7439
|
+
const microphone = devices?.selected.audioinput ?? host.devices.resolve("audioinput");
|
|
7440
|
+
const speaker = devices?.selected.audiooutput ?? host.devices.resolve("audiooutput");
|
|
7441
|
+
const stream = useMicrophoneStream(microphone);
|
|
7442
|
+
const level = useAudioLevel(stream);
|
|
7443
|
+
const [testing, setTesting] = (0, import_react4.useState)(false);
|
|
7444
|
+
(0, import_react4.useEffect)(() => {
|
|
7445
|
+
void host.devices.refresh();
|
|
7446
|
+
}, [host]);
|
|
7447
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { className: "mx-meet__section", "aria-label": "Devices", children: [
|
|
7448
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DevicePicker, { kind: "audioinput", label: "Microphone", onPick }),
|
|
7449
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.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_runtime3.jsx)("span", { style: { width: `${Math.min(100, Math.round(level * 160))}%` } }) }),
|
|
7450
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DevicePicker, { kind: "audiooutput", label: "Speaker", onPick }),
|
|
7451
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
7452
|
+
"button",
|
|
7453
|
+
{
|
|
7454
|
+
type: "button",
|
|
7455
|
+
className: "mx-meet__chip",
|
|
7456
|
+
disabled: testing,
|
|
7457
|
+
onClick: () => {
|
|
7458
|
+
setTesting(true);
|
|
7459
|
+
void playTestSound(speaker).finally(() => setTesting(false));
|
|
7460
|
+
},
|
|
7461
|
+
children: [
|
|
7462
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SpeakerIcon, { width: 14, height: 14 }),
|
|
7463
|
+
" ",
|
|
7464
|
+
testing ? "Playing\u2026" : "Test speaker"
|
|
7465
|
+
]
|
|
7466
|
+
}
|
|
7467
|
+
),
|
|
7468
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DevicePicker, { kind: "videoinput", label: "Camera", onPick })
|
|
7469
|
+
] });
|
|
7470
|
+
}
|
|
7471
|
+
function EffectsSettings({ preview }) {
|
|
7472
|
+
const effects = useMediaEffects();
|
|
7473
|
+
const [error, setError] = (0, import_react4.useState)(null);
|
|
7474
|
+
const fileRef = (0, import_react4.useRef)(null);
|
|
7475
|
+
const supported = effects.backgroundsSupported || preview != null;
|
|
7476
|
+
const apply = (next) => {
|
|
7477
|
+
setError(null);
|
|
7478
|
+
void effects.set(next);
|
|
7479
|
+
if (preview != null) {
|
|
7480
|
+
const merged = { ...effects.effects, ...next };
|
|
7481
|
+
void preview.setEffect(toVideoEffect(merged)).catch(
|
|
7482
|
+
() => setError("This browser could not run the background effect.")
|
|
7483
|
+
);
|
|
7484
|
+
}
|
|
7485
|
+
};
|
|
7486
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { className: "mx-meet__section", "aria-label": "Effects", children: [
|
|
7487
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "mx-meet__toggle", children: [
|
|
7488
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
7489
|
+
"input",
|
|
7490
|
+
{
|
|
7491
|
+
type: "checkbox",
|
|
7492
|
+
checked: effects.effects.noiseSuppression,
|
|
7493
|
+
onChange: (event) => apply({ noiseSuppression: event.target.checked })
|
|
7494
|
+
}
|
|
7495
|
+
),
|
|
7496
|
+
"Noise suppression",
|
|
7497
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "mx-meet__hint", children: "Filters keyboards, fans and background chatter." })
|
|
7498
|
+
] }),
|
|
7499
|
+
supported ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__field", children: [
|
|
7500
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "mx-meet__field-label", children: "Background" }),
|
|
7501
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mx-meet__pills", role: "radiogroup", "aria-label": "Background", children: ["none", "blur", "image"].map((kind) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
7502
|
+
"button",
|
|
7503
|
+
{
|
|
7504
|
+
type: "button",
|
|
7505
|
+
role: "radio",
|
|
7506
|
+
"aria-checked": effects.effects.background === kind,
|
|
7507
|
+
className: effects.effects.background === kind ? "mx-meet__pill mx-meet__pill--on" : "mx-meet__pill",
|
|
7508
|
+
onClick: () => {
|
|
7509
|
+
if (kind === "image") {
|
|
7510
|
+
fileRef.current?.click();
|
|
7511
|
+
return;
|
|
7512
|
+
}
|
|
7513
|
+
apply({ background: kind });
|
|
7514
|
+
},
|
|
7515
|
+
children: kind === "none" ? "None" : kind === "blur" ? "Blur" : "Picture\u2026"
|
|
7516
|
+
},
|
|
7517
|
+
kind
|
|
7518
|
+
)) }),
|
|
7519
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
7520
|
+
"input",
|
|
7521
|
+
{
|
|
7522
|
+
ref: fileRef,
|
|
7523
|
+
type: "file",
|
|
7524
|
+
accept: "image/*",
|
|
7525
|
+
hidden: true,
|
|
7526
|
+
onChange: (event) => {
|
|
7527
|
+
const file = event.target.files?.[0];
|
|
7528
|
+
event.target.value = "";
|
|
7529
|
+
if (file === void 0) return;
|
|
7530
|
+
void shrinkImage(file).then((url) => apply({ background: "image", backgroundImage: url })).catch(() => setError("That picture could not be read. Try a JPEG or PNG."));
|
|
7531
|
+
}
|
|
7532
|
+
}
|
|
7533
|
+
),
|
|
7534
|
+
error !== null ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mx-meet__error", children: error }) : null
|
|
7535
|
+
] }) : null
|
|
5615
7536
|
] });
|
|
5616
7537
|
}
|
|
7538
|
+
function toVideoEffect(effects) {
|
|
7539
|
+
if (effects.background === "blur") return { kind: "blur" };
|
|
7540
|
+
if (effects.background === "image" && effects.backgroundImage !== null) {
|
|
7541
|
+
return { kind: "image", imageUrl: effects.backgroundImage };
|
|
7542
|
+
}
|
|
7543
|
+
return { kind: "none" };
|
|
7544
|
+
}
|
|
7545
|
+
async function shrinkImage(file) {
|
|
7546
|
+
const url = URL.createObjectURL(file);
|
|
7547
|
+
try {
|
|
7548
|
+
const image = new Image();
|
|
7549
|
+
image.src = url;
|
|
7550
|
+
await image.decode();
|
|
7551
|
+
const scale = Math.min(1, 1280 / image.naturalWidth);
|
|
7552
|
+
const canvas = document.createElement("canvas");
|
|
7553
|
+
canvas.width = Math.round(image.naturalWidth * scale);
|
|
7554
|
+
canvas.height = Math.round(image.naturalHeight * scale);
|
|
7555
|
+
const context = canvas.getContext("2d");
|
|
7556
|
+
if (context === null) throw new Error("no 2d context");
|
|
7557
|
+
context.drawImage(image, 0, 0, canvas.width, canvas.height);
|
|
7558
|
+
return canvas.toDataURL("image/jpeg", 0.85);
|
|
7559
|
+
} finally {
|
|
7560
|
+
URL.revokeObjectURL(url);
|
|
7561
|
+
}
|
|
7562
|
+
}
|
|
7563
|
+
async function playTestSound(sinkId) {
|
|
7564
|
+
const Context = globalThis.AudioContext;
|
|
7565
|
+
if (Context === void 0) return;
|
|
7566
|
+
const context = new Context();
|
|
7567
|
+
const destination = context.createMediaStreamDestination();
|
|
7568
|
+
const gain = context.createGain();
|
|
7569
|
+
gain.gain.value = 0.2;
|
|
7570
|
+
gain.connect(destination);
|
|
7571
|
+
for (const [frequency, start] of [
|
|
7572
|
+
[660, 0],
|
|
7573
|
+
[880, 0.25]
|
|
7574
|
+
]) {
|
|
7575
|
+
const oscillator = context.createOscillator();
|
|
7576
|
+
oscillator.frequency.value = frequency;
|
|
7577
|
+
oscillator.connect(gain);
|
|
7578
|
+
oscillator.start(context.currentTime + start);
|
|
7579
|
+
oscillator.stop(context.currentTime + start + 0.22);
|
|
7580
|
+
}
|
|
7581
|
+
const audio = new Audio();
|
|
7582
|
+
audio.srcObject = destination.stream;
|
|
7583
|
+
const sinkable = audio;
|
|
7584
|
+
if (sinkId !== null && sinkId !== "default" && sinkable.setSinkId !== void 0) {
|
|
7585
|
+
await sinkable.setSinkId(sinkId).catch(() => void 0);
|
|
7586
|
+
}
|
|
7587
|
+
await audio.play().catch(() => void 0);
|
|
7588
|
+
await new Promise((resolve) => setTimeout(resolve, 700));
|
|
7589
|
+
audio.pause();
|
|
7590
|
+
await context.close();
|
|
7591
|
+
}
|
|
7592
|
+
function useMicrophoneStream(deviceId2) {
|
|
7593
|
+
const [stream, setStream] = (0, import_react4.useState)(null);
|
|
7594
|
+
(0, import_react4.useEffect)(() => {
|
|
7595
|
+
if (deviceId2 === "off") return void 0;
|
|
7596
|
+
let live = true;
|
|
7597
|
+
let opened = null;
|
|
7598
|
+
const media = globalThis.navigator?.mediaDevices;
|
|
7599
|
+
if (media === void 0) return void 0;
|
|
7600
|
+
void media.getUserMedia({ audio: deviceId2 === null ? true : { deviceId: { exact: deviceId2 } } }).then((next) => {
|
|
7601
|
+
if (!live) {
|
|
7602
|
+
next.getTracks().forEach((track) => track.stop());
|
|
7603
|
+
return;
|
|
7604
|
+
}
|
|
7605
|
+
opened = next;
|
|
7606
|
+
setStream(next);
|
|
7607
|
+
}).catch(() => setStream(null));
|
|
7608
|
+
return () => {
|
|
7609
|
+
live = false;
|
|
7610
|
+
opened?.getTracks().forEach((track) => track.stop());
|
|
7611
|
+
setStream(null);
|
|
7612
|
+
};
|
|
7613
|
+
}, [deviceId2]);
|
|
7614
|
+
return stream;
|
|
7615
|
+
}
|
|
5617
7616
|
function LobbyPanel() {
|
|
5618
7617
|
const lobby = useLobby();
|
|
5619
7618
|
if (!lobby.canAdmit || lobby.waiting.length === 0) return null;
|
|
@@ -5839,16 +7838,40 @@ function PreJoinBody({
|
|
|
5839
7838
|
const room = useRoom();
|
|
5840
7839
|
const devices = useDeviceSnapshot();
|
|
5841
7840
|
const recording = useRecording();
|
|
7841
|
+
const effects = useMediaEffects();
|
|
7842
|
+
const { notice } = useModerationNotice();
|
|
5842
7843
|
const [mode, setMode] = (0, import_react4.useState)("video");
|
|
5843
7844
|
const [microphone, setMicrophone] = (0, import_react4.useState)(true);
|
|
5844
7845
|
const [joining, setJoining] = (0, import_react4.useState)(false);
|
|
7846
|
+
const camera = devices?.selected.videoinput ?? host.devices.resolve("videoinput");
|
|
7847
|
+
const mic = devices?.selected.audioinput ?? host.devices.resolve("audioinput");
|
|
7848
|
+
const preview = useCameraPreview(mode === "video" ? camera : void 0, effects.effects);
|
|
7849
|
+
const micStream = useMicrophoneStream(microphone ? mic : "off");
|
|
7850
|
+
const level = useAudioLevel(micStream);
|
|
7851
|
+
const [speakerTesting, setSpeakerTesting] = (0, import_react4.useState)(false);
|
|
5845
7852
|
(0, import_react4.useEffect)(() => {
|
|
5846
7853
|
void host.devices.refresh();
|
|
5847
7854
|
}, [host]);
|
|
5848
7855
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__prejoin", children: [
|
|
5849
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.
|
|
7856
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__preview", children: [
|
|
7857
|
+
mode === "video" && preview.preview !== null ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
7858
|
+
"video",
|
|
7859
|
+
{
|
|
7860
|
+
className: "mx-meet__video mx-meet__video--mirror",
|
|
7861
|
+
autoPlay: true,
|
|
7862
|
+
playsInline: true,
|
|
7863
|
+
muted: true,
|
|
7864
|
+
ref: (element) => {
|
|
7865
|
+
if (element !== null) preview.preview?.attach(element);
|
|
7866
|
+
}
|
|
7867
|
+
}
|
|
7868
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mx-meet__tile-placeholder", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Avatar, { name: host.identity.displayName, url: host.identity.avatarUrl, size: 96 }) }),
|
|
7869
|
+
mode === "video" && preview.error !== null ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mx-meet__preview-note", children: preview.error }) : null,
|
|
7870
|
+
microphone ? /* @__PURE__ */ (0, import_jsx_runtime3.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_runtime3.jsx)("span", { style: { width: `${Math.min(100, Math.round(level * 160))}%` } }) }) : null
|
|
7871
|
+
] }),
|
|
5850
7872
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__prejoin-panel", children: [
|
|
5851
7873
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h1", { className: "mx-meet__prejoin-title", children: "Ready to join?" }),
|
|
7874
|
+
notice?.kind === "removed" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mx-meet__warning", children: notice.text }) : null,
|
|
5852
7875
|
devices?.labelsHidden === true ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { className: "mx-meet__panel-note", children: [
|
|
5853
7876
|
"Your browser is hiding device names until you allow camera and microphone access.",
|
|
5854
7877
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
@@ -5865,7 +7888,30 @@ function PreJoinBody({
|
|
|
5865
7888
|
] }) : null,
|
|
5866
7889
|
devices?.fallbackNotice != null ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mx-meet__warning", children: devices.fallbackNotice }) : null,
|
|
5867
7890
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DevicePicker, { kind: "audioinput", label: "Microphone" }),
|
|
7891
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__field-row", children: [
|
|
7892
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DevicePicker, { kind: "audiooutput", label: "Speaker" }),
|
|
7893
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
7894
|
+
"button",
|
|
7895
|
+
{
|
|
7896
|
+
type: "button",
|
|
7897
|
+
className: "mx-meet__chip",
|
|
7898
|
+
disabled: speakerTesting,
|
|
7899
|
+
onClick: () => {
|
|
7900
|
+
setSpeakerTesting(true);
|
|
7901
|
+
void playTestSound(devices?.selected.audiooutput ?? host.devices.resolve("audiooutput")).finally(
|
|
7902
|
+
() => setSpeakerTesting(false)
|
|
7903
|
+
);
|
|
7904
|
+
},
|
|
7905
|
+
children: [
|
|
7906
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SpeakerIcon, { width: 14, height: 14 }),
|
|
7907
|
+
" ",
|
|
7908
|
+
speakerTesting ? "Playing\u2026" : "Test"
|
|
7909
|
+
]
|
|
7910
|
+
}
|
|
7911
|
+
)
|
|
7912
|
+
] }),
|
|
5868
7913
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DevicePicker, { kind: "videoinput", label: "Camera" }),
|
|
7914
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(EffectsSettings, { preview: preview.preview }),
|
|
5869
7915
|
recording.consentNotice !== null ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mx-meet__consent", children: recording.consentNotice }) : null,
|
|
5870
7916
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__prejoin-actions", children: [
|
|
5871
7917
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "mx-meet__toggle", children: [
|
|
@@ -5897,6 +7943,7 @@ function PreJoinBody({
|
|
|
5897
7943
|
className: "mx-meet__primary",
|
|
5898
7944
|
onClick: () => {
|
|
5899
7945
|
setJoining(true);
|
|
7946
|
+
preview.release();
|
|
5900
7947
|
void room.join({
|
|
5901
7948
|
roomName,
|
|
5902
7949
|
mode,
|
|
@@ -5916,9 +7963,49 @@ function PreJoinBody({
|
|
|
5916
7963
|
] })
|
|
5917
7964
|
] });
|
|
5918
7965
|
}
|
|
7966
|
+
function useCameraPreview(deviceId2, effects) {
|
|
7967
|
+
const [preview, setPreview] = (0, import_react4.useState)(null);
|
|
7968
|
+
const [error, setError] = (0, import_react4.useState)(null);
|
|
7969
|
+
const current = (0, import_react4.useRef)(null);
|
|
7970
|
+
const initialEffect = (0, import_react4.useRef)(toVideoEffect(effects));
|
|
7971
|
+
initialEffect.current = toVideoEffect(effects);
|
|
7972
|
+
(0, import_react4.useEffect)(() => {
|
|
7973
|
+
if (deviceId2 === void 0) return void 0;
|
|
7974
|
+
let live = true;
|
|
7975
|
+
setError(null);
|
|
7976
|
+
void createCameraPreview(deviceId2, initialEffect.current).then((created) => {
|
|
7977
|
+
if (!live) {
|
|
7978
|
+
created.stop();
|
|
7979
|
+
return;
|
|
7980
|
+
}
|
|
7981
|
+
current.current = created;
|
|
7982
|
+
setPreview(created);
|
|
7983
|
+
}).catch((thrown) => {
|
|
7984
|
+
if (!live) return;
|
|
7985
|
+
const failure = thrown;
|
|
7986
|
+
setError([failure.message, failure.remedy].filter(Boolean).join(". ") || "The camera did not start.");
|
|
7987
|
+
});
|
|
7988
|
+
return () => {
|
|
7989
|
+
live = false;
|
|
7990
|
+
current.current?.stop();
|
|
7991
|
+
current.current = null;
|
|
7992
|
+
setPreview(null);
|
|
7993
|
+
};
|
|
7994
|
+
}, [deviceId2]);
|
|
7995
|
+
return {
|
|
7996
|
+
preview,
|
|
7997
|
+
error,
|
|
7998
|
+
release: () => {
|
|
7999
|
+
current.current?.stop();
|
|
8000
|
+
current.current = null;
|
|
8001
|
+
setPreview(null);
|
|
8002
|
+
}
|
|
8003
|
+
};
|
|
8004
|
+
}
|
|
5919
8005
|
function DevicePicker({
|
|
5920
8006
|
kind,
|
|
5921
|
-
label
|
|
8007
|
+
label,
|
|
8008
|
+
onPick
|
|
5922
8009
|
}) {
|
|
5923
8010
|
const host = useRequiredMeetHost();
|
|
5924
8011
|
const devices = useDeviceSnapshot();
|
|
@@ -5931,7 +8018,10 @@ function DevicePicker({
|
|
|
5931
8018
|
{
|
|
5932
8019
|
className: "mx-meet__select",
|
|
5933
8020
|
value: devices?.selected[kind] ?? host.devices.resolve(kind) ?? "",
|
|
5934
|
-
onChange: (event) =>
|
|
8021
|
+
onChange: (event) => {
|
|
8022
|
+
if (onPick !== void 0) onPick(kind, event.target.value);
|
|
8023
|
+
else host.devices.select(kind, event.target.value);
|
|
8024
|
+
},
|
|
5935
8025
|
children: list.map((device) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: device.deviceId, children: device.label.length > 0 ? device.label : `${label} (name hidden)` }, device.deviceId))
|
|
5936
8026
|
}
|
|
5937
8027
|
)
|
|
@@ -6138,7 +8228,7 @@ function ScheduleMeetingBody({
|
|
|
6138
8228
|
host
|
|
6139
8229
|
}) {
|
|
6140
8230
|
const [title, setTitle] = (0, import_react4.useState)("");
|
|
6141
|
-
const [
|
|
8231
|
+
const [when2, setWhen] = (0, import_react4.useState)("");
|
|
6142
8232
|
const [kind, setKind] = (0, import_react4.useState)("scheduled");
|
|
6143
8233
|
const [busy, setBusy] = (0, import_react4.useState)(false);
|
|
6144
8234
|
const [link, setLink] = (0, import_react4.useState)(null);
|
|
@@ -6158,7 +8248,7 @@ function ScheduleMeetingBody({
|
|
|
6158
8248
|
hostUserId: host.identity.userId,
|
|
6159
8249
|
title: title.trim(),
|
|
6160
8250
|
kind,
|
|
6161
|
-
scheduledFor:
|
|
8251
|
+
scheduledFor: when2.length > 0 ? new Date(when2).toISOString() : null
|
|
6162
8252
|
}).then((meeting) => {
|
|
6163
8253
|
setLink(meetingLink(appBaseUrl, meeting.slug));
|
|
6164
8254
|
onScheduled?.(meeting.slug);
|
|
@@ -6188,7 +8278,7 @@ function ScheduleMeetingBody({
|
|
|
6188
8278
|
{
|
|
6189
8279
|
className: "mx-meet__input",
|
|
6190
8280
|
type: "datetime-local",
|
|
6191
|
-
value:
|
|
8281
|
+
value: when2,
|
|
6192
8282
|
onChange: (event) => setWhen(event.target.value)
|
|
6193
8283
|
}
|
|
6194
8284
|
)
|
|
@@ -6768,4 +8858,55 @@ function createMeetingInviteHandler(options) {
|
|
|
6768
8858
|
}
|
|
6769
8859
|
};
|
|
6770
8860
|
}
|
|
8861
|
+
|
|
8862
|
+
// src/core/management.ts
|
|
8863
|
+
async function refusal(response, operation) {
|
|
8864
|
+
let body = null;
|
|
8865
|
+
try {
|
|
8866
|
+
body = await response.json();
|
|
8867
|
+
} catch {
|
|
8868
|
+
body = null;
|
|
8869
|
+
}
|
|
8870
|
+
const said = readServerRefusal(body);
|
|
8871
|
+
return new MeetError(
|
|
8872
|
+
response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport",
|
|
8873
|
+
`${operation}: ${said?.reason ?? `the server answered ${response.status}`}`,
|
|
8874
|
+
said?.remedy || "The meeting itself is saved \u2014 only the telling failed. Try again; the call is idempotent."
|
|
8875
|
+
);
|
|
8876
|
+
}
|
|
8877
|
+
function count(body, key) {
|
|
8878
|
+
const value = body[key];
|
|
8879
|
+
return typeof value === "number" ? value : 0;
|
|
8880
|
+
}
|
|
8881
|
+
async function announceMeeting(api, meetingId) {
|
|
8882
|
+
const operation = "meet.announceMeeting";
|
|
8883
|
+
const response = await api.request({
|
|
8884
|
+
path: meetingPath(MEET_ROUTES.meetingAnnounce, meetingId),
|
|
8885
|
+
method: "POST",
|
|
8886
|
+
requireAuth: true,
|
|
8887
|
+
operation
|
|
8888
|
+
});
|
|
8889
|
+
if (!response.ok) throw await refusal(response, operation);
|
|
8890
|
+
const body = await response.json();
|
|
8891
|
+
return {
|
|
8892
|
+
invitations: count(body, "invitations"),
|
|
8893
|
+
updates: count(body, "updates"),
|
|
8894
|
+
cancellations: count(body, "cancellations"),
|
|
8895
|
+
alreadyCurrent: count(body, "already_current"),
|
|
8896
|
+
remindersQueued: count(body, "reminders_queued"),
|
|
8897
|
+
remindersCancelled: count(body, "reminders_cancelled")
|
|
8898
|
+
};
|
|
8899
|
+
}
|
|
8900
|
+
async function pushMeetingToGoogleCalendar(api, meetingId, target) {
|
|
8901
|
+
const operation = "meet.pushMeetingToGoogleCalendar";
|
|
8902
|
+
const response = await api.request({
|
|
8903
|
+
path: meetingPath(MEET_ROUTES.meetingGoogleCalendar, meetingId),
|
|
8904
|
+
method: "POST",
|
|
8905
|
+
requireAuth: true,
|
|
8906
|
+
operation,
|
|
8907
|
+
body: { connection_id: target.connectionId, calendar_id: target.calendarId ?? "primary" }
|
|
8908
|
+
});
|
|
8909
|
+
if (!response.ok) throw await refusal(response, operation);
|
|
8910
|
+
return await response.json();
|
|
8911
|
+
}
|
|
6771
8912
|
//# sourceMappingURL=react.cjs.map
|