mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-05-23 03:07:48 +00:00
Compare commits
43 Commits
8892
...
tests-expe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b378d972e0 | ||
|
|
99a5d7eaa9 | ||
|
|
dad4fb9e06 | ||
|
|
3772b9a5ae | ||
|
|
89b9c75242 | ||
|
|
b24b60b735 | ||
|
|
486a1f6511 | ||
|
|
80b3f1d7d4 | ||
|
|
421b21edeb | ||
|
|
a58b0d9a85 | ||
|
|
1aca8ab985 | ||
|
|
f9daba728f | ||
|
|
fbb6456317 | ||
|
|
52ead26bed | ||
|
|
8d1da83e3c | ||
|
|
5453b615f5 | ||
|
|
81a7301a3e | ||
|
|
1138b7779b | ||
|
|
2fd653d928 | ||
|
|
012c9fb329 | ||
|
|
fdf95444e9 | ||
|
|
919c60b3d2 | ||
|
|
e02c4e8f7f | ||
|
|
3fd9ce5f11 | ||
|
|
93022b3281 | ||
|
|
5d63b31071 | ||
|
|
4432f727a4 | ||
|
|
6f1bdb513a | ||
|
|
ad144e6fd3 | ||
|
|
076d77a982 | ||
|
|
5afdda7568 | ||
|
|
6cb57c472c | ||
|
|
e026bac42c | ||
|
|
b29e48d471 | ||
|
|
99cb5e6c40 | ||
|
|
01834903c2 | ||
|
|
2929317972 | ||
|
|
57865d74c6 | ||
|
|
f8f331a576 | ||
|
|
c610e955cd | ||
|
|
53899947a9 | ||
|
|
bf23107e7a | ||
|
|
6784921429 |
@@ -14,3 +14,12 @@ trim_trailing_whitespace = false
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
|
||||
[*.{java,kt}]
|
||||
indent_size = 4
|
||||
|
||||
[*.xml]
|
||||
indent_size = 2
|
||||
|
||||
[*.{swift,m,mm,h}]
|
||||
indent_size = 4
|
||||
|
||||
@@ -37,7 +37,6 @@ public class JitsiMeetActivityDelegate {
|
||||
* React Native module.
|
||||
*/
|
||||
private static PermissionListener permissionListener;
|
||||
private static Callback permissionsCallback;
|
||||
|
||||
/**
|
||||
* Tells whether or not the permissions request is currently in progress.
|
||||
@@ -142,11 +141,6 @@ public class JitsiMeetActivityDelegate {
|
||||
if (reactInstanceManager != null) {
|
||||
reactInstanceManager.onHostResume(activity, new DefaultHardwareBackBtnHandlerImpl(activity));
|
||||
}
|
||||
|
||||
if (permissionsCallback != null) {
|
||||
permissionsCallback.invoke();
|
||||
permissionsCallback = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,15 +163,10 @@ public class JitsiMeetActivityDelegate {
|
||||
|
||||
public static void onRequestPermissionsResult(
|
||||
final int requestCode, final String[] permissions, final int[] grantResults) {
|
||||
permissionsCallback = new Callback() {
|
||||
@Override
|
||||
public void invoke(Object... args) {
|
||||
if (permissionListener != null
|
||||
&& permissionListener.onRequestPermissionsResult(requestCode, permissions, grantResults)) {
|
||||
permissionListener = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
// Invoke the callback immediately
|
||||
if (permissionListener != null && permissionListener.onRequestPermissionsResult(requestCode, permissions, grantResults)) {
|
||||
permissionListener = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void requestPermissions(Activity activity, String[] permissions, int requestCode, PermissionListener listener) {
|
||||
|
||||
@@ -99,6 +99,7 @@ public class JitsiMeetOngoingConferenceService extends Service implements Ongoin
|
||||
|
||||
public static void launch(Context context, HashMap<String, Object> extraData) {
|
||||
List<String> permissionsList = new ArrayList<>();
|
||||
Activity activity = (Activity) context;
|
||||
|
||||
PermissionListener listener = new PermissionListener() {
|
||||
@Override
|
||||
@@ -134,7 +135,7 @@ public class JitsiMeetOngoingConferenceService extends Service implements Ongoin
|
||||
|
||||
if (permissionsArray.length > 0) {
|
||||
JitsiMeetActivityDelegate.requestPermissions(
|
||||
(Activity) context,
|
||||
activity,
|
||||
permissionsArray,
|
||||
PERMISSIONS_REQUEST_CODE,
|
||||
listener
|
||||
@@ -159,12 +160,20 @@ public class JitsiMeetOngoingConferenceService extends Service implements Ongoin
|
||||
stopSelf();
|
||||
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
|
||||
} else {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK | ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE);
|
||||
} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
|
||||
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK);
|
||||
} else {
|
||||
startForeground(NOTIFICATION_ID, notification);
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK | ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE);
|
||||
} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
|
||||
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK);
|
||||
} else {
|
||||
startForeground(NOTIFICATION_ID, notification);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Handle ForegroundServiceStartNotAllowedException when app is in background and cannot start foreground service.
|
||||
// See: https://developer.android.com/develop/background-work/services/fgs/restrictions-bg-start#wiu-restrictions
|
||||
JitsiMeetLogger.w(TAG + " Failed to start foreground service", e);
|
||||
stopSelf();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -139,6 +139,9 @@ var config = {
|
||||
// Disables polls feature.
|
||||
// disablePolls: false,
|
||||
|
||||
// Disables chat feature entirely including notifications, sounds, and private messages.
|
||||
// disableChat: false,
|
||||
|
||||
// Disables demote button from self-view
|
||||
// disableSelfDemote: false,
|
||||
|
||||
@@ -904,6 +907,8 @@ var config = {
|
||||
// alwaysVisible: false,
|
||||
// // Indicates whether the toolbar should still autohide when chat is open
|
||||
// autoHideWhileChatIsOpen: false,
|
||||
// // Default background color for the main toolbar. Accepts any valid CSS color.
|
||||
// // backgroundColor: '#ffffff',
|
||||
// },
|
||||
|
||||
// Overrides the buttons displayed in the main toolbar. Depending on the screen size the number of displayed
|
||||
|
||||
@@ -114,6 +114,9 @@
|
||||
"error": "Errore: il tuo messaggio non è stato inviato. Motivo: {{error}}",
|
||||
"everyone": "Tutti",
|
||||
"fieldPlaceHolder": "Scrivi qui il tuo messaggio",
|
||||
"fileAccessibleTitle": "{{user}} ha caricato un file",
|
||||
"fileAccessibleTitleMe": "ho caricato un file",
|
||||
"fileDeleted": "Un file è stato eliminato",
|
||||
"guestsChatIndicator": "(ospite)",
|
||||
"lobbyChatMessageTo": "Messaggio a {{recipient}} in sala d'attesa",
|
||||
"message": "Messaggio",
|
||||
@@ -280,7 +283,6 @@
|
||||
"Submit": "Invia",
|
||||
"Understand": "Accetto, mantieni microfono e videocamera disattivati per ora",
|
||||
"UnderstandAndUnmute": "Accetto, riattiva microfono e videocamera",
|
||||
"WaitForHostMsg": "La riunione non è ancora iniziata. Se sei l'organizzatore, per favore autenticati. Altrimenti, attendi l'arrivo dell'organizzatore.",
|
||||
"WaitForHostNoAuthMsg": "La riunione non è ancora iniziata perché nessun organizzatore si è ancora collegato. Si prega di attendere.",
|
||||
"WaitingForHostButton": "Attendi l'organizzatore",
|
||||
"WaitingForHostTitle": "In attesa dell'organizzatore…",
|
||||
@@ -523,6 +525,7 @@
|
||||
"tokenAuthFailedWithReasons": "Non sei autorizzato a partecipare a questa chiamata. Possibile motivo: {{reason}}",
|
||||
"tokenAuthUnsupported": "Il token URL non è supportato.",
|
||||
"transcribing": "Trascrizione in corso",
|
||||
"unauthenticatedAccessDisabled": "Questa chiamata richiede l'autenticazione. Si prega di accedere per procedere.",
|
||||
"unlockRoom": "Rimuovi la $t(lockRoomPassword) alla riunione",
|
||||
"user": "Utente",
|
||||
"userIdentifier": "Identificatore utente",
|
||||
@@ -570,10 +573,12 @@
|
||||
"downloadStarted": "Download del file iniziato",
|
||||
"dragAndDrop": "Trascina e rilascia i file qui o da qualsiasi altra parte nella schermata",
|
||||
"fileAlreadyUploaded": "Questo file è già stato caricato nella riunione.",
|
||||
"fileRemovedByOther": "Il tuo file '{{ fileName }}' è stato rimosso",
|
||||
"fileTooLargeDescription": "Assicurati che il file non superi {{ maxFileSize }}.",
|
||||
"fileTooLargeTitle": "Il file selezionato è troppo grande",
|
||||
"fileUploadProgress": "Caricamento del file in corso",
|
||||
"fileUploadedSuccessfully": "Il file è stato caricato con successo",
|
||||
"newFileNotification": "{{ participantName }} ha condiviso '{{ fileName }}'",
|
||||
"removeFile": "Rimuovi",
|
||||
"removeFileSuccess": "File rimosso con successo",
|
||||
"uploadFailedDescription": "Si prega di riprovare.",
|
||||
@@ -748,7 +753,8 @@
|
||||
"notificationTitle": "Sala d'attesa",
|
||||
"passwordJoinButton": "Entra",
|
||||
"title": "Sala d'attesa",
|
||||
"toggleLabel": "Attiva sala d'attesa"
|
||||
"toggleLabel": "Attiva sala d'attesa",
|
||||
"waitForModerator": "La riunione non è ancora iniziata, perché non è arrivato alcun organizzatore. Se vuoi diventarlo autenticati, altrimenti attendi."
|
||||
},
|
||||
"localRecording": {
|
||||
"clientState": {
|
||||
@@ -964,6 +970,9 @@
|
||||
"by": "Da {{ name }}",
|
||||
"closeButton": "Chiudi sondaggio",
|
||||
"create": {
|
||||
"accessibilityLabel": {
|
||||
"send": "Invia sondaggio"
|
||||
},
|
||||
"addOption": "Aggiungi opzione",
|
||||
"answerPlaceholder": "Opzione {{index}}",
|
||||
"cancel": "Annulla",
|
||||
@@ -972,8 +981,7 @@
|
||||
"pollQuestion": "Domanda del sondaggio",
|
||||
"questionPlaceholder": "Fai una domanda",
|
||||
"removeOption": "Elimina opzione",
|
||||
"save": "Salva",
|
||||
"send": "Invia"
|
||||
"save": "Salva"
|
||||
},
|
||||
"errors": {
|
||||
"notUniqueOption": "Le opzioni devono essere uniche"
|
||||
@@ -1617,6 +1625,8 @@
|
||||
"noMainParticipantsTitle": "La riunione non è ancora iniziata.",
|
||||
"noVisitorLobby": "Non puoi partecipare se la sala d'attesa è attiva per la riunione.",
|
||||
"notAllowedPromotion": "Un partecipante deve autorizzare la tua richiesta prima.",
|
||||
"requestToJoin": "Mano alzata",
|
||||
"requestToJoinDescription": "La tua richiesta è stata inviata ai relatori. Tieni duro!",
|
||||
"title": "Sei uno spettatore nella riunione"
|
||||
},
|
||||
"waitingMessage": "Ti unirai alla riunione quando inizierà!"
|
||||
|
||||
@@ -112,7 +112,12 @@
|
||||
"disabled": "聊天已禁用",
|
||||
"enter": "加入会议室",
|
||||
"error": "错误:你的消息未发送。原因:{{error}}",
|
||||
"everyone": "所有人",
|
||||
"fieldPlaceHolder": "在这里输入你的信息",
|
||||
"fileAccessibleTitle": "{{user}}上传了一个文件",
|
||||
"fileAccessibleTitleMe": "我上传了一个文件",
|
||||
"fileDeleted": "文件已被删除",
|
||||
"guestsChatIndicator": "(访客)",
|
||||
"lobbyChatMessageTo": "等候室聊天消息发送至{{recipient}}",
|
||||
"message": "信息",
|
||||
"messageAccessibleTitle": "{{user}}:",
|
||||
@@ -300,6 +305,12 @@
|
||||
"alreadySharedVideoTitle": "同一时间只允许一个视频分享",
|
||||
"applicationWindow": "应用程序窗口",
|
||||
"authenticationRequired": "需要身份验证",
|
||||
"cameraCaptureDialog": {
|
||||
"description": "使用手机摄像头拍照并发送",
|
||||
"ok": "打开相机",
|
||||
"reject": "暂不使用",
|
||||
"title": "拍照"
|
||||
},
|
||||
"cameraConstraintFailedError": "你的摄像头未满足某些必要条件。",
|
||||
"cameraNotFoundError": "找不到摄像头",
|
||||
"cameraNotSendingData": "我们无法访问你的摄像头,请检查是否有其他应用程序正在使用此设备,从设置菜单中选择另一个设备,或尝试重新加载应用程序。",
|
||||
@@ -375,22 +386,34 @@
|
||||
"micTimeoutError": "无法开启音频设备,发生超时!",
|
||||
"micUnknownError": "由于未知原因,无法使用麦克风。",
|
||||
"moderationAudioLabel": "允许参会者自己解除静音",
|
||||
"moderationDesktopLabel": "允许非主持人共享屏幕",
|
||||
"moderationVideoLabel": "允许参会者自己开启视频",
|
||||
"muteEveryoneDialog": "参会者可以在任何时候解除自己的静音。",
|
||||
"muteEveryoneDialogModerationOn": "参会者可以在任何时候请求发言。",
|
||||
"muteEveryoneElseDialog": "静音后,你将无法为其解除静音,但是他们可以随时解除自己的静音。",
|
||||
"muteEveryoneElseTitle": "除了{{whom}}以外的将所有人静音?",
|
||||
"muteEveryoneElsesDesktopDialog": "一旦停止共享,你将无法重新开启他们的屏幕共享,但他们可以随时重新开启。",
|
||||
"muteEveryoneElsesDesktopTitle": "停止除了{{whom}}以外所有人的屏幕共享?",
|
||||
"muteEveryoneElsesVideoDialog": "一旦关闭,你将无法重新开启他们的摄像头,但他们随时可以重新开启。",
|
||||
"muteEveryoneElsesVideoTitle": "除了{{whom}}以外,关闭所有人的摄像头?",
|
||||
"muteEveryoneSelf": "你自己",
|
||||
"muteEveryoneStartMuted": "现在所有人都已静音",
|
||||
"muteEveryoneTitle": "静音所有人?",
|
||||
"muteEveryonesDesktopDialog": "参会者可以随时共享他们的屏幕",
|
||||
"muteEveryonesDesktopDialogModerationOn": "参会者可以随时请求共享他们的屏幕",
|
||||
"muteEveryonesDesktopTitle": "停止所有人的屏幕共享?",
|
||||
"muteEveryonesVideoDialog": "参会者可以随时开启他们的摄像头",
|
||||
"muteEveryonesVideoDialogModerationOn": "参会者可以随时请求开启他们的摄像头",
|
||||
"muteEveryonesVideoDialogOk": "关闭",
|
||||
"muteEveryonesVideoTitle": "关闭所有人的摄像头?",
|
||||
"muteParticipantBody": "你将无法为他们解除静音,但是他们可以随时解除自己的静音。",
|
||||
"muteParticipantButton": "静音",
|
||||
"muteParticipantsDesktopBody": "你无法重新开启他们的屏幕共享,但他们可以随时重新开启。",
|
||||
"muteParticipantsDesktopBodyModerationOn": "你和他们都无法重新开启屏幕共享",
|
||||
"muteParticipantsDesktopButton": "停止屏幕共享",
|
||||
"muteParticipantsDesktopDialog": "你确定要停止这个参会者的屏幕共享吗?你将无法重新开启他们的屏幕共享,但他们可以随时重新开启。",
|
||||
"muteParticipantsDesktopDialogModerationOn": "你确定要停止这个参会者的屏幕共享吗?你和他们都无法重新开启屏幕共享。",
|
||||
"muteParticipantsDesktopTitle": "停止这个参会者的屏幕共享?",
|
||||
"muteParticipantsVideoBody": "你无法重新开启摄像头,但他们随时可以重新开启。",
|
||||
"muteParticipantsVideoBodyModerationOn": "你和他们都无法重新开启摄像头",
|
||||
"muteParticipantsVideoButton": "关闭摄像头",
|
||||
@@ -503,6 +526,7 @@
|
||||
"tokenAuthFailedWithReasons": "抱歉,你无法加入此通话,原因:",
|
||||
"tokenAuthUnsupported": "Token地址不支持",
|
||||
"transcribing": "转录中",
|
||||
"unauthenticatedAccessDisabled": "此会议需要身份验证,请先登录后继续。",
|
||||
"unlockRoom": "移除会议$t(lockRoomPassword)",
|
||||
"user": "用户",
|
||||
"userIdentifier": "用户ID",
|
||||
@@ -547,11 +571,17 @@
|
||||
"downloadFailedDescription": "请稍后重试",
|
||||
"downloadFailedTitle": "下载失败",
|
||||
"downloadFile": "下载",
|
||||
"downloadStarted": "文件下载已开始",
|
||||
"dragAndDrop": "拖拽文件到此处上传",
|
||||
"fileAlreadyUploaded": "文件已上传至本次会议",
|
||||
"fileRemovedByOther": "你的文件{{fileName}}已被移除",
|
||||
"fileTooLargeDescription": "请确保文件不超过 {{ maxFileSize }}",
|
||||
"fileTooLargeTitle": "文件太大",
|
||||
"fileUploadProgress": "文件上传进度",
|
||||
"fileUploadedSuccessfully": "文件上传成功",
|
||||
"newFileNotification": "{{participantName}}分享了{{fileName}}",
|
||||
"removeFile": "移除",
|
||||
"removeFileSuccess": "文件移除成功",
|
||||
"uploadFailedDescription": "请稍后重试",
|
||||
"uploadFailedTitle": "上传失败",
|
||||
"uploadFile": "文件共享"
|
||||
@@ -724,7 +754,8 @@
|
||||
"notificationTitle": "等候室",
|
||||
"passwordJoinButton": "加入",
|
||||
"title": "等候室",
|
||||
"toggleLabel": "开启等候室模式"
|
||||
"toggleLabel": "开启等候室模式",
|
||||
"waitForModerator": "会议尚未开始,暂无主持人入会。如需成为主持人请先登录,或耐心等待会议开始。"
|
||||
},
|
||||
"localRecording": {
|
||||
"clientState": {
|
||||
@@ -767,8 +798,10 @@
|
||||
"me": "我",
|
||||
"notify": {
|
||||
"OldElectronAPPTitle": "安全漏洞!",
|
||||
"allowAll": "允许全部",
|
||||
"allowAudio": "允许开启麦克风",
|
||||
"allowBoth": "允许音视频",
|
||||
"allowDesktop": "允许屏幕共享",
|
||||
"allowVideo": "允许开启摄像头",
|
||||
"allowedUnmute": "你可以解除麦克风静音、启动摄像头或共享屏幕。",
|
||||
"audioUnmuteBlockedDescription": "由于系统限制,麦克风解除静音操作被暂时阻止。",
|
||||
@@ -782,6 +815,7 @@
|
||||
"dataChannelClosedDescription": "桥接通道已断开,视频质量可能会被限制为最低设置",
|
||||
"dataChannelClosedDescriptionWithAudio": "桥接通道已断开,音视频可能会出现卡顿或中断",
|
||||
"dataChannelClosedWithAudio": "音视频质量可能受影响",
|
||||
"desktopMutedRemotelyTitle": "你的屏幕共享已被{{participantDisplayName}}停止",
|
||||
"disabledIframe": "嵌入仅用于演示,本次通话将在{{timeout}}分钟后自动断开",
|
||||
"disabledIframeSecondaryNative": "嵌入{{domain}}仅用于演示,本次通话将在{{timeout}}分钟后自动断开",
|
||||
"disabledIframeSecondaryWeb": "嵌入{{domain}}仅用于演示,本次通话将在{{timeout}}分钟后自动断开。如需在正式环境嵌入,请使用<a href='{{jaasDomain}}' rel='noopener noreferrer' target='_blank'>Jitsi服务</a>!",
|
||||
@@ -839,6 +873,7 @@
|
||||
"oldElectronClientDescription1": "你似乎正在使用存在已知安全漏洞的旧版Jitsi Meet客户端,请确保您更新到我们的",
|
||||
"oldElectronClientDescription2": "最新版本",
|
||||
"oldElectronClientDescription3": "!",
|
||||
"openChat": "打开聊天",
|
||||
"participantWantsToJoin": "想要加入会议",
|
||||
"participantsWantToJoin": "想要加入会议",
|
||||
"passwordRemovedRemotely": "其他参会者移除了$t(lockRoomPasswordUppercase)",
|
||||
@@ -862,6 +897,7 @@
|
||||
"suggestRecordingDescription": "是否需要录制本次会议?",
|
||||
"suggestRecordingTitle": "录制会议",
|
||||
"unmute": "解除静音",
|
||||
"unmuteScreen": "开始屏幕共享",
|
||||
"unmuteVideo": "开启摄像头",
|
||||
"videoMutedRemotelyDescription": "你可随时重新开启视频",
|
||||
"videoMutedRemotelyTitle": "{{participantDisplayName}}已关闭你的视频",
|
||||
@@ -881,11 +917,14 @@
|
||||
"admit": "同意加入",
|
||||
"admitAll": "全部同意加入",
|
||||
"allow": "允许参会者:",
|
||||
"allowDesktop": "允许屏幕共享",
|
||||
"allowVideo": "允许开启摄像头",
|
||||
"askDesktop": "请求共享屏幕",
|
||||
"askUnmute": "请求取消静音",
|
||||
"audioModeration": "自行解除静音",
|
||||
"blockEveryoneMicCamera": "禁用所有人的麦克风和摄像头",
|
||||
"breakoutRooms": "分组讨论室",
|
||||
"desktopModeration": "开始屏幕共享",
|
||||
"goLive": "开始直播",
|
||||
"invite": "邀请其他人",
|
||||
"lowerAllHands": "取消全部举手",
|
||||
@@ -897,6 +936,8 @@
|
||||
"muteAll": "全体静音",
|
||||
"muteEveryoneElse": "静音其他人",
|
||||
"reject": "拒绝",
|
||||
"stopDesktop": "停止屏幕共享",
|
||||
"stopEveryonesDesktop": "停止所有人的屏幕共享",
|
||||
"stopEveryonesVideo": "关闭所有人摄像头",
|
||||
"stopVideo": "关闭摄像头",
|
||||
"unblockEveryoneMicCamera": "允许所有人开启麦克风和摄像头",
|
||||
@@ -906,9 +947,11 @@
|
||||
"headings": {
|
||||
"lobby": "等候室(({{count}}人)",
|
||||
"participantsList": "会议参会者({{count}}人)",
|
||||
"viewerRequests": "观众请求({{count}}人)",
|
||||
"visitorInQueue": "(排队中:{{count}}人)",
|
||||
"visitorRequests": "(请求加入:{{count}}人)",
|
||||
"visitors": "观众(({{count}}人)",
|
||||
"visitorsList": "观众({{count}}人)",
|
||||
"waitingLobby": "在等候室等待({{count}}人)"
|
||||
},
|
||||
"search": "搜索参会者",
|
||||
@@ -929,6 +972,9 @@
|
||||
"by": "由{{ name }}发起",
|
||||
"closeButton": "结束投票",
|
||||
"create": {
|
||||
"accessibilityLabel": {
|
||||
"send": "发送投票"
|
||||
},
|
||||
"addOption": "添加选项",
|
||||
"answerPlaceholder": "选项{{index}}",
|
||||
"cancel": "取消",
|
||||
@@ -1345,6 +1391,20 @@
|
||||
"videounmute": "打开摄像头"
|
||||
},
|
||||
"addPeople": "添加成员到通话中",
|
||||
"advancedAudioSettings": {
|
||||
"aec": {
|
||||
"label": "回声消除"
|
||||
},
|
||||
"agc": {
|
||||
"label": "自动增益控制"
|
||||
},
|
||||
"ns": {
|
||||
"label": "降噪"
|
||||
},
|
||||
"stereo": {
|
||||
"label": "立体声"
|
||||
}
|
||||
},
|
||||
"audioOnlyOff": "关闭省流模式",
|
||||
"audioOnlyOn": "开启省流模式",
|
||||
"audioRoute": "选择音频设备",
|
||||
@@ -1416,6 +1476,7 @@
|
||||
"reactionHeart": "发送爱心",
|
||||
"reactionLaugh": "发送大笑",
|
||||
"reactionLike": "发送点赞",
|
||||
"reactionLove": "发送爱心",
|
||||
"reactionSilence": "发送沉默",
|
||||
"reactionSurprised": "发送惊讶",
|
||||
"reactions": "互动表情",
|
||||
@@ -1501,6 +1562,8 @@
|
||||
"connectionInfo": "连接信息",
|
||||
"demote": "设为观众",
|
||||
"domute": "静音",
|
||||
"domuteDesktop": "停止屏幕共享",
|
||||
"domuteDesktopOfOthers": "停止屏幕共享给其他人",
|
||||
"domuteOthers": "静音其他人",
|
||||
"domuteVideo": "关闭摄像头",
|
||||
"domuteVideoOfOthers": "关闭其他人摄像头",
|
||||
@@ -1565,6 +1628,8 @@
|
||||
"noMainParticipantsTitle": "会议尚未开始",
|
||||
"noVisitorLobby": "当前会议已开启等候室,暂无法加入",
|
||||
"notAllowedPromotion": "需由会议成员同意才能参与讨论",
|
||||
"requestToJoin": "举手请求",
|
||||
"requestToJoinDescription": "你的请求已发送给主持人,请稍候!",
|
||||
"title": "你当前为会议观众"
|
||||
},
|
||||
"waitingMessage": "会议开始后将自动加入"
|
||||
|
||||
@@ -112,7 +112,12 @@
|
||||
"disabled": "聊天訊息已停用",
|
||||
"enter": "加入聊天室",
|
||||
"error": "錯誤:您的訊息未被傳送。原因:{{error}}",
|
||||
"everyone": "所有人",
|
||||
"fieldPlaceHolder": "在此輸入您的訊息",
|
||||
"fileAccessibleTitle": "{{user}}上傳了一個檔案",
|
||||
"fileAccessibleTitleMe": "我上傳了一個檔案",
|
||||
"fileDeleted": "檔案已被刪除",
|
||||
"guestsChatIndicator": "(訪客)",
|
||||
"lobbyChatMessageTo": "大廳聊天訊息傳送至 {{recipient}}",
|
||||
"message": "訊息",
|
||||
"messageAccessibleTitle": "{{user}}:",
|
||||
@@ -300,6 +305,12 @@
|
||||
"alreadySharedVideoTitle": "同一時間只允許一位影像分享",
|
||||
"applicationWindow": "應用程式視窗",
|
||||
"authenticationRequired": "需要驗證",
|
||||
"cameraCaptureDialog": {
|
||||
"description": "使用手機攝影機拍照並傳送",
|
||||
"ok": "開啟相機",
|
||||
"reject": "暫不使用",
|
||||
"title": "拍照"
|
||||
},
|
||||
"cameraConstraintFailedError": "您的網路攝影機不符合要求。",
|
||||
"cameraNotFoundError": "找不到網路攝影機。",
|
||||
"cameraNotSendingData": "我們無法存取您的網路攝影機,請檢查是否有其他應用程式正在使用這個裝置,並從裝置選單裡選擇其他設備或者重新載入。",
|
||||
@@ -375,22 +386,34 @@
|
||||
"micTimeoutError": "無法啟動音訊裝置,連線逾時!",
|
||||
"micUnknownError": "不明原因造成麥克風無法使用。",
|
||||
"moderationAudioLabel": "允許與會者自我解除靜音",
|
||||
"moderationDesktopLabel": "允許非主持人共享螢幕",
|
||||
"moderationVideoLabel": "允許與會者開啟視訊",
|
||||
"muteEveryoneDialog": "與會者可以隨時解除自己的靜音狀態。",
|
||||
"muteEveryoneDialogModerationOn": "與會者可以隨時請求發言。",
|
||||
"muteEveryoneElseDialog": "靜音後,您就不能再解除對方的靜音,但對方可以隨時解除自己的靜音狀態。",
|
||||
"muteEveryoneElseTitle": "是否要讓除了 {{whom}} 以外的人靜音?",
|
||||
"muteEveryoneElsesDesktopDialog": "一旦停止共享,您將無法重新開啟他們的螢幕共享,但他們可以隨時重新開啟。",
|
||||
"muteEveryoneElsesDesktopTitle": "停止除了{{whom}}以外所有人的螢幕共享?",
|
||||
"muteEveryoneElsesVideoDialog": "一旦停用,您就不能再重新開啟對方的網路攝影機,但對方隨時能重新開啟自己的網路攝影機。",
|
||||
"muteEveryoneElsesVideoTitle": "是否要關閉除了 {{whom}} 以外的人的網路攝影機?",
|
||||
"muteEveryoneSelf": "您自己",
|
||||
"muteEveryoneStartMuted": "現在所有人皆已靜音",
|
||||
"muteEveryoneTitle": "要將所有人靜音嗎?",
|
||||
"muteEveryonesDesktopDialog": "與會者可以隨時共享他們的螢幕",
|
||||
"muteEveryonesDesktopDialogModerationOn": "與會者可以隨時請求共享他們的螢幕",
|
||||
"muteEveryonesDesktopTitle": "停止所有人的螢幕共享?",
|
||||
"muteEveryonesVideoDialog": "與會者隨時可以重新開啟自己的網路攝影機。",
|
||||
"muteEveryonesVideoDialogModerationOn": "與會者可以隨時傳送開啟視訊請求。",
|
||||
"muteEveryonesVideoDialogOk": "停用",
|
||||
"muteEveryonesVideoTitle": "要關閉所有人的網路攝影機嗎?",
|
||||
"muteParticipantBody": "您無法對他們解除靜音,但是他們自己隨時可以解除靜音。",
|
||||
"muteParticipantButton": "靜音",
|
||||
"muteParticipantsDesktopBody": "您無法重新開啟他們的螢幕共享,但他們可以隨時重新開啟。",
|
||||
"muteParticipantsDesktopBodyModerationOn": "您和他們都無法重新開啟螢幕共享",
|
||||
"muteParticipantsDesktopButton": "停止螢幕分享",
|
||||
"muteParticipantsDesktopDialog": "您確定要停止這位與會者的螢幕共享嗎?您將無法重新開啟他們的螢幕共享,但他們可以隨時重新開啟。",
|
||||
"muteParticipantsDesktopDialogModerationOn": "您確定要停止這位與會者的螢幕共享嗎?您和他們都無法重新開啟螢幕共享。",
|
||||
"muteParticipantsDesktopTitle": "停止這位與會者的螢幕共享?",
|
||||
"muteParticipantsVideoBody": "您無法重新開啟,只有對方能自己重新開啟。",
|
||||
"muteParticipantsVideoBodyModerationOn": "您和他都無法再將視訊重新開啟。",
|
||||
"muteParticipantsVideoButton": "停用網路攝影機",
|
||||
@@ -503,6 +526,7 @@
|
||||
"tokenAuthFailedWithReasons": "抱歉,您無法參加這個通話,可能原因:{{reason}}",
|
||||
"tokenAuthUnsupported": "不支援權杖網址。",
|
||||
"transcribing": "轉錄中",
|
||||
"unauthenticatedAccessDisabled": "此會議需要身份驗證,請先登入後繼續。",
|
||||
"unlockRoom": "移除會議 $t(lockRoomPassword)",
|
||||
"user": "使用者",
|
||||
"userIdentifier": "使用者 ID",
|
||||
@@ -547,11 +571,17 @@
|
||||
"downloadFailedDescription": "請重試",
|
||||
"downloadFailedTitle": "下載失敗",
|
||||
"downloadFile": "下載",
|
||||
"downloadStarted": "檔案下載已開始",
|
||||
"dragAndDrop": "將檔案拖曳至此或畫面任一處上傳",
|
||||
"fileAlreadyUploaded": "檔案已上傳至此會議",
|
||||
"fileRemovedByOther": "您的檔案「{{fileName}}」已被移除",
|
||||
"fileTooLargeDescription": "請確認檔案未超過 {{ maxFileSize }}",
|
||||
"fileTooLargeTitle": "檔案過大",
|
||||
"fileUploadProgress": "檔案上傳進度",
|
||||
"fileUploadedSuccessfully": "檔案上傳成功",
|
||||
"newFileNotification": "{{participantName}}分享了「{{fileName}}」",
|
||||
"removeFile": "移除",
|
||||
"removeFileSuccess": "檔案移除成功",
|
||||
"uploadFailedDescription": "請重試",
|
||||
"uploadFailedTitle": "上傳失敗",
|
||||
"uploadFile": "分享檔案"
|
||||
@@ -724,7 +754,8 @@
|
||||
"notificationTitle": "大廳",
|
||||
"passwordJoinButton": "加入",
|
||||
"title": "大廳",
|
||||
"toggleLabel": "啟用大廳模式"
|
||||
"toggleLabel": "啟用大廳模式",
|
||||
"waitForModerator": "會議尚未開始,暫無主持人入會。如需成為主持人請先登入,或耐心等待會議開始。"
|
||||
},
|
||||
"localRecording": {
|
||||
"clientState": {
|
||||
@@ -767,8 +798,10 @@
|
||||
"me": "我",
|
||||
"notify": {
|
||||
"OldElectronAPPTitle": "安全漏洞!",
|
||||
"allowAll": "允許全部",
|
||||
"allowAudio": "允許音訊",
|
||||
"allowBoth": "允許音訊與視訊",
|
||||
"allowDesktop": "允許螢幕分享",
|
||||
"allowVideo": "允許視訊",
|
||||
"allowedUnmute": "您可以將麥克風解除靜音、開啟視訊,或是分享您的螢幕。",
|
||||
"audioUnmuteBlockedDescription": "麥克風解除靜音操作由於系統限制而被暫時封鎖。",
|
||||
@@ -782,6 +815,7 @@
|
||||
"dataChannelClosedDescription": "橋接通道已斷開,視訊品質降至最低設定。",
|
||||
"dataChannelClosedDescriptionWithAudio": "橋接通道已斷開,音訊和視訊可能會受到影響。",
|
||||
"dataChannelClosedWithAudio": "音訊和視訊品質可能會降低。",
|
||||
"desktopMutedRemotelyTitle": "您的螢幕分享已被{{participantDisplayName}}停止",
|
||||
"disabledIframe": "嵌入僅供示範使用,此通話將於 {{timeout}} 分鐘後中斷連線。",
|
||||
"disabledIframeSecondaryNative": "嵌入 {{domain}} 僅供示範,此通話將於 {{timeout}} 分鐘後中斷。",
|
||||
"disabledIframeSecondaryWeb": "嵌入 {{domain}} 僅供示範,此通話將於 {{timeout}} 分鐘後中斷,請使用 <a href='{{jaasDomain}}' rel='noopener noreferrer' target='_blank'>Jitsi 服務</a> 來進行正式嵌入!",
|
||||
@@ -839,6 +873,7 @@
|
||||
"oldElectronClientDescription1": "您似乎正在使用存在已知安全漏洞的過時 Jitsi Meet 用戶端,請盡快更新至我們的",
|
||||
"oldElectronClientDescription2": "最新版本",
|
||||
"oldElectronClientDescription3": "!",
|
||||
"openChat": "開啟聊天",
|
||||
"participantWantsToJoin": "希望加入會議",
|
||||
"participantsWantToJoin": "希望加入會議",
|
||||
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) 已被其他與會者移除",
|
||||
@@ -862,6 +897,7 @@
|
||||
"suggestRecordingDescription": "是否要開始錄製這場會議?",
|
||||
"suggestRecordingTitle": "錄製此會議",
|
||||
"unmute": "取消靜音",
|
||||
"unmuteScreen": "開始螢幕分享",
|
||||
"unmuteVideo": "啟用視訊",
|
||||
"videoMutedRemotelyDescription": "您隨時可以再次啟用。",
|
||||
"videoMutedRemotelyTitle": "您的視訊已被 {{participantDisplayName}} 停用",
|
||||
@@ -881,11 +917,14 @@
|
||||
"admit": "準許",
|
||||
"admitAll": "準許所有人",
|
||||
"allow": "允許與會者能夠:",
|
||||
"allowDesktop": "允許螢幕分享",
|
||||
"allowVideo": "允許視訊",
|
||||
"askDesktop": "請求共享螢幕",
|
||||
"askUnmute": "要求解除靜音",
|
||||
"audioModeration": "自我解除靜音",
|
||||
"blockEveryoneMicCamera": "停用所有人的麥克風和網路攝影機",
|
||||
"breakoutRooms": "分組討論室",
|
||||
"desktopModeration": "開始螢幕分享",
|
||||
"goLive": "開始直播",
|
||||
"invite": "邀請他人",
|
||||
"lowerAllHands": "全部取消舉手",
|
||||
@@ -897,6 +936,8 @@
|
||||
"muteAll": "靜音所有人",
|
||||
"muteEveryoneElse": "靜音其他人",
|
||||
"reject": "拒絕",
|
||||
"stopDesktop": "停止螢幕分享",
|
||||
"stopEveryonesDesktop": "停止所有人的螢幕分享",
|
||||
"stopEveryonesVideo": "停用所有人的視訊",
|
||||
"stopVideo": "停用視訊",
|
||||
"unblockEveryoneMicCamera": "解除封鎖所有人的麥克風及網路攝影機",
|
||||
@@ -906,9 +947,11 @@
|
||||
"headings": {
|
||||
"lobby": "大廳({{count}} 人)",
|
||||
"participantsList": "會議與會者({{count}} 人)",
|
||||
"viewerRequests": "觀眾請求({{count}}人)",
|
||||
"visitorInQueue": "({{count}} 人等候中)",
|
||||
"visitorRequests": "{{count}} 人申請",
|
||||
"visitors": "訪客({{count}} 人)",
|
||||
"visitorsList": "觀眾({{count}}人)",
|
||||
"waitingLobby": "於大廳等候({{count}} 人)"
|
||||
},
|
||||
"search": "搜尋與會者",
|
||||
@@ -929,6 +972,9 @@
|
||||
"by": "由 {{ name }}",
|
||||
"closeButton": "結束投票",
|
||||
"create": {
|
||||
"accessibilityLabel": {
|
||||
"send": "傳送投票"
|
||||
},
|
||||
"addOption": "新增選項",
|
||||
"answerPlaceholder": "選項 {{index}}",
|
||||
"cancel": "取消",
|
||||
@@ -1345,6 +1391,20 @@
|
||||
"videounmute": "啟用網路攝影機"
|
||||
},
|
||||
"addPeople": "新增人員到您的通話中",
|
||||
"advancedAudioSettings": {
|
||||
"aec": {
|
||||
"label": "回聲消除"
|
||||
},
|
||||
"agc": {
|
||||
"label": "自動增益控制"
|
||||
},
|
||||
"ns": {
|
||||
"label": "降噪"
|
||||
},
|
||||
"stereo": {
|
||||
"label": "立體聲"
|
||||
}
|
||||
},
|
||||
"audioOnlyOff": "停用低頻寬模式",
|
||||
"audioOnlyOn": "啟用低頻寬模式",
|
||||
"audioRoute": "選擇音訊裝置",
|
||||
@@ -1416,6 +1476,7 @@
|
||||
"reactionHeart": "傳送愛心反應",
|
||||
"reactionLaugh": "傳送大笑反應",
|
||||
"reactionLike": "傳送比讚反應",
|
||||
"reactionLove": "傳送愛心",
|
||||
"reactionSilence": "傳送沉默反應",
|
||||
"reactionSurprised": "傳送驚訝反應",
|
||||
"reactions": "反應",
|
||||
@@ -1501,6 +1562,8 @@
|
||||
"connectionInfo": "連線資訊",
|
||||
"demote": "轉為訪客",
|
||||
"domute": "靜音",
|
||||
"domuteDesktop": "停止螢幕分享",
|
||||
"domuteDesktopOfOthers": "停止螢幕分享給其他人",
|
||||
"domuteOthers": "靜音其他人",
|
||||
"domuteVideo": "停用網路攝影機",
|
||||
"domuteVideoOfOthers": "停用其他人的網路攝影機",
|
||||
@@ -1565,6 +1628,8 @@
|
||||
"noMainParticipantsTitle": "會議尚未開始",
|
||||
"noVisitorLobby": "此會議啟用大廳,暫時無法加入",
|
||||
"notAllowedPromotion": "需由與會者同意您的申請",
|
||||
"requestToJoin": "舉手請求",
|
||||
"requestToJoinDescription": "您的請求已傳送給主持人,請稍候!",
|
||||
"title": "您是會議中的訪客"
|
||||
},
|
||||
"waitingMessage": "會議開始後您將自動加入!"
|
||||
|
||||
@@ -126,8 +126,16 @@
|
||||
"messagebox": "Type a message",
|
||||
"newMessages": "New messages",
|
||||
"nickname": {
|
||||
"featureChat": "chat",
|
||||
"featureClosedCaptions": "closed captions",
|
||||
"featureFileSharing": "file sharing",
|
||||
"featurePolls": "polls",
|
||||
"popover": "Choose a nickname",
|
||||
"title": "Enter a nickname to use chat",
|
||||
"titleWith1Features": "Enter a nickname to use {{feature1}}",
|
||||
"titleWith2Features": "Enter a nickname to use {{feature1}} and {{feature2}}",
|
||||
"titleWith3Features": "Enter a nickname to use {{feature1}}, {{feature2}} and {{feature3}}",
|
||||
"titleWith4Features": "Enter a nickname to use {{feature1}}, {{feature2}}, {{feature3}} and {{feature4}}",
|
||||
"titleWithCC": "Enter a nickname to use chat and closed captions",
|
||||
"titleWithPolls": "Enter a nickname to use chat and polls",
|
||||
"titleWithPollsAndCC": "Enter a nickname to use chat, polls and closed captions",
|
||||
@@ -1429,6 +1437,7 @@
|
||||
"exitFullScreen": "Exit full screen",
|
||||
"exitTileView": "Exit tile view",
|
||||
"feedback": "Leave feedback",
|
||||
"fileSharing": "File sharing",
|
||||
"giphy": "Toggle GIPHY menu",
|
||||
"hangup": "Leave the meeting",
|
||||
"help": "Help",
|
||||
@@ -1464,6 +1473,7 @@
|
||||
"openReactionsMenu": "Open reactions menu",
|
||||
"participants": "Participants",
|
||||
"pip": "Enter Picture-in-Picture mode",
|
||||
"polls": "Polls",
|
||||
"privateMessage": "Send private message",
|
||||
"profile": "Edit your profile",
|
||||
"raiseHand": "Raise your hand",
|
||||
|
||||
@@ -340,6 +340,7 @@ function initCommands() {
|
||||
|
||||
APP.store.dispatch(setAssumedBandwidthBps(value));
|
||||
},
|
||||
|
||||
'set-blurred-background': blurType => {
|
||||
const tracks = APP.store.getState()['features/base/tracks'];
|
||||
const videoTrack = getLocalVideoTrack(tracks)?.jitsiTrack;
|
||||
|
||||
@@ -158,11 +158,10 @@ const VideoLayout = {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const state = APP.store.getState();
|
||||
const currentContainer = largeVideo.getCurrentContainer();
|
||||
const currentContainerType = largeVideo.getCurrentContainerType();
|
||||
const isOnLarge = this.isCurrentlyOnLarge(id);
|
||||
const state = APP.store.getState();
|
||||
const participant = getParticipantById(state, id);
|
||||
const videoTrack = getVideoTrackByParticipant(state, participant);
|
||||
const videoStream = videoTrack?.jitsiTrack;
|
||||
|
||||
1373
package-lock.json
generated
1373
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -72,7 +72,7 @@
|
||||
"js-md5": "0.6.1",
|
||||
"js-sha512": "0.8.0",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2101.0.0+8061f52a/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2109.0.0+cb9d000c/lib-jitsi-meet.tgz",
|
||||
"lodash-es": "4.17.21",
|
||||
"null-loader": "4.0.1",
|
||||
"optional-require": "1.0.3",
|
||||
@@ -136,7 +136,7 @@
|
||||
"@babel/preset-env": "7.25.9",
|
||||
"@babel/preset-react": "7.25.9",
|
||||
"@jitsi/eslint-config": "6.0.4",
|
||||
"@react-native-community/cli": "15.0.1",
|
||||
"@react-native-community/cli": "17.0.1",
|
||||
"@react-native-community/cli-platform-android": "15.0.1",
|
||||
"@react-native-community/cli-platform-ios": "15.0.1",
|
||||
"@react-native/babel-preset": "0.77.2",
|
||||
|
||||
@@ -600,31 +600,6 @@ export function createRemoteVideoMenuButtonEvent(buttonName: string, attributes
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The rtcstats websocket onclose event. We send this to amplitude in order
|
||||
* to detect trace ws prematurely closing.
|
||||
*
|
||||
* @param {Object} closeEvent - The event with which the websocket closed.
|
||||
* @returns {Object} The event in a format suitable for sending via
|
||||
* sendAnalytics.
|
||||
*/
|
||||
export function createRTCStatsTraceCloseEvent(closeEvent: { code: string; reason: string; }) {
|
||||
const event: {
|
||||
action: string;
|
||||
code?: string;
|
||||
reason?: string;
|
||||
source: string;
|
||||
} = {
|
||||
action: 'trace.onclose',
|
||||
source: 'rtcstats'
|
||||
};
|
||||
|
||||
event.code = closeEvent.code;
|
||||
event.reason = closeEvent.reason;
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event indicating that an action related to screen sharing
|
||||
* occurred (e.g. It was started or stopped).
|
||||
|
||||
@@ -139,7 +139,7 @@ function _upgradeRoleStarted(thenableWithCancel: Object) {
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function hideLoginDialog() {
|
||||
return hideDialog(LoginDialog);
|
||||
return hideDialog('LoginDialog', LoginDialog);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -199,7 +199,7 @@ export function enableModeratorLogin() {
|
||||
* @returns {Action}
|
||||
*/
|
||||
export function openWaitForOwnerDialog() {
|
||||
return openDialog(WaitForOwnerDialog);
|
||||
return openDialog('WaitForOwnerDialog', WaitForOwnerDialog);
|
||||
}
|
||||
|
||||
|
||||
@@ -240,7 +240,7 @@ export function waitForOwner() {
|
||||
* @returns {Action}
|
||||
*/
|
||||
export function openLoginDialog() {
|
||||
return openDialog(LoginDialog);
|
||||
return openDialog('LoginDialog', LoginDialog);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -65,7 +65,7 @@ export function openTokenAuthUrl(tokenAuthServiceUrl: string): any {
|
||||
|
||||
// Show warning for leaving conference only when in a conference.
|
||||
if (!browser.isElectron() && getState()['features/base/conference'].conference) {
|
||||
dispatch(openDialog(LoginQuestionDialog, {
|
||||
dispatch(openDialog('LoginQuestionDialog', LoginQuestionDialog, {
|
||||
handler: () => {
|
||||
// Give time for the dialog to close.
|
||||
setTimeout(() => redirect(), 500);
|
||||
|
||||
@@ -209,7 +209,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
|
||||
case STOP_WAIT_FOR_OWNER:
|
||||
_clearExistingWaitForOwnerTimeout(store);
|
||||
store.dispatch(hideDialog(WaitForOwnerDialog));
|
||||
store.dispatch(hideDialog('WaitForOwnerDialog', WaitForOwnerDialog));
|
||||
break;
|
||||
|
||||
case UPGRADE_ROLE_FINISHED: {
|
||||
|
||||
@@ -55,6 +55,28 @@ function getFirstGraphemeUpper(word: string) {
|
||||
return splitter.splitGraphemes(word)[0].toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips bracketed annotations from a display name. Handles multiple bracket types like (),
|
||||
* [], and {}.
|
||||
*
|
||||
* @param {string} name - The display name to clean.
|
||||
* @returns {string} The cleaned display name without bracketed annotations.
|
||||
*/
|
||||
function stripBracketedAnnotations(name: string): string {
|
||||
// Match content within any of the bracket types at the end of the string
|
||||
// This regex matches: (...) or [...] or {...} at the end
|
||||
const bracketRegex = /\s*[([{][^)\]}]*[)\]}]$/;
|
||||
|
||||
let cleaned = name;
|
||||
|
||||
// Remove all trailing bracketed annotations (handle multiple occurrences)
|
||||
while (bracketRegex.test(cleaned)) {
|
||||
cleaned = cleaned.replace(bracketRegex, '');
|
||||
}
|
||||
|
||||
return cleaned.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates initials for a simple string.
|
||||
*
|
||||
@@ -64,7 +86,15 @@ function getFirstGraphemeUpper(word: string) {
|
||||
export function getInitials(s?: string) {
|
||||
// We don't want to use the domain part of an email address, if it is one
|
||||
const initialsBasis = split(s, '@')[0];
|
||||
const [ firstWord, ...remainingWords ] = initialsBasis.split(wordSplitRegex).filter(Boolean);
|
||||
|
||||
// Strip bracketed annotations (e.g., "(Department)", "[Team]", "{Org}")
|
||||
// to prevent them from being considered as name parts
|
||||
const cleanedName = stripBracketedAnnotations(initialsBasis);
|
||||
|
||||
// Fallback to original if cleaned name is empty
|
||||
const nameForInitials = cleanedName || initialsBasis;
|
||||
|
||||
const [ firstWord, ...remainingWords ] = nameForInitials.split(wordSplitRegex).filter(Boolean);
|
||||
|
||||
return getFirstGraphemeUpper(firstWord) + getFirstGraphemeUpper(remainingWords.pop() || '');
|
||||
}
|
||||
|
||||
@@ -285,6 +285,7 @@ export interface IConfig {
|
||||
disableAudioLevels?: boolean;
|
||||
disableBeforeUnloadHandlers?: boolean;
|
||||
disableCameraTintForeground?: boolean;
|
||||
disableChat?: boolean;
|
||||
disableChatSmileys?: boolean;
|
||||
disableDeepLinking?: boolean;
|
||||
disableFilmstripAutohiding?: boolean;
|
||||
@@ -393,6 +394,7 @@ export interface IConfig {
|
||||
disabled?: boolean;
|
||||
initialWidth?: number;
|
||||
minParticipantCountForTopPanel?: number;
|
||||
stageFilmstripParticipants?: number;
|
||||
};
|
||||
flags?: {
|
||||
ssrcRewritingEnabled: boolean;
|
||||
@@ -616,6 +618,10 @@ export interface IConfig {
|
||||
toolbarConfig?: {
|
||||
alwaysVisible?: boolean;
|
||||
autoHideWhileChatIsOpen?: boolean;
|
||||
/**
|
||||
* Background color for the main toolbar. Accepts any valid CSS color.
|
||||
*/
|
||||
backgroundColor?: string;
|
||||
initialTimeout?: number;
|
||||
timeout?: number;
|
||||
};
|
||||
|
||||
@@ -94,6 +94,7 @@ export default [
|
||||
'disableAudioLevels',
|
||||
'disableBeforeUnloadHandlers',
|
||||
'disableCameraTintForeground',
|
||||
'disableChat',
|
||||
'disableChatSmileys',
|
||||
'disableDeepLinking',
|
||||
'disabledNotifications',
|
||||
|
||||
@@ -14,6 +14,7 @@ import logger from './logger';
|
||||
/**
|
||||
* Signals Dialog to close its dialog.
|
||||
*
|
||||
* @param {string|undefined} name - The name of the component for logging purposes.
|
||||
* @param {Object} [component] - The {@code Dialog} component to close/hide. If
|
||||
* {@code undefined}, closes/hides {@code Dialog} regardless of which
|
||||
* component it's rendering; otherwise, closes/hides {@code Dialog} only if
|
||||
@@ -23,8 +24,8 @@ import logger from './logger';
|
||||
* component: (React.Component | undefined)
|
||||
* }}
|
||||
*/
|
||||
export function hideDialog(component?: ComponentType<any>) {
|
||||
logger.info(`Hide dialog: ${getComponentDisplayName(component)}`);
|
||||
export function hideDialog(name?: string, component?: ComponentType<any>) {
|
||||
logger.info(`Hide dialog: ${name}`);
|
||||
|
||||
return {
|
||||
type: HIDE_DIALOG,
|
||||
@@ -48,6 +49,7 @@ export function hideSheet() {
|
||||
/**
|
||||
* Signals Dialog to open dialog.
|
||||
*
|
||||
* @param {string} name - The name of the component for logging purposes.
|
||||
* @param {Object} component - The component to display as dialog.
|
||||
* @param {Object} [componentProps] - The React {@code Component} props of the
|
||||
* specified {@code component}.
|
||||
@@ -57,8 +59,8 @@ export function hideSheet() {
|
||||
* componentProps: (Object | undefined)
|
||||
* }}
|
||||
*/
|
||||
export function openDialog(component: ComponentType<any>, componentProps?: Object) {
|
||||
logger.info(`Open dialog: ${getComponentDisplayName(component)}`);
|
||||
export function openDialog(name: string, component: ComponentType<any>, componentProps?: Object) {
|
||||
logger.info(`Open dialog: ${name}`);
|
||||
|
||||
return {
|
||||
type: OPEN_DIALOG,
|
||||
@@ -92,35 +94,18 @@ export function openSheet(component: ComponentType<any>, componentProps?: Object
|
||||
* is not already open. If it is open, then Dialog is signaled to close its
|
||||
* dialog.
|
||||
*
|
||||
* @param {string} name - The name of the component for logging purposes.
|
||||
* @param {Object} component - The component to display as dialog.
|
||||
* @param {Object} [componentProps] - The React {@code Component} props of the
|
||||
* specified {@code component}.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function toggleDialog(component: ComponentType<any>, componentProps?: Object) {
|
||||
export function toggleDialog(name: string, component: ComponentType<any>, componentProps?: Object) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
if (isDialogOpen(getState, component)) {
|
||||
dispatch(hideDialog(component));
|
||||
dispatch(hideDialog(name, component));
|
||||
} else {
|
||||
dispatch(openDialog(component, componentProps));
|
||||
dispatch(openDialog(name, component, componentProps));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a printable name for a dialog component.
|
||||
*
|
||||
* @param {Object} component - The component to extract the name for.
|
||||
*
|
||||
* @returns {string} The display name.
|
||||
*/
|
||||
function getComponentDisplayName(component?: ComponentType<any>) {
|
||||
if (!component) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const name = component.displayName ?? component.name ?? 'Component';
|
||||
|
||||
return name.replace('withI18nextTranslation(Connect(', '') // dialogs with translations
|
||||
.replace('))', ''); // dialogs with translations suffix
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ const options: i18next.InitOptions = {
|
||||
interpolation: {
|
||||
escapeValue: false // not needed for react as it escapes by default
|
||||
},
|
||||
load: 'languageOnly',
|
||||
load: 'all',
|
||||
ns: [ 'main', 'languages', 'countries', 'translation-languages' ],
|
||||
react: {
|
||||
// re-render when a new resource bundle is added
|
||||
|
||||
@@ -26,7 +26,7 @@ MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: AnyA
|
||||
case SET_VIDEO_MUTED: {
|
||||
if (LocalRecordingManager.isRecordingLocally() && LocalRecordingManager.selfRecording.on) {
|
||||
if (action.muted && LocalRecordingManager.selfRecording.withVideo) {
|
||||
dispatch(openDialog(StopRecordingDialog, { localRecordingVideoStop: true }));
|
||||
dispatch(openDialog('StopRecordingDialog', StopRecordingDialog, { localRecordingVideoStop: true }));
|
||||
|
||||
return;
|
||||
} else if (!action.muted && !LocalRecordingManager.selfRecording.withVideo) {
|
||||
|
||||
@@ -62,6 +62,65 @@ const AVATAR_CHECKER_FUNCTIONS = [
|
||||
];
|
||||
/* eslint-enable arrow-body-style */
|
||||
|
||||
/**
|
||||
* Returns the list of active speakers that should be moved to the top of the sorted list of participants so that the
|
||||
* dominant speaker is visible always on the vertical filmstrip in stage layout.
|
||||
*
|
||||
* @param {Function | Object} stateful - The (whole) redux state, or redux's {@code getState} function to be used to
|
||||
* retrieve the state.
|
||||
* @returns {Array<string>}
|
||||
*/
|
||||
export function getActiveSpeakersToBeDisplayed(stateful: IStateful) {
|
||||
const state = toState(stateful);
|
||||
const {
|
||||
dominantSpeaker,
|
||||
fakeParticipants,
|
||||
sortedRemoteVirtualScreenshareParticipants,
|
||||
speakersList
|
||||
} = state['features/base/participants'];
|
||||
const { visibleRemoteParticipants } = state['features/filmstrip'];
|
||||
let activeSpeakers = new Map(speakersList);
|
||||
|
||||
// Do not re-sort the active speakers if dominant speaker is currently visible.
|
||||
if (dominantSpeaker && visibleRemoteParticipants.has(dominantSpeaker)) {
|
||||
return activeSpeakers;
|
||||
}
|
||||
let availableSlotsForActiveSpeakers = visibleRemoteParticipants.size;
|
||||
|
||||
if (activeSpeakers.has(dominantSpeaker ?? '')) {
|
||||
activeSpeakers.delete(dominantSpeaker ?? '');
|
||||
}
|
||||
|
||||
// Add dominant speaker to the beginning of the list (not including self) since the active speaker list is always
|
||||
// alphabetically sorted.
|
||||
if (dominantSpeaker && dominantSpeaker !== getLocalParticipant(state)?.id) {
|
||||
const updatedSpeakers = Array.from(activeSpeakers);
|
||||
|
||||
updatedSpeakers.splice(0, 0, [ dominantSpeaker, getParticipantById(state, dominantSpeaker)?.name ?? '' ]);
|
||||
activeSpeakers = new Map(updatedSpeakers);
|
||||
}
|
||||
|
||||
// Remove screenshares from the count.
|
||||
if (sortedRemoteVirtualScreenshareParticipants) {
|
||||
availableSlotsForActiveSpeakers -= sortedRemoteVirtualScreenshareParticipants.size * 2;
|
||||
for (const screenshare of Array.from(sortedRemoteVirtualScreenshareParticipants.keys())) {
|
||||
const ownerId = getVirtualScreenshareParticipantOwnerId(screenshare as string);
|
||||
|
||||
activeSpeakers.delete(ownerId);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove fake participants from the count.
|
||||
if (fakeParticipants) {
|
||||
availableSlotsForActiveSpeakers -= fakeParticipants.size;
|
||||
}
|
||||
const truncatedSpeakersList = Array.from(activeSpeakers).slice(0, availableSlotsForActiveSpeakers);
|
||||
|
||||
truncatedSpeakersList.sort((a: any, b: any) => a[1].localeCompare(b[1]));
|
||||
|
||||
return new Map(truncatedSpeakersList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the first loadable avatar URL for a participant.
|
||||
*
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
isRemoteScreenshareParticipant,
|
||||
isScreenShareParticipant
|
||||
} from './functions';
|
||||
import logger from './logger';
|
||||
import { FakeParticipant, ILocalParticipant, IParticipant, ISourceInfo } from './types';
|
||||
|
||||
/**
|
||||
@@ -76,12 +77,12 @@ const DEFAULT_STATE = {
|
||||
numberOfParticipantsNotSupportingE2EE: 0,
|
||||
overwrittenNameList: {},
|
||||
pinnedParticipant: undefined,
|
||||
previousSpeakers: new Set<string>(),
|
||||
raisedHandsQueue: [],
|
||||
remote: new Map(),
|
||||
remoteVideoSources: new Set<string>(),
|
||||
sortedRemoteVirtualScreenshareParticipants: new Map(),
|
||||
sortedRemoteParticipants: new Map()
|
||||
sortedRemoteParticipants: new Map(),
|
||||
speakersList: new Map()
|
||||
};
|
||||
|
||||
export interface IParticipantsState {
|
||||
@@ -94,12 +95,12 @@ export interface IParticipantsState {
|
||||
numberOfParticipantsNotSupportingE2EE: number;
|
||||
overwrittenNameList: { [id: string]: string; };
|
||||
pinnedParticipant?: string;
|
||||
previousSpeakers: Set<string>;
|
||||
raisedHandsQueue: Array<{ hasBeenNotified?: boolean; id: string; raisedHandTimestamp: number; }>;
|
||||
remote: Map<string, IParticipant>;
|
||||
remoteVideoSources: Set<string>;
|
||||
sortedRemoteParticipants: Map<string, string>;
|
||||
sortedRemoteVirtualScreenshareParticipants: Map<string, string>;
|
||||
speakersList: Map<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -156,10 +157,22 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
|
||||
const { participant } = action;
|
||||
const { id, previousSpeakers = [] } = participant;
|
||||
const { dominantSpeaker, local } = state;
|
||||
const newSpeakers = [ id, ...previousSpeakers ];
|
||||
const sortedSpeakersList: Array<Array<string>> = [];
|
||||
|
||||
// Build chronologically ordered Set of remote speakers (excluding local)
|
||||
const previousSpeakersSet: Set<string>
|
||||
= new Set(previousSpeakers.filter((speaker: string) => speaker !== local?.id));
|
||||
for (const speaker of newSpeakers) {
|
||||
if (speaker !== local?.id) {
|
||||
const remoteParticipant = state.remote.get(speaker);
|
||||
|
||||
remoteParticipant
|
||||
&& sortedSpeakersList.push(
|
||||
[ speaker, _getDisplayName(state, remoteParticipant?.name) ]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Keep the remote speaker list sorted alphabetically.
|
||||
sortedSpeakersList.sort((a, b) => a[1].localeCompare(b[1]));
|
||||
|
||||
// Only one dominant speaker is allowed.
|
||||
if (dominantSpeaker) {
|
||||
@@ -169,8 +182,8 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
|
||||
if (_updateParticipantProperty(state, id, 'dominantSpeaker', true)) {
|
||||
return {
|
||||
...state,
|
||||
dominantSpeaker: id,
|
||||
previousSpeakers: previousSpeakersSet
|
||||
dominantSpeaker: id, // @ts-ignore
|
||||
speakersList: new Map(sortedSpeakersList)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -352,6 +365,8 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
|
||||
sortedRemoteVirtualScreenshareParticipants.sort((a, b) => a[1].localeCompare(b[1]));
|
||||
|
||||
state.sortedRemoteVirtualScreenshareParticipants = new Map(sortedRemoteVirtualScreenshareParticipants);
|
||||
|
||||
logger.debug('Remote screenshare participant joined', id);
|
||||
}
|
||||
|
||||
// Exclude the screenshare participant from the fake participant count to avoid duplicates.
|
||||
@@ -423,7 +438,7 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
|
||||
}
|
||||
|
||||
// Remove the participant from the list of speakers.
|
||||
state.previousSpeakers.delete(id);
|
||||
state.speakersList.has(id) && state.speakersList.delete(id);
|
||||
|
||||
if (pinnedParticipant === id) {
|
||||
state.pinnedParticipant = undefined;
|
||||
@@ -436,6 +451,8 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
|
||||
if (sortedRemoteVirtualScreenshareParticipants.has(id)) {
|
||||
sortedRemoteVirtualScreenshareParticipants.delete(id);
|
||||
state.sortedRemoteVirtualScreenshareParticipants = new Map(sortedRemoteVirtualScreenshareParticipants);
|
||||
|
||||
logger.debug('Remote screenshare participant left', id);
|
||||
}
|
||||
|
||||
if (oldParticipant && !oldParticipant.fakeParticipant && !isLocalScreenShare) {
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
getRemoteScreensharesBasedOnPresence,
|
||||
getVirtualScreenshareParticipantOwnerId
|
||||
} from './functions';
|
||||
import logger from './logger';
|
||||
import { FakeParticipant } from './types';
|
||||
|
||||
StateListenerRegistry.register(
|
||||
@@ -69,14 +70,19 @@ function _createOrRemoveVirtualParticipants(
|
||||
const addedScreenshareSourceNames = difference(newScreenshareSourceNames, oldScreenshareSourceNames);
|
||||
|
||||
if (removedScreenshareSourceNames.length) {
|
||||
removedScreenshareSourceNames.forEach(id => dispatch(participantLeft(id, conference, {
|
||||
fakeParticipant: FakeParticipant.RemoteScreenShare
|
||||
})));
|
||||
removedScreenshareSourceNames.forEach(id => {
|
||||
logger.debug('Dispatching participantLeft for virtual screenshare', id);
|
||||
dispatch(participantLeft(id, conference, {
|
||||
fakeParticipant: FakeParticipant.RemoteScreenShare
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
if (addedScreenshareSourceNames.length) {
|
||||
addedScreenshareSourceNames.forEach(id => dispatch(
|
||||
createVirtualScreenshareParticipant(id, false, conference)));
|
||||
addedScreenshareSourceNames.forEach(id => {
|
||||
logger.debug('Creating virtual screenshare participant', id);
|
||||
dispatch(createVirtualScreenshareParticipant(id, false, conference));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { isTrackStreamingStatusActive } from '../../connection-indicator/functions';
|
||||
import { handleToggleVideoMuted } from '../../toolbox/actions.any';
|
||||
import { muteLocal } from '../../video-menu/actions.any';
|
||||
import { MEDIA_TYPE, VIDEO_TYPE } from '../media/constants';
|
||||
import { getParticipantById, isScreenShareParticipant } from '../participants/functions';
|
||||
import {
|
||||
@@ -78,3 +80,43 @@ export function isRemoteVideoReceived({ getState }: IStore, id: string): boolean
|
||||
|
||||
return Boolean(videoTrack && !videoTrack.muted && isTrackStreamingStatusActive(videoTrack));
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutes the local audio. Same as clicking the audio mute button.
|
||||
*
|
||||
* @param {IStore} store - The redux store.
|
||||
* @returns {Promise} Resolves when the action is complete.
|
||||
*/
|
||||
export function audioMute({ dispatch }: IStore) {
|
||||
return dispatch(muteLocal(true, MEDIA_TYPE.AUDIO));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmutes the local audio. Same as clicking the audio unmute button.
|
||||
*
|
||||
* @param {IStore} store - The redux store.
|
||||
* @returns {Promise} Resolves when the action is complete.
|
||||
*/
|
||||
export function audioUnmute({ dispatch }: IStore) {
|
||||
return dispatch(muteLocal(false, MEDIA_TYPE.AUDIO));
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutes the local video. Same as clicking the video mute button.
|
||||
*
|
||||
* @param {IStore} store - The redux store.
|
||||
* @returns {Promise} Resolves when the action is complete.
|
||||
*/
|
||||
export function videoMute({ dispatch }: IStore) {
|
||||
return dispatch(handleToggleVideoMuted(true, true, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmutes the local video. Same as clicking the video unmute button.
|
||||
*
|
||||
* @param {IStore} store - The redux store.
|
||||
* @returns {Promise} Resolves when the action is complete.
|
||||
*/
|
||||
export function videoUnmute({ dispatch }: IStore) {
|
||||
return dispatch(handleToggleVideoMuted(false, true, true));
|
||||
}
|
||||
|
||||
@@ -8,11 +8,15 @@ import { getJitsiMeetGlobalNS } from '../util/helpers';
|
||||
|
||||
import { setConnectionState } from './actions';
|
||||
import {
|
||||
audioMute,
|
||||
audioUnmute,
|
||||
getLocalCameraEncoding,
|
||||
getRemoteVideoType,
|
||||
isLargeVideoReceived,
|
||||
isRemoteVideoReceived,
|
||||
isTestModeEnabled
|
||||
isTestModeEnabled,
|
||||
videoMute,
|
||||
videoUnmute
|
||||
} from './functions';
|
||||
import logger from './logger';
|
||||
|
||||
@@ -85,10 +89,14 @@ function _bindTortureHelpers(store: IStore) {
|
||||
|
||||
// All torture helper methods go in here
|
||||
getJitsiMeetGlobalNS().testing = {
|
||||
audioMute: audioMute.bind(null, store),
|
||||
audioUnmute: audioUnmute.bind(null, store),
|
||||
getRemoteVideoType: getRemoteVideoType.bind(null, store),
|
||||
isLargeVideoReceived: isLargeVideoReceived.bind(null, store),
|
||||
getLocalCameraEncoding: getLocalCameraEncoding.bind(null, store),
|
||||
isRemoteVideoReceived: isRemoteVideoReceived.bind(null, store)
|
||||
isRemoteVideoReceived: isRemoteVideoReceived.bind(null, store),
|
||||
videoMute: videoMute.bind(null, store),
|
||||
videoUnmute: videoUnmute.bind(null, store),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -294,7 +294,7 @@ export function setCameraFacingMode(facingMode: string | undefined) {
|
||||
* @returns {Object} - The open dialog action.
|
||||
*/
|
||||
export function openAllowToggleCameraDialog(onAllow: Function, initiatorId: string) {
|
||||
return openDialog(AllowToggleCameraDialog, {
|
||||
return openDialog('AllowToggleCameraDialog', AllowToggleCameraDialog, {
|
||||
onAllow,
|
||||
initiatorId
|
||||
});
|
||||
|
||||
@@ -211,18 +211,21 @@ export function getLocalJitsiAudioTrackSettings(state: IReduxState) {
|
||||
const jitsiTrack = getLocalJitsiAudioTrack(state);
|
||||
|
||||
if (!jitsiTrack) {
|
||||
const config = state['features/base/config'];
|
||||
const disableAP = Boolean(config?.disableAP);
|
||||
const disableAGC = Boolean(config?.disableAGC);
|
||||
const disableAEC = Boolean(config?.disableAEC);
|
||||
const disableNS = Boolean(config?.disableNS);
|
||||
const stereo = Boolean(config?.audioQuality?.stereo);
|
||||
const {
|
||||
audioQuality,
|
||||
disableAEC = false,
|
||||
disableAGC = false,
|
||||
disableAP = false,
|
||||
disableNS = false
|
||||
} = state['features/base/config'] || {};
|
||||
|
||||
const enableStereo = Boolean(audioQuality?.stereo);
|
||||
|
||||
return {
|
||||
autoGainControl: !disableAP && !disableAGC,
|
||||
channelCount: stereo ? 2 : 1,
|
||||
echoCancellation: !disableAP && !disableAEC,
|
||||
noiseSuppression: !disableAP && !disableNS
|
||||
autoGainControl: enableStereo ? false : !disableAP && !disableAGC,
|
||||
channelCount: enableStereo ? 2 : 1,
|
||||
echoCancellation: enableStereo ? false : !disableAP && !disableAEC,
|
||||
noiseSuppression: enableStereo ? false : !disableAP && !disableNS
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ const BreakoutRoomContextMenu = ({ room, actions = ALL_ACTIONS }: IProps) => {
|
||||
}, [ dispatch, room ]);
|
||||
|
||||
const onRenameBreakoutRoom = useCallback(() => {
|
||||
dispatch(openDialog(BreakoutRoomNamePrompt, {
|
||||
dispatch(openDialog('BreakoutRoomNamePrompt', BreakoutRoomNamePrompt, {
|
||||
breakoutRoomJid: room.jid,
|
||||
initialRoomName: room.name
|
||||
}));
|
||||
|
||||
@@ -22,7 +22,7 @@ export * from './actions.any';
|
||||
* }}
|
||||
*/
|
||||
export function openUpdateCalendarEventDialog(eventId: string) {
|
||||
return openDialog(UpdateCalendarEventDialog, { eventId });
|
||||
return openDialog('UpdateCalendarEventDialog', UpdateCalendarEventDialog, { eventId });
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -220,6 +220,34 @@ export function openCCPanel() {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the chat panel with polls tab active.
|
||||
*
|
||||
* @returns {Object} The redux action.
|
||||
*/
|
||||
export function openPollsPanel() {
|
||||
return async (dispatch: IStore['dispatch']) => {
|
||||
dispatch(setFocusedTab(ChatTabs.POLLS));
|
||||
dispatch({
|
||||
type: OPEN_CHAT
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the chat panel with file sharing tab active.
|
||||
*
|
||||
* @returns {Object} The redux action.
|
||||
*/
|
||||
export function openFileSharingPanel() {
|
||||
return async (dispatch: IStore['dispatch']) => {
|
||||
dispatch(setFocusedTab(ChatTabs.FILE_SHARING));
|
||||
dispatch({
|
||||
type: OPEN_CHAT
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initiates the sending of messages between a moderator and a lobby attendee.
|
||||
|
||||
@@ -1,30 +1,34 @@
|
||||
import { IStore } from '../app/types';
|
||||
import { IParticipant } from '../base/participants/types';
|
||||
import { navigate } from '../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
import { screen } from '../mobile/navigation/routes';
|
||||
|
||||
import { OPEN_CHAT } from './actionTypes';
|
||||
import { setFocusedTab } from './actions.any';
|
||||
import { ChatTabs } from './constants';
|
||||
|
||||
export * from './actions.any';
|
||||
|
||||
/**
|
||||
* Displays the chat panel.
|
||||
* Displays the chat panel with the CHAT tab active.
|
||||
*
|
||||
* @param {Object} participant - The recipient for the private chat.
|
||||
* @param {boolean} disablePolls - Checks if polls are disabled.
|
||||
*
|
||||
* @returns {{
|
||||
* participant: participant,
|
||||
* type: OPEN_CHAT
|
||||
* }}
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function openChat(participant?: IParticipant | undefined | Object, disablePolls?: boolean) {
|
||||
if (disablePolls) {
|
||||
navigate(screen.conference.chat);
|
||||
}
|
||||
navigate(screen.conference.chatandpolls.main);
|
||||
return (dispatch: IStore['dispatch']) => {
|
||||
if (disablePolls) {
|
||||
navigate(screen.conference.chat);
|
||||
} else {
|
||||
navigate(screen.conference.chatandpolls.main);
|
||||
}
|
||||
|
||||
return {
|
||||
participant,
|
||||
type: OPEN_CHAT
|
||||
dispatch(setFocusedTab(ChatTabs.CHAT));
|
||||
dispatch({
|
||||
participant,
|
||||
type: OPEN_CHAT
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,12 +8,13 @@ import {
|
||||
SET_CHAT_WIDTH,
|
||||
SET_USER_CHAT_WIDTH
|
||||
} from './actionTypes';
|
||||
import { closeChat } from './actions.any';
|
||||
import { closeChat, setFocusedTab } from './actions.any';
|
||||
import { ChatTabs } from './constants';
|
||||
|
||||
export * from './actions.any';
|
||||
|
||||
/**
|
||||
* Displays the chat panel.
|
||||
* Displays the chat panel with the CHAT tab active.
|
||||
*
|
||||
* @param {Object} participant - The recipient for the private chat.
|
||||
* @param {Object} _disablePolls - Used on native.
|
||||
@@ -24,6 +25,7 @@ export * from './actions.any';
|
||||
*/
|
||||
export function openChat(participant?: Object, _disablePolls?: boolean) {
|
||||
return function(dispatch: IStore['dispatch']) {
|
||||
dispatch(setFocusedTab(ChatTabs.CHAT));
|
||||
dispatch({
|
||||
participant,
|
||||
type: OPEN_CHAT
|
||||
|
||||
@@ -10,7 +10,7 @@ import { arePollsDisabled } from '../../../conference/functions.any';
|
||||
import { navigate } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
import { screen } from '../../../mobile/navigation/routes';
|
||||
import { getUnreadPollCount } from '../../../polls/functions';
|
||||
import { getUnreadCount, getUnreadFilesCount } from '../../functions';
|
||||
import { getUnreadCount, getUnreadFilesCount, isChatDisabled } from '../../functions';
|
||||
|
||||
interface IProps extends AbstractButtonProps {
|
||||
|
||||
@@ -65,7 +65,7 @@ class ChatButton extends AbstractButton<IProps> {
|
||||
* @returns {IProps}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
const enabled = getFeatureFlag(state, CHAT_ENABLED, true);
|
||||
const enabled = getFeatureFlag(state, CHAT_ENABLED, true) && !isChatDisabled(state);
|
||||
const { visible = enabled } = ownProps;
|
||||
|
||||
return {
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
toggleChat
|
||||
} from '../../actions.web';
|
||||
import { CHAT_SIZE, ChatTabs, OPTION_GROUPCHAT, SMALL_WIDTH_THRESHOLD } from '../../constants';
|
||||
import { getChatMaxSize } from '../../functions';
|
||||
import { getChatMaxSize, getFocusedTab, isChatDisabled } from '../../functions';
|
||||
import { IChatProps as AbstractProps } from '../../types';
|
||||
|
||||
import ChatHeader from './ChatHeader';
|
||||
@@ -41,13 +41,18 @@ interface IProps extends AbstractProps {
|
||||
/**
|
||||
* The currently focused tab.
|
||||
*/
|
||||
_focusedTab: ChatTabs;
|
||||
_focusedTab?: ChatTabs;
|
||||
|
||||
/**
|
||||
* True if the CC tab is enabled and false otherwise.
|
||||
*/
|
||||
_isCCTabEnabled: boolean;
|
||||
|
||||
/**
|
||||
* True if chat is disabled.
|
||||
*/
|
||||
_isChatDisabled: boolean;
|
||||
|
||||
/**
|
||||
* True if file sharing tab is enabled.
|
||||
*/
|
||||
@@ -217,6 +222,7 @@ const Chat = ({
|
||||
_isOpen,
|
||||
_isPollsEnabled,
|
||||
_isCCTabEnabled,
|
||||
_isChatDisabled,
|
||||
_isFileSharingTabEnabled,
|
||||
_focusedTab,
|
||||
_isResizing,
|
||||
@@ -229,6 +235,11 @@ const Chat = ({
|
||||
dispatch,
|
||||
t
|
||||
}: IProps) => {
|
||||
// If no tabs are available, don't render the chat panel at all.
|
||||
if (_isChatDisabled && !_isPollsEnabled && !_isCCTabEnabled && !_isFileSharingTabEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { classes, cx } = useStyles({ _isResizing, width: _width });
|
||||
const [ isMouseDown, setIsMouseDown ] = useState(false);
|
||||
const [ mousePosition, setMousePosition ] = useState<number | null>(null);
|
||||
@@ -416,7 +427,7 @@ const Chat = ({
|
||||
return (
|
||||
<>
|
||||
{renderTabs()}
|
||||
<div
|
||||
{!_isChatDisabled && (<div
|
||||
aria-labelledby = { ChatTabs.CHAT }
|
||||
className = { cx(
|
||||
classes.chatPanel,
|
||||
@@ -442,7 +453,7 @@ const Chat = ({
|
||||
)}
|
||||
<ChatInput
|
||||
onSend = { onSendMessage } />
|
||||
</div>
|
||||
</div>) }
|
||||
{ _isPollsEnabled && (
|
||||
<>
|
||||
<div
|
||||
@@ -484,8 +495,18 @@ const Chat = ({
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
function renderTabs() {
|
||||
let tabs = [
|
||||
{
|
||||
// The only way focused tab will be undefined is when no tab is enabled. Therefore this function won't be
|
||||
// executed because Chat component won't render anything. This should never happen but adding the check
|
||||
// here to make TS happy (when passing the _focusedTab in the selected prop for Tabs).
|
||||
if (!_focusedTab) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let tabs = [];
|
||||
|
||||
// Only add chat tab if chat is not disabled.
|
||||
if (!_isChatDisabled) {
|
||||
tabs.push({
|
||||
accessibilityLabel: t('chat.tabs.chat'),
|
||||
countBadge:
|
||||
_focusedTab !== ChatTabs.CHAT && _unreadMessagesCount > 0 ? _unreadMessagesCount : undefined,
|
||||
@@ -493,8 +514,8 @@ const Chat = ({
|
||||
controlsId: `${ChatTabs.CHAT}-panel`,
|
||||
icon: IconMessage,
|
||||
title: t('chat.tabs.chat')
|
||||
}
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
if (_isPollsEnabled) {
|
||||
tabs.push({
|
||||
@@ -564,6 +585,8 @@ const Chat = ({
|
||||
{_showNamePrompt
|
||||
? <DisplayNameForm
|
||||
isCCTabEnabled = { _isCCTabEnabled }
|
||||
isChatDisabled = { _isChatDisabled }
|
||||
isFileSharingEnabled = { _isFileSharingTabEnabled }
|
||||
isPollsEnabled = { _isPollsEnabled } />
|
||||
: renderChat()}
|
||||
<div
|
||||
@@ -602,7 +625,7 @@ const Chat = ({
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState, _ownProps: any) {
|
||||
const { isOpen, focusedTab, messages, unreadMessagesCount, unreadFilesCount, width, isResizing } = state['features/chat'];
|
||||
const { isOpen, messages, unreadMessagesCount, unreadFilesCount, width, isResizing } = state['features/chat'];
|
||||
const { unreadPollsCount } = state['features/polls'];
|
||||
const _localParticipant = getLocalParticipant(state);
|
||||
|
||||
@@ -611,8 +634,9 @@ function _mapStateToProps(state: IReduxState, _ownProps: any) {
|
||||
_isOpen: isOpen,
|
||||
_isPollsEnabled: !arePollsDisabled(state),
|
||||
_isCCTabEnabled: isCCTabEnabled(state),
|
||||
_isChatDisabled: isChatDisabled(state),
|
||||
_isFileSharingTabEnabled: isFileSharingEnabled(state),
|
||||
_focusedTab: focusedTab,
|
||||
_focusedTab: getFocusedTab(state),
|
||||
_messages: messages,
|
||||
_unreadMessagesCount: unreadMessagesCount,
|
||||
_unreadPollsCount: unreadPollsCount,
|
||||
|
||||
@@ -9,6 +9,7 @@ import { IconMessage } from '../../../base/icons/svg';
|
||||
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
|
||||
import { closeOverflowMenuIfOpen } from '../../../toolbox/actions.web';
|
||||
import { toggleChat } from '../../actions.web';
|
||||
import { isChatDisabled } from '../../functions';
|
||||
|
||||
import ChatCounter from './ChatCounter';
|
||||
|
||||
@@ -91,7 +92,8 @@ class ChatButton extends AbstractButton<IProps> {
|
||||
*/
|
||||
const mapStateToProps = (state: IReduxState) => {
|
||||
return {
|
||||
_chatOpen: state['features/chat'].isOpen
|
||||
_chatOpen: state['features/chat'].isOpen,
|
||||
visible: !isChatDisabled(state)
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -2,12 +2,12 @@ import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import Icon from '../../../base/icons/components/Icon';
|
||||
import { IconCloseLarge } from '../../../base/icons/svg';
|
||||
import { isFileSharingEnabled } from '../../../file-sharing/functions.any';
|
||||
import { toggleChat } from '../../actions.web';
|
||||
import { ChatTabs } from '../../constants';
|
||||
import { getFocusedTab, isChatDisabled } from '../../functions';
|
||||
|
||||
interface IProps {
|
||||
|
||||
@@ -40,7 +40,8 @@ interface IProps {
|
||||
function ChatHeader({ className, isCCTabEnabled, isPollsEnabled }: IProps) {
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
const { focusedTab } = useSelector((state: IReduxState) => state['features/chat']);
|
||||
const _isChatDisabled = useSelector(isChatDisabled);
|
||||
const focusedTab = useSelector(getFocusedTab);
|
||||
const fileSharingTabEnabled = useSelector(isFileSharingEnabled);
|
||||
|
||||
const onCancel = useCallback(() => {
|
||||
@@ -56,7 +57,7 @@ function ChatHeader({ className, isCCTabEnabled, isPollsEnabled }: IProps) {
|
||||
|
||||
let title = 'chat.title';
|
||||
|
||||
if (focusedTab === ChatTabs.CHAT) {
|
||||
if (!_isChatDisabled && focusedTab === ChatTabs.CHAT) {
|
||||
title = 'chat.tabs.chat';
|
||||
} else if (isPollsEnabled && focusedTab === ChatTabs.POLLS) {
|
||||
title = 'chat.tabs.polls';
|
||||
@@ -64,6 +65,11 @@ function ChatHeader({ className, isCCTabEnabled, isPollsEnabled }: IProps) {
|
||||
title = 'chat.tabs.closedCaptions';
|
||||
} else if (fileSharingTabEnabled && focusedTab === ChatTabs.FILE_SHARING) {
|
||||
title = 'chat.tabs.fileSharing';
|
||||
} else {
|
||||
// If the focused tab is not enabled, don't render the header.
|
||||
// This should not happen in normal circumstances since Chat.tsx already checks
|
||||
// if any tabs are available before rendering.
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -25,6 +25,16 @@ interface IProps extends WithTranslation {
|
||||
*/
|
||||
isCCTabEnabled: boolean;
|
||||
|
||||
/**
|
||||
* Whether chat is disabled.
|
||||
*/
|
||||
isChatDisabled: boolean;
|
||||
|
||||
/**
|
||||
* Whether file sharing is enabled.
|
||||
*/
|
||||
isFileSharingEnabled: boolean;
|
||||
|
||||
/**
|
||||
* Whether the polls feature is enabled or not.
|
||||
*/
|
||||
@@ -74,18 +84,31 @@ class DisplayNameForm extends Component<IProps, IState> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
override render() {
|
||||
const { isCCTabEnabled, isPollsEnabled, t } = this.props;
|
||||
const { isCCTabEnabled, isChatDisabled, isFileSharingEnabled, isPollsEnabled, t } = this.props;
|
||||
|
||||
let title = 'chat.nickname.title';
|
||||
// Build array of enabled feature names (translated).
|
||||
const features = [
|
||||
!isChatDisabled ? t('chat.nickname.featureChat') : '',
|
||||
isPollsEnabled ? t('chat.nickname.featurePolls') : '',
|
||||
isFileSharingEnabled ? t('chat.nickname.featureFileSharing') : '',
|
||||
isCCTabEnabled ? t('chat.nickname.featureClosedCaptions') : ''
|
||||
].filter(Boolean);
|
||||
|
||||
if (isCCTabEnabled && isPollsEnabled) {
|
||||
title = 'chat.nickname.titleWithPollsAndCC';
|
||||
} else if (isCCTabEnabled) {
|
||||
title = 'chat.nickname.titleWithCC';
|
||||
} else if (isPollsEnabled) {
|
||||
title = 'chat.nickname.titleWithPolls';
|
||||
// Return null if no features available - component won't render.
|
||||
if (features.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Build translation arguments dynamically: { feature1: "chat", feature2: "polls", ... }
|
||||
const translationArgs = features.reduce((acc, feature, index) => {
|
||||
acc[`feature${index + 1}`] = feature;
|
||||
|
||||
return acc;
|
||||
}, {} as Record<string, string>);
|
||||
|
||||
// Use dynamic translation key: "titleWith1Features", "titleWith2Features", etc.
|
||||
const title = t(`chat.nickname.titleWith${features.length}Features`, translationArgs);
|
||||
|
||||
return (
|
||||
<div id = 'nickname'>
|
||||
<form onSubmit = { this._onSubmit }>
|
||||
|
||||
@@ -12,11 +12,14 @@ import { isJwtFeatureEnabled } from '../base/jwt/functions';
|
||||
import { getParticipantById, isPrivateChatEnabled } from '../base/participants/functions';
|
||||
import { IParticipant } from '../base/participants/types';
|
||||
import { escapeRegexp } from '../base/util/helpers';
|
||||
import { arePollsDisabled } from '../conference/functions.any';
|
||||
import { isFileSharingEnabled } from '../file-sharing/functions.any';
|
||||
import { getParticipantsPaneWidth } from '../participants-pane/functions';
|
||||
import { isCCTabEnabled } from '../subtitles/functions.any';
|
||||
import { VIDEO_SPACE_MIN_SIZE } from '../video-layout/constants';
|
||||
import { IVisitorChatParticipant } from '../visitors/types';
|
||||
|
||||
import { MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL, TIMESTAMP_FORMAT } from './constants';
|
||||
import { ChatTabs, MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL, TIMESTAMP_FORMAT } from './constants';
|
||||
import { IMessage } from './types';
|
||||
|
||||
/**
|
||||
@@ -153,6 +156,53 @@ export function areSmileysDisabled(state: IReduxState) {
|
||||
return disableChatSmileys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the chat feature is disabled.
|
||||
*
|
||||
* @param {IReduxState} state - The redux state.
|
||||
* @returns {boolean} True if chat is disabled, false otherwise.
|
||||
*/
|
||||
export function isChatDisabled(state: IReduxState): boolean {
|
||||
return Boolean(state['features/base/config']?.disableChat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default focused tab based on what features are enabled.
|
||||
* Returns the first available tab in priority order: CHAT -> POLLS -> FILE_SHARING -> CLOSED_CAPTIONS.
|
||||
*
|
||||
* @param {IReduxState} state - The redux state.
|
||||
* @returns {ChatTabs | undefined} The default focused tab.
|
||||
*/
|
||||
export function getDefaultFocusedTab(state: IReduxState): ChatTabs | undefined {
|
||||
if (!isChatDisabled(state)) {
|
||||
return ChatTabs.CHAT;
|
||||
}
|
||||
|
||||
if (!arePollsDisabled(state)) {
|
||||
return ChatTabs.POLLS;
|
||||
}
|
||||
|
||||
if (isFileSharingEnabled(state)) {
|
||||
return ChatTabs.FILE_SHARING;
|
||||
}
|
||||
|
||||
if (isCCTabEnabled(state)) {
|
||||
return ChatTabs.CLOSED_CAPTIONS;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently focused tab or the default focused tab if none is set.
|
||||
*
|
||||
* @param {IReduxState} state - The redux state.
|
||||
* @returns {ChatTabs | undefined} The focused tab or undefined if no tabs are available.
|
||||
*/
|
||||
export function getFocusedTab(state: IReduxState): ChatTabs | undefined {
|
||||
return state['features/chat'].focusedTab || getDefaultFocusedTab(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the timestamp to display for the message.
|
||||
*
|
||||
|
||||
23
react/features/chat/hooks.web.ts
Normal file
23
react/features/chat/hooks.web.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import ChatButton from './components/web/ChatButton';
|
||||
import { isChatDisabled } from './functions';
|
||||
|
||||
const chat = {
|
||||
key: 'chat',
|
||||
Content: ChatButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
/**
|
||||
* A hook that returns the chat button if chat is not disabled.
|
||||
*
|
||||
* @returns {Object | undefined} - The chat button object or undefined.
|
||||
*/
|
||||
export function useChatButton() {
|
||||
const _isChatDisabled = useSelector(isChatDisabled);
|
||||
|
||||
if (!_isChatDisabled) {
|
||||
return chat;
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,8 @@ import { IParticipant } from '../base/participants/types';
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
|
||||
import { playSound, registerSound, unregisterSound } from '../base/sounds/actions';
|
||||
import { arePollsDisabled } from '../conference/functions.any';
|
||||
import { isFileSharingEnabled } from '../file-sharing/functions.any';
|
||||
import { addGif } from '../gifs/actions';
|
||||
import { extractGifURL, getGifDisplayMode, isGifEnabled, isGifMessage } from '../gifs/function.any';
|
||||
import { showMessageNotification } from '../notifications/actions';
|
||||
@@ -34,6 +36,7 @@ import { ADD_REACTION_MESSAGE } from '../reactions/actionTypes';
|
||||
import { pushReactions } from '../reactions/actions.any';
|
||||
import { ENDPOINT_REACTION_NAME } from '../reactions/constants';
|
||||
import { getReactionMessageFromBuffer, isReactionsEnabled } from '../reactions/functions.any';
|
||||
import { isCCTabEnabled } from '../subtitles/functions.any';
|
||||
import { showToolbox } from '../toolbox/actions';
|
||||
import { getDisplayName } from '../visitors/functions';
|
||||
|
||||
@@ -66,7 +69,9 @@ import {
|
||||
} from './constants';
|
||||
import {
|
||||
getDisplayNameSuffix,
|
||||
getFocusedTab,
|
||||
getUnreadCount,
|
||||
isChatDisabled,
|
||||
isSendGroupChatDisabled,
|
||||
isVisitorChatParticipant
|
||||
} from './functions';
|
||||
@@ -181,23 +186,28 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
|
||||
case SET_FOCUSED_TAB:
|
||||
case OPEN_CHAT: {
|
||||
const focusedTab = action.tabId || getState()['features/chat'].focusedTab;
|
||||
const state = store.getState();
|
||||
const focusedTab = action.tabId || getFocusedTab(state);
|
||||
|
||||
if (focusedTab === ChatTabs.CHAT) {
|
||||
// Don't allow opening chat if it's disabled AND user is trying to open the CHAT tab.
|
||||
if (isChatDisabled(state)) {
|
||||
return next(action);
|
||||
}
|
||||
unreadCount = 0;
|
||||
|
||||
if (typeof APP !== 'undefined') {
|
||||
APP.API.notifyChatUpdated(unreadCount, true);
|
||||
}
|
||||
|
||||
const { privateMessageRecipient } = store.getState()['features/chat'];
|
||||
const { privateMessageRecipient } = state['features/chat'];
|
||||
|
||||
if (
|
||||
isSendGroupChatDisabled(store.getState())
|
||||
isSendGroupChatDisabled(state)
|
||||
&& privateMessageRecipient
|
||||
&& !action.participant
|
||||
) {
|
||||
const participant = getParticipantById(store.getState(), privateMessageRecipient.id);
|
||||
const participant = getParticipantById(state, privateMessageRecipient.id);
|
||||
|
||||
if (participant) {
|
||||
action.participant = participant;
|
||||
@@ -207,7 +217,21 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
}
|
||||
}
|
||||
} else if (focusedTab === ChatTabs.POLLS) {
|
||||
// Don't allow opening chat panel if polls are disabled AND user is trying to open the POLLS tab.
|
||||
if (arePollsDisabled(state)) {
|
||||
return next(action);
|
||||
}
|
||||
dispatch(resetUnreadPollsCount());
|
||||
|
||||
// Don't allow opening chat panel if file sharing is disabled AND user is trying to open the
|
||||
// FILE_SHARING tab.
|
||||
} else if (focusedTab === ChatTabs.FILE_SHARING && !isFileSharingEnabled(state)) {
|
||||
return next(action);
|
||||
|
||||
// Don't allow opening chat panel if closed captions are disabled AND user is trying to open the
|
||||
// CLOSED_CAPTIONS tab.
|
||||
} else if (focusedTab === ChatTabs.CLOSED_CAPTIONS && !isCCTabEnabled(state)) {
|
||||
return next(action);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -239,7 +263,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
const participantExists = getParticipantById(state, shouldSendPrivateMessageTo.id);
|
||||
|
||||
if (participantExists || shouldSendPrivateMessageTo.isFromVisitor) {
|
||||
dispatch(openDialog(ChatPrivacyDialog, {
|
||||
dispatch(openDialog('ChatPrivacyDialog', ChatPrivacyDialog, {
|
||||
message: action.message,
|
||||
participantID: shouldSendPrivateMessageTo.id,
|
||||
isFromVisitor: shouldSendPrivateMessageTo.isFromVisitor,
|
||||
@@ -576,6 +600,11 @@ function _handleReceivedMessage({ dispatch, getState }: IStore,
|
||||
const { isOpen: isChatOpen } = state['features/chat'];
|
||||
const { soundsIncomingMessage: soundEnabled, userSelectedNotifications } = state['features/base/settings'];
|
||||
|
||||
// Don't play sound or show notifications if chat is disabled.
|
||||
if (isChatDisabled(state)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (soundEnabled && shouldPlaySound && !isChatOpen) {
|
||||
dispatch(playSound(INCOMING_MSG_SOUND_ID));
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ const DEFAULT_STATE = {
|
||||
privateMessageRecipient: undefined,
|
||||
lobbyMessageRecipient: undefined,
|
||||
isLobbyChatActive: false,
|
||||
focusedTab: ChatTabs.CHAT,
|
||||
focusedTab: undefined,
|
||||
isResizing: false,
|
||||
width: {
|
||||
current: CHAT_SIZE,
|
||||
@@ -44,7 +44,7 @@ const DEFAULT_STATE = {
|
||||
};
|
||||
|
||||
export interface IChatState {
|
||||
focusedTab: ChatTabs;
|
||||
focusedTab?: ChatTabs;
|
||||
groupChatWithPermissions: boolean;
|
||||
isLobbyChatActive: boolean;
|
||||
isOpen: boolean;
|
||||
|
||||
@@ -22,7 +22,7 @@ export function notifyKickedOut(participant: any, submit?: Function) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(openDialog(AlertDialog, {
|
||||
dispatch(openDialog('AlertDialog', AlertDialog, {
|
||||
contentKey: {
|
||||
key: participant ? 'dialog.kickTitle' : 'dialog.kickSystemTitle',
|
||||
params: {
|
||||
@@ -52,7 +52,7 @@ export function notifyConferenceFailed(reasonKey: string, submit?: Function) {
|
||||
// we have to push the opening of the dialog to the queue
|
||||
// so that we make sure it will be visible after the events
|
||||
// of conference destroyed are done
|
||||
setTimeout(() => dispatch(openDialog(AlertDialog, {
|
||||
setTimeout(() => dispatch(openDialog('AlertDialog', AlertDialog, {
|
||||
contentKey: {
|
||||
key: reasonKey
|
||||
},
|
||||
@@ -60,7 +60,7 @@ export function notifyConferenceFailed(reasonKey: string, submit?: Function) {
|
||||
},
|
||||
onSubmit: () => {
|
||||
submit?.();
|
||||
dispatch(hideDialog(AlertDialog));
|
||||
dispatch(hideDialog('AlertDialog', AlertDialog));
|
||||
}
|
||||
})));
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@ import logger from './logger';
|
||||
*/
|
||||
export function openLeaveReasonDialog(title?: string) {
|
||||
return (dispatch: IStore['dispatch']): Promise<void> => new Promise(resolve => {
|
||||
dispatch(openDialog(LeaveReasonDialog, {
|
||||
dispatch(openDialog('LeaveReasonDialog', LeaveReasonDialog, {
|
||||
onClose: resolve,
|
||||
title
|
||||
}));
|
||||
|
||||
@@ -26,7 +26,7 @@ function SpeakerStatsLabel() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const onClick = () => {
|
||||
dispatch(openDialog(SpeakerStats, { conference }));
|
||||
dispatch(openDialog('SpeakerStats', SpeakerStats, { conference }));
|
||||
};
|
||||
|
||||
if (count <= 2 || _isSpeakerStatsDisabled) {
|
||||
|
||||
@@ -327,7 +327,7 @@ export function _mapDispatchToProps(dispatch: IStore['dispatch']) {
|
||||
* @returns {void}
|
||||
*/
|
||||
_onOpenBandwidthDialog() {
|
||||
dispatch(openDialog(BandwidthSettingsDialog));
|
||||
dispatch(openDialog('BandwidthSettingsDialog', BandwidthSettingsDialog));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ type Options = {
|
||||
export function showDesktopPicker(options: Options = {}, onSourceChoose: Function) {
|
||||
const { desktopSharingSources } = options;
|
||||
|
||||
return openDialog(DesktopPicker, {
|
||||
return openDialog('DesktopPicker', DesktopPicker, {
|
||||
desktopSharingSources,
|
||||
onSourceChoose
|
||||
});
|
||||
|
||||
@@ -259,6 +259,29 @@ class AudioDevicesSelection extends AbstractDialogTab<IProps, {}> {
|
||||
|
||||
const newValue = name === 'channelCount' ? (checked ? 2 : 1) : checked;
|
||||
|
||||
if (name === 'channelCount' && newValue === 2) {
|
||||
super._onChange({
|
||||
audioSettings: {
|
||||
autoGainControl: false,
|
||||
channelCount: 2,
|
||||
echoCancellation: false,
|
||||
noiseSuppression: false
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
} else if (name !== 'channelCount' && newValue === true) {
|
||||
super._onChange({
|
||||
audioSettings: {
|
||||
...audioSettings,
|
||||
[name]: newValue,
|
||||
channelCount: 1
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
super._onChange({
|
||||
audioSettings: {
|
||||
...audioSettings,
|
||||
|
||||
@@ -14,7 +14,7 @@ export function openDisplayNamePrompt({ onPostSubmit, validateInput }: {
|
||||
onPostSubmit?: Function;
|
||||
validateInput?: Function;
|
||||
}) {
|
||||
return openDialog(DisplayNamePrompt, {
|
||||
return openDialog('DisplayNamePrompt', DisplayNamePrompt, {
|
||||
onPostSubmit,
|
||||
validateInput
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||
case SETTINGS_UPDATED: {
|
||||
if (action.settings.displayName
|
||||
&& isDialogOpen(getState, DisplayNamePrompt)) {
|
||||
dispatch(hideDialog(DisplayNamePrompt));
|
||||
dispatch(hideDialog('DisplayNamePrompt', DisplayNamePrompt));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ StateListenerRegistry.register(
|
||||
});
|
||||
|
||||
conference.on(JitsiConferenceEvents.E2EE_VERIFICATION_READY, (pId: string, sas: object) => {
|
||||
dispatch(openDialog(ParticipantVerificationDialog, { pId,
|
||||
dispatch(openDialog('ParticipantVerificationDialog', ParticipantVerificationDialog, { pId,
|
||||
sas }));
|
||||
});
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ class EmbedMeetingButton extends AbstractButton<AbstractButtonProps> {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
sendAnalytics(createToolbarEvent('embed.meeting'));
|
||||
dispatch(openDialog(EmbedMeetingDialog));
|
||||
dispatch(openDialog('EmbedMeetingDialog', EmbedMeetingDialog));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ export function maybeOpenFeedbackDialog(conference: IJitsiConference, title?: st
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function openFeedbackDialog(conference?: IJitsiConference, title?: string, onClose?: Function) {
|
||||
return openDialog(FeedbackDialog, {
|
||||
return openDialog('FeedbackDialog', FeedbackDialog, {
|
||||
conference,
|
||||
onClose,
|
||||
title
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { IconShareDoc } from '../../../base/icons/svg';
|
||||
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
|
||||
import { openFileSharingPanel } from '../../../chat/actions.any';
|
||||
import { isFileSharingEnabled } from '../../functions.any';
|
||||
|
||||
/**
|
||||
* Component that renders a button to open the file sharing panel.
|
||||
*
|
||||
* @augments AbstractButton
|
||||
*/
|
||||
class FileSharingButton extends AbstractButton<AbstractButtonProps> {
|
||||
override icon = IconShareDoc;
|
||||
override label = 'toolbar.fileSharing';
|
||||
override tooltip = 'toolbar.fileSharing';
|
||||
|
||||
/**
|
||||
* Handles clicking the button to open the file sharing panel.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
override _handleClick() {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(openFileSharingPanel());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the Redux state to the props of this component.
|
||||
*
|
||||
* @param {IReduxState} state - The Redux state.
|
||||
* @returns {Object} - Mapped props.
|
||||
*/
|
||||
function mapStateToProps(state: IReduxState) {
|
||||
return {
|
||||
visible: isFileSharingEnabled(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(mapStateToProps)(FileSharingButton));
|
||||
23
react/features/file-sharing/hooks.web.ts
Normal file
23
react/features/file-sharing/hooks.web.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import FileSharingButton from './components/web/FileSharingButton';
|
||||
import { isFileSharingEnabled } from './functions.any';
|
||||
|
||||
const fileSharing = {
|
||||
key: 'filesharing',
|
||||
Content: FileSharingButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
/**
|
||||
* A hook that returns the file sharing button if it is enabled and undefined otherwise.
|
||||
*
|
||||
* @returns {Object | undefined} - The file sharing button object or undefined.
|
||||
*/
|
||||
export function useFileSharingButton() {
|
||||
const isEnabled = useSelector(isFileSharingEnabled);
|
||||
|
||||
if (isEnabled) {
|
||||
return fileSharing;
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,12 @@
|
||||
import { IReduxState, IStore } from '../app/types';
|
||||
import { getParticipantById, getVirtualScreenshareParticipantOwnerId } from '../base/participants/functions';
|
||||
import {
|
||||
getActiveSpeakersToBeDisplayed,
|
||||
getVirtualScreenshareParticipantOwnerId
|
||||
} from '../base/participants/functions';
|
||||
|
||||
import { setRemoteParticipants } from './actions';
|
||||
import { isFilmstripScrollVisible } from './functions';
|
||||
|
||||
/**
|
||||
* Returns an array containing the first `count` items from a set.
|
||||
*
|
||||
* @param {Set<T>} set - The set from which to take items.
|
||||
* @param {number} count - The number of items to take.
|
||||
* @returns {T[]} An array containing the taken items.
|
||||
* @private
|
||||
*/
|
||||
function _takeFirstN<T>(set: Set<T>, count: number): T[] {
|
||||
const result: T[] = [];
|
||||
|
||||
for (const item of set) {
|
||||
if (result.length >= count) break;
|
||||
result.push(item);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the reorderd list of the remote participants.
|
||||
*
|
||||
@@ -32,7 +16,7 @@ function _takeFirstN<T>(set: Set<T>, count: number): T[] {
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
export function updateRemoteParticipants(store: IStore, force?: boolean, participantId?: string): void {
|
||||
export function updateRemoteParticipants(store: IStore, force?: boolean, participantId?: string) {
|
||||
const state = store.getState();
|
||||
let reorderedParticipants = [];
|
||||
const { sortedRemoteVirtualScreenshareParticipants } = state['features/base/participants'];
|
||||
@@ -49,65 +33,44 @@ export function updateRemoteParticipants(store: IStore, force?: boolean, partici
|
||||
}
|
||||
|
||||
const {
|
||||
dominantSpeaker,
|
||||
fakeParticipants,
|
||||
previousSpeakers,
|
||||
sortedRemoteParticipants
|
||||
} = state['features/base/participants'];
|
||||
const { visibleRemoteParticipants } = state['features/filmstrip'];
|
||||
const remoteParticipants = new Map(sortedRemoteParticipants);
|
||||
const screenShareParticipants = sortedRemoteVirtualScreenshareParticipants
|
||||
? [ ...sortedRemoteVirtualScreenshareParticipants.keys() ] : [];
|
||||
const sharedVideos = fakeParticipants ? Array.from(fakeParticipants.keys()) : [];
|
||||
const speakers: Set<string> = new Set();
|
||||
const dominant = dominantSpeaker ? getParticipantById(state, dominantSpeaker) : undefined;
|
||||
const config = state['features/base/config'];
|
||||
const defaultRemoteDisplayName = config?.defaultRemoteDisplayName ?? 'Fellow Jitster';
|
||||
|
||||
// Generate the remote active speakers list.
|
||||
if (dominant && !dominant.local) {
|
||||
speakers.add(dominant.id);
|
||||
}
|
||||
previousSpeakers.forEach(id => speakers.add(id));
|
||||
const speakers = getActiveSpeakersToBeDisplayed(state);
|
||||
|
||||
for (const screenshare of screenShareParticipants) {
|
||||
const ownerId = getVirtualScreenshareParticipantOwnerId(screenshare);
|
||||
|
||||
remoteParticipants.delete(ownerId);
|
||||
remoteParticipants.delete(screenshare);
|
||||
speakers.delete(ownerId);
|
||||
}
|
||||
|
||||
// Calculate the number of slots available for active speakers and then sort them alphabetically to ensure
|
||||
// consistent order.
|
||||
const numberOfActiveSpeakerSlots
|
||||
= visibleRemoteParticipants.size - screenShareParticipants.length - sharedVideos.length;
|
||||
const activeSpeakersDisplayed = _takeFirstN(speakers, numberOfActiveSpeakerSlots)
|
||||
.sort((a: string, b: string) => {
|
||||
return (getParticipantById(state, a)?.name ?? defaultRemoteDisplayName)
|
||||
.localeCompare(getParticipantById(state, b)?.name ?? defaultRemoteDisplayName);
|
||||
});
|
||||
|
||||
for (const sharedVideo of sharedVideos) {
|
||||
remoteParticipants.delete(sharedVideo);
|
||||
}
|
||||
for (const speaker of speakers.keys()) {
|
||||
remoteParticipants.delete(speaker);
|
||||
}
|
||||
|
||||
// Always update the order of the thubmnails.
|
||||
const participantsWithScreenShare = screenShareParticipants.reduce<string[]>((acc, screenshare) => {
|
||||
const ownerId = getVirtualScreenshareParticipantOwnerId(screenshare);
|
||||
|
||||
acc.push(ownerId);
|
||||
acc.push(screenshare);
|
||||
remoteParticipants.delete(ownerId);
|
||||
remoteParticipants.delete(screenshare);
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
// Always update the order of the thumbnails.
|
||||
reorderedParticipants = [
|
||||
...participantsWithScreenShare,
|
||||
...sharedVideos,
|
||||
...activeSpeakersDisplayed,
|
||||
...Array.from(speakers.keys()),
|
||||
...Array.from(remoteParticipants.keys())
|
||||
];
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { getParticipantById } from '../base/participants/functions';
|
||||
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
|
||||
|
||||
import { isFilmstripScrollVisible, updateRemoteParticipants } from './functions';
|
||||
@@ -26,15 +25,7 @@ StateListenerRegistry.register(
|
||||
*/
|
||||
StateListenerRegistry.register(
|
||||
/* selector */ state => state['features/base/participants'].dominantSpeaker,
|
||||
/* listener */ (dominantSpeaker, store) => {
|
||||
const { visibleRemoteParticipants } = store.getState()['features/filmstrip'];
|
||||
const dominant = getParticipantById(store.getState(), dominantSpeaker);
|
||||
|
||||
// Only update the remote participants if the dominant speaker is not currently visible.
|
||||
if (!dominant?.local && !visibleRemoteParticipants.has(dominantSpeaker)) {
|
||||
updateRemoteParticipants(store);
|
||||
}
|
||||
});
|
||||
/* listener */ (dominantSpeaker, store) => updateRemoteParticipants(store));
|
||||
|
||||
/**
|
||||
* Listens for changes in the filmstrip scroll visibility.
|
||||
|
||||
@@ -559,7 +559,7 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<IProps, IState> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_showFailedInviteAlert() {
|
||||
this.props.dispatch(openDialog(AlertDialog, {
|
||||
this.props.dispatch(openDialog('AlertDialog', AlertDialog, {
|
||||
contentKey: {
|
||||
key: 'inviteDialog.alertText'
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ class DialInSummary extends PureComponent<IProps> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_onError() {
|
||||
this.props.dispatch(openDialog(DialInSummaryErrorDialog));
|
||||
this.props.dispatch(openDialog('DialInSummaryErrorDialog', DialInSummaryErrorDialog));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -41,7 +41,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
function _beginAddPeople({ dispatch }: IStore, next: Function, action: AnyAction) {
|
||||
const result = next(action);
|
||||
|
||||
dispatch(openDialog(AddPeopleDialog));
|
||||
dispatch(openDialog('AddPeopleDialog', AddPeopleDialog));
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -60,7 +60,7 @@ function _beginAddPeople({ dispatch }: IStore, next: Function, action: AnyAction
|
||||
* @returns {*} The value returned by {@code next(action)}.
|
||||
*/
|
||||
function _hideAddPeopleDialog({ dispatch }: IStore, next: Function, action: AnyAction) {
|
||||
dispatch(hideDialog(AddPeopleDialog));
|
||||
dispatch(hideDialog('AddPeopleDialog', AddPeopleDialog));
|
||||
|
||||
return next(action);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import { isFeatureDisabled } from './functions';
|
||||
export function maybeShowPremiumFeatureDialog(feature: ParticipantFeaturesKey) {
|
||||
return function(dispatch: IStore['dispatch'], getState: IStore['getState']) {
|
||||
if (isFeatureDisabled(getState(), feature)) {
|
||||
dispatch(openDialog(PremiumFeatureDialog));
|
||||
dispatch(openDialog('PremiumFeatureDialog', PremiumFeatureDialog));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
getVirtualScreenshareParticipantByOwnerId
|
||||
} from '../base/participants/functions';
|
||||
import { toState } from '../base/redux/functions';
|
||||
import { isStageFilmstripAvailable } from '../filmstrip/functions';
|
||||
import { getAutoPinSetting } from '../video-layout/functions';
|
||||
|
||||
import {
|
||||
@@ -18,7 +19,6 @@ import {
|
||||
SET_LARGE_VIDEO_DIMENSIONS,
|
||||
UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION
|
||||
} from './actionTypes';
|
||||
import { shouldHideLargeVideo } from './functions';
|
||||
|
||||
/**
|
||||
* Action to select the participant to be displayed in LargeVideo based on the
|
||||
@@ -34,8 +34,12 @@ export function selectParticipantInLargeVideo(participant?: string) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const state = getState();
|
||||
|
||||
// Skip large video updates when the large video container is hidden.
|
||||
if (shouldHideLargeVideo(state)) {
|
||||
if (isStageFilmstripAvailable(state, 2)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Keep Etherpad open.
|
||||
if (state['features/etherpad'].editing) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -161,26 +165,18 @@ function _electParticipantInLargeVideo(state: IReduxState) {
|
||||
}
|
||||
}
|
||||
|
||||
// Next, pick the dominant speaker or the last active speaker if the dominant speaker is local.
|
||||
// Next, pick the dominant speaker (other than self).
|
||||
participant = getDominantSpeakerParticipant(state);
|
||||
let speakerId: string | undefined;
|
||||
if (participant && !participant.local) {
|
||||
// Return the screensharing participant id associated with this endpoint if multi-stream is enabled and
|
||||
// auto_pin_latest_screen_share setting is disabled.
|
||||
const screenshareParticipant = getVirtualScreenshareParticipantByOwnerId(state, participant.id);
|
||||
|
||||
if (participant?.local) {
|
||||
const { previousSpeakers } = state['features/base/participants'];
|
||||
|
||||
if (previousSpeakers?.size) {
|
||||
speakerId = previousSpeakers.keys().next().value;
|
||||
}
|
||||
} else if (participant) {
|
||||
speakerId = participant.id;
|
||||
return screenshareParticipant?.id ?? participant.id;
|
||||
}
|
||||
|
||||
// Return the screensharing participant id associated with this endpoint.
|
||||
if (speakerId) {
|
||||
const screenshareParticipant = getVirtualScreenshareParticipantByOwnerId(state, speakerId);
|
||||
|
||||
return screenshareParticipant?.id ?? speakerId;
|
||||
}
|
||||
// In case this is the local participant.
|
||||
participant = undefined;
|
||||
|
||||
// Next, pick the most recent participant with video.
|
||||
const lastVisibleRemoteParticipant = _electLastVisibleRemoteParticipant(state);
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { IReduxState } from '../app/types';
|
||||
import { getParticipantById } from '../base/participants/functions';
|
||||
import { isStageFilmstripAvailable } from '../filmstrip/functions';
|
||||
import { shouldDisplayTileView } from '../video-layout/functions.any';
|
||||
|
||||
/**
|
||||
* Selector for the participant currently displaying on the large video.
|
||||
@@ -14,17 +12,3 @@ export function getLargeVideoParticipant(state: IReduxState) {
|
||||
|
||||
return getParticipantById(state, participantId ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the large video container should be hidden.
|
||||
* Large video is hidden in tile view, stage filmstrip mode (with multiple participants),
|
||||
* or when editing etherpad.
|
||||
*
|
||||
* @param {IReduxState} state - The Redux state.
|
||||
* @returns {boolean} True if large video should be hidden, false otherwise.
|
||||
*/
|
||||
export function shouldHideLargeVideo(state: IReduxState): boolean {
|
||||
return shouldDisplayTileView(state)
|
||||
|| isStageFilmstripAvailable(state, 2)
|
||||
|| Boolean(state['features/etherpad']?.editing);
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
|
||||
|
||||
import { SELECT_LARGE_VIDEO_PARTICIPANT } from './actionTypes';
|
||||
import { selectParticipantInLargeVideo } from './actions.any';
|
||||
import { shouldHideLargeVideo } from './functions';
|
||||
|
||||
/**
|
||||
* Updates the large video when transitioning from a hidden state to visible state.
|
||||
* This ensures the large video is properly updated when exiting tile view, stage filmstrip,
|
||||
* whiteboard, or etherpad editing modes.
|
||||
*/
|
||||
StateListenerRegistry.register(
|
||||
/* selector */ state => shouldHideLargeVideo(state),
|
||||
/* listener */ (isHidden, { dispatch }) => {
|
||||
// When transitioning from hidden to visible state, select participant (because currently it is undefined).
|
||||
// Otherwise set it to undefined because we don't show the large video.
|
||||
if (!isHidden) {
|
||||
dispatch(selectParticipantInLargeVideo());
|
||||
} else {
|
||||
dispatch({
|
||||
type: SELECT_LARGE_VIDEO_PARTICIPANT,
|
||||
participantId: undefined
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -1 +0,0 @@
|
||||
import './subscriber.any';
|
||||
|
||||
@@ -4,7 +4,6 @@ import StateListenerRegistry from '../base/redux/StateListenerRegistry';
|
||||
import { getVideoTrackByParticipant } from '../base/tracks/functions.web';
|
||||
|
||||
import { getLargeVideoParticipant } from './functions';
|
||||
import './subscriber.any';
|
||||
|
||||
/**
|
||||
* Updates the on stage participant video.
|
||||
|
||||
@@ -467,7 +467,7 @@ export function _mapStateToProps(state: IReduxState) {
|
||||
_knocking: knocking,
|
||||
_lobbyChatMessages: messages,
|
||||
_lobbyMessageRecipient: lobbyMessageRecipient?.name,
|
||||
_login: showModeratorLogin,
|
||||
_login: showModeratorLogin && !state['features/base/jwt'].jwt,
|
||||
_hangUp: showHangUp,
|
||||
_isLobbyChatActive: isLobbyChatActive,
|
||||
_meetingName: getConferenceName(state),
|
||||
|
||||
@@ -4,7 +4,6 @@ import { createMaterialTopTabNavigator } from '@react-navigation/material-top-ta
|
||||
import React from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../../../app/types';
|
||||
import {
|
||||
getClientHeight,
|
||||
getClientWidth
|
||||
@@ -12,6 +11,7 @@ import {
|
||||
import { setFocusedTab } from '../../../../../chat/actions.any';
|
||||
import Chat from '../../../../../chat/components/native/Chat';
|
||||
import { ChatTabs } from '../../../../../chat/constants';
|
||||
import { getFocusedTab } from '../../../../../chat/functions';
|
||||
import { resetUnreadPollsCount } from '../../../../../polls/actions';
|
||||
import PollsPane from '../../../../../polls/components/native/PollsPane';
|
||||
import { screen } from '../../../routes';
|
||||
@@ -23,8 +23,8 @@ const ChatAndPolls = () => {
|
||||
const clientHeight = useSelector(getClientHeight);
|
||||
const clientWidth = useSelector(getClientWidth);
|
||||
const dispatch = useDispatch();
|
||||
const { focusedTab } = useSelector((state: IReduxState) => state['features/chat']);
|
||||
const initialRouteName = focusedTab === ChatTabs.POLLS
|
||||
const currentFocusedTab = useSelector(getFocusedTab);
|
||||
const initialRouteName = currentFocusedTab === ChatTabs.POLLS
|
||||
? screen.conference.chatandpolls.tab.polls
|
||||
: screen.conference.chatandpolls.tab.chat;
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import PageReloadDialog from '../base/dialog/components/native/PageReloadDialog'
|
||||
*/
|
||||
export function openPageReloadDialog(
|
||||
conferenceError?: Error, configError?: Error, connectionError?: ConnectionFailedError) {
|
||||
return openDialog(PageReloadDialog, {
|
||||
return openDialog('PageReloadDialog', PageReloadDialog, {
|
||||
conferenceError,
|
||||
configError,
|
||||
connectionError
|
||||
|
||||
@@ -34,7 +34,7 @@ export default function RenameButton({ breakoutRoomJid, name }: IProps) {
|
||||
const dispatch = useDispatch();
|
||||
const { classes, cx } = useStyles();
|
||||
const onRename = useCallback(() => {
|
||||
dispatch(openDialog(BreakoutRoomNamePrompt, {
|
||||
dispatch(openDialog('BreakoutRoomNamePrompt', BreakoutRoomNamePrompt, {
|
||||
breakoutRoomJid,
|
||||
initialRoomName: name
|
||||
}));
|
||||
|
||||
@@ -68,7 +68,7 @@ export const RoomContextMenu = ({
|
||||
}, [ dispatch, room ]);
|
||||
|
||||
const onRenameBreakoutRoom = useCallback(() => {
|
||||
dispatch(openDialog(BreakoutRoomNamePrompt, {
|
||||
dispatch(openDialog('BreakoutRoomNamePrompt', BreakoutRoomNamePrompt, {
|
||||
breakoutRoomJid: room?.jid,
|
||||
initialRoomName: room?.name
|
||||
}));
|
||||
|
||||
@@ -32,7 +32,7 @@ import styles from './styles';
|
||||
export const ContextMenuMore = () => {
|
||||
const dispatch = useDispatch();
|
||||
const muteAllVideo = useCallback(() => {
|
||||
dispatch(openDialog(MuteEveryonesVideoDialog));
|
||||
dispatch(openDialog('MuteEveryonesVideoDialog', MuteEveryonesVideoDialog));
|
||||
dispatch(hideSheet());
|
||||
}, [ dispatch ]);
|
||||
const conference = useSelector(getCurrentConference);
|
||||
|
||||
@@ -42,7 +42,7 @@ const ParticipantsPaneFooter = (): JSX.Element => {
|
||||
getFeatureFlag(state, BREAKOUT_ROOMS_BUTTON_ENABLED, true)
|
||||
);
|
||||
const openMoreMenu = useCallback(() => dispatch(openSheet(ContextMenuMore)), [ dispatch ]);
|
||||
const muteAll = useCallback(() => dispatch(openDialog(MuteEveryoneDialog)),
|
||||
const muteAll = useCallback(() => dispatch(openDialog('MuteEveryoneDialog', MuteEveryoneDialog)),
|
||||
[ dispatch ]);
|
||||
const showMoreActions = useSelector(isMoreActionsVisible);
|
||||
const showMuteAll = useSelector(isMuteAllVisible);
|
||||
|
||||
@@ -112,10 +112,10 @@ export const FooterContextMenu = ({ isOpen, onDrawerClose, onMouseLeave }: IProp
|
||||
const { classes } = useStyles();
|
||||
|
||||
const muteAllVideo = useCallback(
|
||||
() => dispatch(openDialog(MuteEveryonesVideoDialog)), [ dispatch ]);
|
||||
() => dispatch(openDialog('MuteEveryonesVideoDialog', MuteEveryonesVideoDialog)), [ dispatch ]);
|
||||
|
||||
const muteAllDesktop = useCallback(
|
||||
() => dispatch(openDialog(MuteEveryonesDesktopDialog)), [ dispatch ]);
|
||||
() => dispatch(openDialog('MuteEveryonesDesktopDialog', MuteEveryonesDesktopDialog)), [ dispatch ]);
|
||||
|
||||
const openModeratorSettings = () => dispatch(openSettingsDialog(SETTINGS_TABS.MODERATOR));
|
||||
|
||||
|
||||
@@ -181,7 +181,7 @@ const ParticipantsPane = () => {
|
||||
}, []);
|
||||
|
||||
const onMuteAll = useCallback(() => {
|
||||
dispatch(openDialog(MuteEveryoneDialog));
|
||||
dispatch(openDialog('MuteEveryoneDialog', MuteEveryoneDialog));
|
||||
}, []);
|
||||
|
||||
const onToggleContext = useCallback(() => {
|
||||
|
||||
45
react/features/polls/components/web/PollsButton.tsx
Normal file
45
react/features/polls/components/web/PollsButton.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { IconInfo } from '../../../base/icons/svg';
|
||||
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
|
||||
import { openPollsPanel } from '../../../chat/actions.any';
|
||||
import { arePollsDisabled } from '../../../conference/functions.any';
|
||||
|
||||
/**
|
||||
* Component that renders a button to open the polls panel.
|
||||
*
|
||||
* @augments AbstractButton
|
||||
*/
|
||||
class PollsButton extends AbstractButton<AbstractButtonProps> {
|
||||
override icon = IconInfo;
|
||||
override label = 'toolbar.polls';
|
||||
override tooltip = 'toolbar.polls';
|
||||
|
||||
/**
|
||||
* Handles clicking the button to open the polls panel.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
override _handleClick() {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(openPollsPanel());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the Redux state to the props of this component.
|
||||
*
|
||||
* @param {IReduxState} state - The Redux state.
|
||||
* @returns {Object} - Mapped props.
|
||||
*/
|
||||
function mapStateToProps(state: IReduxState) {
|
||||
return {
|
||||
visible: !arePollsDisabled(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(mapStateToProps)(PollsButton));
|
||||
24
react/features/polls/hooks.web.ts
Normal file
24
react/features/polls/hooks.web.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { arePollsDisabled } from '../conference/functions.any';
|
||||
|
||||
import PollsButton from './components/web/PollsButton';
|
||||
|
||||
const polls = {
|
||||
key: 'polls',
|
||||
Content: PollsButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
/**
|
||||
* A hook that returns the polls button if it is enabled and undefined otherwise.
|
||||
*
|
||||
* @returns {Object | undefined} - The polls button object or undefined.
|
||||
*/
|
||||
export function usePollsButton() {
|
||||
const isPollsDisabled = useSelector(arePollsDisabled);
|
||||
|
||||
if (!isPollsDisabled) {
|
||||
return polls;
|
||||
}
|
||||
}
|
||||
@@ -110,7 +110,7 @@ class ReactionMenuDialog extends PureComponent<IProps> {
|
||||
*/
|
||||
_onCancel() {
|
||||
if (this.props._isOpen) {
|
||||
this.props.dispatch(hideDialog(ReactionMenu_));
|
||||
this.props.dispatch(hideDialog('ReactionMenu_', ReactionMenu_));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ class ReactionsMenuButton extends AbstractButton<IProps> {
|
||||
* @returns {void}
|
||||
*/
|
||||
override _handleClick() {
|
||||
this.props.dispatch(openDialog(ReactionMenuDialog));
|
||||
this.props.dispatch(openDialog('ReactionMenuDialog', ReactionMenuDialog));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -76,7 +76,7 @@ export function showRecordingLimitNotification(streamType: string) {
|
||||
*/
|
||||
export function showStartRecordingNotification() {
|
||||
return (dispatch: IStore['dispatch']) => {
|
||||
const openDialogCallback = () => dispatch(openDialog(StartRecordingDialog));
|
||||
const openDialogCallback = () => dispatch(openDialog('StartRecordingDialog', StartRecordingDialog));
|
||||
|
||||
dispatch(showStartRecordingNotificationWithCallback(openDialogCallback));
|
||||
};
|
||||
|
||||
@@ -34,7 +34,7 @@ class LiveStreamButton extends AbstractLiveStreamButton<Props> {
|
||||
const { _isLiveStreamRunning, dispatch } = this.props;
|
||||
|
||||
if (_isLiveStreamRunning) {
|
||||
dispatch(openDialog(StopLiveStreamDialog));
|
||||
dispatch(openDialog('StopLiveStreamDialog', StopLiveStreamDialog));
|
||||
} else {
|
||||
navigate(screen.conference.liveStream);
|
||||
}
|
||||
|
||||
@@ -26,10 +26,10 @@ class LiveStreamButton extends AbstractLiveStreamButton<IProps> {
|
||||
*/
|
||||
override _onHandleClick() {
|
||||
const { _isLiveStreamRunning, dispatch } = this.props;
|
||||
const dialogComponent = _isLiveStreamRunning ? StopLiveStreamDialog : StartLiveStreamDialog;
|
||||
const dialogName = _isLiveStreamRunning ? 'StopLiveStreamDialog' : 'StartLiveStreamDialog';
|
||||
|
||||
dispatch(openDialog(
|
||||
_isLiveStreamRunning ? StopLiveStreamDialog : StartLiveStreamDialog
|
||||
));
|
||||
dispatch(openDialog(dialogName, dialogComponent));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ export default class AbstractHighlightButton<P extends IProps, S={}> extends Com
|
||||
const dialogShown = dispatch(maybeShowPremiumFeatureDialog(MEET_FEATURES.RECORDING));
|
||||
|
||||
if (!dialogShown) {
|
||||
dispatch(openDialog(StartRecordingDialog));
|
||||
dispatch(openDialog('StartRecordingDialog', StartRecordingDialog));
|
||||
}
|
||||
} ],
|
||||
appearance: NOTIFICATION_TYPE.NORMAL
|
||||
|
||||
@@ -37,7 +37,7 @@ class RecordButton extends AbstractRecordButton<Props> {
|
||||
const { _isRecordingRunning, dispatch } = this.props;
|
||||
|
||||
if (_isRecordingRunning) {
|
||||
dispatch(openDialog(StopRecordingDialog));
|
||||
dispatch(openDialog('StopRecordingDialog', StopRecordingDialog));
|
||||
} else {
|
||||
navigate(screen.conference.recording);
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ export class HighlightButton extends AbstractHighlightButton<IProps, IState> {
|
||||
const dialogShown = dispatch(maybeShowPremiumFeatureDialog(MEET_FEATURES.RECORDING));
|
||||
|
||||
if (!dialogShown) {
|
||||
dispatch(openDialog(StartRecordingDialog));
|
||||
dispatch(openDialog('StartRecordingDialog', StartRecordingDialog));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,10 +26,10 @@ class RecordingButton extends AbstractRecordButton<IProps> {
|
||||
*/
|
||||
override _onHandleClick() {
|
||||
const { _isRecordingRunning, dispatch } = this.props;
|
||||
const dialogComponent = _isRecordingRunning ? StopRecordingDialog : StartRecordingDialog;
|
||||
const dialogName = _isRecordingRunning ? 'StopRecordingDialog' : 'StartRecordingDialog';
|
||||
|
||||
dispatch(openDialog(
|
||||
_isRecordingRunning ? StopRecordingDialog : StartRecordingDialog
|
||||
));
|
||||
dispatch(openDialog(dialogName, dialogComponent));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -427,6 +427,6 @@ function _showExplicitConsentDialog(recorderSession: any, dispatch: IStore['disp
|
||||
dispatch(setVideoUnmutePermissions(true, true));
|
||||
dispatch(setAudioMuted(true));
|
||||
dispatch(setVideoMuted(true));
|
||||
dispatch(openDialog(RecordingConsentDialog));
|
||||
dispatch(openDialog('RecordingConsentDialog', RecordingConsentDialog));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ let permissionsReplyListener: Function | undefined,
|
||||
* @public
|
||||
*/
|
||||
export function openRemoteControlAuthorizationDialog(participantId: string) {
|
||||
return openDialog(RemoteControlAuthorizationDialog, { participantId });
|
||||
return openDialog('RemoteControlAuthorizationDialog', RemoteControlAuthorizationDialog, { participantId });
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -71,7 +71,7 @@ export function endRoomLockRequest(
|
||||
= password
|
||||
? dispatch(setPassword(conference, conference.lock, password))
|
||||
: Promise.resolve();
|
||||
const endRoomLockRequest_ = () => dispatch(hideDialog(SecurityDialog));
|
||||
const endRoomLockRequest_ = () => dispatch(hideDialog('SecurityDialog', SecurityDialog));
|
||||
|
||||
setPassword_.then(endRoomLockRequest_, endRoomLockRequest_);
|
||||
};
|
||||
@@ -90,7 +90,7 @@ export function endRoomLockRequest(
|
||||
* }}
|
||||
*/
|
||||
export function _openPasswordRequiredPrompt(conference: IJitsiConference) {
|
||||
return openDialog(PasswordRequiredPrompt, { conference });
|
||||
return openDialog('PasswordRequiredPrompt', PasswordRequiredPrompt, { conference });
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -74,7 +74,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
* @returns {*}
|
||||
*/
|
||||
function _conferenceJoined({ dispatch }: IStore, next: Function, action: AnyAction) {
|
||||
dispatch(hideDialog(PasswordRequiredPrompt));
|
||||
dispatch(hideDialog('PasswordRequiredPrompt', PasswordRequiredPrompt));
|
||||
|
||||
return next(action);
|
||||
}
|
||||
@@ -104,7 +104,7 @@ function _conferenceFailed({ dispatch }: IStore, next: Function, action: AnyActi
|
||||
dispatch(_openPasswordRequiredPrompt(conference));
|
||||
}
|
||||
} else {
|
||||
dispatch(hideDialog(PasswordRequiredPrompt));
|
||||
dispatch(hideDialog('PasswordRequiredPrompt', PasswordRequiredPrompt));
|
||||
}
|
||||
|
||||
return next(action);
|
||||
|
||||
@@ -28,7 +28,7 @@ export function showSalesforceNotification() {
|
||||
customActionNameKey: [ 'notify.linkToSalesforceKey' ],
|
||||
customActionHandler: [ () => {
|
||||
dispatch(hideNotification(SALESFORCE_LINK_NOTIFICATION_ID));
|
||||
dispatch(openDialog(SalesforceLinkDialog));
|
||||
dispatch(openDialog('SalesforceLinkDialog', SalesforceLinkDialog));
|
||||
} ],
|
||||
appearance: NOTIFICATION_TYPE.NORMAL
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
|
||||
@@ -59,7 +59,7 @@ export function startAudioScreenShareFlow() {
|
||||
|
||||
// If we're already in a normal screen sharing session, warn the user.
|
||||
if (isScreenVideoShared(state)) {
|
||||
dispatch(openDialog(ShareMediaWarningDialog, { _isAudioScreenShareWarning: true }));
|
||||
dispatch(openDialog('ShareMediaWarningDialog', ShareMediaWarningDialog, { _isAudioScreenShareWarning: true }));
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -76,7 +76,7 @@ export function startAudioScreenShareFlow() {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(openDialog(ShareAudioDialog));
|
||||
dispatch(openDialog('ShareAudioDialog', ShareAudioDialog));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ export function startScreenShareFlow(enabled: boolean) {
|
||||
|
||||
// If we're in an audio screen sharing session, warn the user.
|
||||
if (audioOnlySharing) {
|
||||
dispatch(openDialog(ShareMediaWarningDialog, { _isAudioScreenShareWarning: false }));
|
||||
dispatch(openDialog('ShareMediaWarningDialog', ShareMediaWarningDialog, { _isAudioScreenShareWarning: false }));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ export function openCameraCaptureDialog(callback: Function, componentProps: ICam
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(openDialog(CameraCaptureDialog, {
|
||||
dispatch(openDialog('CameraCaptureDialog', CameraCaptureDialog, {
|
||||
callback,
|
||||
componentProps
|
||||
}));
|
||||
|
||||
@@ -10,6 +10,6 @@ import { SecurityDialog } from './components/security-dialog';
|
||||
*/
|
||||
export function toggleSecurityDialog() {
|
||||
return function(dispatch: IStore['dispatch']) {
|
||||
dispatch(toggleDialog(SecurityDialog));
|
||||
dispatch(toggleDialog('SecurityDialog', SecurityDialog));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ export function openLogoutDialog() {
|
||||
const config = state['features/base/config'];
|
||||
const logoutUrl = config.tokenLogoutUrl;
|
||||
|
||||
dispatch(openDialog(LogoutDialog, {
|
||||
dispatch(openDialog('LogoutDialog', LogoutDialog, {
|
||||
onLogout() {
|
||||
if (isTokenAuthEnabled(config)) {
|
||||
if (logoutUrl) {
|
||||
|
||||
@@ -53,7 +53,7 @@ export function openLogoutDialog() {
|
||||
const { conference } = state['features/base/conference'];
|
||||
const { jwt } = state['features/base/jwt'];
|
||||
|
||||
dispatch(openDialog(LogoutDialog, {
|
||||
dispatch(openDialog('LogoutDialog', LogoutDialog, {
|
||||
onLogout() {
|
||||
if (isTokenAuthEnabled(config) && config.tokenAuthUrlAutoRedirect && jwt) {
|
||||
|
||||
@@ -90,7 +90,7 @@ export function openLogoutDialog() {
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function openSettingsDialog(defaultTab?: string, isDisplayedOnWelcomePage?: boolean) {
|
||||
return openDialog(SettingsDialog, {
|
||||
return openDialog('SettingsDialog', SettingsDialog, {
|
||||
defaultTab,
|
||||
isDisplayedOnWelcomePage
|
||||
});
|
||||
|
||||
@@ -96,6 +96,11 @@ function normalizeCurrentLanguage(language: string) {
|
||||
return;
|
||||
}
|
||||
|
||||
// First check if the language code exists as-is (e.g., 'zh-CN', 'fr-CA')
|
||||
if (LANGUAGES.includes(language)) {
|
||||
return language;
|
||||
}
|
||||
|
||||
const [ country, lang ] = language.split('-');
|
||||
const jitsiNormalized = `${country}${lang ?? ''}`;
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ export function setSharedVideoStatus({ videoUrl, status, time, ownerId, muted }:
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function showSharedVideoDialog(onPostSubmit: Function) {
|
||||
return openDialog(SharedVideoDialog, { onPostSubmit });
|
||||
return openDialog('SharedVideoDialog', SharedVideoDialog, { onPostSubmit });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -181,7 +181,7 @@ export function showConfirmPlayingDialog(actor: String, onSubmit: Function) {
|
||||
// shows only one dialog at a time
|
||||
dispatch(setConfirmShowVideo(false));
|
||||
|
||||
dispatch(openDialog(ShareVideoConfirmDialog, {
|
||||
dispatch(openDialog('ShareVideoConfirmDialog', ShareVideoConfirmDialog, {
|
||||
actorName: actor,
|
||||
onSubmit: () => {
|
||||
dispatch(setConfirmShowVideo(true));
|
||||
@@ -198,6 +198,6 @@ export function showConfirmPlayingDialog(actor: String, onSubmit: Function) {
|
||||
*/
|
||||
export function hideConfirmPlayingDialog() {
|
||||
return (dispatch: IStore['dispatch']) => {
|
||||
dispatch(hideDialog(ShareVideoConfirmDialog));
|
||||
dispatch(hideDialog('ShareVideoConfirmDialog', ShareVideoConfirmDialog));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ class SpeakerStatsButton extends AbstractSpeakerStatsButton {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
sendAnalytics(createToolbarEvent('speaker.stats'));
|
||||
dispatch(openDialog(SpeakerStats));
|
||||
dispatch(openDialog('SpeakerStats', SpeakerStats));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user