mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2025-12-30 19:32:27 +00:00
Compare commits
22 Commits
6963
...
rm-dead-co
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38068b33e5 | ||
|
|
22ded30b61 | ||
|
|
533deea5fd | ||
|
|
46c6d1057d | ||
|
|
45aa53b1a6 | ||
|
|
7d65123495 | ||
|
|
e1ac000cd1 | ||
|
|
f98036efa1 | ||
|
|
23aeafcc93 | ||
|
|
0ffe2c2c87 | ||
|
|
dec58afe46 | ||
|
|
2aa770e532 | ||
|
|
1a113ba733 | ||
|
|
3a5833829c | ||
|
|
e6d1f039d2 | ||
|
|
fef78152e1 | ||
|
|
84221c5c13 | ||
|
|
3e59359563 | ||
|
|
e69db9b878 | ||
|
|
ae7e441e21 | ||
|
|
6b8afbcceb | ||
|
|
d712a565f8 |
@@ -76,7 +76,6 @@ dependencies {
|
||||
implementation project(':react-native-get-random-values')
|
||||
implementation project(':react-native-immersive')
|
||||
implementation project(':react-native-keep-awake')
|
||||
implementation project(':react-native-masked-view_masked-view')
|
||||
implementation project(':react-native-orientation-locker')
|
||||
implementation project(':react-native-pager-view')
|
||||
implementation project(':react-native-performance')
|
||||
|
||||
@@ -119,7 +119,6 @@ class ReactInstanceManagerHolder {
|
||||
new com.oblador.performance.PerformancePackage(),
|
||||
new com.reactnativecommunity.slider.ReactSliderPackage(),
|
||||
new com.brentvatne.react.ReactVideoPackage(),
|
||||
new org.reactnative.maskedview.RNCMaskedViewPackage(),
|
||||
new com.reactnativecommunity.webview.RNCWebViewPackage(),
|
||||
new com.kevinresol.react_native_default_preference.RNDefaultPreferencePackage(),
|
||||
new com.learnium.RNDeviceInfo.RNDeviceInfo(),
|
||||
|
||||
30
config.js
30
config.js
@@ -542,12 +542,15 @@ var config = {
|
||||
// Disables responsive tiles.
|
||||
// disableResponsiveTiles: false,
|
||||
|
||||
// Hides lobby button
|
||||
// DEPRECATED. Please use `securityUi?.hideLobbyButton` instead.
|
||||
// Hides lobby button.
|
||||
// hideLobbyButton: false,
|
||||
|
||||
// DEPRECATED. Please use `lobby?.autoKnock` instead.
|
||||
// If Lobby is enabled starts knocking automatically.
|
||||
// autoKnockLobby: false,
|
||||
|
||||
// DEPRECATED. Please use `lobby?.enableChat` instead.
|
||||
// Enable lobby chat.
|
||||
// enableLobbyChat: true,
|
||||
|
||||
@@ -572,6 +575,22 @@ var config = {
|
||||
// customUrl: ''
|
||||
// },
|
||||
|
||||
// Configs for the lobby screen.
|
||||
// lobby {
|
||||
// // If Lobby is enabled, it starts knocking automatically. Replaces `autoKnockLobby`.
|
||||
// autoKnock: false,
|
||||
// // Enables the lobby chat. Replaces `enableLobbyChat`.
|
||||
// enableChat: true,
|
||||
// },
|
||||
|
||||
// Configs for the security related UI elements.
|
||||
// securityUi: {
|
||||
// // Hides the lobby button. Replaces `hideLobbyButton`.
|
||||
// hideLobbyButton: false,
|
||||
// // Hides the possibility to set and enter a lobby password.
|
||||
// disableLobbyPassword: false,
|
||||
// },
|
||||
|
||||
// Disable app shortcuts that are registered upon joining a conference
|
||||
// disableShortcuts: false,
|
||||
|
||||
@@ -799,6 +818,14 @@ var config = {
|
||||
// 'microphone', 'camera', 'select-background', 'invite', 'settings'
|
||||
// hiddenPremeetingButtons: [],
|
||||
|
||||
// An array with custom option buttons for the participant context menu
|
||||
// type: Array<{ icon: string; id: string; text: string; }>
|
||||
// customParticipantMenuButtons: [],
|
||||
|
||||
// An array with custom option buttons for the toolbar
|
||||
// type: Array<{ icon: string; id: string; text: string; }>
|
||||
// customToolbarButtons: [],
|
||||
|
||||
// Stats
|
||||
//
|
||||
|
||||
@@ -1335,6 +1362,7 @@ var config = {
|
||||
deploymentInfo
|
||||
dialOutAuthUrl
|
||||
dialOutCodesUrl
|
||||
dialOutRegionUrl
|
||||
disableRemoteControl
|
||||
displayJids
|
||||
externalConnectUrl
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
display: inline-block;
|
||||
|
||||
&-content {
|
||||
background: $menuBG;
|
||||
border-radius: 3px;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
position: relative;
|
||||
right: auto;
|
||||
margin-bottom: 8px;
|
||||
max-height: 456px;
|
||||
overflow: auto;
|
||||
width: 300px;
|
||||
|
||||
&-ul {
|
||||
margin:0;
|
||||
padding:0;
|
||||
@@ -16,90 +16,37 @@
|
||||
}
|
||||
}
|
||||
|
||||
&-header {
|
||||
color: #fff;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin-top: 8px;
|
||||
padding: 8px 16px;
|
||||
|
||||
&-icon {
|
||||
display: inline-block;
|
||||
|
||||
svg {
|
||||
fill: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
&--bordered {
|
||||
border-bottom: 1px solid #4C4D50;
|
||||
}
|
||||
|
||||
&-text {
|
||||
margin-left: 12px;
|
||||
}
|
||||
&-header:hover {
|
||||
background-color: initial;
|
||||
cursor: initial;
|
||||
}
|
||||
|
||||
&-entry {
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
padding: 8px 0;
|
||||
margin-left: 48px;
|
||||
&-entry-text {
|
||||
display: inline-block;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 213px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
|
||||
&--selected {
|
||||
background: #131519;
|
||||
cursor: initial;
|
||||
margin-left: 0;
|
||||
padding-left: 18px;
|
||||
}
|
||||
|
||||
&-text {
|
||||
color: #fff;
|
||||
display: inline-block;
|
||||
line-height: 24px;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 213px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
&.left-margin {
|
||||
margin-left: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
&-speaker {
|
||||
position: relative;
|
||||
|
||||
&-ul {
|
||||
margin:0;
|
||||
padding:0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
&:hover, &:focus-within, &:focus {
|
||||
.audio-preview-entry {
|
||||
background: #36383C;
|
||||
margin-left: 0;
|
||||
padding-left: 48px;
|
||||
|
||||
&--selected {
|
||||
padding-left: 18px;
|
||||
background: $newToolbarBackgroundColor;
|
||||
}
|
||||
}
|
||||
|
||||
.audio-preview-test-button {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.audio-preview-entry-text {
|
||||
max-width: 178px;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.audio-preview-entry-text {
|
||||
max-width: 238px;
|
||||
}
|
||||
@@ -108,19 +55,6 @@
|
||||
&-microphone {
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
.audio-preview-entry {
|
||||
background: #36383C;
|
||||
margin-left: 0;
|
||||
padding-left: 48px;
|
||||
|
||||
&--selected {
|
||||
background: $newToolbarBackgroundColor;
|
||||
padding-left: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--nometer {
|
||||
.audio-preview-entry-text {
|
||||
max-width: 238px;
|
||||
@@ -140,42 +74,21 @@
|
||||
display: inline-block;
|
||||
width: 14px;
|
||||
|
||||
& svg {
|
||||
fill: #1C2025;
|
||||
}
|
||||
|
||||
&--check {
|
||||
background: #31B76A;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
&--exclamation {
|
||||
margin-left: 6px;
|
||||
|
||||
& svg {
|
||||
fill: #E54B4B;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-hr {
|
||||
border-top: 1px solid #4C4D50;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
&-test-button {
|
||||
display: none;
|
||||
background: #FFF;
|
||||
border: 1px solid #D1DBE8;
|
||||
border-radius: 3px;
|
||||
color: #1C2025;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
font-size: 0.8rem;
|
||||
line-height: 24px;
|
||||
padding: 2px 8px;
|
||||
padding: 4px 10px;
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 5px;
|
||||
top: 6px;
|
||||
}
|
||||
|
||||
&-meter-mic {
|
||||
@@ -184,9 +97,7 @@
|
||||
top: 14px;
|
||||
}
|
||||
|
||||
// Override @atlaskit/InlineDialog container which is made with styled components
|
||||
& > div:nth-child(2) {
|
||||
outline: none;
|
||||
padding: 0;
|
||||
&-checkbox-container {
|
||||
padding: 10px 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,28 +3,28 @@
|
||||
display: inline-block;
|
||||
|
||||
& > svg {
|
||||
fill: #4E5E6C;
|
||||
fill: #525252;
|
||||
width: 38px;
|
||||
}
|
||||
}
|
||||
|
||||
&.metr--disabled {
|
||||
& > svg {
|
||||
fill: #4E5E6C;
|
||||
fill: #525252;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.metr-l-0 {
|
||||
rect:first-child {
|
||||
fill: #31B76A;
|
||||
fill: #1EC26A;
|
||||
}
|
||||
}
|
||||
|
||||
@for $i from 1 through 7 {
|
||||
.metr-l-#{$i} {
|
||||
rect:nth-child(-n+#{$i+1}) {
|
||||
fill: #31B76A;
|
||||
fill: #1EC26A;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,15 +5,15 @@
|
||||
.popupmenu__contents {
|
||||
.popupmenu__volume-slider {
|
||||
&::-webkit-slider-runnable-track {
|
||||
background-color: $popupSliderColor;
|
||||
background-color: #246FE5;
|
||||
}
|
||||
|
||||
&::-moz-range-track {
|
||||
background-color: $popupSliderColor;
|
||||
background-color: #246FE5;
|
||||
}
|
||||
|
||||
&::-ms-fill-lower {
|
||||
background-color: $popupSliderColor;
|
||||
background-color: #246FE5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,6 @@ $errorColor: #c61600;
|
||||
|
||||
// Popover colors
|
||||
$popoverFontColor: #ffffff !important;
|
||||
$popupSliderColor: #0376da;
|
||||
|
||||
// Toolbar
|
||||
$toolbarBackground: rgba(0, 0, 0, 0.5);
|
||||
|
||||
@@ -469,7 +469,7 @@ PODS:
|
||||
- React-Core
|
||||
- RNDeviceInfo (8.4.8):
|
||||
- React-Core
|
||||
- RNGestureHandler (2.8.0):
|
||||
- RNGestureHandler (2.9.0):
|
||||
- React-Core
|
||||
- RNGoogleSignin (7.0.4):
|
||||
- GoogleSignIn (~> 6.0.0)
|
||||
@@ -773,7 +773,7 @@ SPEC CHECKSUMS:
|
||||
RNCClipboard: 41d8d918092ae8e676f18adada19104fa3e68495
|
||||
RNDefaultPreference: 08bdb06cfa9188d5da97d4642dac745218d7fb31
|
||||
RNDeviceInfo: 0400a6d0c94186d1120c3cbd97b23abc022187a9
|
||||
RNGestureHandler: 62232ba8f562f7dea5ba1b3383494eb5bf97a4d3
|
||||
RNGestureHandler: 071d7a9ad81e8b83fe7663b303d132406a7d8f39
|
||||
RNGoogleSignin: c4381751eefd73c552b923ba347a9bfc6f18771c
|
||||
RNScreens: 40a2cb40a02a609938137a1e0acfbf8fc9eebf19
|
||||
RNSound: 27e8268bdb0a1f191f219a33267f7e0445e8d62f
|
||||
|
||||
@@ -40,19 +40,10 @@ static NSString *const PiPEnabledFeatureFlag = @"pip.enabled";
|
||||
|
||||
#pragma mark Initializers
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
[self initWithXXX];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)coder {
|
||||
self = [super initWithCoder:coder];
|
||||
if (self) {
|
||||
[self initWithXXX];
|
||||
[self doInitialize];
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -61,7 +52,7 @@ static NSString *const PiPEnabledFeatureFlag = @"pip.enabled";
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self initWithXXX];
|
||||
[self doInitialize];
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -71,9 +62,9 @@ static NSString *const PiPEnabledFeatureFlag = @"pip.enabled";
|
||||
* Internal initialization:
|
||||
*
|
||||
* - sets the background color
|
||||
* - initializes the external API scope
|
||||
* - registers necessary observers
|
||||
*/
|
||||
- (void)initWithXXX {
|
||||
- (void)doInitialize {
|
||||
// Set a background color which is in accord with the JavaScript and Android
|
||||
// parts of the application and causes less perceived visual flicker than
|
||||
// the default background color.
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
"chat": {
|
||||
"enter": "Entrar na sala",
|
||||
"error": "Erro: a sua mensagem não foi enviada. Motivo: {{error}}",
|
||||
"fieldPlaceHolder": "Escreva aqui a sua mensagem",
|
||||
"fieldPlaceHolder": "Aa",
|
||||
"lobbyChatMessageTo": "Mensagem de chat na sala de espera para {{recipient}}",
|
||||
"message": "Mensagem",
|
||||
"messageAccessibleTitle": "{{user}} disse:",
|
||||
@@ -147,6 +147,7 @@
|
||||
"bridgeCount": "Servidores: ",
|
||||
"codecs": "Codecs (A/V): ",
|
||||
"connectedTo": "Ligado a:",
|
||||
"e2eeVerified": "E2EE verificada:",
|
||||
"framerate": "Taxa de frames:",
|
||||
"less": "Mostrar menos",
|
||||
"localaddress": "Endereço local:",
|
||||
@@ -266,7 +267,7 @@
|
||||
"e2eeWarning": "AVISO: Nem todos os participantes neste encontro parecem ter apoio para a encriptação de ponta a ponta. Se o permitir, eles não o poderão ver nem ouvir.",
|
||||
"e2eeWillDisableDueToMaxModeDescription": "AVISO: A encriptação de ponta a ponta será automaticamente desativada se mais participantes aderirem à conferência.",
|
||||
"embedMeeting": "Embutir reunião",
|
||||
"enterDisplayName": "Digite o seu nome aqui",
|
||||
"enterDisplayName": "Digite o seu nome",
|
||||
"error": "Erro",
|
||||
"gracefulShutdown": "O nosso serviço está atualmente em manutenção. Por favor, tente novamente mais tarde.",
|
||||
"grantModeratorDialog": "Tem a certeza que quer conceder direitos de moderador a {{participantName}}?",
|
||||
@@ -408,6 +409,10 @@
|
||||
"user": "Utilizador",
|
||||
"userIdentifier": "Identificador do utilizador",
|
||||
"userPassword": "Palavra-passe do utilizador",
|
||||
"verifyParticipantConfirm": "Coincidem",
|
||||
"verifyParticipantDismiss": "Não coincidem",
|
||||
"verifyParticipantQuestion": "EXPERIMENTAL: Perguntar ao participante {{participantName}} se vêem o mesmo conteúdo, na mesma ordem.",
|
||||
"verifyParticipantTitle": "Verificação pelo utilizador",
|
||||
"videoLink": "Link do vídeo",
|
||||
"viewUpgradeOptions": "Ver opções de actualização",
|
||||
"viewUpgradeOptionsContent": "Para obter acesso ilimitado a funcionalidades premium como gravação, transcrições, RTMP Streaming & mais, terá de actualizar o seu plano.",
|
||||
@@ -437,9 +442,6 @@
|
||||
"noResults": "Não foram encontrados resultados :(",
|
||||
"search": "Procurar no GIPHY"
|
||||
},
|
||||
"helpView": {
|
||||
"title": "Centro de ajuda"
|
||||
},
|
||||
"incomingCall": {
|
||||
"answer": "Responder",
|
||||
"audioCallTitle": "Chamada recebida",
|
||||
@@ -563,7 +565,6 @@
|
||||
"lobby": {
|
||||
"admit": "Aceitar",
|
||||
"admitAll": "Aceitar todos",
|
||||
"allow": "Permitir",
|
||||
"backToKnockModeButton": "Peça para aderir",
|
||||
"chat": "Chat",
|
||||
"dialogTitle": "Modo sala de espera",
|
||||
@@ -649,6 +650,8 @@
|
||||
"connectedOneMember": "{{name}} entrou na reunião",
|
||||
"connectedThreePlusMembers": "{{name}} e muitos outros entraram na reunião",
|
||||
"connectedTwoMembers": "{{first}} e {{second}} entraram na reunião",
|
||||
"dataChannelClosed": "Deficiência na qualidade do vídeo",
|
||||
"dataChannelClosedDescription": "O canal de ponte foi desconectado e, portanto, a qualidade do vídeo está limitada à sua configuração mais baixa.",
|
||||
"disconnected": "desconectado",
|
||||
"displayNotifications": "Mostrar notificações para",
|
||||
"focus": "Foco da conferência",
|
||||
@@ -709,6 +712,8 @@
|
||||
"reactionSoundsForAll": "Desativar sons para todos",
|
||||
"screenShareNoAudio": "A caixa de compartilhar áudio não foi marcada no ecrã de seleção da janela.",
|
||||
"screenShareNoAudioTitle": "Não foi possível partilhar o áudio do sistema!",
|
||||
"screenSharingAudioOnlyDescription": "Note por favor que ao partilhar o seu ecrã está a afectar o modo \"Melhor desempenho\" e irá utilizar mais largura de banda.",
|
||||
"screenSharingAudioOnlyTitle": "Modo \"Melhor desempenho\"",
|
||||
"selfViewTitle": "Pode sempre reexibir a autovisualização a partir das definições",
|
||||
"somebody": "Alguém",
|
||||
"startSilentDescription": "Volte à reunião para habilitar o áudio",
|
||||
@@ -858,9 +863,6 @@
|
||||
"rejected": "Rejeitado",
|
||||
"ringing": "Tocando..."
|
||||
},
|
||||
"privacyView": {
|
||||
"title": "Privacidade"
|
||||
},
|
||||
"profile": {
|
||||
"avatar": "avatar",
|
||||
"setDisplayNameLabel": "Definir seu nome de exibição",
|
||||
@@ -914,6 +916,7 @@
|
||||
"localRecordingVideoWarning": "Para gravar o seu vídeo deve tê-lo ligado quando iniciar a gravação",
|
||||
"localRecordingWarning": "Certifique-se de selecionar o separador actual a fim de utilizar o vídeo e áudio corretos. A gravação está actualmente limitada a 1 GB, o que é cerca de 100 minutos.",
|
||||
"loggedIn": "Conectado como {{userName}}",
|
||||
"noMicPermission": "Não foi possível criar a faixa de microfone. Por favor, conceda permissão para utilizar o microfone.",
|
||||
"noStreams": "Não foi detetado nenhum sinal áudio ou vídeo.",
|
||||
"off": "Gravação parada",
|
||||
"offBy": "{{name}} parou a gravação",
|
||||
@@ -964,7 +967,7 @@
|
||||
"incomingMessage": "Receber uma mensagem",
|
||||
"language": "Idioma",
|
||||
"loggedIn": "Sessão iniciada como {{name}}",
|
||||
"maxStageParticipants": "Número máximo de participantes que podem ser afixados",
|
||||
"maxStageParticipants": "Número máximo de participantes que podem ser afixados (EXPERIMENTAL)",
|
||||
"microphones": "Microfones",
|
||||
"moderator": "Moderador",
|
||||
"more": "Mais",
|
||||
@@ -983,7 +986,7 @@
|
||||
"sounds": "Sons",
|
||||
"speakers": "Participantes",
|
||||
"startAudioMuted": "Todos começam com microfone desligado",
|
||||
"startReactionsMuted": "Sons de reação silenciados para todos",
|
||||
"startReactionsMuted": "Todos começam com os sons de reação desativados",
|
||||
"startVideoMuted": "Todos começam com câmara desligada",
|
||||
"talkWhileMuted": "Falar com o microfone desligado",
|
||||
"title": "Definições"
|
||||
@@ -1003,6 +1006,7 @@
|
||||
"displayName": "Nome de exibição",
|
||||
"displayNamePlaceholderText": "Ex: João Dias",
|
||||
"email": "Email",
|
||||
"emailPlaceholderText": "email@example.com",
|
||||
"goTo": "Ir para",
|
||||
"header": "Configurações",
|
||||
"help": "Ajuda",
|
||||
@@ -1291,6 +1295,7 @@
|
||||
"show": "Mostrar no palco",
|
||||
"showSelfView": "Mostrar autovisualização",
|
||||
"unpinFromStage": "Desafixar",
|
||||
"verify": "Verificar participante",
|
||||
"videoMuted": "Câmara desativada",
|
||||
"videomute": "Participante parou a câmara"
|
||||
},
|
||||
@@ -1358,6 +1363,7 @@
|
||||
"recentList": "Recente",
|
||||
"recentListDelete": "Remover",
|
||||
"recentListEmpty": "A sua lista recente está atualmente vazia. Converse com a sua equipa e encontrará aqui todas as suas reuniões recentes.",
|
||||
"recentMeetings": "As suas reuniões recentes",
|
||||
"reducedUIText": "Bem-vindo ao {{app}}!",
|
||||
"roomNameAllowedChars": "Nome da reunião não deve conter qualquer um destes caracteres: ?. &, :, ', \", %, #.",
|
||||
"roomname": "Digite o nome da sala",
|
||||
@@ -1366,6 +1372,7 @@
|
||||
"settings": "Definições",
|
||||
"startMeeting": "Iniciar reunião",
|
||||
"terms": "Termos",
|
||||
"title": "Videoconferências mais seguras, flexíveis e totalmente gratuitas"
|
||||
"title": "Videoconferências mais seguras, flexíveis e totalmente gratuitas",
|
||||
"upcomingMeetings": "As suas próximas reuniões"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1941,6 +1941,21 @@ class API {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application ( if API is enabled) that a participant menu button was clicked.
|
||||
*
|
||||
* @param {string} key - The key of the participant menu button.
|
||||
* @param {string} participantId - The ID of the participant for with the participant menu button was clicked.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyParticipantMenuButtonClicked(key, participantId) {
|
||||
this._sendEvent({
|
||||
name: 'participant-menu-button-clicked',
|
||||
key,
|
||||
participantId
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes the allocated resources.
|
||||
*
|
||||
|
||||
1
modules/API/external/external_api.js
vendored
1
modules/API/external/external_api.js
vendored
@@ -140,6 +140,7 @@ const events = {
|
||||
'raise-hand-updated': 'raiseHandUpdated',
|
||||
'recording-link-available': 'recordingLinkAvailable',
|
||||
'recording-status-changed': 'recordingStatusChanged',
|
||||
'participant-menu-button-clicked': 'participantMenuButtonClick',
|
||||
'video-ready-to-close': 'readyToClose',
|
||||
'video-conference-joined': 'videoConferenceJoined',
|
||||
'video-conference-left': 'videoConferenceLeft',
|
||||
|
||||
26
package-lock.json
generated
26
package-lock.json
generated
@@ -94,7 +94,7 @@
|
||||
"react-native-default-preference": "1.4.4",
|
||||
"react-native-device-info": "8.4.8",
|
||||
"react-native-dialog": "https://github.com/jitsi/react-native-dialog/releases/download/v9.2.2-jitsi.1/react-native-dialog-9.2.2.tgz",
|
||||
"react-native-gesture-handler": "2.8.0",
|
||||
"react-native-gesture-handler": "2.9.0",
|
||||
"react-native-get-random-values": "1.7.2",
|
||||
"react-native-immersive": "2.0.0",
|
||||
"react-native-keep-awake": "4.0.0",
|
||||
@@ -5654,9 +5654,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sideway/formula": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz",
|
||||
"integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg=="
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz",
|
||||
"integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg=="
|
||||
},
|
||||
"node_modules/@sideway/pinpoint": {
|
||||
"version": "2.0.0",
|
||||
@@ -16293,9 +16293,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-gesture-handler": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.8.0.tgz",
|
||||
"integrity": "sha512-poOSfz/w0IyD6Qwq7aaIRRfEaVTl1ecQFoyiIbpOpfNTjm2B1niY2FLrdVQIOtIOe+K9nH55Qal04nr4jGkHdQ==",
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.9.0.tgz",
|
||||
"integrity": "sha512-a0BcH3Qb1tgVqUutc6d3VuWQkI1AM3+fJx8dkxzZs9t06qA27QgURYFoklpabuWpsUTzuKRpxleykp25E8m7tg==",
|
||||
"dependencies": {
|
||||
"@egjs/hammerjs": "^2.0.17",
|
||||
"hoist-non-react-statics": "^3.3.0",
|
||||
@@ -24599,9 +24599,9 @@
|
||||
}
|
||||
},
|
||||
"@sideway/formula": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz",
|
||||
"integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg=="
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz",
|
||||
"integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg=="
|
||||
},
|
||||
"@sideway/pinpoint": {
|
||||
"version": "2.0.0",
|
||||
@@ -32719,9 +32719,9 @@
|
||||
"integrity": "sha512-MKbuBbovO8eGiAM9i6o0nrdBXivhRpzPQ+aVBXGJEPMH7RrCSNUKaCoEpkjfGHlTxjZimi6WjDCjjzCRSHlV1A=="
|
||||
},
|
||||
"react-native-gesture-handler": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.8.0.tgz",
|
||||
"integrity": "sha512-poOSfz/w0IyD6Qwq7aaIRRfEaVTl1ecQFoyiIbpOpfNTjm2B1niY2FLrdVQIOtIOe+K9nH55Qal04nr4jGkHdQ==",
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.9.0.tgz",
|
||||
"integrity": "sha512-a0BcH3Qb1tgVqUutc6d3VuWQkI1AM3+fJx8dkxzZs9t06qA27QgURYFoklpabuWpsUTzuKRpxleykp25E8m7tg==",
|
||||
"requires": {
|
||||
"@egjs/hammerjs": "^2.0.17",
|
||||
"hoist-non-react-statics": "^3.3.0",
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
"react-native-default-preference": "1.4.4",
|
||||
"react-native-device-info": "8.4.8",
|
||||
"react-native-dialog": "https://github.com/jitsi/react-native-dialog/releases/download/v9.2.2-jitsi.1/react-native-dialog-9.2.2.tgz",
|
||||
"react-native-gesture-handler": "2.8.0",
|
||||
"react-native-gesture-handler": "2.9.0",
|
||||
"react-native-get-random-values": "1.7.2",
|
||||
"react-native-immersive": "2.0.0",
|
||||
"react-native-keep-awake": "4.0.0",
|
||||
|
||||
@@ -206,6 +206,8 @@ export interface IConfig {
|
||||
};
|
||||
};
|
||||
corsAvatarURLs?: Array<string>;
|
||||
customParticipantMenuButtons?: Array<{ icon: string; id: string; text: string; }>;
|
||||
customToolbarButtons?: Array<{ icon: string; id: string; text: string; }>;
|
||||
deeplinking?: IDeeplinkingConfig;
|
||||
defaultLanguage?: string;
|
||||
defaultLocalDisplayName?: string;
|
||||
@@ -394,6 +396,10 @@ export interface IConfig {
|
||||
validatorRegExpString?: string;
|
||||
};
|
||||
liveStreamingEnabled?: boolean;
|
||||
lobby?: {
|
||||
autoKnock?: boolean;
|
||||
enableChat?: boolean;
|
||||
};
|
||||
localRecording?: {
|
||||
disable?: boolean;
|
||||
disableSelfRecording?: boolean;
|
||||
@@ -464,6 +470,10 @@ export interface IConfig {
|
||||
enabled?: boolean;
|
||||
mode?: 'always' | 'recording';
|
||||
};
|
||||
securityUi?: {
|
||||
disableLobbyPassword?: boolean;
|
||||
hideLobbyButton?: boolean;
|
||||
};
|
||||
serviceUrl?: string;
|
||||
sipInviteUrl?: string;
|
||||
speakerStats?: {
|
||||
|
||||
@@ -185,6 +185,7 @@ export default [
|
||||
'inviteAppName',
|
||||
'liveStreaming',
|
||||
'liveStreamingEnabled',
|
||||
'lobby',
|
||||
'localRecording',
|
||||
'localSubject',
|
||||
'logging',
|
||||
@@ -209,6 +210,7 @@ export default [
|
||||
'resolution',
|
||||
'salesforceUrl',
|
||||
'screenshotCapture',
|
||||
'securityUi',
|
||||
'speakerStats',
|
||||
'startAudioMuted',
|
||||
'startAudioOnly',
|
||||
|
||||
@@ -316,3 +316,13 @@ export function getDialOutStatusUrl(state: IReduxState) {
|
||||
export function getDialOutUrl(state: IReduxState) {
|
||||
return state['features/base/config'].guestDialOutUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector to return the security UI config.
|
||||
*
|
||||
* @param {IReduxState} state - State object.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function getSecurityUiConfig(state: IReduxState) {
|
||||
return state['features/base/config']?.securityUi || {};
|
||||
}
|
||||
|
||||
@@ -32,9 +32,16 @@ export function getReplaceParticipant(state: IReduxState): string | undefined {
|
||||
* @returns {Array<string>} - The list of enabled toolbar buttons.
|
||||
*/
|
||||
export function getToolbarButtons(state: IReduxState): Array<string> {
|
||||
const { toolbarButtons } = state['features/base/config'];
|
||||
const { toolbarButtons, customToolbarButtons } = state['features/base/config'];
|
||||
const customButtons = customToolbarButtons?.map(({ id }) => id);
|
||||
|
||||
return Array.isArray(toolbarButtons) ? toolbarButtons : TOOLBAR_BUTTONS;
|
||||
const buttons = Array.isArray(toolbarButtons) ? toolbarButtons : TOOLBAR_BUTTONS;
|
||||
|
||||
if (customButtons) {
|
||||
buttons.push(...customButtons);
|
||||
}
|
||||
|
||||
return buttons;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,3 +108,30 @@ export function _setDeeplinkingDefaults(deeplinking: IDeeplinkingConfig) {
|
||||
android.dynamicLink.isi = android.dynamicLink.isi || '1165103905';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of buttons that have that notify the api when clicked.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {Array} - The list of buttons.
|
||||
*/
|
||||
export function getButtonsWithNotifyClick(state: IReduxState): Array<{ key: string; preventExecution: boolean; }> {
|
||||
const { buttonsWithNotifyClick, customToolbarButtons } = state['features/base/config'];
|
||||
const customButtons = customToolbarButtons?.map(({ id }) => {
|
||||
return {
|
||||
key: id,
|
||||
preventExecution: false
|
||||
};
|
||||
});
|
||||
|
||||
const buttons = Array.isArray(buttonsWithNotifyClick)
|
||||
? buttonsWithNotifyClick as Array<{ key: string; preventExecution: boolean; }>
|
||||
: [];
|
||||
|
||||
if (customButtons) {
|
||||
buttons.push(...customButtons);
|
||||
}
|
||||
|
||||
return buttons;
|
||||
}
|
||||
|
||||
|
||||
@@ -535,6 +535,30 @@ function _translateLegacyConfig(oldValue: IConfig) {
|
||||
};
|
||||
}
|
||||
|
||||
if (oldValue.autoKnockLobby !== undefined
|
||||
&& newValue.lobby?.autoKnock === undefined) {
|
||||
newValue.lobby = {
|
||||
...newValue.lobby || {},
|
||||
autoKnock: oldValue.autoKnockLobby
|
||||
};
|
||||
}
|
||||
|
||||
if (oldValue.enableLobbyChat !== undefined
|
||||
&& newValue.lobby?.enableChat === undefined) {
|
||||
newValue.lobby = {
|
||||
...newValue.lobby || {},
|
||||
enableChat: oldValue.enableLobbyChat
|
||||
};
|
||||
}
|
||||
|
||||
if (oldValue.hideLobbyButton !== undefined
|
||||
&& newValue.securityUi?.hideLobbyButton === undefined) {
|
||||
newValue.securityUi = {
|
||||
...newValue.securityUi || {},
|
||||
hideLobbyButton: oldValue.hideLobbyButton
|
||||
};
|
||||
}
|
||||
|
||||
_setDeeplinkingDefaults(newValue.deeplinking as IDeeplinkingConfig);
|
||||
|
||||
return newValue;
|
||||
|
||||
@@ -21,6 +21,7 @@ import StartRecordingDialog from '../../recording/components/Recording/web/Start
|
||||
import StopRecordingDialog from '../../recording/components/Recording/web/StopRecordingDialog';
|
||||
// @ts-ignore
|
||||
import RemoteControlAuthorizationDialog from '../../remote-control/components/RemoteControlAuthorizationDialog';
|
||||
import PasswordRequiredPrompt from '../../room-lock/components/PasswordRequiredPrompt.web';
|
||||
import SalesforceLinkDialog from '../../salesforce/components/web/SalesforceLinkDialog';
|
||||
import ShareAudioDialog from '../../screen-share/components/web/ShareAudioDialog';
|
||||
import ShareScreenWarningDialog from '../../screen-share/components/web/ShareScreenWarningDialog';
|
||||
@@ -50,7 +51,7 @@ const NEW_DIALOG_LIST = [ KeyboardShortcutsDialog, ChatPrivacyDialog, DisplayNam
|
||||
SharedVideoDialog, SpeakerStats, LanguageSelectorDialog, MuteEveryoneDialog, MuteEveryonesVideoDialog,
|
||||
GrantModeratorDialog, KickRemoteParticipantDialog, MuteRemoteParticipantsVideoDialog, VideoQualityDialog,
|
||||
VirtualBackgroundDialog, LoginDialog, WaitForOwnerDialog, DesktopPicker, RemoteControlAuthorizationDialog,
|
||||
LogoutDialog, SalesforceLinkDialog, ParticipantVerificationDialog ];
|
||||
LogoutDialog, SalesforceLinkDialog, ParticipantVerificationDialog, PasswordRequiredPrompt ];
|
||||
|
||||
// This function is necessary while the transition from @atlaskit dialog to our component is ongoing.
|
||||
const isNewDialog = (component: any) => NEW_DIALOG_LIST.some(comp => comp === component);
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.5 6.75C1.5 6.33579 1.83579 6 2.25 6H21.75C22.1642 6 22.5 6.33579 22.5 6.75C22.5 7.16421 22.1642 7.5 21.75 7.5H2.25C1.83579 7.5 1.5 7.16421 1.5 6.75Z" />
|
||||
<path d="M1.5 17.25C1.5 16.8358 1.83579 16.5 2.25 16.5H21.75C22.1642 16.5 22.5 16.8358 22.5 17.25C22.5 17.6642 22.1642 18 21.75 18H2.25C1.83579 18 1.5 17.6642 1.5 17.25Z" />
|
||||
<path d="M2.25 11.25C1.83579 11.25 1.5 11.5858 1.5 12C1.5 12.4142 1.83579 12.75 2.25 12.75H21.75C22.1642 12.75 22.5 12.4142 22.5 12C22.5 11.5858 22.1642 11.25 21.75 11.25H2.25Z" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 623 B |
@@ -1,6 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.1602 8.24439C13.3281 8.16225 14.25 7.18879 14.25 6C14.25 4.75736 13.2426 3.75 12 3.75C10.7574 3.75 9.75 4.75736 9.75 6C9.75 7.18151 10.6607 8.15032 11.8184 8.24278C11.5197 8.21896 11.2375 8.13687 10.9831 8.00775C9.85816 9.83693 8.19346 10.3318 6.74461 10.3424C6.66364 9.17333 5.68961 8.25 4.5 8.25C3.25736 8.25 2.25 9.25736 2.25 10.5C2.25 11.7426 3.25736 12.75 4.5 12.75C5.32135 12.75 6.03995 12.31 6.43279 11.6527C6.39557 11.715 6.35542 11.7754 6.31253 11.8336C6.67901 11.8506 7.06503 11.8447 7.4618 11.805C8.64456 11.6868 9.95784 11.2623 11.0918 10.2228C11.4736 9.87283 11.8205 9.46641 12.1289 9.0006C12.4727 9.47803 12.8468 9.89069 13.2474 10.2432C14.3929 11.2513 15.6592 11.6829 16.8118 11.8042C17.1152 11.8362 17.4101 11.8467 17.6932 11.8412C17.6739 11.8152 17.6551 11.7887 17.6369 11.7619C18.0415 12.3582 18.725 12.75 19.5 12.75C20.7426 12.75 21.75 11.7426 21.75 10.5C21.75 9.25736 20.7426 8.25 19.5 8.25C18.3129 8.25 17.3405 9.16938 17.256 10.335C15.9245 10.2658 14.3912 9.7053 13.1957 7.90649C12.8918 8.09752 12.5389 8.21778 12.1602 8.24439ZM12 6.75C12.4142 6.75 12.75 6.41421 12.75 6C12.75 5.58579 12.4142 5.25 12 5.25C11.5858 5.25 11.25 5.58579 11.25 6C11.25 6.41421 11.5858 6.75 12 6.75ZM20.25 10.5C20.25 10.9142 19.9142 11.25 19.5 11.25C19.0858 11.25 18.75 10.9142 18.75 10.5C18.75 10.0858 19.0858 9.75 19.5 9.75C19.9142 9.75 20.25 10.0858 20.25 10.5ZM4.5 11.25C4.91421 11.25 5.25 10.9142 5.25 10.5C5.25 10.0858 4.91421 9.75 4.5 9.75C4.08579 9.75 3.75 10.0858 3.75 10.5C3.75 10.9142 4.08579 11.25 4.5 11.25Z" />
|
||||
<path d="M17.9485 12.1296C18.3135 12.4773 18.7952 12.7036 19.3289 12.7437L19.2591 12.9706C19.1623 13.2853 18.8715 13.5 18.5423 13.5C18.0377 13.5 17.677 13.0117 17.8254 12.5294L17.9485 12.1296Z" />
|
||||
<path d="M12.75 18.0001C13.1642 18.0001 13.5 18.3359 13.5 18.7501C13.5 19.1643 13.1642 19.5001 12.75 19.5001H7.85792C7.19941 19.5001 6.61791 19.0706 6.42425 18.4412L4.6712 12.7437C5.20487 12.7036 5.6866 12.4773 6.05164 12.1296L7.85792 18.0001H12.75Z" />
|
||||
<path d="M11.25 18.0002C10.8358 18.0002 10.5 18.336 10.5 18.7502C10.5 19.1644 10.8358 19.5002 11.25 19.5002H16.1421C16.8006 19.5002 17.3821 19.0707 17.5757 18.4413L19.3289 12.7437C18.7952 12.7036 18.3135 12.4773 17.9485 12.1296L16.1421 18.0002H11.25Z" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.3 KiB |
@@ -20,7 +20,6 @@ export { default as IconCode } from './code.svg';
|
||||
export { default as IconConnection } from './connection.svg';
|
||||
export { default as IconConnectionInactive } from './ninja.svg';
|
||||
export { default as IconCopy } from './copy.svg';
|
||||
export { default as IconCrown } from './host.svg';
|
||||
export { default as IconDeviceHeadphone } from './headset.svg';
|
||||
export { default as IconDotsHorizontal } from './dots-horizontal.svg';
|
||||
export { default as IconDownload } from './download.svg';
|
||||
@@ -47,11 +46,11 @@ export { default as IconHelp } from './help.svg';
|
||||
export { default as IconHighlight } from './highlight.svg';
|
||||
export { default as IconImage } from './image.svg';
|
||||
export { default as IconInfoCircle } from './info-circle.svg';
|
||||
export { default as IconBurger } from './burger.svg';
|
||||
export { default as IconMessage } from './message.svg';
|
||||
export { default as IconMeter } from './meter.svg';
|
||||
export { default as IconMic } from './mic.svg';
|
||||
export { default as IconMicSlash } from './mic-slash.svg';
|
||||
export { default as IconModerator } from './moderator.svg';
|
||||
export { default as IconNoiseSuppressionOff } from './noise-suppression-off.svg';
|
||||
export { default as IconNoiseSuppressionOn } from './noise-suppression-on.svg';
|
||||
export { default as IconArrowRight } from './arrow-right.svg';
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<svg width="38" height="12" viewBox="0 0 38 12" fill="#5E6D7A" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="3" height="12" rx="1"/>
|
||||
<rect x="5" width="3" height="12" rx="1" />
|
||||
<rect x="10" width="3" height="12" rx="1" />
|
||||
<rect x="15" width="3" height="12" rx="1" />
|
||||
<rect x="20" width="3" height="12" rx="1" />
|
||||
<rect x="25" width="3" height="12" rx="1" />
|
||||
<rect x="30" width="3" height="12" rx="1" />
|
||||
<rect x="35" width="3" height="12" rx="1" />
|
||||
<svg width="38" height="12" viewBox="0 0 38 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="3" height="12" rx="1" />
|
||||
<rect x="5" width="3" height="12" rx="1" />
|
||||
<rect x="10" width="3" height="12" rx="1" />
|
||||
<rect x="15" width="3" height="12" rx="1" />
|
||||
<rect x="20" width="3" height="12" rx="1" />
|
||||
<rect x="25" width="3" height="12" rx="1" />
|
||||
<rect x="30" width="3" height="12" rx="1" />
|
||||
<rect x="35" width="3" height="12" rx="1" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 457 B After Width: | Height: | Size: 487 B |
3
react/features/base/icons/svg/moderator.svg
Normal file
3
react/features/base/icons/svg/moderator.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill-rule="evenodd" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.5 3H19.5C20.3284 3 21 3.67157 21 4.5V19.5C21 20.3284 20.3284 21 19.5 21H4.5C3.67157 21 3 20.3284 3 19.5V4.5C3 3.67157 3.67157 3 4.5 3ZM1.5 4.5C1.5 2.84315 2.84315 1.5 4.5 1.5H19.5C21.1569 1.5 22.5 2.84315 22.5 4.5V19.5C22.5 21.1569 21.1569 22.5 19.5 22.5H4.5C2.84315 22.5 1.5 21.1569 1.5 19.5V4.5ZM8.71837 7H6.30005V17.9091H8.19636V10.3984H8.29756L11.3125 17.8771H12.7294L15.7443 10.4144H15.8455V17.9091H17.7418V7H15.3235L12.0849 14.9048H11.957L8.71837 7Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 622 B |
@@ -52,7 +52,7 @@ const DEFAULT_STATE: ISettingsState = {
|
||||
};
|
||||
|
||||
export interface ISettingsState {
|
||||
audioOutputDeviceId?: string | boolean;
|
||||
audioOutputDeviceId?: string;
|
||||
audioSettingsVisible?: boolean;
|
||||
avatarURL?: string;
|
||||
cameraDeviceId?: string | boolean;
|
||||
@@ -108,6 +108,7 @@ Object.keys(DEFAULT_STATE).forEach(key => {
|
||||
|
||||
// we want to filter these props, to not be stored as they represent
|
||||
// what is currently opened/used as devices
|
||||
// @ts-ignore
|
||||
filterSubtree.audioOutputDeviceId = false;
|
||||
filterSubtree.cameraDeviceId = false;
|
||||
filterSubtree.micDeviceId = false;
|
||||
|
||||
@@ -34,6 +34,8 @@ const getComputedOuterHeight = (element: HTMLElement) => {
|
||||
|
||||
interface IProps {
|
||||
|
||||
[key: `aria-${string}`]: string;
|
||||
|
||||
/**
|
||||
* Accessibility label for menu container.
|
||||
*/
|
||||
@@ -59,6 +61,11 @@ interface IProps {
|
||||
*/
|
||||
hidden?: boolean;
|
||||
|
||||
/**
|
||||
* Optional id.
|
||||
*/
|
||||
id?: string;
|
||||
|
||||
/**
|
||||
* Whether or not the menu is already in a drawer.
|
||||
*/
|
||||
@@ -98,6 +105,11 @@ interface IProps {
|
||||
* Callback for the mouse leaving the component.
|
||||
*/
|
||||
onMouseLeave?: (e?: React.MouseEvent) => void;
|
||||
|
||||
/**
|
||||
* Tab index for the menu.
|
||||
*/
|
||||
tabIndex?: number;
|
||||
}
|
||||
|
||||
const MAX_HEIGHT = 400;
|
||||
@@ -108,7 +120,7 @@ const useStyles = makeStyles()(theme => {
|
||||
backgroundColor: theme.palette.ui01,
|
||||
border: `1px solid ${theme.palette.ui04}`,
|
||||
borderRadius: `${Number(theme.shape.borderRadius)}px`,
|
||||
boxShadow: '0px 4px 25px 4px rgba(20, 20, 20, 0.6)',
|
||||
boxShadow: '0px 1px 2px rgba(41, 41, 41, 0.25)',
|
||||
color: theme.palette.text01,
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegular),
|
||||
marginTop: `${(participantsPaneTheme.panePadding * 2) + theme.typography.bodyShortRegular.fontSize}px`,
|
||||
@@ -146,6 +158,7 @@ const ContextMenu = ({
|
||||
className,
|
||||
entity,
|
||||
hidden,
|
||||
id,
|
||||
inDrawer,
|
||||
isDrawerOpen,
|
||||
offsetTarget,
|
||||
@@ -153,7 +166,8 @@ const ContextMenu = ({
|
||||
onKeyDown,
|
||||
onDrawerClose,
|
||||
onMouseEnter,
|
||||
onMouseLeave
|
||||
onMouseLeave,
|
||||
tabIndex
|
||||
}: IProps) => {
|
||||
const [ isHidden, setIsHidden ] = useState(true);
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
@@ -217,11 +231,14 @@ const ContextMenu = ({
|
||||
isHidden && styles.contextMenuHidden,
|
||||
className
|
||||
) }
|
||||
id = { id }
|
||||
onClick = { onClick }
|
||||
onKeyDown = { onKeyDown }
|
||||
onMouseEnter = { onMouseEnter }
|
||||
onMouseLeave = { onMouseLeave }
|
||||
ref = { containerRef }>
|
||||
ref = { containerRef }
|
||||
role = 'menu'
|
||||
tabIndex = { tabIndex }>
|
||||
{children}
|
||||
</div>;
|
||||
};
|
||||
|
||||
@@ -13,6 +13,11 @@ export interface IProps {
|
||||
*/
|
||||
accessibilityLabel: string;
|
||||
|
||||
/**
|
||||
* Component children.
|
||||
*/
|
||||
children?: ReactNode;
|
||||
|
||||
/**
|
||||
* CSS class name used for custom styles.
|
||||
*/
|
||||
@@ -54,6 +59,11 @@ export interface IProps {
|
||||
*/
|
||||
onKeyPress?: (e?: React.KeyboardEvent) => void;
|
||||
|
||||
/**
|
||||
* Whether the item is marked as selected.
|
||||
*/
|
||||
selected?: boolean;
|
||||
|
||||
/**
|
||||
* TestId of the element, if any.
|
||||
*/
|
||||
@@ -62,7 +72,7 @@ export interface IProps {
|
||||
/**
|
||||
* Action text.
|
||||
*/
|
||||
text: string;
|
||||
text?: string;
|
||||
|
||||
/**
|
||||
* Class name for the text.
|
||||
@@ -90,9 +100,19 @@ const useStyles = makeStyles()(theme => {
|
||||
|
||||
'&:active': {
|
||||
backgroundColor: theme.palette.ui03
|
||||
},
|
||||
|
||||
'&:focus': {
|
||||
boxShadow: `inset 0 0 0 2px ${theme.palette.action01Hover}`
|
||||
}
|
||||
},
|
||||
|
||||
selected: {
|
||||
borderLeft: `3px solid ${theme.palette.action01Hover}`,
|
||||
paddingLeft: '13px',
|
||||
backgroundColor: theme.palette.ui02
|
||||
},
|
||||
|
||||
contextMenuItemDisabled: {
|
||||
pointerEvents: 'none'
|
||||
},
|
||||
@@ -120,6 +140,7 @@ const useStyles = makeStyles()(theme => {
|
||||
|
||||
const ContextMenuItem = ({
|
||||
accessibilityLabel,
|
||||
children,
|
||||
className,
|
||||
customIcon,
|
||||
disabled,
|
||||
@@ -128,6 +149,7 @@ const ContextMenuItem = ({
|
||||
onClick,
|
||||
onKeyDown,
|
||||
onKeyPress,
|
||||
selected,
|
||||
testId,
|
||||
text,
|
||||
textClassName }: IProps) => {
|
||||
@@ -141,6 +163,7 @@ const ContextMenuItem = ({
|
||||
className = { cx(styles.contextMenuItem,
|
||||
_overflowDrawer && styles.contextMenuItemDrawer,
|
||||
disabled && styles.contextMenuItemDisabled,
|
||||
selected && styles.selected,
|
||||
className
|
||||
) }
|
||||
data-testid = { testId }
|
||||
@@ -148,13 +171,15 @@ const ContextMenuItem = ({
|
||||
key = { text }
|
||||
onClick = { disabled ? undefined : onClick }
|
||||
onKeyDown = { disabled ? undefined : onKeyDown }
|
||||
onKeyPress = { disabled ? undefined : onKeyPress }>
|
||||
onKeyPress = { disabled ? undefined : onKeyPress }
|
||||
role = 'menuitem'>
|
||||
{customIcon ? customIcon
|
||||
: icon && <Icon
|
||||
className = { styles.contextMenuItemIcon }
|
||||
size = { 20 }
|
||||
src = { icon } />}
|
||||
<span className = { cx(textClassName) }>{text}</span>
|
||||
{text && <span className = { cx(styles.text, textClassName) }>{text}</span>}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -25,7 +25,7 @@ const useStyles = makeStyles()(theme => {
|
||||
},
|
||||
|
||||
'& + &:not(:empty)': {
|
||||
borderTop: `1px solid ${theme.palette.ui04}`
|
||||
borderTop: `1px solid ${theme.palette.ui03}`
|
||||
},
|
||||
|
||||
'&:first-of-type': {
|
||||
|
||||
@@ -45,6 +45,7 @@ const useStyles = makeStyles()(theme => {
|
||||
avatar: {
|
||||
margin: `${theme.spacing(1)} ${theme.spacing(2)} ${theme.spacing(3)} 0`,
|
||||
position: 'sticky',
|
||||
flexShrink: 0,
|
||||
top: 0
|
||||
}
|
||||
};
|
||||
|
||||
@@ -147,7 +147,7 @@ const styles = (theme: Theme) => {
|
||||
},
|
||||
|
||||
icon: {
|
||||
padding: '6px',
|
||||
padding: '4px',
|
||||
borderRadius: '4px',
|
||||
|
||||
'&.status-high': {
|
||||
|
||||
@@ -103,7 +103,7 @@ export const ConnectionIndicatorIcon = ({
|
||||
<span className = { emptyIconWrapperClassName }>
|
||||
<Icon
|
||||
className = { clsx(classes.icon, colorClass) }
|
||||
size = { 12 }
|
||||
size = { 16 }
|
||||
src = { IconConnection } />
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { IconCrown } from '../../../base/icons';
|
||||
import { BaseIndicator } from '../../../base/react';
|
||||
|
||||
/**
|
||||
* Thumbnail badge showing that the participant is a conference moderator.
|
||||
*/
|
||||
export default class ModeratorIndicator extends Component<{}> {
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<BaseIndicator icon = { IconCrown } />
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
|
||||
import React from 'react';
|
||||
|
||||
// @ts-ignore
|
||||
import { IconModerator } from '../../../base/icons';
|
||||
// @ts-ignore
|
||||
import { BaseIndicator } from '../../../base/react';
|
||||
|
||||
/**
|
||||
* Thumbnail badge showing that the participant is a conference moderator.
|
||||
*
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
const ModeratorIndicator = (): JSX.Element => <BaseIndicator icon = { IconModerator } />;
|
||||
|
||||
export default ModeratorIndicator;
|
||||
@@ -853,6 +853,7 @@ class Filmstrip extends PureComponent <IProps, IState> {
|
||||
{ ...actions }>
|
||||
<Icon
|
||||
aria-label = { t('toolbar.accessibilityLabel.toggleFilmstrip') }
|
||||
size = { 24 }
|
||||
src = { icon } />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
/* @flow */
|
||||
/* eslint-disable lines-around-comment */
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { IconCrown } from '../../../base/icons';
|
||||
// @ts-ignore
|
||||
import { IconModerator } from '../../../base/icons';
|
||||
// @ts-ignore
|
||||
import { BaseIndicator } from '../../../base/react';
|
||||
|
||||
/**
|
||||
@@ -13,17 +15,17 @@ type Props = {
|
||||
/**
|
||||
* From which side of the indicator the tooltip should appear from.
|
||||
*/
|
||||
tooltipPosition: string
|
||||
tooltipPosition: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* React {@code Component} for showing a moderator icon with a tooltip.
|
||||
*
|
||||
* @returns {Component}
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
const ModeratorIndicator = ({ tooltipPosition }: Props) => (
|
||||
const ModeratorIndicator = ({ tooltipPosition }: Props): JSX.Element => (
|
||||
<BaseIndicator
|
||||
icon = { IconCrown }
|
||||
icon = { IconModerator }
|
||||
iconSize = { 16 }
|
||||
tooltipKey = 'videothumbnail.moderator'
|
||||
tooltipPosition = { tooltipPosition } />
|
||||
@@ -337,7 +337,7 @@ const defaultStyles = (theme: Theme) => {
|
||||
|
||||
activeSpeaker: {
|
||||
'& .active-speaker-indicator': {
|
||||
boxShadow: `inset 0px 0px 0px 4px ${theme.palette.link01Active} !important`
|
||||
boxShadow: `inset 0px 0px 0px 3px ${theme.palette.action01Hover} !important`
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ export const styles = (theme: Theme) => {
|
||||
height: '24px',
|
||||
position: 'absolute' as const,
|
||||
borderRadius: '4px',
|
||||
top: 'calc(-24px - 3px)',
|
||||
top: 'calc(-24px - 2px)',
|
||||
left: 'calc(50% - 16px)',
|
||||
opacity: 0,
|
||||
transition: 'opacity .3s',
|
||||
@@ -51,7 +51,7 @@ export const styles = (theme: Theme) => {
|
||||
|
||||
toggleVerticalFilmstripContainer: {
|
||||
transform: 'rotate(-90deg)',
|
||||
left: 'calc(-24px - 3px - 4px)',
|
||||
left: 'calc(-24px - 2px - 4px)',
|
||||
top: 'calc(50% - 12px)'
|
||||
},
|
||||
|
||||
|
||||
@@ -35,6 +35,11 @@ export type Props = {
|
||||
*/
|
||||
_dialOutAuthUrl: string,
|
||||
|
||||
/**
|
||||
* The URL for validating if an outbound destination is allowed.
|
||||
*/
|
||||
_dialOutRegionUrl: string;
|
||||
|
||||
/**
|
||||
* Whether or not to show Dial Out functionality.
|
||||
*/
|
||||
@@ -235,7 +240,9 @@ export default class AbstractAddPeopleDialog<P: Props, S: State>
|
||||
_query(query = '') {
|
||||
const {
|
||||
_addPeopleEnabled: addPeopleEnabled,
|
||||
_appId: appId,
|
||||
_dialOutAuthUrl: dialOutAuthUrl,
|
||||
_dialOutRegionUrl: dialOutRegionUrl,
|
||||
_dialOutEnabled: dialOutEnabled,
|
||||
_jwt: jwt,
|
||||
_peopleSearchQueryTypes: peopleSearchQueryTypes,
|
||||
@@ -244,8 +251,10 @@ export default class AbstractAddPeopleDialog<P: Props, S: State>
|
||||
} = this.props;
|
||||
const options = {
|
||||
addPeopleEnabled,
|
||||
appId,
|
||||
dialOutAuthUrl,
|
||||
dialOutEnabled,
|
||||
dialOutRegionUrl,
|
||||
jwt,
|
||||
peopleSearchQueryTypes,
|
||||
peopleSearchUrl,
|
||||
@@ -275,14 +284,17 @@ export function _mapStateToProps(state: Object) {
|
||||
const {
|
||||
callFlowsEnabled,
|
||||
dialOutAuthUrl,
|
||||
dialOutRegionUrl,
|
||||
peopleSearchQueryTypes,
|
||||
peopleSearchUrl
|
||||
} = state['features/base/config'];
|
||||
|
||||
return {
|
||||
_addPeopleEnabled: isAddPeopleEnabled(state),
|
||||
_appId: state['features/base/jwt']?.tenant,
|
||||
_callFlowsEnabled: callFlowsEnabled,
|
||||
_dialOutAuthUrl: dialOutAuthUrl,
|
||||
_dialOutRegionUrl: dialOutRegionUrl,
|
||||
_dialOutEnabled: isDialOutEnabled(state),
|
||||
_jwt: state['features/base/jwt'].jwt,
|
||||
_peopleSearchQueryTypes: peopleSearchQueryTypes,
|
||||
|
||||
@@ -8,6 +8,7 @@ import { isJwtFeatureEnabled } from '../base/jwt/functions';
|
||||
import { JitsiRecordingConstants } from '../base/lib-jitsi-meet';
|
||||
import { getLocalParticipant, isLocalParticipantModerator } from '../base/participants/functions';
|
||||
import { toState } from '../base/redux/functions';
|
||||
import { doGetJSON } from '../base/util/httpUtils';
|
||||
import { parseURLParams } from '../base/util/parseURLParams';
|
||||
import {
|
||||
StatusCode,
|
||||
@@ -55,6 +56,34 @@ export function checkDialNumber(
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an ajax request to check if the outbound call is permitted.
|
||||
*
|
||||
* @param {string} dialOutRegionUrl - The config endpoint.
|
||||
* @param {string} jwt - The jwt token.
|
||||
* @param {string} appId - The customer id.
|
||||
* @param {string} phoneNumber - The destination phone number.
|
||||
* @returns {Promise} - The promise created by the request.
|
||||
*/
|
||||
export function checkOutboundDestination(
|
||||
dialOutRegionUrl: string,
|
||||
jwt: string,
|
||||
appId: string,
|
||||
phoneNumber: string
|
||||
): Promise<any> {
|
||||
return doGetJSON(dialOutRegionUrl, true, {
|
||||
body: JSON.stringify({
|
||||
appId,
|
||||
phoneNumber
|
||||
}),
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${jwt}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all non-numeric characters from a string.
|
||||
*
|
||||
@@ -76,6 +105,11 @@ export type GetInviteResultsOptions = {
|
||||
*/
|
||||
addPeopleEnabled: boolean;
|
||||
|
||||
/**
|
||||
* The customer id.
|
||||
*/
|
||||
appId: string;
|
||||
|
||||
/**
|
||||
* The endpoint to use for checking phone number validity.
|
||||
*/
|
||||
@@ -86,6 +120,11 @@ export type GetInviteResultsOptions = {
|
||||
*/
|
||||
dialOutEnabled: boolean;
|
||||
|
||||
/**
|
||||
* The endpoint to use for checking dial permission to an outbound destination.
|
||||
*/
|
||||
dialOutRegionUrl: string;
|
||||
|
||||
/**
|
||||
* The jwt token to pass to the search service.
|
||||
*/
|
||||
@@ -123,8 +162,10 @@ export function getInviteResultsForQuery(
|
||||
const text = query.trim();
|
||||
|
||||
const {
|
||||
dialOutAuthUrl,
|
||||
addPeopleEnabled,
|
||||
appId,
|
||||
dialOutAuthUrl,
|
||||
dialOutRegionUrl,
|
||||
dialOutEnabled,
|
||||
peopleSearchQueryTypes,
|
||||
peopleSearchUrl,
|
||||
@@ -187,7 +228,7 @@ export function getInviteResultsForQuery(
|
||||
}
|
||||
|
||||
return Promise.all([ peopleSearchPromise, phoneNumberPromise ])
|
||||
.then(([ peopleResults, phoneResults ]) => {
|
||||
.then(async ([ peopleResults, phoneResults ]) => {
|
||||
const results: any[] = [
|
||||
...peopleResults
|
||||
];
|
||||
@@ -203,14 +244,26 @@ export function getInviteResultsForQuery(
|
||||
= peopleResults.find(result => result.type === INVITE_TYPES.PHONE);
|
||||
|
||||
if (!hasPhoneResult && typeof phoneResults.allow === 'boolean') {
|
||||
results.push({
|
||||
const result = {
|
||||
allowed: phoneResults.allow,
|
||||
country: phoneResults.country,
|
||||
type: INVITE_TYPES.PHONE,
|
||||
number: phoneResults.phone,
|
||||
originalEntry: text,
|
||||
showCountryCodeReminder: !hasCountryCode
|
||||
});
|
||||
};
|
||||
|
||||
if (!phoneResults.allow) {
|
||||
try {
|
||||
const response = await checkOutboundDestination(dialOutRegionUrl, jwt, appId, text);
|
||||
|
||||
result.allowed = response.allowed;
|
||||
} catch (error) {
|
||||
logger.error('Error checking permission to dial to outbound destination', error);
|
||||
}
|
||||
}
|
||||
|
||||
results.push(result);
|
||||
}
|
||||
|
||||
if (sipInviteEnabled && isASipAddress(text)) {
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
SET_PASSWORD_JOIN_FAILED
|
||||
} from './actionTypes';
|
||||
import { LOBBY_CHAT_INITIALIZED, MODERATOR_IN_CHAT_WITH_LEFT } from './constants';
|
||||
import { getKnockingParticipants, getLobbyEnabled } from './functions';
|
||||
import { getKnockingParticipants, getLobbyConfig, getLobbyEnabled } from './functions';
|
||||
import { IKnockingParticipant } from './types';
|
||||
|
||||
/**
|
||||
@@ -389,9 +389,9 @@ export function setLobbyMessageListener() {
|
||||
return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const state = getState();
|
||||
const conference = getCurrentConference(state);
|
||||
const { enableLobbyChat = true } = state['features/base/config'];
|
||||
const { enableChat = true } = getLobbyConfig(state);
|
||||
|
||||
if (!enableLobbyChat) {
|
||||
if (!enableChat) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import { conferenceWillJoin, getConferenceName } from '../../base/conference';
|
||||
import { getSecurityUiConfig } from '../../base/config/functions.any';
|
||||
import { INVITE_ENABLED, getFeatureFlag } from '../../base/flags';
|
||||
import { getLocalParticipant } from '../../base/participants';
|
||||
import { getFieldValue } from '../../base/react';
|
||||
@@ -443,6 +444,7 @@ export function _mapStateToProps(state: Object): $Shape<Props> {
|
||||
const { disableInviteFunctions } = state['features/base/config'];
|
||||
const { knocking, passwordJoinFailed } = state['features/lobby'];
|
||||
const { iAmSipGateway } = state['features/base/config'];
|
||||
const { disableLobbyPassword } = getSecurityUiConfig(state);
|
||||
const showCopyUrlButton = inviteEnabledFlag || !disableInviteFunctions;
|
||||
const deviceStatusVisible = isDeviceStatusVisible(state);
|
||||
const { membersOnly } = state['features/base/conference'];
|
||||
@@ -460,7 +462,7 @@ export function _mapStateToProps(state: Object): $Shape<Props> {
|
||||
_participantId: participantId,
|
||||
_participantName: localParticipant?.name,
|
||||
_passwordJoinFailed: passwordJoinFailed,
|
||||
_renderPassword: !iAmSipGateway,
|
||||
_renderPassword: !iAmSipGateway && !disableLobbyPassword,
|
||||
showCopyUrlButton
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { PureComponent } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { getSecurityUiConfig } from '../../../base/config/functions.any';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { isLocalParticipantModerator } from '../../../base/participants/functions';
|
||||
import { connect } from '../../../base/redux/functions';
|
||||
@@ -83,25 +84,22 @@ class LobbySection extends PureComponent<IProps, IState> {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div id = 'lobby-section'>
|
||||
<p
|
||||
className = 'description'
|
||||
role = 'banner'>
|
||||
{ t('lobby.enableDialogText') }
|
||||
</p>
|
||||
<div className = 'control-row'>
|
||||
<label htmlFor = 'lobby-section-switch'>
|
||||
{ t('lobby.toggleLabel') }
|
||||
</label>
|
||||
<Switch
|
||||
checked = { this.state.lobbyEnabled }
|
||||
id = 'lobby-section-switch'
|
||||
onChange = { this._onToggleLobby } />
|
||||
</div>
|
||||
<div id = 'lobby-section'>
|
||||
<p
|
||||
className = 'description'
|
||||
role = 'banner'>
|
||||
{ t('lobby.enableDialogText') }
|
||||
</p>
|
||||
<div className = 'control-row'>
|
||||
<label htmlFor = 'lobby-section-switch'>
|
||||
{ t('lobby.toggleLabel') }
|
||||
</label>
|
||||
<Switch
|
||||
checked = { this.state.lobbyEnabled }
|
||||
id = 'lobby-section-switch'
|
||||
onChange = { this._onToggleLobby } />
|
||||
</div>
|
||||
<div className = 'separator-line' />
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -129,7 +127,7 @@ class LobbySection extends PureComponent<IProps, IState> {
|
||||
*/
|
||||
function mapStateToProps(state: IReduxState): Partial<IProps> {
|
||||
const { conference } = state['features/base/conference'];
|
||||
const { hideLobbyButton } = state['features/base/config'];
|
||||
const { hideLobbyButton } = getSecurityUiConfig(state);
|
||||
|
||||
return {
|
||||
_lobbyEnabled: state['features/lobby'].lobbyEnabled,
|
||||
|
||||
@@ -44,6 +44,15 @@ export function getKnockingParticipantsById(state: IReduxState) {
|
||||
return getKnockingParticipants(state).map(participant => participant.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector to return the lobby config.
|
||||
*
|
||||
* @param {IReduxState} state - State object.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function getLobbyConfig(state: IReduxState) {
|
||||
return state['features/base/config']?.lobby || {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that handles the visibility of the lobby chat message.
|
||||
@@ -56,13 +65,13 @@ export function showLobbyChatButton(
|
||||
) {
|
||||
return function(state: IReduxState) {
|
||||
|
||||
const { enableLobbyChat = true } = state['features/base/config'];
|
||||
const { enableChat = true } = getLobbyConfig(state);
|
||||
const { lobbyMessageRecipient, isLobbyChatActive } = state['features/chat'];
|
||||
const conference = getCurrentConference(state);
|
||||
|
||||
const lobbyLocalId = conference?.myLobbyUserId();
|
||||
|
||||
if (!enableLobbyChat) {
|
||||
if (!enableChat) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -124,7 +124,6 @@ const useStyles = (theme: Theme) => {
|
||||
*/
|
||||
class NotificationsContainer extends Component<IProps> {
|
||||
_api: Object;
|
||||
_timeouts: Map<string, number>;
|
||||
|
||||
/**
|
||||
* Initializes a new {@code NotificationsContainer} instance.
|
||||
@@ -134,8 +133,6 @@ class NotificationsContainer extends Component<IProps> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this._timeouts = new Map();
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onDismissed = this._onDismissed.bind(this);
|
||||
|
||||
@@ -186,13 +183,6 @@ class NotificationsContainer extends Component<IProps> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_onDismissed(uid: string) {
|
||||
const timeout = this._timeouts.get(`${uid}`);
|
||||
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
this._timeouts.delete(`${uid}`);
|
||||
}
|
||||
|
||||
this.props.dispatch(hideNotification(uid));
|
||||
}
|
||||
|
||||
|
||||
@@ -12,24 +12,9 @@ import ConnectionStatusComponent
|
||||
import RemoteVideoMenu from '../video-menu/components/native/RemoteVideoMenu';
|
||||
|
||||
import { SET_VOLUME } from './actionTypes';
|
||||
import {
|
||||
ContextMenuLobbyParticipantReject
|
||||
// @ts-ignore
|
||||
} from './components/native';
|
||||
import RoomParticipantMenu from './components/native/RoomParticipantMenu';
|
||||
|
||||
export * from './actions.any';
|
||||
/* eslint-enable lines-around-comment */
|
||||
|
||||
/**
|
||||
* Displays the context menu for the selected lobby participant.
|
||||
*
|
||||
* @param {Object} participant - The selected lobby participant.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function showContextMenuReject(participant: Object) {
|
||||
return openSheet(ContextMenuLobbyParticipantReject, { participant });
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Displays the connection status for the local meeting participant.
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
getParticipantCount,
|
||||
isEveryoneModerator
|
||||
} from '../../../base/participants/functions';
|
||||
import { withPixelLineHeight } from '../../../base/styles/functions.web';
|
||||
import ContextMenu from '../../../base/ui/components/web/ContextMenu';
|
||||
import ContextMenuItemGroup from '../../../base/ui/components/web/ContextMenuItemGroup';
|
||||
import { isInBreakoutRoom } from '../../../breakout-rooms/functions';
|
||||
@@ -45,6 +46,7 @@ const useStyles = makeStyles()(theme => {
|
||||
},
|
||||
|
||||
text: {
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegular),
|
||||
color: theme.palette.text02,
|
||||
padding: '10px 16px',
|
||||
height: '40px',
|
||||
|
||||
@@ -11,13 +11,16 @@ const useStyles = makeStyles()(theme => {
|
||||
backgroundColor: theme.palette.ui03,
|
||||
borderRadius: '100%',
|
||||
height: '16px',
|
||||
width: '16px',
|
||||
minWidth: '16px',
|
||||
color: theme.palette.text01,
|
||||
...withPixelLineHeight(theme.typography.labelBold),
|
||||
pointerEvents: 'none',
|
||||
position: 'absolute',
|
||||
right: '-4px',
|
||||
top: '-3px'
|
||||
top: '-3px',
|
||||
textAlign: 'center',
|
||||
boxSizing: 'border-box',
|
||||
paddingTop: '2px'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ import { IReduxState } from '../app/types';
|
||||
import { getRoomName } from '../base/conference/functions';
|
||||
import { getDialOutStatusUrl, getDialOutUrl } from '../base/config/functions';
|
||||
import { isAudioMuted, isVideoMutedByUser } from '../base/media/functions';
|
||||
import { getLobbyConfig } from '../lobby/functions';
|
||||
|
||||
/**
|
||||
* Selector for the visibility of the 'join by phone' button.
|
||||
@@ -159,11 +160,12 @@ export function isPrejoinPageVisible(state: IReduxState): boolean {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function shouldAutoKnock(state: IReduxState): boolean {
|
||||
const { iAmRecorder, iAmSipGateway, autoKnockLobby, prejoinConfig } = state['features/base/config'];
|
||||
const { iAmRecorder, iAmSipGateway, prejoinConfig } = state['features/base/config'];
|
||||
const { userSelectedSkipPrejoin } = state['features/base/settings'];
|
||||
const { autoKnock } = getLobbyConfig(state);
|
||||
const isPrejoinEnabled = prejoinConfig?.enabled;
|
||||
|
||||
return Boolean(((isPrejoinEnabled && !userSelectedSkipPrejoin)
|
||||
|| autoKnockLobby || (iAmRecorder && iAmSipGateway))
|
||||
|| autoKnock || (iAmRecorder && iAmSipGateway))
|
||||
&& !state['features/lobby'].knocking);
|
||||
}
|
||||
|
||||
@@ -67,7 +67,9 @@ export const KEYS = {
|
||||
BACKSLASH: '\\',
|
||||
MINUS: '-',
|
||||
EQUAL: '=',
|
||||
SLASH: '/'
|
||||
SLASH: '/',
|
||||
ASTERISK: '*',
|
||||
PLUS: '+'
|
||||
};
|
||||
|
||||
/* eslint-disable max-len */
|
||||
@@ -114,6 +116,11 @@ const keyCodeToKey = {
|
||||
103: KEYS.NUMPAD_7,
|
||||
104: KEYS.NUMPAD_8,
|
||||
105: KEYS.NUMPAD_9,
|
||||
106: KEYS.ASTERISK,
|
||||
107: KEYS.PLUS,
|
||||
109: KEYS.MINUS,
|
||||
110: KEYS.PERIOD,
|
||||
111: KEYS.SLASH,
|
||||
112: KEYS.F1,
|
||||
113: KEYS.F2,
|
||||
114: KEYS.F3,
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
// @flow
|
||||
import React, { Component } from 'react';
|
||||
import type { Dispatch } from 'redux';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { setPassword } from '../../base/conference';
|
||||
import { Dialog } from '../../base/dialog';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { connect } from '../../base/redux';
|
||||
import { IStore } from '../../app/types';
|
||||
import { setPassword } from '../../base/conference/actions';
|
||||
import { IJitsiConference } from '../../base/conference/reducer';
|
||||
import { translate } from '../../base/i18n/functions';
|
||||
import Dialog from '../../base/ui/components/web/Dialog';
|
||||
import Input from '../../base/ui/components/web/Input';
|
||||
import { _cancelPasswordRequiredPrompt } from '../actions';
|
||||
|
||||
@@ -13,23 +14,18 @@ import { _cancelPasswordRequiredPrompt } from '../actions';
|
||||
* The type of the React {@code Component} props of
|
||||
* {@link PasswordRequiredPrompt}.
|
||||
*/
|
||||
type Props = {
|
||||
interface IProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* The JitsiConference which requires a password.
|
||||
*/
|
||||
conference: Object,
|
||||
conference: IJitsiConference;
|
||||
|
||||
/**
|
||||
* The redux store's {@code dispatch} function.
|
||||
*/
|
||||
dispatch: Dispatch<any>,
|
||||
|
||||
/**
|
||||
* The translate function.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
dispatch: IStore['dispatch'];
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} state of
|
||||
@@ -40,14 +36,14 @@ type State = {
|
||||
/**
|
||||
* The password entered by the local participant.
|
||||
*/
|
||||
password: string
|
||||
}
|
||||
password?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a React Component which prompts the user when a password is
|
||||
* required to join a conference.
|
||||
*/
|
||||
class PasswordRequiredPrompt extends Component<Props, State> {
|
||||
class PasswordRequiredPrompt extends Component<IProps, State> {
|
||||
state = {
|
||||
password: ''
|
||||
};
|
||||
@@ -58,7 +54,7 @@ class PasswordRequiredPrompt extends Component<Props, State> {
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
@@ -76,12 +72,10 @@ class PasswordRequiredPrompt extends Component<Props, State> {
|
||||
render() {
|
||||
return (
|
||||
<Dialog
|
||||
disableBlanketClickDismiss = { true }
|
||||
isModal = { false }
|
||||
disableBackdropClose = { true }
|
||||
onCancel = { this._onCancel }
|
||||
onSubmit = { this._onSubmit }
|
||||
titleKey = 'dialog.passwordRequired'
|
||||
width = 'small'>
|
||||
titleKey = 'dialog.passwordRequired'>
|
||||
{ this._renderBody() }
|
||||
</Dialog>
|
||||
);
|
||||
@@ -98,6 +92,7 @@ class PasswordRequiredPrompt extends Component<Props, State> {
|
||||
<div>
|
||||
<Input
|
||||
autoFocus = { true }
|
||||
className = 'dialog-bottom-margin'
|
||||
label = { this.props.t('dialog.passwordLabel') }
|
||||
name = 'lockKey'
|
||||
onChange = { this._onPasswordChanged }
|
||||
@@ -107,8 +102,6 @@ class PasswordRequiredPrompt extends Component<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
_onPasswordChanged: ({ target: { value: * }}) => void;
|
||||
|
||||
/**
|
||||
* Notifies this dialog that password has changed.
|
||||
*
|
||||
@@ -122,8 +115,6 @@ class PasswordRequiredPrompt extends Component<Props, State> {
|
||||
});
|
||||
}
|
||||
|
||||
_onCancel: () => boolean;
|
||||
|
||||
/**
|
||||
* Dispatches action to cancel and dismiss this dialog.
|
||||
*
|
||||
@@ -138,8 +129,6 @@ class PasswordRequiredPrompt extends Component<Props, State> {
|
||||
return true;
|
||||
}
|
||||
|
||||
_onSubmit: () => boolean;
|
||||
|
||||
/**
|
||||
* Dispatches action to submit value from this dialog.
|
||||
*
|
||||
@@ -3,6 +3,7 @@
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { createToolbarEvent, sendAnalytics } from '../../../analytics';
|
||||
import { getSecurityUiConfig } from '../../../base/config/functions.any';
|
||||
import {
|
||||
LOBBY_MODE_ENABLED,
|
||||
MEETING_PASSWORD_ENABLED,
|
||||
@@ -81,7 +82,7 @@ export default class AbstractSecurityDialogButton<P: Props, S:*>
|
||||
*/
|
||||
export function _mapStateToProps(state: Object) {
|
||||
const { conference } = state['features/base/conference'];
|
||||
const { hideLobbyButton } = state['features/base/config'];
|
||||
const { hideLobbyButton } = getSecurityUiConfig(state);
|
||||
const { locked } = state['features/base/conference'];
|
||||
const { lobbyEnabled } = state['features/lobby'];
|
||||
const lobbySupported = conference && conference.isLobbySupported();
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
} from 'react-native';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { getSecurityUiConfig } from '../../../../base/config/functions.any';
|
||||
import { MEETING_PASSWORD_ENABLED, getFeatureFlag } from '../../../../base/flags';
|
||||
import { translate } from '../../../../base/i18n';
|
||||
import JitsiScreen from '../../../../base/modal/components/JitsiScreen';
|
||||
@@ -502,7 +503,7 @@ class SecurityDialog extends PureComponent<Props, State> {
|
||||
*/
|
||||
function _mapStateToProps(state: Object): Object {
|
||||
const { conference, locked, password } = state['features/base/conference'];
|
||||
const { hideLobbyButton } = state['features/base/config'];
|
||||
const { disableLobbyPassword, hideLobbyButton } = getSecurityUiConfig(state);
|
||||
const { lobbyEnabled } = state['features/lobby'];
|
||||
const { roomPasswordNumberOfDigits } = state['features/base/config'];
|
||||
const lobbySupported = conference && conference.isLobbySupported();
|
||||
@@ -518,7 +519,7 @@ function _mapStateToProps(state: Object): Object {
|
||||
_lockedConference: Boolean(conference && locked),
|
||||
_password: password,
|
||||
_passwordNumberOfDigits: roomPasswordNumberOfDigits,
|
||||
_roomPasswordControls: visible
|
||||
_roomPasswordControls: visible && !disableLobbyPassword
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { IReduxState } from '../../../../app/types';
|
||||
import { setPassword as setPass } from '../../../../base/conference/actions';
|
||||
import { getSecurityUiConfig } from '../../../../base/config/functions.any';
|
||||
import { isLocalParticipantModerator } from '../../../../base/participants/functions';
|
||||
import { connect } from '../../../../base/redux/functions';
|
||||
import Dialog from '../../../../base/ui/components/web/Dialog';
|
||||
@@ -37,6 +38,11 @@ interface IProps {
|
||||
*/
|
||||
_conference: Object;
|
||||
|
||||
/**
|
||||
* Whether to hide the lobby password section.
|
||||
*/
|
||||
_disableLobbyPassword?: boolean;
|
||||
|
||||
/**
|
||||
* The value for how the conference is locked (or undefined if not locked)
|
||||
* as defined by room-lock constants.
|
||||
@@ -73,6 +79,7 @@ function SecurityDialog({
|
||||
_buttonsWithNotifyClick,
|
||||
_canEditPassword,
|
||||
_conference,
|
||||
_disableLobbyPassword,
|
||||
_locked,
|
||||
_password,
|
||||
_passwordNumberOfDigits,
|
||||
@@ -94,16 +101,21 @@ function SecurityDialog({
|
||||
titleKey = 'security.title'>
|
||||
<div className = 'security-dialog'>
|
||||
<LobbySection />
|
||||
<PasswordSection
|
||||
buttonsWithNotifyClick = { _buttonsWithNotifyClick }
|
||||
canEditPassword = { _canEditPassword }
|
||||
conference = { _conference }
|
||||
locked = { _locked }
|
||||
password = { _password }
|
||||
passwordEditEnabled = { passwordEditEnabled }
|
||||
passwordNumberOfDigits = { _passwordNumberOfDigits }
|
||||
setPassword = { setPassword }
|
||||
setPasswordEditEnabled = { setPasswordEditEnabled } />
|
||||
{!_disableLobbyPassword && (
|
||||
<>
|
||||
<div className = 'separator-line' />
|
||||
<PasswordSection
|
||||
buttonsWithNotifyClick = { _buttonsWithNotifyClick }
|
||||
canEditPassword = { _canEditPassword }
|
||||
conference = { _conference }
|
||||
locked = { _locked }
|
||||
password = { _password }
|
||||
passwordEditEnabled = { passwordEditEnabled }
|
||||
passwordNumberOfDigits = { _passwordNumberOfDigits }
|
||||
setPassword = { setPassword }
|
||||
setPasswordEditEnabled = { setPasswordEditEnabled } />
|
||||
</>
|
||||
)}
|
||||
{
|
||||
_showE2ee ? <>
|
||||
<div className = 'separator-line' />
|
||||
@@ -131,7 +143,11 @@ function mapStateToProps(state: IReduxState) {
|
||||
locked,
|
||||
password
|
||||
} = state['features/base/conference'];
|
||||
const { roomPasswordNumberOfDigits, buttonsWithNotifyClick } = state['features/base/config'];
|
||||
const {
|
||||
roomPasswordNumberOfDigits,
|
||||
buttonsWithNotifyClick
|
||||
} = state['features/base/config'];
|
||||
const { disableLobbyPassword } = getSecurityUiConfig(state);
|
||||
|
||||
const showE2ee = Boolean(e2eeSupported) && isLocalParticipantModerator(state);
|
||||
|
||||
@@ -140,6 +156,7 @@ function mapStateToProps(state: IReduxState) {
|
||||
_canEditPassword: isLocalParticipantModerator(state),
|
||||
_conference: conference,
|
||||
_dialIn: state['features/invite'],
|
||||
_disableLobbyPassword: disableLobbyPassword,
|
||||
_locked: locked,
|
||||
_password: password,
|
||||
_passwordNumberOfDigits: roomPasswordNumberOfDigits,
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { translate } from '../../../../base/i18n';
|
||||
import { IconMic, IconVolumeUp } from '../../../../base/icons';
|
||||
import { IReduxState, IStore } from '../../../../app/types';
|
||||
import { translate } from '../../../../base/i18n/functions';
|
||||
import { IconMic, IconVolumeUp } from '../../../../base/icons/svg';
|
||||
import JitsiMeetJS from '../../../../base/lib-jitsi-meet';
|
||||
import { equals } from '../../../../base/redux';
|
||||
import { createLocalAudioTracks } from '../../../functions';
|
||||
import { equals } from '../../../../base/redux/functions';
|
||||
import Checkbox from '../../../../base/ui/components/web/Checkbox';
|
||||
import ContextMenu from '../../../../base/ui/components/web/ContextMenu';
|
||||
import ContextMenuItem from '../../../../base/ui/components/web/ContextMenuItem';
|
||||
import ContextMenuItemGroup from '../../../../base/ui/components/web/ContextMenuItemGroup';
|
||||
import { toggleNoiseSuppression } from '../../../../noise-suppression/actions';
|
||||
import { isNoiseSuppressionEnabled } from '../../../../noise-suppression/functions';
|
||||
import { isPrejoinPageVisible } from '../../../../prejoin/functions';
|
||||
import { createLocalAudioTracks } from '../../../functions.web';
|
||||
|
||||
import AudioSettingsHeader from './AudioSettingsHeader';
|
||||
import MicrophoneEntry from './MicrophoneEntry';
|
||||
import SpeakerEntry from './SpeakerEntry';
|
||||
|
||||
@@ -22,65 +29,75 @@ const browser = JitsiMeetJS.util.browser;
|
||||
* @param {Function} t - The translation function.
|
||||
* @returns {string}
|
||||
*/
|
||||
function transformDefaultDeviceLabel(deviceId, label, t) {
|
||||
function transformDefaultDeviceLabel(deviceId: string, label: string, t: Function) {
|
||||
return deviceId === 'default'
|
||||
? t('settings.sameAsSystem', { label: label.replace('Default - ', '') })
|
||||
: label;
|
||||
}
|
||||
|
||||
export type Props = {
|
||||
export interface IProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
/**
|
||||
* The deviceId of the microphone in use.
|
||||
*/
|
||||
currentMicDeviceId: string,
|
||||
currentMicDeviceId: string;
|
||||
|
||||
/**
|
||||
/**
|
||||
* The deviceId of the output device in use.
|
||||
*/
|
||||
currentOutputDeviceId: string,
|
||||
currentOutputDeviceId?: string;
|
||||
|
||||
/**
|
||||
* Used to decide whether to measure audio levels for microphone devices.
|
||||
*/
|
||||
measureAudioLevels: boolean,
|
||||
measureAudioLevels: boolean;
|
||||
|
||||
/**
|
||||
* Used to set a new microphone as the current one.
|
||||
*/
|
||||
setAudioInputDevice: Function,
|
||||
|
||||
/**
|
||||
* Used to set a new output device as the current one.
|
||||
*/
|
||||
setAudioOutputDevice: Function,
|
||||
|
||||
/**
|
||||
* A list of objects containing the labels and deviceIds
|
||||
* of all the output devices.
|
||||
*/
|
||||
outputDevices: Object[],
|
||||
|
||||
/**
|
||||
/**
|
||||
* A list with objects containing the labels and deviceIds
|
||||
* of all the input devices.
|
||||
*/
|
||||
microphoneDevices: Object[],
|
||||
microphoneDevices: Array<{ deviceId: string; label: string; }>;
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
* Whether noise suppression is enabled or not.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
noiseSuppressionEnabled: boolean;
|
||||
|
||||
/**
|
||||
* A list of objects containing the labels and deviceIds
|
||||
* of all the output devices.
|
||||
*/
|
||||
outputDevices: Array<{ deviceId: string; label: string; }>;
|
||||
|
||||
/**
|
||||
* Whether the prejoin page is visible or not.
|
||||
*/
|
||||
prejoinVisible: boolean;
|
||||
|
||||
/**
|
||||
* Used to set a new microphone as the current one.
|
||||
*/
|
||||
setAudioInputDevice: Function;
|
||||
|
||||
/**
|
||||
* Used to set a new output device as the current one.
|
||||
*/
|
||||
setAudioOutputDevice: Function;
|
||||
|
||||
/**
|
||||
* Function to toggle noise suppression.
|
||||
*/
|
||||
toggleSuppression: () => void;
|
||||
}
|
||||
|
||||
type State = {
|
||||
|
||||
/**
|
||||
/**
|
||||
* An list of objects, each containing the microphone label, audio track, device id
|
||||
* and track error if the case.
|
||||
*/
|
||||
audioTracks: Object[]
|
||||
}
|
||||
audioTracks: Array<{ deviceId: string; hasError: boolean; jitsiTrack: any; label: string; }>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a React {@link Component} which displays a list of all
|
||||
@@ -88,9 +105,8 @@ type State = {
|
||||
*
|
||||
* @augments Component
|
||||
*/
|
||||
class AudioSettingsContent extends Component<Props, State> {
|
||||
class AudioSettingsContent extends Component<IProps, State> {
|
||||
_componentWasUnmounted: boolean;
|
||||
_audioContentRef: Object;
|
||||
microphoneHeaderId = 'microphone_settings_header';
|
||||
speakerHeaderId = 'speaker_settings_header';
|
||||
|
||||
@@ -101,13 +117,11 @@ class AudioSettingsContent extends Component<Props, State> {
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props) {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this._onMicrophoneEntryClick = this._onMicrophoneEntryClick.bind(this);
|
||||
this._onSpeakerEntryClick = this._onSpeakerEntryClick.bind(this);
|
||||
this._onEscClick = this._onEscClick.bind(this);
|
||||
this._audioContentRef = React.createRef();
|
||||
|
||||
this.state = {
|
||||
audioTracks: props.microphoneDevices.map(({ deviceId, label }) => {
|
||||
@@ -120,23 +134,6 @@ class AudioSettingsContent extends Component<Props, State> {
|
||||
})
|
||||
};
|
||||
}
|
||||
_onEscClick: (KeyboardEvent) => void;
|
||||
|
||||
/**
|
||||
* Click handler for the speaker entries.
|
||||
*
|
||||
* @param {KeyboardEvent} event - Esc key click to close the popup.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onEscClick(event) {
|
||||
if (event.key === 'Escape') {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this._audioContentRef.current.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
_onMicrophoneEntryClick: (string) => void;
|
||||
|
||||
/**
|
||||
* Click handler for the microphone entries.
|
||||
@@ -144,19 +141,17 @@ class AudioSettingsContent extends Component<Props, State> {
|
||||
* @param {string} deviceId - The deviceId for the clicked microphone.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onMicrophoneEntryClick(deviceId) {
|
||||
_onMicrophoneEntryClick(deviceId: string) {
|
||||
this.props.setAudioInputDevice(deviceId);
|
||||
}
|
||||
|
||||
_onSpeakerEntryClick: (string) => void;
|
||||
|
||||
/**
|
||||
* Click handler for the speaker entries.
|
||||
*
|
||||
* @param {string} deviceId - The deviceId for the clicked speaker.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onSpeakerEntryClick(deviceId) {
|
||||
_onSpeakerEntryClick(deviceId: string) {
|
||||
this.props.setAudioOutputDevice(deviceId);
|
||||
}
|
||||
|
||||
@@ -169,7 +164,8 @@ class AudioSettingsContent extends Component<Props, State> {
|
||||
* @param {Function} t - The translation function.
|
||||
* @returns {React$Node}
|
||||
*/
|
||||
_renderMicrophoneEntry(data, index, length, t) {
|
||||
_renderMicrophoneEntry(data: { deviceId: string; hasError: boolean; jitsiTrack: any; label: string; },
|
||||
index: number, length: number, t: Function) {
|
||||
const { deviceId, jitsiTrack, hasError } = data;
|
||||
const label = transformDefaultDeviceLabel(deviceId, data.label, t);
|
||||
const isSelected = deviceId === this.props.currentMicDeviceId;
|
||||
@@ -200,7 +196,7 @@ class AudioSettingsContent extends Component<Props, State> {
|
||||
* @param {Function} t - The translation function.
|
||||
* @returns {React$Node}
|
||||
*/
|
||||
_renderSpeakerEntry(data, index, length, t) {
|
||||
_renderSpeakerEntry(data: { deviceId: string; label: string; }, index: number, length: number, t: Function) {
|
||||
const { deviceId } = data;
|
||||
const label = transformDefaultDeviceLabel(deviceId, data.label, t);
|
||||
const key = `se-${index}`;
|
||||
@@ -253,9 +249,9 @@ class AudioSettingsContent extends Component<Props, State> {
|
||||
* @param {Object} audioTracks - The object holding the audio tracks.
|
||||
* @returns {void}
|
||||
*/
|
||||
_disposeTracks(audioTracks) {
|
||||
_disposeTracks(audioTracks: Array<{ jitsiTrack: any; }>) {
|
||||
audioTracks.forEach(({ jitsiTrack }) => {
|
||||
jitsiTrack && jitsiTrack.dispose();
|
||||
jitsiTrack?.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -283,7 +279,7 @@ class AudioSettingsContent extends Component<Props, State> {
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
componentDidUpdate(prevProps) {
|
||||
componentDidUpdate(prevProps: IProps) {
|
||||
if (!equals(this.props.microphoneDevices, prevProps.microphoneDevices)) {
|
||||
this._setTracks();
|
||||
}
|
||||
@@ -296,55 +292,82 @@ class AudioSettingsContent extends Component<Props, State> {
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { outputDevices, t } = this.props;
|
||||
const { outputDevices, t, noiseSuppressionEnabled, toggleSuppression, prejoinVisible } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
aria-labelledby = 'audio-settings-button'
|
||||
className = 'audio-preview-content'
|
||||
id = 'audio-settings-dialog'
|
||||
onKeyDown = { this._onEscClick }
|
||||
ref = { this._audioContentRef }
|
||||
role = 'menu'
|
||||
tabIndex = { -1 }>
|
||||
<div role = 'menuitem'>
|
||||
<AudioSettingsHeader
|
||||
IconComponent = { IconMic }
|
||||
id = { this.microphoneHeaderId }
|
||||
text = { t('settings.microphones') } />
|
||||
<ContextMenu
|
||||
aria-labelledby = 'audio-settings-button'
|
||||
className = 'audio-preview-content'
|
||||
hidden = { false }
|
||||
id = 'audio-settings-dialog'
|
||||
tabIndex = { -1 }>
|
||||
<ContextMenuItemGroup>
|
||||
<ContextMenuItem
|
||||
accessibilityLabel = { t('settings.microphones') }
|
||||
className = 'audio-preview-header'
|
||||
icon = { IconMic }
|
||||
id = { this.microphoneHeaderId }
|
||||
text = { t('settings.microphones') } />
|
||||
<ul
|
||||
aria-labelledby = { this.microphoneHeaderId }
|
||||
className = 'audio-preview-content-ul'
|
||||
role = 'radiogroup'
|
||||
tabIndex = { -1 }>
|
||||
{this.state.audioTracks.map((data, i) =>
|
||||
this._renderMicrophoneEntry(data, i, this.state.audioTracks.length, t)
|
||||
)}
|
||||
</ul>
|
||||
</ContextMenuItemGroup>
|
||||
{ outputDevices.length > 0 && (
|
||||
<ContextMenuItemGroup>
|
||||
<ContextMenuItem
|
||||
accessibilityLabel = { t('settings.speakers') }
|
||||
className = 'audio-preview-header'
|
||||
icon = { IconVolumeUp }
|
||||
id = { this.speakerHeaderId }
|
||||
text = { t('settings.speakers') } />
|
||||
<ul
|
||||
aria-labelledby = 'microphone_settings_header'
|
||||
aria-labelledby = { this.speakerHeaderId }
|
||||
className = 'audio-preview-content-ul'
|
||||
role = 'radiogroup'
|
||||
tabIndex = '-1'>
|
||||
{this.state.audioTracks.map((data, i) =>
|
||||
this._renderMicrophoneEntry(data, i, this.state.audioTracks.length, t)
|
||||
tabIndex = { -1 }>
|
||||
{ outputDevices.map((data, i) =>
|
||||
this._renderSpeakerEntry(data, i, outputDevices.length, t)
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
{ outputDevices.length > 0 && (
|
||||
<div role = 'menuitem'>
|
||||
<hr className = 'audio-preview-hr' />
|
||||
<AudioSettingsHeader
|
||||
IconComponent = { IconVolumeUp }
|
||||
id = { this.speakerHeaderId }
|
||||
text = { t('settings.speakers') } />
|
||||
<ul
|
||||
aria-labelledby = 'speaker_settings_header'
|
||||
className = 'audio-preview-content-ul'
|
||||
role = 'radiogroup'
|
||||
tabIndex = '-1'>
|
||||
{ outputDevices.map((data, i) =>
|
||||
this._renderSpeakerEntry(data, i, outputDevices.length, t)
|
||||
)}
|
||||
</ul>
|
||||
</div>)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</ContextMenuItemGroup>)
|
||||
}
|
||||
{!prejoinVisible && (
|
||||
<ContextMenuItemGroup>
|
||||
<div
|
||||
className = 'audio-preview-checkbox-container'
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick = { e => e.stopPropagation() }>
|
||||
<Checkbox
|
||||
checked = { noiseSuppressionEnabled }
|
||||
label = { t('toolbar.noiseSuppression') }
|
||||
onChange = { toggleSuppression } />
|
||||
</div>
|
||||
</ContextMenuItemGroup>
|
||||
)}
|
||||
</ContextMenu>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(AudioSettingsContent);
|
||||
const mapStateToProps = (state: IReduxState) => {
|
||||
return {
|
||||
noiseSuppressionEnabled: isNoiseSuppressionEnabled(state),
|
||||
prejoinVisible: isPrejoinPageVisible(state)
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch: IStore['dispatch']) => {
|
||||
return {
|
||||
toggleSuppression() {
|
||||
dispatch(toggleNoiseSuppression());
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default translate(connect(mapStateToProps, mapDispatchToProps)(AudioSettingsContent));
|
||||
@@ -1,64 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Icon, IconCheck, IconExclamationSolid } from '../../../../base/icons';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link AudioSettingsEntry}.
|
||||
*/
|
||||
export type Props = {
|
||||
|
||||
/**
|
||||
* The text for this component.
|
||||
*/
|
||||
children: React$Node,
|
||||
|
||||
/**
|
||||
* Flag indicating an error.
|
||||
*/
|
||||
hasError?: boolean,
|
||||
|
||||
/**
|
||||
* The id for the label, that contains the item text.
|
||||
*/
|
||||
labelId?: string,
|
||||
|
||||
/**
|
||||
* Flag indicating the selection state.
|
||||
*/
|
||||
isSelected: boolean,
|
||||
};
|
||||
|
||||
/**
|
||||
* React {@code Component} representing an entry for the audio settings.
|
||||
*
|
||||
* @returns { ReactElement}
|
||||
*/
|
||||
export default function AudioSettingsEntry(
|
||||
{ children, hasError, labelId, isSelected }: Props) {
|
||||
|
||||
const className = `audio-preview-entry ${isSelected
|
||||
? 'audio-preview-entry--selected' : ''}`;
|
||||
|
||||
return (
|
||||
<div className = { className }>
|
||||
{isSelected && (
|
||||
<Icon
|
||||
className = 'audio-preview-icon audio-preview-icon--check'
|
||||
color = '#1C2025'
|
||||
size = { 14 }
|
||||
src = { IconCheck } />
|
||||
)}
|
||||
<span
|
||||
className = 'audio-preview-entry-text'
|
||||
id = { labelId }>
|
||||
{children}
|
||||
</span>
|
||||
{hasError && <Icon
|
||||
className = 'audio-preview-icon audio-preview-icon--exclamation'
|
||||
size = { 16 }
|
||||
src = { IconExclamationSolid } />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Icon } from '../../../../base/icons';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link AudioSettingsHeader}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The id used for the Header-text.
|
||||
*/
|
||||
id?: string,
|
||||
|
||||
/**
|
||||
* The Icon used for the Header.
|
||||
*/
|
||||
IconComponent: Function,
|
||||
|
||||
/**
|
||||
* The text of the Header.
|
||||
*/
|
||||
text: string,
|
||||
};
|
||||
|
||||
/**
|
||||
* React {@code Component} representing the Header of an audio option group.
|
||||
*
|
||||
* @returns { ReactElement}
|
||||
*/
|
||||
export default function AudioSettingsHeader({ IconComponent, id, text }: Props) {
|
||||
return (
|
||||
<div
|
||||
className = 'audio-preview-header'
|
||||
role = 'heading'>
|
||||
<div className = 'audio-preview-header-icon'>
|
||||
{ <Icon
|
||||
size = { 20 }
|
||||
src = { IconComponent } />}
|
||||
</div>
|
||||
<div
|
||||
className = 'audio-preview-header-text'
|
||||
id = { id } >{text}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
// @flow
|
||||
import React, { ReactNode } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { areAudioLevelsEnabled } from '../../../../base/config/functions';
|
||||
import { IReduxState } from '../../../../app/types';
|
||||
import { areAudioLevelsEnabled } from '../../../../base/config/functions.web';
|
||||
import {
|
||||
setAudioInputDeviceAndUpdateSettings,
|
||||
setAudioOutputDevice as setAudioOutputDeviceAction
|
||||
@@ -12,39 +12,38 @@ import {
|
||||
getAudioOutputDeviceData
|
||||
} from '../../../../base/devices/functions.web';
|
||||
import Popover from '../../../../base/popover/components/Popover.web';
|
||||
import { connect } from '../../../../base/redux';
|
||||
import { SMALL_MOBILE_WIDTH } from '../../../../base/responsive-ui/constants';
|
||||
import {
|
||||
getCurrentMicDeviceId,
|
||||
getCurrentOutputDeviceId
|
||||
} from '../../../../base/settings';
|
||||
} from '../../../../base/settings/functions.web';
|
||||
import { toggleAudioSettings } from '../../../actions';
|
||||
import { getAudioSettingsVisibility } from '../../../functions';
|
||||
import { getAudioSettingsVisibility } from '../../../functions.web';
|
||||
|
||||
import AudioSettingsContent, { type Props as AudioSettingsContentProps } from './AudioSettingsContent';
|
||||
import AudioSettingsContent, { type IProps as AudioSettingsContentProps } from './AudioSettingsContent';
|
||||
|
||||
|
||||
type Props = AudioSettingsContentProps & {
|
||||
interface IProps extends AudioSettingsContentProps {
|
||||
|
||||
/**
|
||||
* Component's children (the audio button).
|
||||
*/
|
||||
children: React$Node,
|
||||
children: ReactNode;
|
||||
|
||||
/**
|
||||
* Flag controlling the visibility of the popup.
|
||||
*/
|
||||
isOpen: boolean,
|
||||
isOpen: boolean;
|
||||
|
||||
/**
|
||||
* Callback executed when the popup closes.
|
||||
*/
|
||||
onClose: Function,
|
||||
onClose: Function;
|
||||
|
||||
/**
|
||||
* The popup placement enum value.
|
||||
*/
|
||||
popupPlacement: string
|
||||
popupPlacement: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,7 +63,7 @@ function AudioSettingsPopup({
|
||||
outputDevices,
|
||||
popupPlacement,
|
||||
measureAudioLevels
|
||||
}: Props) {
|
||||
}: IProps) {
|
||||
return (
|
||||
<div className = 'audio-preview'>
|
||||
<Popover
|
||||
@@ -92,16 +91,16 @@ function AudioSettingsPopup({
|
||||
* @param {Object} state - Redux state.
|
||||
* @returns {Object}
|
||||
*/
|
||||
function mapStateToProps(state) {
|
||||
function mapStateToProps(state: IReduxState) {
|
||||
const { clientWidth } = state['features/base/responsive-ui'];
|
||||
|
||||
return {
|
||||
popupPlacement: clientWidth <= SMALL_MOBILE_WIDTH ? 'auto' : 'top-end',
|
||||
popupPlacement: clientWidth <= Number(SMALL_MOBILE_WIDTH) ? 'auto' : 'top-end',
|
||||
currentMicDeviceId: getCurrentMicDeviceId(state),
|
||||
currentOutputDeviceId: getCurrentOutputDeviceId(state),
|
||||
isOpen: getAudioSettingsVisibility(state),
|
||||
microphoneDevices: getAudioInputDeviceData(state),
|
||||
outputDevices: getAudioOutputDeviceData(state),
|
||||
isOpen: Boolean(getAudioSettingsVisibility(state)),
|
||||
microphoneDevices: getAudioInputDeviceData(state) ?? [],
|
||||
outputDevices: getAudioOutputDeviceData(state) ?? [],
|
||||
measureAudioLevels: areAudioLevelsEnabled(state)
|
||||
};
|
||||
}
|
||||
@@ -1,34 +1,33 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Icon, IconMeter } from '../../../../base/icons';
|
||||
import Icon from '../../../../base/icons/components/Icon';
|
||||
import { IconMeter } from '../../../../base/icons/svg';
|
||||
|
||||
type Props = {
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* Own class name for the component.
|
||||
*/
|
||||
className: string,
|
||||
className: string;
|
||||
|
||||
/**
|
||||
* Flag indicating whether the component is greyed out/disabled.
|
||||
*/
|
||||
isDisabled?: boolean,
|
||||
isDisabled?: boolean;
|
||||
|
||||
/**
|
||||
* The level of the meter.
|
||||
* Should be between 0 and 7 as per the used SVG.
|
||||
*/
|
||||
level: number,
|
||||
};
|
||||
level: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* React {@code Component} representing an audio level meter.
|
||||
*
|
||||
* @returns { ReactElement}
|
||||
*/
|
||||
export default function({ className, isDisabled, level }: Props) {
|
||||
export default function({ className, isDisabled, level }: IProps) {
|
||||
let ownClassName;
|
||||
|
||||
if (level > -1) {
|
||||
@@ -1,61 +1,80 @@
|
||||
// @flow
|
||||
|
||||
import clsx from 'clsx';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import Icon from '../../../../base/icons/components/Icon';
|
||||
import { IconCheck, IconExclamationSolid } from '../../../../base/icons/svg';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import JitsiMeetJS from '../../../../base/lib-jitsi-meet/_';
|
||||
import ContextMenuItem from '../../../../base/ui/components/web/ContextMenuItem';
|
||||
|
||||
import AudioSettingsEntry, { type Props as AudioSettingsEntryProps } from './AudioSettingsEntry';
|
||||
import Meter from './Meter';
|
||||
|
||||
const JitsiTrackEvents = JitsiMeetJS.events.track;
|
||||
|
||||
type Props = AudioSettingsEntryProps & {
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The text for this component.
|
||||
*/
|
||||
children: string;
|
||||
|
||||
/**
|
||||
* The deviceId of the microphone.
|
||||
*/
|
||||
deviceId: string,
|
||||
deviceId: string;
|
||||
|
||||
/**
|
||||
* Flag indicating if there is a problem with the device.
|
||||
*/
|
||||
hasError?: boolean,
|
||||
hasError?: boolean;
|
||||
|
||||
/**
|
||||
* Flag indicating if there is a problem with the device.
|
||||
*/
|
||||
index?: number,
|
||||
index?: number;
|
||||
|
||||
/**
|
||||
* Flag indicating the selection state.
|
||||
*/
|
||||
isSelected: boolean;
|
||||
|
||||
/**
|
||||
* The audio track for the current entry.
|
||||
*/
|
||||
jitsiTrack: Object,
|
||||
jitsiTrack: any;
|
||||
|
||||
/**
|
||||
* The id for the label, that contains the item text.
|
||||
*/
|
||||
labelId?: string;
|
||||
|
||||
/**
|
||||
* The length of the microphone list.
|
||||
*/
|
||||
length: number,
|
||||
length: number;
|
||||
|
||||
|
||||
/**
|
||||
* Click handler for component.
|
||||
*/
|
||||
onClick: Function,
|
||||
listHeaderId: string,
|
||||
listHeaderId: string;
|
||||
|
||||
/**
|
||||
* Used to decide whether to listen to audio level changes.
|
||||
*/
|
||||
measureAudioLevels: boolean,
|
||||
}
|
||||
measureAudioLevels: boolean;
|
||||
|
||||
/**
|
||||
* Click handler for component.
|
||||
*/
|
||||
onClick: Function;
|
||||
};
|
||||
|
||||
type State = {
|
||||
|
||||
/**
|
||||
* The audio level.
|
||||
*/
|
||||
level: number
|
||||
}
|
||||
level: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* React {@code Component} representing an entry for the microphone audio settings.
|
||||
@@ -81,8 +100,6 @@ export default class MicrophoneEntry extends Component<Props, State> {
|
||||
this._updateLevel = this._updateLevel.bind(this);
|
||||
}
|
||||
|
||||
_onClick: () => void;
|
||||
|
||||
/**
|
||||
* Click handler for the entry.
|
||||
*
|
||||
@@ -92,13 +109,6 @@ export default class MicrophoneEntry extends Component<Props, State> {
|
||||
this.props.onClick(this.props.deviceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Key pressed handler for the entry.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onKeyPress: (KeyboardEvent) => void;
|
||||
|
||||
/**
|
||||
* Key pressed handler for the entry.
|
||||
*
|
||||
@@ -107,22 +117,20 @@ export default class MicrophoneEntry extends Component<Props, State> {
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onKeyPress(e) {
|
||||
_onKeyPress(e: React.KeyboardEvent) {
|
||||
if (e.key === ' ') {
|
||||
e.preventDefault();
|
||||
this.props.onClick(this.props.deviceId);
|
||||
}
|
||||
}
|
||||
|
||||
_updateLevel: (number) => void;
|
||||
|
||||
/**
|
||||
* Updates the level of the meter.
|
||||
*
|
||||
* @param {number} num - The audio level provided by the jitsiTrack.
|
||||
* @returns {void}
|
||||
*/
|
||||
_updateLevel(num) {
|
||||
_updateLevel(num: number) {
|
||||
this.setState({
|
||||
level: Math.floor(num / 0.125)
|
||||
});
|
||||
@@ -147,8 +155,8 @@ export default class MicrophoneEntry extends Component<Props, State> {
|
||||
* @param {Object} jitsiTrack - The jitsiTrack to unsubscribe from.
|
||||
* @returns {void}
|
||||
*/
|
||||
_stopListening(jitsiTrack) {
|
||||
jitsiTrack && jitsiTrack.off(JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED, this._updateLevel);
|
||||
_stopListening(jitsiTrack?: any) {
|
||||
jitsiTrack?.off(JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED, this._updateLevel);
|
||||
this.setState({
|
||||
level: -1
|
||||
});
|
||||
@@ -202,9 +210,9 @@ export default class MicrophoneEntry extends Component<Props, State> {
|
||||
measureAudioLevels
|
||||
} = this.props;
|
||||
|
||||
const deviceTextId: string = `choose_microphone${deviceId}`;
|
||||
const deviceTextId = `choose_microphone${deviceId}`;
|
||||
|
||||
const labelledby: string = `${listHeaderId} ${deviceTextId} `;
|
||||
const labelledby = `${listHeaderId} ${deviceTextId} `;
|
||||
|
||||
const className = `audio-preview-microphone ${measureAudioLevels
|
||||
? 'audio-preview-microphone--withmeter' : 'audio-preview-microphone--nometer'}`;
|
||||
@@ -220,12 +228,17 @@ export default class MicrophoneEntry extends Component<Props, State> {
|
||||
onKeyPress = { this._onKeyPress }
|
||||
role = 'radio'
|
||||
tabIndex = { 0 }>
|
||||
<AudioSettingsEntry
|
||||
hasError = { hasError }
|
||||
isSelected = { isSelected }
|
||||
labelId = { deviceTextId }>
|
||||
{children}
|
||||
</AudioSettingsEntry>
|
||||
<ContextMenuItem
|
||||
accessibilityLabel = ''
|
||||
icon = { isSelected ? IconCheck : undefined }
|
||||
selected = { isSelected }
|
||||
text = { children }
|
||||
textClassName = { clsx('audio-preview-entry-text', !isSelected && 'left-margin') }>
|
||||
{hasError && <Icon
|
||||
className = 'audio-preview-icon audio-preview-icon--exclamation'
|
||||
size = { 16 }
|
||||
src = { IconExclamationSolid } />}
|
||||
</ContextMenuItem>
|
||||
{ Boolean(jitsiTrack) && measureAudioLevels && <Meter
|
||||
className = 'audio-preview-meter-mic'
|
||||
isDisabled = { hasError }
|
||||
@@ -1,163 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import logger from '../../../logger';
|
||||
|
||||
import AudioSettingsEntry from './AudioSettingsEntry';
|
||||
import TestButton from './TestButton';
|
||||
|
||||
const TEST_SOUND_PATH = 'sounds/ring.mp3';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link SpeakerEntry}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
|
||||
/**
|
||||
* The text label for the entry.
|
||||
*/
|
||||
children: React$Node,
|
||||
|
||||
/**
|
||||
* Flag controlling the selection state of the entry.
|
||||
*/
|
||||
isSelected: boolean,
|
||||
|
||||
/**
|
||||
* Flag controlling the selection state of the entry.
|
||||
*/
|
||||
index: number,
|
||||
|
||||
/**
|
||||
* Flag controlling the selection state of the entry.
|
||||
*/
|
||||
length: number,
|
||||
|
||||
/**
|
||||
* The deviceId of the speaker.
|
||||
*/
|
||||
deviceId: string,
|
||||
|
||||
/**
|
||||
* Click handler for the component.
|
||||
*/
|
||||
onClick: Function,
|
||||
listHeaderId: string
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a React {@link Component} which displays an audio
|
||||
* output settings entry. The user can click and play a test sound.
|
||||
*
|
||||
* @augments Component
|
||||
*/
|
||||
export default class SpeakerEntry extends Component<Props> {
|
||||
/**
|
||||
* A React ref to the HTML element containing the {@code audio} instance.
|
||||
*/
|
||||
audioRef: Object;
|
||||
|
||||
/**
|
||||
* Initializes a new {@code SpeakerEntry} instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.audioRef = React.createRef();
|
||||
this._onTestButtonClick = this._onTestButtonClick.bind(this);
|
||||
this._onClick = this._onClick.bind(this);
|
||||
this._onKeyPress = this._onKeyPress.bind(this);
|
||||
}
|
||||
|
||||
_onClick: () => void;
|
||||
|
||||
/**
|
||||
* Click handler for the entry.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onClick() {
|
||||
this.props.onClick(this.props.deviceId);
|
||||
}
|
||||
|
||||
_onKeyPress: () => void;
|
||||
|
||||
/**
|
||||
* Key pressed handler for the entry.
|
||||
*
|
||||
* @param {Object} e - The event.
|
||||
* @private
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onKeyPress(e) {
|
||||
if (e.key === ' ') {
|
||||
e.preventDefault();
|
||||
this.props.onClick(this.props.deviceId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_onTestButtonClick: Object => void;
|
||||
|
||||
/**
|
||||
* Click handler for Test button.
|
||||
* Sets the current audio output id and plays a sound.
|
||||
*
|
||||
* @param {Object} e - The sythetic event.
|
||||
* @returns {void}
|
||||
*/
|
||||
async _onTestButtonClick(e) {
|
||||
e.stopPropagation();
|
||||
|
||||
try {
|
||||
await this.audioRef.current.setSinkId(this.props.deviceId);
|
||||
this.audioRef.current.play();
|
||||
} catch (err) {
|
||||
logger.log('Could not set sink id', err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { children, isSelected, index, deviceId, length, listHeaderId } = this.props;
|
||||
const deviceTextId: string = `choose_speaker${deviceId}`;
|
||||
const labelledby: string = `${listHeaderId} ${deviceTextId} `;
|
||||
|
||||
return (
|
||||
<li
|
||||
aria-checked = { isSelected }
|
||||
aria-labelledby = { labelledby }
|
||||
aria-posinset = { index }
|
||||
aria-setsize = { length }
|
||||
className = 'audio-preview-speaker'
|
||||
onClick = { this._onClick }
|
||||
onKeyPress = { this._onKeyPress }
|
||||
role = 'radio'
|
||||
tabIndex = { 0 }>
|
||||
<AudioSettingsEntry
|
||||
isSelected = { isSelected }
|
||||
key = { deviceId }
|
||||
labelId = { deviceTextId }>
|
||||
{children}
|
||||
</AudioSettingsEntry>
|
||||
<TestButton
|
||||
onClick = { this._onTestButtonClick }
|
||||
onKeyPress = { this._onTestButtonClick } />
|
||||
<audio
|
||||
preload = 'auto'
|
||||
ref = { this.audioRef }
|
||||
src = { TEST_SOUND_PATH } />
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
140
react/features/settings/components/web/audio/SpeakerEntry.tsx
Normal file
140
react/features/settings/components/web/audio/SpeakerEntry.tsx
Normal file
@@ -0,0 +1,140 @@
|
||||
import clsx from 'clsx';
|
||||
import React, { useRef } from 'react';
|
||||
|
||||
import { IconCheck } from '../../../../base/icons/svg';
|
||||
import Button from '../../../../base/ui/components/web/Button';
|
||||
import ContextMenuItem from '../../../../base/ui/components/web/ContextMenuItem';
|
||||
import { BUTTON_TYPES } from '../../../../base/ui/constants.any';
|
||||
import logger from '../../../logger';
|
||||
|
||||
const TEST_SOUND_PATH = 'sounds/ring.mp3';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link SpeakerEntry}.
|
||||
*/
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* The text label for the entry.
|
||||
*/
|
||||
children: string;
|
||||
|
||||
/**
|
||||
* The deviceId of the speaker.
|
||||
*/
|
||||
deviceId: string;
|
||||
|
||||
/**
|
||||
* Flag controlling the selection state of the entry.
|
||||
*/
|
||||
index: number;
|
||||
|
||||
/**
|
||||
* Flag controlling the selection state of the entry.
|
||||
*/
|
||||
isSelected: boolean;
|
||||
|
||||
/**
|
||||
* Flag controlling the selection state of the entry.
|
||||
*/
|
||||
length: number;
|
||||
|
||||
listHeaderId: string;
|
||||
|
||||
/**
|
||||
* Click handler for the component.
|
||||
*/
|
||||
onClick: Function;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a React {@link Component} which displays an audio
|
||||
* output settings entry. The user can click and play a test sound.
|
||||
*
|
||||
* @param {IProps} props - Component props.
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
const SpeakerEntry = (props: IProps) => {
|
||||
const audioRef = useRef<HTMLAudioElement | null>(null);
|
||||
|
||||
/**
|
||||
* Click handler for the entry.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function _onClick() {
|
||||
props.onClick(props.deviceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Key pressed handler for the entry.
|
||||
*
|
||||
* @param {Object} e - The event.
|
||||
* @private
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function _onKeyPress(e: React.KeyboardEvent) {
|
||||
if (e.key === ' ') {
|
||||
e.preventDefault();
|
||||
props.onClick(props.deviceId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Click handler for Test button.
|
||||
* Sets the current audio output id and plays a sound.
|
||||
*
|
||||
* @param {Object} e - The synthetic event.
|
||||
* @returns {void}
|
||||
*/
|
||||
async function _onTestButtonClick(e: React.KeyboardEvent | React.MouseEvent) {
|
||||
e.stopPropagation();
|
||||
|
||||
try { // @ts-ignore
|
||||
await audioRef.current?.setSinkId(props.deviceId);
|
||||
audioRef.current?.play();
|
||||
} catch (err) {
|
||||
logger.log('Could not set sink id', err);
|
||||
}
|
||||
}
|
||||
|
||||
const { children, isSelected, index, deviceId, length, listHeaderId } = props;
|
||||
const deviceTextId = `choose_speaker${deviceId}`;
|
||||
const labelledby = `${listHeaderId} ${deviceTextId} `;
|
||||
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
return (
|
||||
<li
|
||||
aria-checked = { isSelected }
|
||||
aria-labelledby = { labelledby }
|
||||
aria-posinset = { index }
|
||||
aria-setsize = { length }
|
||||
className = 'audio-preview-speaker'
|
||||
onClick = { _onClick }
|
||||
onKeyPress = { _onKeyPress }
|
||||
role = 'radio'
|
||||
tabIndex = { 0 }>
|
||||
<ContextMenuItem
|
||||
accessibilityLabel = ''
|
||||
icon = { isSelected ? IconCheck : undefined }
|
||||
selected = { isSelected }
|
||||
text = { children }
|
||||
textClassName = { clsx('audio-preview-entry-text', !isSelected && 'left-margin') }>
|
||||
<Button
|
||||
className = 'audio-preview-test-button'
|
||||
label = 'Test'
|
||||
onClick = { _onTestButtonClick }
|
||||
onKeyPress = { _onTestButtonClick }
|
||||
type = { BUTTON_TYPES.SECONDARY } />
|
||||
</ContextMenuItem>
|
||||
<audio
|
||||
preload = 'auto'
|
||||
ref = { audioRef }
|
||||
src = { TEST_SOUND_PATH } />
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default SpeakerEntry;
|
||||
@@ -1,34 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Click handler for the button.
|
||||
*/
|
||||
onClick: Function,
|
||||
|
||||
/**
|
||||
* Keypress handler for the button.
|
||||
*/
|
||||
onKeyPress: Function,
|
||||
};
|
||||
|
||||
/**
|
||||
* React {@code Component} representing an button used for testing output sound.
|
||||
*
|
||||
* @returns { ReactElement}
|
||||
*/
|
||||
export default function TestButton({ onClick, onKeyPress }: Props) {
|
||||
return (
|
||||
<div
|
||||
className = 'audio-preview-test-button'
|
||||
onClick = { onClick }
|
||||
onKeyPress = { onKeyPress }
|
||||
role = 'button'
|
||||
tabIndex = { 0 }>
|
||||
Test
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -42,7 +42,7 @@ export function createLocalVideoTracks(ids: string[], timeout?: number) {
|
||||
* label: string
|
||||
* }[]>}
|
||||
*/
|
||||
export function createLocalAudioTracks(devices: MediaDeviceInfo[], timeout?: number) {
|
||||
export function createLocalAudioTracks(devices: Array<{ deviceId: string; label: string; }>, timeout?: number) {
|
||||
return Promise.all(
|
||||
devices.map(async ({ deviceId, label }) => {
|
||||
let jitsiTrack = null;
|
||||
|
||||
44
react/features/toolbox/components/web/CustomOptionButton.tsx
Normal file
44
react/features/toolbox/components/web/CustomOptionButton.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
|
||||
|
||||
|
||||
type Props = AbstractButtonProps & {
|
||||
icon: string;
|
||||
text: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Component that renders a custom toolbox button.
|
||||
*
|
||||
* @returns {Component}
|
||||
*/
|
||||
class CustomOptionButton extends AbstractButton<Props, any, any> {
|
||||
// @ts-ignore
|
||||
iconSrc = this.props.icon;
|
||||
|
||||
// @ts-ignore
|
||||
id = this.props.id;
|
||||
|
||||
// @ts-ignore
|
||||
text = this.props.text;
|
||||
|
||||
accessibilityLabel = this.text;
|
||||
|
||||
/**
|
||||
* Custom icon component.
|
||||
*
|
||||
* @param {any} props - Icon's props.
|
||||
* @returns {img}
|
||||
*/
|
||||
icon = (props: any) => (<img
|
||||
src = { this.iconSrc }
|
||||
{ ...props } />);
|
||||
|
||||
label = this.text;
|
||||
tooltip = this.text;
|
||||
}
|
||||
|
||||
export default CustomOptionButton;
|
||||
@@ -1,5 +1,8 @@
|
||||
import { ReactNode, useEffect, useRef, useState } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
|
||||
type Props = {
|
||||
|
||||
@@ -36,6 +39,7 @@ type Props = {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
function DialogPortal({ children, className, style, getRef, setSize }: Props) {
|
||||
const clientWidth = useSelector((state: IReduxState) => state['features/base/responsive-ui'].clientWidth);
|
||||
const [ portalTarget ] = useState(() => {
|
||||
const portalDiv = document.createElement('div');
|
||||
|
||||
@@ -92,7 +96,7 @@ function DialogPortal({ children, className, style, getRef, setSize }: Props) {
|
||||
document.body.removeChild(portalTarget);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
}, [ clientWidth ]);
|
||||
|
||||
return ReactDOM.createPortal(
|
||||
children,
|
||||
|
||||
@@ -12,6 +12,7 @@ import { sendAnalytics } from '../../../analytics/functions';
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { IJitsiConference } from '../../../base/conference/reducer';
|
||||
import {
|
||||
getButtonsWithNotifyClick,
|
||||
getMultipleVideoSendingSupportFeatureFlag,
|
||||
getToolbarButtons,
|
||||
isToolbarButtonEnabled
|
||||
@@ -120,6 +121,7 @@ import HelpButton from '../HelpButton';
|
||||
|
||||
// @ts-ignore
|
||||
import AudioSettingsButton from './AudioSettingsButton';
|
||||
import CustomOptionButton from './CustomOptionButton';
|
||||
import { EndConferenceButton } from './EndConferenceButton';
|
||||
// @ts-ignore
|
||||
import FullscreenButton from './FullscreenButton';
|
||||
@@ -174,6 +176,11 @@ interface IProps extends WithTranslation {
|
||||
*/
|
||||
_conference?: IJitsiConference;
|
||||
|
||||
/**
|
||||
* Custom Toolbar buttons.
|
||||
*/
|
||||
_customToolbarButtons?: Array<{ icon: string; id: string; text: string; }>;
|
||||
|
||||
/**
|
||||
* Whether or not screensharing button is disabled.
|
||||
*/
|
||||
@@ -358,7 +365,8 @@ const styles = () => {
|
||||
right: 'auto',
|
||||
margin: 0,
|
||||
marginBottom: '8px',
|
||||
maxHeight: 'calc(100vh - 100px)'
|
||||
maxHeight: 'calc(100vh - 100px)',
|
||||
width: '240px'
|
||||
},
|
||||
|
||||
hangupMenu: {
|
||||
@@ -714,6 +722,7 @@ class Toolbox extends Component<IProps> {
|
||||
*/
|
||||
_getAllButtons() {
|
||||
const {
|
||||
_customToolbarButtons,
|
||||
_feedbackConfigured,
|
||||
_hasSalesforce,
|
||||
_isIosMobile,
|
||||
@@ -914,6 +923,19 @@ class Toolbox extends Component<IProps> {
|
||||
group: 4
|
||||
};
|
||||
|
||||
const customButtons = _customToolbarButtons?.reduce((prev, { icon, id, text }) => {
|
||||
return {
|
||||
...prev,
|
||||
[id]: {
|
||||
key: id,
|
||||
Content: CustomOptionButton,
|
||||
group: 4,
|
||||
icon,
|
||||
text
|
||||
}
|
||||
};
|
||||
}, {});
|
||||
|
||||
return {
|
||||
microphone,
|
||||
camera,
|
||||
@@ -944,7 +966,8 @@ class Toolbox extends Component<IProps> {
|
||||
embed,
|
||||
feedback,
|
||||
download,
|
||||
help
|
||||
help,
|
||||
...customButtons
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1524,8 +1547,8 @@ function _mapStateToProps(state: IReduxState, ownProps: Partial<IProps>) {
|
||||
const endConferenceSupported = conference?.isEndConferenceSupported() && isLocalParticipantModerator(state);
|
||||
|
||||
const {
|
||||
buttonsWithNotifyClick,
|
||||
callStatsID,
|
||||
customToolbarButtons,
|
||||
disableProfile,
|
||||
iAmRecorder,
|
||||
iAmSipGateway
|
||||
@@ -1543,10 +1566,11 @@ function _mapStateToProps(state: IReduxState, ownProps: Partial<IProps>) {
|
||||
|
||||
return {
|
||||
_backgroundType: state['features/virtual-background'].backgroundType ?? '',
|
||||
_buttonsWithNotifyClick: buttonsWithNotifyClick,
|
||||
_buttonsWithNotifyClick: getButtonsWithNotifyClick(state),
|
||||
_chatOpen: state['features/chat'].isOpen,
|
||||
_clientWidth: clientWidth,
|
||||
_conference: conference,
|
||||
_customToolbarButtons: customToolbarButtons,
|
||||
_desktopSharingEnabled: JitsiMeetJS.isDesktopSharingEnabled(),
|
||||
_desktopSharingButtonDisabled: isDesktopShareButtonDisabled(state),
|
||||
_dialog: Boolean(state['features/base/dialog'].component),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import { openDialog } from '../../base/dialog';
|
||||
import { IconCrown } from '../../base/icons';
|
||||
import { IconModerator } from '../../base/icons';
|
||||
import {
|
||||
PARTICIPANT_ROLE,
|
||||
getLocalParticipant,
|
||||
@@ -35,7 +35,7 @@ export type Props = AbstractButtonProps & {
|
||||
*/
|
||||
export default class AbstractGrantModeratorButton extends AbstractButton<Props, *> {
|
||||
accessibilityLabel = 'toolbar.accessibilityLabel.grantModerator';
|
||||
icon = IconCrown;
|
||||
icon = IconModerator;
|
||||
label = 'videothumbnail.grantModerator';
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import ContextMenuItem from '../../../base/ui/components/web/ContextMenuItem';
|
||||
|
||||
const CustomOptionButton = (
|
||||
{ icon: iconSrc, onClick, text }:
|
||||
{
|
||||
icon: string;
|
||||
onClick: (e?: React.MouseEvent<Element, MouseEvent> | undefined) => void;
|
||||
text: string;
|
||||
}
|
||||
) => {
|
||||
|
||||
const icon = useCallback(props => (<img
|
||||
src = { iconSrc }
|
||||
{ ...props } />), [ iconSrc ]);
|
||||
|
||||
return (
|
||||
<ContextMenuItem
|
||||
accessibilityLabel = { text }
|
||||
icon = { icon }
|
||||
onClick = { onClick }
|
||||
text = { text } />
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomOptionButton;
|
||||
@@ -3,7 +3,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { IconCrown } from '../../../base/icons';
|
||||
import { IconModerator } from '../../../base/icons';
|
||||
import { connect } from '../../../base/redux';
|
||||
import ContextMenuItem from '../../../base/ui/components/web/ContextMenuItem';
|
||||
import AbstractGrantModeratorButton, {
|
||||
@@ -46,7 +46,7 @@ class GrantModeratorButton extends AbstractGrantModeratorButton {
|
||||
<ContextMenuItem
|
||||
accessibilityLabel = { t('toolbar.accessibilityLabel.grantModerator') }
|
||||
className = 'grantmoderatorlink'
|
||||
icon = { IconCrown }
|
||||
icon = { IconModerator }
|
||||
// eslint-disable-next-line react/jsx-handler-names
|
||||
onClick = { this._handleClick }
|
||||
text = { t('videothumbnail.grantModerator') } />
|
||||
|
||||
@@ -26,6 +26,7 @@ import { isForceMuted } from '../../../participants-pane/functions';
|
||||
import { requestRemoteControl, stopController } from '../../../remote-control';
|
||||
import { showOverflowDrawer } from '../../../toolbox/functions.web';
|
||||
|
||||
import CustomOptionButton from './CustomOptionButton';
|
||||
// @ts-ignore
|
||||
import { REMOTE_CONTROL_MENU_STATES } from './RemoteControlButton';
|
||||
// @ts-ignore
|
||||
@@ -144,7 +145,7 @@ const ParticipantContextMenu = ({
|
||||
isForceMuted(participant, MEDIA_TYPE.VIDEO, state));
|
||||
const _isAudioMuted = useSelector((state: IReduxState) => isParticipantAudioMuted(participant, state));
|
||||
const _overflowDrawer: boolean = useSelector(showOverflowDrawer);
|
||||
const { remoteVideoMenu = {}, disableRemoteMute, startSilent }
|
||||
const { remoteVideoMenu = {}, disableRemoteMute, startSilent, customParticipantMenuButtons }
|
||||
= useSelector((state: IReduxState) => state['features/base/config']);
|
||||
const { disableKick, disableGrantModerator, disablePrivateChat } = remoteVideoMenu;
|
||||
const { participantsVolume } = useSelector((state: IReduxState) => state['features/filmstrip']);
|
||||
@@ -171,8 +172,8 @@ const ParticipantContextMenu = ({
|
||||
}
|
||||
, [ thumbnailMenu, _overflowDrawer, drawerParticipant, participant ]);
|
||||
|
||||
const buttons = [];
|
||||
const buttons2 = [];
|
||||
const buttons: JSX.Element[] = [];
|
||||
const buttons2: JSX.Element[] = [];
|
||||
|
||||
const showVolumeSlider = !startSilent
|
||||
&& !isIosMobileBrowser()
|
||||
@@ -277,6 +278,23 @@ const ParticipantContextMenu = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (customParticipantMenuButtons) {
|
||||
customParticipantMenuButtons.forEach(
|
||||
({ icon, id, text }) => {
|
||||
const onClick = useCallback(
|
||||
() => APP.API.notifyParticipantMenuButtonClicked(id, _getCurrentParticipantId()), []);
|
||||
|
||||
buttons2.push(
|
||||
<CustomOptionButton
|
||||
icon = { icon }
|
||||
key = { id }
|
||||
onClick = { onClick }
|
||||
text = { text } />
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const breakoutRoomsButtons: any = [];
|
||||
|
||||
if (!thumbnailMenu && _isModerator) {
|
||||
|
||||
@@ -54,23 +54,22 @@ const styles = (theme: Theme) => {
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '0 5px',
|
||||
padding: '10px 16px',
|
||||
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.ui04
|
||||
backgroundColor: theme.palette.ui02
|
||||
}
|
||||
},
|
||||
|
||||
icon: {
|
||||
minWidth: '20px',
|
||||
padding: '5px',
|
||||
marginRight: '16px',
|
||||
position: 'relative' as const
|
||||
},
|
||||
|
||||
sliderContainer: {
|
||||
position: 'relative' as const,
|
||||
width: '100%',
|
||||
paddingRight: '5px'
|
||||
width: '100%'
|
||||
},
|
||||
|
||||
slider: {
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
"include": ["react/features/**/*.ts", "react/features/**/*.tsx", "./custom.d.ts", "./globals.native.d.ts"],
|
||||
"compilerOptions": {
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"target": "ESNext",
|
||||
"target": "es2020",
|
||||
"jsx": "react-native",
|
||||
"lib": [ "ES2017"],
|
||||
"lib": [ "es2020"],
|
||||
"types": [ "react-native" ],
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "Node",
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
"include": ["react/features/**/*.ts", "react/features/**/*.tsx", "./custom.d.ts", "./globals.d.ts"],
|
||||
"compilerOptions": {
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"module": "es6",
|
||||
"target": "es6",
|
||||
"module": "es2020",
|
||||
"target": "es2020",
|
||||
"jsx": "react",
|
||||
"lib": [ "webworker", "ES2020", "DOM" ],
|
||||
"skipLibCheck": true,
|
||||
|
||||
Reference in New Issue
Block a user