mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-01-23 07:00:19 +00:00
Compare commits
24 Commits
8976
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d32907178 | ||
|
|
481b9a6e58 | ||
|
|
fb3bc3c367 | ||
|
|
9fa5489154 | ||
|
|
9499bf29ed | ||
|
|
f605b5c487 | ||
|
|
88fba5acab | ||
|
|
7bc79bc144 | ||
|
|
3e469019b5 | ||
|
|
d324935501 | ||
|
|
cd11cf6f65 | ||
|
|
5db3d529f4 | ||
|
|
c7d2c9c204 | ||
|
|
9832c7a226 | ||
|
|
12ee929499 | ||
|
|
b9ed42613b | ||
|
|
0d572b3bfb | ||
|
|
609eab5f83 | ||
|
|
7bd0f479b9 | ||
|
|
a634b6b2bc | ||
|
|
0e53bd87ce | ||
|
|
9e89c33796 | ||
|
|
eaffd8b8f7 | ||
|
|
b8444d56ff |
2
.github/workflows/ci-lua.yml
vendored
2
.github/workflows/ci-lua.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
||||
name: Luacheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
|
||||
|
||||
- name: Install luarocks
|
||||
run: sudo apt-get --install-recommends -y install luarocks
|
||||
|
||||
30
.github/workflows/ci.yml
vendored
30
.github/workflows/ci.yml
vendored
@@ -7,8 +7,8 @@ jobs:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
|
||||
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 #v6.2.0
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
@@ -42,8 +42,8 @@ jobs:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
|
||||
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 #v6.2.0
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
@@ -59,8 +59,8 @@ jobs:
|
||||
name: Build mobile bundle (Android)
|
||||
runs-on: macos-15
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
|
||||
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 #v6.2.0
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
@@ -74,8 +74,8 @@ jobs:
|
||||
name: Build mobile bundle (iOS)
|
||||
runs-on: macos-15
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
|
||||
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 #v6.2.0
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
@@ -116,8 +116,8 @@ jobs:
|
||||
rm -rf /host/usr/share/dotnet
|
||||
rm -rf /host/usr/share/swift
|
||||
df -h /
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
|
||||
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 #v6.2.0
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
@@ -137,8 +137,8 @@ jobs:
|
||||
name: Build mobile SDK (iOS)
|
||||
runs-on: macos-15
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
|
||||
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 #v6.2.0
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
@@ -160,7 +160,7 @@ jobs:
|
||||
-workspace ios/jitsi-meet.xcworkspace \
|
||||
-scheme JitsiMeetSDK
|
||||
- name: setup-cocoapods
|
||||
uses: ruby/setup-ruby@v1
|
||||
uses: ruby/setup-ruby@4fc31e1c823882afd7ef55985266a526c589de90 #v1.282.0
|
||||
with:
|
||||
ruby-version: '3.4'
|
||||
bundler-cache: true
|
||||
@@ -187,8 +187,8 @@ jobs:
|
||||
name: Test Debian packages build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
|
||||
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 #v6.2.0
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
|
||||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v8
|
||||
- uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 #v10.1.0
|
||||
with:
|
||||
stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.'
|
||||
stale-pr-message: 'This PR has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.'
|
||||
|
||||
@@ -264,4 +264,4 @@ react/features/sample/
|
||||
- [Jitsi Handbook](https://jitsi.github.io/handbook/) - Comprehensive documentation
|
||||
- [Community Forum](https://community.jitsi.org/) - Ask questions and get support
|
||||
- [Architecture Guide](https://jitsi.github.io/handbook/docs/architecture) - System overview
|
||||
- [Contributing Guidelines](https://jitsi.github.io/handbook/docs/dev-guide/contributing) - Detailed contribution process
|
||||
- [Contributing Guidelines](https://jitsi.github.io/handbook/docs/dev-guide/dev-guide-contributing/) - Detailed contribution process
|
||||
|
||||
@@ -30,6 +30,9 @@ import com.facebook.react.ReactRootView;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
public class JitsiMeetView extends FrameLayout {
|
||||
|
||||
@@ -84,6 +87,10 @@ public class JitsiMeetView extends FrameLayout {
|
||||
result.putInt(key, (int)bValue);
|
||||
} else if (valueType.contentEquals("Bundle")) {
|
||||
result.putBundle(key, mergeProps((Bundle)aValue, (Bundle)bValue));
|
||||
} else if (valueType.contentEquals("String[]")) {
|
||||
// Convert String[] to ArrayList<String> for React Native bridge compatibility
|
||||
String[] stringArray = (String[]) bValue;
|
||||
result.putStringArrayList(key, new ArrayList<>(Arrays.asList(stringArray)));
|
||||
} else {
|
||||
throw new RuntimeException("Unsupported type: " + valueType);
|
||||
}
|
||||
|
||||
@@ -151,6 +151,9 @@ var config = {
|
||||
// Disables self-view settings in UI
|
||||
// disableSelfViewSettings: false,
|
||||
|
||||
// Shows/hides the moderator setting for chat permissions.
|
||||
// showChatPermissionsModeratorSetting: false,
|
||||
|
||||
// screenshotCapture : {
|
||||
// Enables the screensharing capture feature.
|
||||
// enabled: false,
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
"it": "Italiano",
|
||||
"ja": "日本語",
|
||||
"kab": "Taqbaylit",
|
||||
"kk": "Қазақша",
|
||||
"ko": "한국어",
|
||||
"lt": "Lietuvių",
|
||||
"lv": "Latviešu",
|
||||
|
||||
1895
lang/main-da.json
1895
lang/main-da.json
File diff suppressed because it is too large
Load Diff
@@ -1190,7 +1190,7 @@
|
||||
"signedIn": "Momentan wird auf Kalendertermine von {{email}} zugegriffen. Klicken Sie auf die folgende Schaltfläche „Trennen“, um den Zugriff auf die Kalendertermine zu stoppen.",
|
||||
"title": "Kalender"
|
||||
},
|
||||
"chatWithPermissions": "Chat mit Freigaben",
|
||||
"chatWithPermissions": "Chat nur für Moderation erlauben",
|
||||
"desktopShareFramerate": "Framerate für Bildschirmfreigabe",
|
||||
"desktopShareHighFpsWarning": "Eine höhere Framerate könnte sich auf Ihre Datenrate auswirken. Sie müssen die Bildschirmfreigabe neustarten, damit die Einstellung übernommen wird.",
|
||||
"desktopShareWarning": "Sie müssen die Bildschirmfreigabe neustarten, damit die Einstellung übernommen wird.",
|
||||
|
||||
@@ -126,8 +126,16 @@
|
||||
"messagebox": "Digita un messaggio",
|
||||
"newMessages": "Nuovi messaggi",
|
||||
"nickname": {
|
||||
"featureChat": "la chat",
|
||||
"featureClosedCaptions": "i sottotitoli",
|
||||
"featureFileSharing": "la condivisione file",
|
||||
"featurePolls": "i sondaggi",
|
||||
"popover": "Scegli un nickname",
|
||||
"title": "Inserisci un nickname per usare la chat",
|
||||
"titleWith1Features": "Inserisci un nickname per usare {{feature1}}",
|
||||
"titleWith2Features": "Inserisci un nickname per usare {{feature1}} e {{feature2}}",
|
||||
"titleWith3Features": "Inserisci un nickname per usare {{feature1}}, {{feature2}} e {{feature3}}",
|
||||
"titleWith4Features": "Inserisci un nickname per usare {{feature1}}, {{feature2}}, {{feature3}} e {{feature4}}",
|
||||
"titleWithCC": "Inserisci un nickname per usare la chat e i sottotitoli",
|
||||
"titleWithPolls": "Inserisci un nickname per usare la chat e i sondaggi",
|
||||
"titleWithPollsAndCC": "Inserisci un nickname per usare la chat, i sondaggi e i sottotitoli",
|
||||
@@ -159,7 +167,7 @@
|
||||
"installExtensionText": "Installa un'estensione per integrare Google Calendar e Office 365"
|
||||
},
|
||||
"closedCaptionsTab": {
|
||||
"emptyState": "Il contenuto dei sottotitoli sarà disponibile una volta che l'organizzatore lo attiverà",
|
||||
"emptyState": "Il contenuto dei sottotitoli sarà disponibile una volta che un moderatore lo attiverà",
|
||||
"startClosedCaptionsButton": "Attiva sottotitoli"
|
||||
},
|
||||
"connectingOverlay": {
|
||||
@@ -283,9 +291,9 @@
|
||||
"Submit": "Invia",
|
||||
"Understand": "Accetto, mantieni microfono e videocamera disattivati per ora",
|
||||
"UnderstandAndUnmute": "Accetto, riattiva microfono e videocamera",
|
||||
"WaitForHostNoAuthMsg": "La riunione non è ancora iniziata perché nessun organizzatore si è ancora collegato. Si prega di attendere.",
|
||||
"WaitingForHostButton": "Attendi l'organizzatore",
|
||||
"WaitingForHostTitle": "In attesa dell'organizzatore…",
|
||||
"WaitForHostNoAuthMsg": "La riunione non è ancora iniziata perché nessun moderatore si è ancora collegato. Si prega di attendere.",
|
||||
"WaitingForHostButton": "Attendi un moderatore",
|
||||
"WaitingForHostTitle": "In attesa di un moderatore…",
|
||||
"Yes": "Sì",
|
||||
"accessibilityLabel": {
|
||||
"Cancel": "Annulla (chiudi dialogo)",
|
||||
@@ -348,8 +356,8 @@
|
||||
"error": "Errore",
|
||||
"errorRoomCreationRestriction": "Hai provato ad accedere alla riunione troppo presto, torna tra un po'.",
|
||||
"gracefulShutdown": "Il nostro servizio è al momento inattivo per manutenzione. Si prega di riprovare più tardi.",
|
||||
"grantModeratorDialog": "Vuoi rendere relatore questo partecipante?",
|
||||
"grantModeratorTitle": "Fai diventare relatore",
|
||||
"grantModeratorDialog": "Desideri di concedere i permessi da moderatore a {{participantName}}?",
|
||||
"grantModeratorTitle": "Concedi permessi da moderatore",
|
||||
"hide": "Nascondi",
|
||||
"hideShareAudioHelper": "Non mostrare più questa finestra",
|
||||
"incorrectPassword": "Nome utente o password errati",
|
||||
@@ -727,12 +735,12 @@
|
||||
"emailField": "Inserisci il tuo indirizzo email",
|
||||
"enableDialogPasswordField": "Imposta password (opzionale)",
|
||||
"enableDialogSubmit": "Attiva",
|
||||
"enableDialogText": "La sala d'attesa ti permette di proteggere la riunione concedendo l'accesso solo alle persone autorizzate da un organizzatore.",
|
||||
"enableDialogText": "La sala d'attesa ti permette di proteggere la riunione concedendo l'accesso solo alle persone autorizzate da un moderatore.",
|
||||
"enterPasswordButton": "Inserisci password riunione",
|
||||
"enterPasswordTitle": "Inserisci la password per entrare nella riunione",
|
||||
"errorMissingPassword": "Inserisci la password della riunione",
|
||||
"invalidPassword": "Password errata",
|
||||
"joinRejectedMessage": "La tua richiesta d'accesso è stata respinta da un organizzatore.",
|
||||
"joinRejectedMessage": "La tua richiesta d'accesso è stata respinta da un moderatore.",
|
||||
"joinRejectedTitle": "Richiesta d'accesso respinta.",
|
||||
"joinTitle": "Entra nella riunione",
|
||||
"joinWithPasswordMessage": "Tentativo di accesso con password in corso, si prega di attendere…",
|
||||
@@ -754,7 +762,7 @@
|
||||
"passwordJoinButton": "Entra",
|
||||
"title": "Sala d'attesa",
|
||||
"toggleLabel": "Attiva sala d'attesa",
|
||||
"waitForModerator": "La riunione non è ancora iniziata, perché non è arrivato alcun organizzatore. Se vuoi diventarlo autenticati, altrimenti attendi."
|
||||
"waitForModerator": "La riunione non è ancora iniziata, perché non è arrivato alcun moderatore. Se vuoi diventarlo autenticati, altrimenti si prega di attendere."
|
||||
},
|
||||
"localRecording": {
|
||||
"clientState": {
|
||||
@@ -772,11 +780,11 @@
|
||||
"me": "Io",
|
||||
"messages": {
|
||||
"engaged": "Registrazione avviata.",
|
||||
"finished": "La registrazione della sessione {{token}} è terminata. Invia il file della registrazione all'organizzatore.",
|
||||
"finishedModerator": "La registrazione della sessione {{token}} è terminata. La registrazione della traccia è stata salvata. Chiedi ai partecipanti di inviare le loro registrazioni.",
|
||||
"notModerator": "Non sei un relatore. Non puoi avviare o interrompere la registrazione."
|
||||
"finished": "La registrazione della sessione {{token}} è terminata. Invia il file della registrazione al moderatore.",
|
||||
"finishedModerator": "La registrazione della sessione {{token}} è terminata. La registrazione della traccia locale è stata salvata. Si prega di chiedere ai partecipanti di inviare le loro registrazioni.",
|
||||
"notModerator": "Non sei un moderatore. Non puoi avviare o interrompere la registrazione."
|
||||
},
|
||||
"moderator": "Relatore",
|
||||
"moderator": "Moderatore",
|
||||
"no": "No",
|
||||
"participant": "Partecipante",
|
||||
"participantStats": "Statistiche partecipante",
|
||||
@@ -824,7 +832,7 @@
|
||||
"focusFail": "{{component}} non disponibile - nuovo tentativo tra {{ms}} sec",
|
||||
"gifsMenu": "GIPHY",
|
||||
"groupTitle": "Notifiche",
|
||||
"hostAskedUnmute": "Il relatore ti chiede di intervenire.",
|
||||
"hostAskedUnmute": "Il moderatore ti chiede di intervenire.",
|
||||
"invalidTenant": "Nome non valido",
|
||||
"invalidTenantHyphenDescription": "Il nome che hai scelto non è valido (inizia o finisce con '-').",
|
||||
"invalidTenantLengthDescription": "Il nome che hai scelto è troppo lungo.",
|
||||
@@ -846,17 +854,17 @@
|
||||
"localRecordingStopped": "{{name}} ha smesso di registrare.",
|
||||
"me": "Io",
|
||||
"moderationInEffectCSDescription": "Alza la mano, se vuoi condividere lo schermo.",
|
||||
"moderationInEffectCSTitle": "La condivisione schermo è stata bloccata dal relatore",
|
||||
"moderationInEffectCSTitle": "La condivisione schermo è stata bloccata dal moderatore",
|
||||
"moderationInEffectDescription": "Alza la mano, se vuoi prendere la parola.",
|
||||
"moderationInEffectTitle": "Il tuo microfono è stato spento dal relatore",
|
||||
"moderationInEffectTitle": "Il tuo microfono è stato spento dal moderatore",
|
||||
"moderationInEffectVideoDescription": "Alza la mano, se vuoi avviare la tua videocamera.",
|
||||
"moderationInEffectVideoTitle": "La tua videocamera è stata spenta dal relatore",
|
||||
"moderationRequestFromModerator": "Il relatore vorrebbe che tu accendessi il microfono",
|
||||
"moderationInEffectVideoTitle": "La tua videocamera è stata spenta dal moderatore",
|
||||
"moderationRequestFromModerator": "L'organizzatore ti chiede di accendere il microfono",
|
||||
"moderationRequestFromParticipant": "Vuole parlare",
|
||||
"moderationStartedTitle": "Moderazione in corso",
|
||||
"moderationStoppedTitle": "Moderazione interrotta",
|
||||
"moderationToggleDescription": "da {{participantDisplayName}}",
|
||||
"moderator": "Ora sei un relatore!",
|
||||
"moderator": "Ora sei un moderatore!",
|
||||
"muted": "Hai iniziato la conversazione con il microfono disattivato.",
|
||||
"mutedRemotelyDescription": "Puoi sempre attivare il microfono quando vuoi parlare. Spegni il microfono quando hai finito, per evitare rumori di fondo nella riunione.",
|
||||
"mutedRemotelyTitle": "{{participantDisplayName}} ti ha spento il microfono",
|
||||
@@ -1162,7 +1170,7 @@
|
||||
},
|
||||
"security": {
|
||||
"about": "Puoi aggiungere una $t(lockRoomPassword) alla riunione. I partecipanti dovranno inserire la $t(lockRoomPassword) per accedere alla riunione.",
|
||||
"aboutReadOnly": "Gli organizzatori possono aggiungere una $t(lockRoomPassword) alla riunione. I partecipanti dovranno inserire la $t(lockRoomPassword) per accedere alla riunione.",
|
||||
"aboutReadOnly": "I moderatori possono aggiungere una $t(lockRoomPassword) alla riunione. I partecipanti dovranno inserire la $t(lockRoomPassword) per accedere alla riunione.",
|
||||
"insecureRoomNameWarningNative": "Il nome della riunione è troppo semplice. Partecipanti non desiderati potrebbero entrare nella riunione. {{recommendAction}} Per saperne di più sulla sicurezza della tua riunione ",
|
||||
"insecureRoomNameWarningWeb": "Il nome della riunione è troppo semplice. Partecipanti non desiderati potrebbero entrare nella riunione. {{recommendAction}} Per saperne di più sulla sicurezza della tua riunione <a href=\"{{securityUrl}}\" rel=\"security\" target=\"_blank\">clicca qui</a>.",
|
||||
"title": "Impostazioni di sicurezza",
|
||||
@@ -1182,7 +1190,7 @@
|
||||
"signedIn": "Accesso agli eventi del calendario per {{email}} in corso. Clicca sul pulsante Disconnetti per interrompere l’accesso agli eventi sul calendario.",
|
||||
"title": "Calendario"
|
||||
},
|
||||
"chatWithPermissions": "La chat richiede il permesso",
|
||||
"chatWithPermissions": "Disattiva la chat per i partecipanti",
|
||||
"desktopShareFramerate": "Frequenza di aggiornamento condivisone schermo",
|
||||
"desktopShareHighFpsWarning": "Una frequenza di aggiornamento della condivisione dello schermo più alta può influire sulla tua connessione. Devi riavviare la condivisione schermo, per applicare le modifiche.",
|
||||
"desktopShareWarning": "Devi riavviare la condivisione schermo, per applicare le modifiche.",
|
||||
@@ -1195,8 +1203,8 @@
|
||||
"loggedIn": "Connesso come {{name}}",
|
||||
"maxStageParticipants": "Numero massimo di partecipanti che possono essere messi in evidenza nella schermata principale",
|
||||
"microphones": "Microfoni",
|
||||
"moderator": "Relatore",
|
||||
"moderatorOptions": "Opzioni relatore",
|
||||
"moderator": "Moderatore",
|
||||
"moderatorOptions": "Opzioni moderatore",
|
||||
"more": "Generali",
|
||||
"name": "Nome",
|
||||
"noDevice": "Nessuno",
|
||||
@@ -1325,7 +1333,7 @@
|
||||
"feedback": "Lascia un feedback",
|
||||
"fullScreen": "Attiva modalità a schermo intero",
|
||||
"giphy": "Mostra menu GIPHY",
|
||||
"grantModerator": "Concedi permessi da relatore",
|
||||
"grantModerator": "Concedi permessi da moderatore",
|
||||
"hangup": "Lascia la riunione",
|
||||
"heading": "Barra degli strumenti",
|
||||
"help": "Aiuto",
|
||||
@@ -1350,7 +1358,7 @@
|
||||
"muteEveryoneElsesVideoStream": "Spegni la videocamera a tutti gli altri",
|
||||
"muteEveryonesVideoStream": "Spegni la videocamera a tutti",
|
||||
"muteGUMPending": "Connessione del microfono in corso",
|
||||
"noiseSuppression": "Cancellazione del rumore (BETA)",
|
||||
"noiseSuppression": "Cancellazione del rumore",
|
||||
"openChat": "Apri chat",
|
||||
"participants": "Apri pannello partecipanti. {{participantsCount}} partecipanti",
|
||||
"pip": "Attiva modalità Picture-in-Picture",
|
||||
@@ -1415,20 +1423,21 @@
|
||||
"closeParticipantsPane": "Chiudi pannello partecipanti",
|
||||
"closeReactionsMenu": "Chiudi menu reazioni",
|
||||
"closedCaptions": "Sottotitoli",
|
||||
"disableNoiseSuppression": "Disattiva cancellazione del rumore (BETA)",
|
||||
"disableNoiseSuppression": "Disattiva cancellazione del rumore",
|
||||
"disableReactionSounds": "Puoi disattivare i suoni delle reazioni in questa riunione",
|
||||
"documentClose": "Chiudi documento condiviso",
|
||||
"documentOpen": "Apri documento condiviso",
|
||||
"download": "Scarica le nostre app",
|
||||
"e2ee": "Crittografia End-to-End",
|
||||
"embedMeeting": "Incorpora riunione",
|
||||
"enableNoiseSuppression": "Attiva cancellazione del rumore (BETA)",
|
||||
"enableNoiseSuppression": "Attiva cancellazione del rumore",
|
||||
"endConference": "Termina la riunione per tutti",
|
||||
"enterFullScreen": "Mostra a schermo intero",
|
||||
"enterTileView": "Mostra vista a mosaico",
|
||||
"exitFullScreen": "Esci dalla modalità a schermo intero",
|
||||
"exitTileView": "Esci dalla vista a mosaico",
|
||||
"feedback": "Lascia un feedback",
|
||||
"fileSharing": "Condivisione file",
|
||||
"giphy": "Menu GIPHY",
|
||||
"hangup": "Lascia la riunione",
|
||||
"help": "Aiuto",
|
||||
@@ -1457,13 +1466,14 @@
|
||||
"noAudioSignalDialInDesc": "Puoi anche chiamare usando:",
|
||||
"noAudioSignalDialInLinkDesc": "Numeri di telefono",
|
||||
"noAudioSignalTitle": "Nessun suono rilevato dal tuo microfono!",
|
||||
"noiseSuppression": "Cancellazione del rumore (BETA)",
|
||||
"noiseSuppression": "Cancellazione del rumore",
|
||||
"noisyAudioInputDesc": "Sembra che il tuo microfono faccia rumore, si prega di spegnerlo o cambiarlo.",
|
||||
"noisyAudioInputTitle": "Il tuo microfono sembra fare rumore!",
|
||||
"openChat": "Apri chat",
|
||||
"openReactionsMenu": "Apri il menu reazioni",
|
||||
"participants": "Partecipanti",
|
||||
"pip": "Abilita modalità Picture-in-Picture",
|
||||
"polls": "Sondaggi",
|
||||
"privateMessage": "Invia un messaggio privato",
|
||||
"profile": "Modifica profilo",
|
||||
"raiseHand": "Alza la mano",
|
||||
@@ -1565,11 +1575,11 @@
|
||||
"domuteVideo": "Disattiva videocamera",
|
||||
"domuteVideoOfOthers": "Disattiva videocamera a tutti gli altri",
|
||||
"flip": "Specchia",
|
||||
"grantModerator": "Concedi permessi da relatore",
|
||||
"grantModerator": "Concedi permessi da moderatore",
|
||||
"hideSelfView": "Nascondi la tua immagine",
|
||||
"kick": "Espelli",
|
||||
"mirrorVideo": "Specchia il tuo video",
|
||||
"moderator": "Relatore",
|
||||
"moderator": "Moderatore",
|
||||
"mute": "Il partecipante ha il microfono spento",
|
||||
"muted": "Microfono spento",
|
||||
"pinToStage": "Metti in primo piano",
|
||||
@@ -1616,7 +1626,7 @@
|
||||
"description": "Adesso sei uno spettatore in questa riunione.",
|
||||
"raiseHand": "Alza la mano",
|
||||
"title": "Ingresso nella riunione in corso",
|
||||
"wishToSpeak": "Se vuoi parlare, si prega di alzare la mano sotto e aspettare l'autorizzazione del relatore."
|
||||
"wishToSpeak": "Per parlare si prega di alzare la mano sotto e aspettare l'autorizzazione del moderatore."
|
||||
},
|
||||
"labelTooltip": "Numero di spettatori: {{count}}",
|
||||
"notification": {
|
||||
@@ -1626,7 +1636,7 @@
|
||||
"noVisitorLobby": "Non puoi partecipare se la sala d'attesa è attiva per la riunione.",
|
||||
"notAllowedPromotion": "Un partecipante deve autorizzare la tua richiesta prima.",
|
||||
"requestToJoin": "Mano alzata",
|
||||
"requestToJoinDescription": "La tua richiesta è stata inviata ai relatori. Tieni duro!",
|
||||
"requestToJoinDescription": "La tua richiesta è stata inviata ai moderatori. Tieni duro!",
|
||||
"title": "Sei uno spettatore nella riunione"
|
||||
},
|
||||
"waitingMessage": "Ti unirai alla riunione quando inizierà!"
|
||||
@@ -1667,7 +1677,7 @@
|
||||
"mobileDownLoadLinkAndroid": "Scarica applicazione per Android",
|
||||
"mobileDownLoadLinkFDroid": "Scarica applicazione da F-Droid",
|
||||
"mobileDownLoadLinkIos": "Scarica applicazione per iOS",
|
||||
"moderatedMessage": "O <a href=\"{{url}}\" rel=\"noopener noreferrer\" target=\"_blank\">prepara un URL di riunione</a> in anticipo, quando sei l'unico organizzatore.",
|
||||
"moderatedMessage": "O <a href=\"{{url}}\" rel=\"noopener noreferrer\" target=\"_blank\">prepara un URL di riunione</a> in anticipo, quando sei l'unico moderatore.",
|
||||
"privacy": "Privacy",
|
||||
"recentList": "Recenti",
|
||||
"recentListDelete": "Cancella",
|
||||
|
||||
1703
lang/main-kk.json
Normal file
1703
lang/main-kk.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -114,6 +114,10 @@
|
||||
"error": "Erro: a sua mensagem não foi enviada. Motivo: {{error}}",
|
||||
"everyone": "Todos",
|
||||
"fieldPlaceHolder": "Aa",
|
||||
"fileAccessibleTitle": "{{user}} carregou um ficheiro",
|
||||
"fileAccessibleTitleMe": "carreguei um arquivo",
|
||||
"fileDeleted": "Um ficheiro foi eliminado",
|
||||
"guestsChatIndicator": "(convidado)",
|
||||
"lobbyChatMessageTo": "Mensagem de chat na sala de espera para {{recipient}}",
|
||||
"message": "Mensagem",
|
||||
"messageAccessibleTitle": "{{user}} disse:",
|
||||
@@ -122,8 +126,16 @@
|
||||
"messagebox": "Escreva uma mensagem",
|
||||
"newMessages": "Novas mensagens",
|
||||
"nickname": {
|
||||
"featureChat": "chat",
|
||||
"featureClosedCaptions": "legendas ocultas",
|
||||
"featureFileSharing": "partilha de ficheiros",
|
||||
"featurePolls": "sondagens",
|
||||
"popover": "Escolha um apelido",
|
||||
"title": "Introduza um apelido para usar o chat",
|
||||
"titleWith1Features": "Insira um apelido para usar {{feature1}}",
|
||||
"titleWith2Features": "Insira um apelido para usar {{feature1}} e {{feature2}}",
|
||||
"titleWith3Features": "Insira um apelido para usar {{feature1}}, {{feature2}} e {{feature3}}",
|
||||
"titleWith4Features": "Insira um apelido para usar {{feature1}}, {{feature2}}, {{feature3}} e {{feature4}}",
|
||||
"titleWithCC": "Insira um apelido para usar o chat e as legendas ocultas",
|
||||
"titleWithPolls": "Digite um apelido para usar o chat e as sondagens",
|
||||
"titleWithPollsAndCC": "Insira um apelido para utilizar o chat, as sondagens e as legendas ocultas",
|
||||
@@ -279,7 +291,6 @@
|
||||
"Submit": "Submeter",
|
||||
"Understand": "Entendo, mantenha-me em silêncio por enquanto.",
|
||||
"UnderstandAndUnmute": "Entendo, por favor, desative o silêncio.",
|
||||
"WaitForHostMsg": "A conferência ainda não começou porque ainda não chegaram moderadores. Se quiser ser um moderador, inicie a sessão. Caso contrário, aguarde.",
|
||||
"WaitForHostNoAuthMsg": "A conferência ainda não começou porque ainda não chegaram os moderadores. Por favor, aguarde.",
|
||||
"WaitingForHostButton": "Esperar pelo moderador",
|
||||
"WaitingForHostTitle": "À espera de um moderador…",
|
||||
@@ -522,6 +533,7 @@
|
||||
"tokenAuthFailedWithReasons": "Lamentamos, mas não está autorizado a participar nesta chamada. Razões possíveis: {{reason}}",
|
||||
"tokenAuthUnsupported": "O URL de token não é suportado.",
|
||||
"transcribing": "Transcrição",
|
||||
"unauthenticatedAccessDisabled": "Esta chamada requer autenticação. Por favor, inicie sessão para prosseguir.",
|
||||
"unlockRoom": "Retirar reunião $t(lockRoomPassword)",
|
||||
"user": "Utilizador",
|
||||
"userIdentifier": "Identificador do utilizador",
|
||||
@@ -566,13 +578,17 @@
|
||||
"downloadFailedDescription": "Por favor, tente novamente.",
|
||||
"downloadFailedTitle": "Falha no descarregar",
|
||||
"downloadFile": "Descarregar",
|
||||
"downloadStarted": "O download do ficheiro foi iniciado",
|
||||
"dragAndDrop": "Arraste e solte os ficheiros aqui ou em qualquer lugar do ecrã",
|
||||
"fileAlreadyUploaded": "O ficheiro já foi carregado para esta reunião.",
|
||||
"fileRemovedByOther": "O seu ficheiro '{{ fileName }}' foi removido",
|
||||
"fileTooLargeDescription": "Certifique-se de que o ficheiro não exceda {{ maxFileSize }}.",
|
||||
"fileTooLargeTitle": "O ficheiro selecionado é muito grande",
|
||||
"fileUploadProgress": "Progresso do envio do ficheiro",
|
||||
"fileUploadedSuccessfully": "Ficheiro carregado com sucesso",
|
||||
"newFileNotification": "{{ participantName }} partilhou '{{ fileName }}'",
|
||||
"removeFile": "Remover",
|
||||
"removeFileSuccess": "Ficheiro removido com sucesso",
|
||||
"uploadFailedDescription": "Por favor, tente novamente.",
|
||||
"uploadFailedTitle": "Falha ao carregar",
|
||||
"uploadFile": "Partilhar ficheiro"
|
||||
@@ -745,7 +761,8 @@
|
||||
"notificationTitle": "Sala de espera",
|
||||
"passwordJoinButton": "Solicitar",
|
||||
"title": "Sala de espera",
|
||||
"toggleLabel": "Ativar sala de espera"
|
||||
"toggleLabel": "Ativar sala de espera",
|
||||
"waitForModerator": "A conferência ainda não começou porque não chegou nenhum moderador. Se deseja tornar-se um moderador, faça login. Caso contrário, aguarde."
|
||||
},
|
||||
"localRecording": {
|
||||
"clientState": {
|
||||
@@ -862,6 +879,7 @@
|
||||
"oldElectronClientDescription1": "Parece estar a utilizar uma versão antiga do cliente Jitsi Meet que tem vulnerabilidades de segurança conhecidas. Por favor, certifique-se de que actualiza a nossa ",
|
||||
"oldElectronClientDescription2": "compilação mais recente",
|
||||
"oldElectronClientDescription3": " agora!",
|
||||
"openChat": "Abrir chat",
|
||||
"participantWantsToJoin": "Deseja juntar-se à reunião",
|
||||
"participantsWantToJoin": "Desejam juntar-se à reunião",
|
||||
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) removido por outro participante",
|
||||
@@ -960,6 +978,9 @@
|
||||
"by": "Por {{ name }}",
|
||||
"closeButton": "Fechar sondagem",
|
||||
"create": {
|
||||
"accessibilityLabel": {
|
||||
"send": "Enviar sondagem"
|
||||
},
|
||||
"addOption": "Adicionar opção",
|
||||
"answerPlaceholder": "Opção {{index}}",
|
||||
"cancel": "Cancelar",
|
||||
@@ -968,8 +989,7 @@
|
||||
"pollQuestion": "Pergunta de Sondagem",
|
||||
"questionPlaceholder": "Faça uma pergunta",
|
||||
"removeOption": "Remover opção",
|
||||
"save": "Guardar",
|
||||
"send": "Enviar"
|
||||
"save": "Guardar"
|
||||
},
|
||||
"errors": {
|
||||
"notUniqueOption": "As opções devem ser únicas"
|
||||
@@ -1338,7 +1358,7 @@
|
||||
"muteEveryoneElsesVideo": "Parar o vídeo de todos os outros",
|
||||
"muteEveryonesVideo": "Parar o vídeo de todos",
|
||||
"muteGUMPending": "A ligar o seu microfone",
|
||||
"noiseSuppression": "Supressão extra de ruído (BETA)",
|
||||
"noiseSuppression": "Supressão extra de ruído",
|
||||
"openChat": "Abrir chat",
|
||||
"participants": "Abrir painel de participantes. {{participantsCount}} participantes",
|
||||
"pip": "Mudar para o modo Picture-in-Picture",
|
||||
@@ -1376,6 +1396,20 @@
|
||||
"videounmute": "Iniciar câmara"
|
||||
},
|
||||
"addPeople": "Adicione pessoas à sua chamada",
|
||||
"advancedAudioSettings": {
|
||||
"aec": {
|
||||
"label": "Cancelamento de eco acústico"
|
||||
},
|
||||
"agc": {
|
||||
"label": "Controlo automático de ganho"
|
||||
},
|
||||
"ns": {
|
||||
"label": "Supressão de ruído"
|
||||
},
|
||||
"stereo": {
|
||||
"label": "Estéreo"
|
||||
}
|
||||
},
|
||||
"audioOnlyOff": "Desativar modo de largura de banda baixa",
|
||||
"audioOnlyOn": "Ativar modo de largura de banda baixa",
|
||||
"audioRoute": "Selecionar o dispositivo de som",
|
||||
@@ -1389,20 +1423,21 @@
|
||||
"closeParticipantsPane": "Fechar painel de participantes",
|
||||
"closeReactionsMenu": "Fechar menu de reações",
|
||||
"closedCaptions": "Legendas ocultas",
|
||||
"disableNoiseSuppression": "Desativar supressão de ruído extra (BETA)",
|
||||
"disableNoiseSuppression": "Desativar supressão de ruído extra",
|
||||
"disableReactionSounds": "Pode desactivar os sons de reacção para esta reunião",
|
||||
"documentClose": "Fechar documento partilhado",
|
||||
"documentOpen": "Abrir documento partilhado",
|
||||
"download": "Descarregar as nossas aplicações",
|
||||
"e2ee": "Criptografia ponta a ponta",
|
||||
"embedMeeting": "Incorporar reunião",
|
||||
"enableNoiseSuppression": "Ativar supressão extra de ruído (BETA)",
|
||||
"enableNoiseSuppression": "Ativar supressão extra de ruído",
|
||||
"endConference": "Terminar reunião para todos",
|
||||
"enterFullScreen": "Ver em ecrã completo",
|
||||
"enterTileView": "Ver em quadrícula",
|
||||
"exitFullScreen": "Sair de ecrã completo",
|
||||
"exitTileView": "Sair de quadrícula",
|
||||
"feedback": "Deixar comentários",
|
||||
"fileSharing": "Partilha de ficheiros",
|
||||
"giphy": "Ativar/Desativar o menu GIPHY",
|
||||
"hangup": "Sair da reunião",
|
||||
"help": "Ajuda",
|
||||
@@ -1431,13 +1466,14 @@
|
||||
"noAudioSignalDialInDesc": "Também pode marcar usando:",
|
||||
"noAudioSignalDialInLinkDesc": "Números de marcação",
|
||||
"noAudioSignalTitle": "Não há nenhuma entrada vinda do seu microfone!",
|
||||
"noiseSuppression": "Supressão extra de ruído (BETA)",
|
||||
"noiseSuppression": "Supressão extra de ruído",
|
||||
"noisyAudioInputDesc": "Parece que o seu microfone está a fazer barulho, por favor considere silenciar ou mudar de dispositivo.",
|
||||
"noisyAudioInputTitle": "Seu microfone parece estar barulhento!",
|
||||
"openChat": "Abrir chat",
|
||||
"openReactionsMenu": "Abrir menu de reações",
|
||||
"participants": "Participantes",
|
||||
"pip": "Entrar no modo Picture-in-Picture",
|
||||
"polls": "Sondagens",
|
||||
"privateMessage": "Enviar mensagem privada",
|
||||
"profile": "Editar o seu perfil",
|
||||
"raiseHand": "Levantar a mão",
|
||||
@@ -1447,6 +1483,7 @@
|
||||
"reactionHeart": "Enviar reação com coração",
|
||||
"reactionLaugh": "Enviar reação de risos",
|
||||
"reactionLike": "Enviar reação de aprovado",
|
||||
"reactionLove": "Enviar reação de amor",
|
||||
"reactionSilence": "Enviar reação de silêncio",
|
||||
"reactionSurprised": "Enviar reação de surpreendido",
|
||||
"reactions": "Reações",
|
||||
@@ -1598,6 +1635,8 @@
|
||||
"noMainParticipantsTitle": "Esta reunião ainda não começou.",
|
||||
"noVisitorLobby": "Não é possível aderir enquanto houver uma sala de espera activada para a reunião.",
|
||||
"notAllowedPromotion": "É necessário que um participante autorize primeiro o seu pedido.",
|
||||
"requestToJoin": "Mão levantada",
|
||||
"requestToJoinDescription": "A sua solicitação foi enviada aos moderadores. Aguarde um momento!",
|
||||
"title": "É um espectador na reunião"
|
||||
},
|
||||
"waitingMessage": "Participará na reunião assim que esta estiver em direto!"
|
||||
|
||||
@@ -1190,7 +1190,7 @@
|
||||
"signedIn": "Currently accessing calendar events for {{email}}. Click the Disconnect button below to stop accessing calendar events.",
|
||||
"title": "Calendar"
|
||||
},
|
||||
"chatWithPermissions": "Chat requires permission",
|
||||
"chatWithPermissions": "Disable chat for non-moderators",
|
||||
"desktopShareFramerate": "Desktop sharing frame rate",
|
||||
"desktopShareHighFpsWarning": "A higher frame rate for desktop sharing might affect your bandwidth. You need to restart the screen share for the new settings to take effect.",
|
||||
"desktopShareWarning": "You need to restart the screen share for the new settings to take effect.",
|
||||
|
||||
@@ -582,6 +582,7 @@ export interface IConfig {
|
||||
};
|
||||
serviceUrl?: string;
|
||||
sharedVideoAllowedURLDomains?: Array<string>;
|
||||
showChatPermissionsModeratorSetting?: boolean;
|
||||
sipInviteUrl?: string;
|
||||
speakerStats?: {
|
||||
disableSearch?: boolean;
|
||||
|
||||
@@ -220,6 +220,7 @@ export default [
|
||||
'resolution',
|
||||
'screenshotCapture',
|
||||
'securityUi',
|
||||
'showChatPermissionsModeratorSetting',
|
||||
'speakerStats',
|
||||
'startAudioMuted',
|
||||
'startAudioOnly',
|
||||
|
||||
@@ -8,13 +8,14 @@ import i18next from './i18next';
|
||||
*
|
||||
* @param {string} language - The language e.g. 'en', 'fr'.
|
||||
* @param {string} url - The url of the translation bundle.
|
||||
* @param {string} ns - The namespace of the translation bundle.
|
||||
* @returns {void}
|
||||
*/
|
||||
export async function changeLanguageBundle(language: string, url: string) {
|
||||
export async function changeLanguageBundle(language: string, url: string, ns = 'main') {
|
||||
const res = await fetch(url);
|
||||
const bundle = await res.json();
|
||||
|
||||
i18next.addResourceBundle(language, 'main', bundle, true, true);
|
||||
i18next.addResourceBundle(language, ns, bundle, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -58,6 +58,14 @@ export const DEFAULT_LANGUAGE = 'en';
|
||||
*/
|
||||
export const TRANSLATION_LANGUAGES_HEAD: Array<string> = [ DEFAULT_LANGUAGE ];
|
||||
|
||||
/**
|
||||
* The available/supported i18n namespaces.
|
||||
*
|
||||
* @public
|
||||
* @type {Array<string>}
|
||||
*/
|
||||
export const SUPPORTED_NS = [ 'main', 'languages', 'countries', 'translation-languages' ];
|
||||
|
||||
/**
|
||||
* The options to initialize i18next with.
|
||||
*
|
||||
@@ -81,7 +89,7 @@ const options: i18next.InitOptions = {
|
||||
escapeValue: false // not needed for react as it escapes by default
|
||||
},
|
||||
load: 'all',
|
||||
ns: [ 'main', 'languages', 'countries', 'translation-languages' ],
|
||||
ns: SUPPORTED_NS,
|
||||
react: {
|
||||
// re-render when a new resource bundle is added
|
||||
// @ts-expect-error. Fixed in i18next 19.6.1.
|
||||
|
||||
@@ -4,7 +4,7 @@ import MiddlewareRegistry from '../redux/MiddlewareRegistry';
|
||||
|
||||
import { I18NEXT_INITIALIZED, LANGUAGE_CHANGED } from './actionTypes';
|
||||
import { changeLanguageBundle } from './functions';
|
||||
import i18next from './i18next';
|
||||
import i18next, { SUPPORTED_NS } from './i18next';
|
||||
import logger from './logger';
|
||||
|
||||
/**
|
||||
@@ -19,9 +19,10 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
case LANGUAGE_CHANGED:
|
||||
case SET_DYNAMIC_BRANDING_DATA: {
|
||||
const { language } = i18next;
|
||||
const { labels } = action.type === SET_DYNAMIC_BRANDING_DATA
|
||||
const data = action.type === SET_DYNAMIC_BRANDING_DATA
|
||||
? action.value
|
||||
: store.getState()['features/dynamic-branding'];
|
||||
const labels = data?.labels;
|
||||
|
||||
if (language && labels?.[language]) {
|
||||
changeLanguageBundle(language, labels[language])
|
||||
@@ -30,6 +31,17 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
});
|
||||
}
|
||||
|
||||
SUPPORTED_NS.forEach(ns => {
|
||||
const nsLabels = data?.[`labels-${ns}`];
|
||||
|
||||
if (language && nsLabels?.[language]) {
|
||||
changeLanguageBundle(language, nsLabels[language], ns)
|
||||
.catch(err => {
|
||||
logger.log(`Error setting dynamic language bundle for ${ns}`, err);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Update transcription language, if applicable.
|
||||
if (action.type === SET_DYNAMIC_BRANDING_DATA) {
|
||||
const { defaultTranscriptionLanguage } = action.value;
|
||||
|
||||
@@ -10,6 +10,10 @@ import { parseURLParams } from '../util/parseURLParams';
|
||||
import { JWT_VALIDATION_ERRORS, MEET_FEATURES } from './constants';
|
||||
import logger from './logger';
|
||||
|
||||
/**
|
||||
* Note that this is just client-side code and it intentionally does not verify the signature of the JWT.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Retrieves the JSON Web Token (JWT), if any, defined by a specific
|
||||
* {@link URL}.
|
||||
|
||||
@@ -818,7 +818,7 @@ export const addPeopleFeatureControl = (stateful: IStateful) => {
|
||||
* @param {Function} dispatch - The Redux dispatch function.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export const setShareDialogVisiblity = (addPeopleFeatureEnabled: boolean, dispatch: IStore['dispatch']) => {
|
||||
export const setShareDialogVisiblity = (addPeopleFeatureEnabled: boolean | undefined, dispatch: IStore['dispatch']) => {
|
||||
if (addPeopleFeatureEnabled) {
|
||||
dispatch(toggleShareDialog(false));
|
||||
} else {
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
TRACK_UPDATED,
|
||||
TRACK_WILL_CREATE
|
||||
} from './actionTypes';
|
||||
import { toggleScreensharing } from './actions';
|
||||
import {
|
||||
createLocalTracksF,
|
||||
getCameraFacingMode,
|
||||
@@ -385,8 +386,22 @@ export function trackAdded(track: any) {
|
||||
const mediaType = track.getVideoType() === VIDEO_TYPE.DESKTOP
|
||||
? MEDIA_TYPE.SCREENSHARE
|
||||
: track.getType();
|
||||
|
||||
let isReceivingData, noDataFromSourceNotificationInfo, participantId;
|
||||
|
||||
// Make screen share toggle off listen to MediaStreamTrack "ended" event
|
||||
// when it's terminated via Android status bar chip.
|
||||
if (navigator.product === 'ReactNative') {
|
||||
const mediaStreamTrack = track?.getTrack?.();
|
||||
|
||||
if (mediaType === MEDIA_TYPE.SCREENSHARE) {
|
||||
const onEnded = () => dispatch(toggleScreensharing(false));
|
||||
|
||||
mediaStreamTrack.addEventListener('ended', onEnded);
|
||||
track._onEnded = onEnded;
|
||||
}
|
||||
}
|
||||
|
||||
if (local) {
|
||||
// Reset the no data from src notification state when we change the track, as it's context is set
|
||||
// on a per device basis.
|
||||
@@ -568,6 +583,16 @@ export function trackRemoved(track: any): {
|
||||
track.removeAllListeners(JitsiTrackEvents.TRACK_VIDEOTYPE_CHANGED);
|
||||
track.removeAllListeners(JitsiTrackEvents.NO_DATA_FROM_SOURCE);
|
||||
|
||||
// Remove MediaStreamTrack "ended" event.
|
||||
if (navigator.product === 'ReactNative') {
|
||||
const mediaStreamTrack = track?.getTrack?.();
|
||||
|
||||
if (track._onEnded) {
|
||||
mediaStreamTrack.removeEventListener('ended', track._onEnded);
|
||||
delete track._onEnded;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: TRACK_REMOVED,
|
||||
track: {
|
||||
|
||||
@@ -240,6 +240,13 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
case PARTICIPANT_JOINED:
|
||||
case PARTICIPANT_LEFT:
|
||||
case PARTICIPANT_UPDATED: {
|
||||
if (action.type === PARTICIPANT_LEFT) {
|
||||
const { privateMessageRecipient } = store.getState()['features/chat'];
|
||||
|
||||
if (action.participant?.id === privateMessageRecipient?.id) {
|
||||
store.dispatch(setPrivateMessageRecipient());
|
||||
}
|
||||
}
|
||||
if (_shouldNotifyPrivateRecipientsChanged(store, action)) {
|
||||
const result = next(action);
|
||||
|
||||
@@ -255,7 +262,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
const conference = getCurrentConference(state);
|
||||
|
||||
if (conference) {
|
||||
// There may be cases when we intend to send a private message but we forget to set the
|
||||
// There may be cases when we intend to send a private message, but we forgot to set the
|
||||
// recipient. This logic tries to mitigate this risk.
|
||||
const shouldSendPrivateMessageTo = _shouldSendPrivateMessageTo(state, action);
|
||||
|
||||
@@ -269,29 +276,32 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
isFromVisitor: shouldSendPrivateMessageTo.isFromVisitor,
|
||||
displayName: shouldSendPrivateMessageTo.name
|
||||
}));
|
||||
|
||||
// the dialog will take care of sending the message after user confirmation
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Sending the message if privacy notice doesn't need to be shown.
|
||||
|
||||
const { privateMessageRecipient, isLobbyChatActive, lobbyMessageRecipient }
|
||||
= state['features/chat'];
|
||||
|
||||
if (typeof APP !== 'undefined') {
|
||||
APP.API.notifySendingChatMessage(action.message, Boolean(privateMessageRecipient));
|
||||
}
|
||||
|
||||
if (isLobbyChatActive && lobbyMessageRecipient) {
|
||||
conference.sendLobbyMessage({
|
||||
type: LOBBY_CHAT_MESSAGE,
|
||||
message: action.message
|
||||
}, lobbyMessageRecipient.id);
|
||||
_persistSentPrivateMessage(store, lobbyMessageRecipient, action.message, true);
|
||||
} else if (privateMessageRecipient) {
|
||||
conference.sendPrivateTextMessage(privateMessageRecipient.id, action.message, 'body', isVisitorChatParticipant(privateMessageRecipient));
|
||||
_persistSentPrivateMessage(store, privateMessageRecipient, action.message);
|
||||
} else {
|
||||
// Sending the message if privacy notice doesn't need to be shown.
|
||||
|
||||
const { privateMessageRecipient, isLobbyChatActive, lobbyMessageRecipient }
|
||||
= state['features/chat'];
|
||||
|
||||
if (typeof APP !== 'undefined') {
|
||||
APP.API.notifySendingChatMessage(action.message, Boolean(privateMessageRecipient));
|
||||
}
|
||||
|
||||
if (isLobbyChatActive && lobbyMessageRecipient) {
|
||||
conference.sendLobbyMessage({
|
||||
type: LOBBY_CHAT_MESSAGE,
|
||||
message: action.message
|
||||
}, lobbyMessageRecipient.id);
|
||||
_persistSentPrivateMessage(store, lobbyMessageRecipient, action.message, true);
|
||||
} else if (privateMessageRecipient) {
|
||||
conference.sendPrivateTextMessage(privateMessageRecipient.id, action.message, 'body', isVisitorChatParticipant(privateMessageRecipient));
|
||||
_persistSentPrivateMessage(store, privateMessageRecipient, action.message);
|
||||
} else {
|
||||
conference.sendTextMessage(action.message);
|
||||
}
|
||||
conference.sendTextMessage(action.message);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import React, { useCallback } from 'react';
|
||||
import { connect, useDispatch } from 'react-redux';
|
||||
|
||||
import { IReduxState, IStore } from '../../../app/types';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import {
|
||||
getLocalParticipant,
|
||||
getParticipantById,
|
||||
@@ -10,14 +9,18 @@ import {
|
||||
hasRaisedHand,
|
||||
isParticipantModerator
|
||||
} from '../../../base/participants/functions';
|
||||
import { FakeParticipant, IParticipant } from '../../../base/participants/types';
|
||||
import { FakeParticipant } from '../../../base/participants/types';
|
||||
import {
|
||||
isParticipantAudioMuted,
|
||||
isParticipantVideoMuted
|
||||
} from '../../../base/tracks/functions.native';
|
||||
import { showContextMenuDetails, showSharedVideoMenu } from '../../actions.native';
|
||||
import type { MediaState } from '../../constants';
|
||||
import { getParticipantAudioMediaState, getParticipantVideoMediaState } from '../../functions';
|
||||
import {
|
||||
getParticipantAudioMediaState,
|
||||
getParticipantVideoMediaState,
|
||||
participantMatchesSearch
|
||||
} from '../../functions';
|
||||
|
||||
import ParticipantItem from './ParticipantItem';
|
||||
|
||||
@@ -59,9 +62,9 @@ interface IProps {
|
||||
_localVideoOwner: boolean;
|
||||
|
||||
/**
|
||||
* The participant ID.
|
||||
* Whether or not the participant name matches the search string.
|
||||
*/
|
||||
_participantID: string;
|
||||
_matchesSearch: boolean;
|
||||
|
||||
/**
|
||||
* True if the participant have raised hand.
|
||||
@@ -74,85 +77,55 @@ interface IProps {
|
||||
_videoMediaState: MediaState;
|
||||
|
||||
/**
|
||||
* The redux dispatch function.
|
||||
* The participant ID.
|
||||
*/
|
||||
dispatch: IStore['dispatch'];
|
||||
participantID: string;
|
||||
|
||||
/**
|
||||
* The participant.
|
||||
* Name of the participant we search for.
|
||||
*/
|
||||
participant?: IParticipant;
|
||||
searchString: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the MeetingParticipantItem component.
|
||||
*/
|
||||
class MeetingParticipantItem extends PureComponent<IProps> {
|
||||
|
||||
/**
|
||||
* Creates new MeetingParticipantItem instance.
|
||||
*
|
||||
* @param {IProps} props - The props of the component.
|
||||
*/
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this._onPress = this._onPress.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles MeetingParticipantItem press events.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onPress() {
|
||||
const {
|
||||
_fakeParticipant,
|
||||
_local,
|
||||
_localVideoOwner,
|
||||
_participantID,
|
||||
dispatch
|
||||
} = this.props;
|
||||
|
||||
const MeetingParticipantItem = ({
|
||||
_audioMediaState,
|
||||
_disableModeratorIndicator,
|
||||
_displayName,
|
||||
_fakeParticipant,
|
||||
_isModerator,
|
||||
_local,
|
||||
_localVideoOwner,
|
||||
_matchesSearch,
|
||||
_raisedHand,
|
||||
_videoMediaState,
|
||||
participantID
|
||||
}: IProps) => {
|
||||
const dispatch = useDispatch();
|
||||
const onPress = useCallback(() => {
|
||||
if (_fakeParticipant && _localVideoOwner) {
|
||||
dispatch(showSharedVideoMenu(_participantID));
|
||||
dispatch(showSharedVideoMenu(participantID));
|
||||
} else if (!_fakeParticipant) {
|
||||
dispatch(showContextMenuDetails(_participantID, _local));
|
||||
dispatch(showContextMenuDetails(participantID, _local));
|
||||
} // else no-op
|
||||
}, [ dispatch ]);
|
||||
|
||||
if (!_matchesSearch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
override render() {
|
||||
const {
|
||||
_audioMediaState,
|
||||
_disableModeratorIndicator,
|
||||
_displayName,
|
||||
_isModerator,
|
||||
_local,
|
||||
_participantID,
|
||||
_raisedHand,
|
||||
_videoMediaState
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<ParticipantItem
|
||||
audioMediaState = { _audioMediaState }
|
||||
disableModeratorIndicator = { _disableModeratorIndicator }
|
||||
displayName = { _displayName }
|
||||
isModerator = { _isModerator }
|
||||
local = { _local }
|
||||
onPress = { this._onPress }
|
||||
participantID = { _participantID }
|
||||
raisedHand = { _raisedHand }
|
||||
videoMediaState = { _videoMediaState } />
|
||||
);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<ParticipantItem
|
||||
audioMediaState = { _audioMediaState }
|
||||
disableModeratorIndicator = { _disableModeratorIndicator }
|
||||
displayName = { _displayName }
|
||||
isModerator = { _isModerator }
|
||||
local = { _local }
|
||||
onPress = { onPress }
|
||||
participantID = { participantID }
|
||||
raisedHand = { _raisedHand }
|
||||
videoMediaState = { _videoMediaState } />
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to the associated props for this component.
|
||||
@@ -163,8 +136,9 @@ class MeetingParticipantItem extends PureComponent<IProps> {
|
||||
* @returns {IProps}
|
||||
*/
|
||||
function mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
const { participant } = ownProps;
|
||||
const { participantID, searchString } = ownProps;
|
||||
const { ownerId } = state['features/shared-video'];
|
||||
const participant = getParticipantById(state, participantID);
|
||||
const localParticipantId = getLocalParticipant(state)?.id;
|
||||
const _isAudioMuted = isParticipantAudioMuted(participant, state);
|
||||
const _isVideoMuted = isParticipantVideoMuted(participant, state);
|
||||
@@ -173,23 +147,24 @@ function mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
const { disableModeratorIndicator } = state['features/base/config'];
|
||||
const raisedHand = hasRaisedHand(participant?.local
|
||||
? participant
|
||||
: getParticipantById(state, participant?.id)
|
||||
: getParticipantById(state, participantID)
|
||||
);
|
||||
const _matchesSearch = participantMatchesSearch(participant, searchString);
|
||||
|
||||
return {
|
||||
_audioMediaState: audioMediaState,
|
||||
_disableModeratorIndicator: disableModeratorIndicator,
|
||||
_displayName: getParticipantDisplayName(state, participant?.id),
|
||||
_displayName: getParticipantDisplayName(state, participantID),
|
||||
_fakeParticipant: participant?.fakeParticipant,
|
||||
_isAudioMuted,
|
||||
_isModerator: isParticipantModerator(participant),
|
||||
_local: Boolean(participant?.local),
|
||||
_localVideoOwner: Boolean(ownerId === localParticipantId),
|
||||
_participantID: participant?.id,
|
||||
_matchesSearch,
|
||||
_raisedHand: raisedHand,
|
||||
_videoMediaState: videoMediaState
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export default translate(connect(mapStateToProps)(MeetingParticipantItem));
|
||||
// @ts-ignore
|
||||
export default connect(mapStateToProps)(MeetingParticipantItem);
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FlatList, Text, TextStyle, View } from 'react-native';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { connect, useDispatch } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import Icon from '../../../base/icons/components/Icon';
|
||||
import { IconAddUser } from '../../../base/icons/svg';
|
||||
import {
|
||||
addPeopleFeatureControl,
|
||||
getLocalParticipant,
|
||||
getParticipantCountWithFake,
|
||||
getRemoteParticipants,
|
||||
getParticipantById,
|
||||
isScreenShareParticipant,
|
||||
setShareDialogVisiblity
|
||||
} from '../../../base/participants/functions';
|
||||
import Button from '../../../base/ui/components/native/Button';
|
||||
@@ -23,57 +22,70 @@ import {
|
||||
import { doInvitePeople } from '../../../invite/actions.native';
|
||||
import { getInviteOthersControl } from '../../../share-room/functions';
|
||||
import { iAmVisitor } from '../../../visitors/functions';
|
||||
import { participantMatchesSearch, shouldRenderInviteButton } from '../../functions';
|
||||
import { getSortedParticipantIds, shouldRenderInviteButton } from '../../functions';
|
||||
|
||||
import MeetingParticipantItem from './MeetingParticipantItem';
|
||||
import styles from './styles';
|
||||
|
||||
interface IProps {
|
||||
currentRoom?: {
|
||||
jid: string;
|
||||
name: string;
|
||||
};
|
||||
iconColor: string;
|
||||
isAddPeopleFeatureEnabled?: boolean | undefined;
|
||||
isShareDialogVisible: boolean;
|
||||
participantsCount?: number;
|
||||
showInviteButton?: boolean;
|
||||
sortedParticipantIds?: Array<string>;
|
||||
visitorsCount?: number | undefined;
|
||||
}
|
||||
|
||||
|
||||
const MeetingParticipantList = ({
|
||||
currentRoom,
|
||||
iconColor,
|
||||
isAddPeopleFeatureEnabled,
|
||||
isShareDialogVisible,
|
||||
participantsCount,
|
||||
showInviteButton,
|
||||
sortedParticipantIds = [],
|
||||
visitorsCount
|
||||
}: IProps): any => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [ searchString, setSearchString ] = useState('');
|
||||
|
||||
const MeetingParticipantList = () => {
|
||||
const currentRoomId = useSelector(getCurrentRoomId);
|
||||
const currentRoom = useSelector(getBreakoutRooms)[currentRoomId];
|
||||
const dispatch = useDispatch();
|
||||
const inviteOthersControl = useSelector(getInviteOthersControl);
|
||||
const isAddPeopleFeatureEnabled = useSelector(addPeopleFeatureControl);
|
||||
const keyExtractor
|
||||
= useCallback((e: undefined, i: number) => i.toString(), []);
|
||||
const localParticipant = useSelector(getLocalParticipant);
|
||||
const _iAmVisitor = useSelector(iAmVisitor);
|
||||
|
||||
const keyExtractor = useCallback((e: undefined, i: number) => i.toString(), []);
|
||||
const onInvite = useCallback(() => {
|
||||
setShareDialogVisiblity(isAddPeopleFeatureEnabled, dispatch);
|
||||
dispatch(doInvitePeople());
|
||||
}, [ dispatch ]);
|
||||
const [ searchString, setSearchString ] = useState('');
|
||||
const onSearchStringChange = useCallback((text: string) =>
|
||||
setSearchString(text), []);
|
||||
const participantsCount = useSelector(getParticipantCountWithFake);
|
||||
const remoteParticipants = useSelector(getRemoteParticipants);
|
||||
const renderParticipant = ({ item/* , index, separators */ }: any) => {
|
||||
const participant = item === localParticipant?.id
|
||||
? localParticipant : remoteParticipants.get(item);
|
||||
|
||||
if (participantMatchesSearch(participant, searchString)) {
|
||||
return (
|
||||
<MeetingParticipantItem
|
||||
key = { item }
|
||||
participant = { participant } />
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
const showInviteButton = useSelector(shouldRenderInviteButton);
|
||||
const sortedRemoteParticipants = useSelector(
|
||||
(state: IReduxState) => state['features/filmstrip'].remoteParticipants);
|
||||
const { t } = useTranslation();
|
||||
const title = currentRoom?.name
|
||||
? `${currentRoom.name} (${participantsCount})`
|
||||
: t('participantsPane.headings.participantsList',
|
||||
{ count: participantsCount });
|
||||
const { color, shareDialogVisible } = inviteOthersControl;
|
||||
const visitorsLabelText = visitorsCount && visitorsCount > 0
|
||||
? t('participantsPane.headings.visitors', { count: visitorsCount })
|
||||
: undefined;
|
||||
|
||||
const renderParticipant = ({ item }: any) => (
|
||||
<MeetingParticipantItem
|
||||
key = { item }
|
||||
participantID = { item }
|
||||
searchString = { searchString } />
|
||||
);
|
||||
|
||||
return (
|
||||
<View style = { styles.meetingListContainer }>
|
||||
<Text style = { styles.visitorsLabel as TextStyle }>
|
||||
{ visitorsLabelText }
|
||||
</Text>
|
||||
<Text
|
||||
style = { styles.meetingListDescription as TextStyle }>
|
||||
{ title }
|
||||
@@ -82,12 +94,12 @@ const MeetingParticipantList = () => {
|
||||
showInviteButton
|
||||
&& <Button
|
||||
accessibilityLabel = 'participantsPane.actions.invite'
|
||||
disabled = { shareDialogVisible }
|
||||
disabled = { isShareDialogVisible }
|
||||
|
||||
// eslint-disable-next-line react/jsx-no-bind, no-confusing-arrow
|
||||
icon = { () => (
|
||||
<Icon
|
||||
color = { color }
|
||||
color = { iconColor }
|
||||
size = { 20 }
|
||||
src = { IconAddUser } />
|
||||
) }
|
||||
@@ -105,10 +117,7 @@ const MeetingParticipantList = () => {
|
||||
placeholder = { t('participantsPane.search') }
|
||||
value = { searchString } />
|
||||
<FlatList
|
||||
data = { _iAmVisitor
|
||||
? [ ...sortedRemoteParticipants ]
|
||||
: [ localParticipant?.id, ...sortedRemoteParticipants ] as Array<any>
|
||||
}
|
||||
data = { sortedParticipantIds as Array<any> }
|
||||
keyExtractor = { keyExtractor }
|
||||
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
@@ -118,4 +127,48 @@ const MeetingParticipantList = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default MeetingParticipantList;
|
||||
/**
|
||||
* Maps (parts of) the redux state to the associated props for this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {IProps}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
let sortedParticipantIds: any = getSortedParticipantIds(state);
|
||||
|
||||
const _iAmVisitor = iAmVisitor(state);
|
||||
|
||||
sortedParticipantIds = sortedParticipantIds.filter((id: any) => {
|
||||
const participant = getParticipantById(state, id);
|
||||
|
||||
if (_iAmVisitor && participant?.local) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !isScreenShareParticipant(participant);
|
||||
});
|
||||
|
||||
const currentRoomId = getCurrentRoomId(state);
|
||||
const currentRoom = getBreakoutRooms(state)[currentRoomId];
|
||||
const inviteOthersControl = getInviteOthersControl(state);
|
||||
const { color, shareDialogVisible } = inviteOthersControl;
|
||||
const isAddPeopleFeatureEnabled = addPeopleFeatureControl(state);
|
||||
const participantsCount = sortedParticipantIds.length;
|
||||
const showInviteButton = shouldRenderInviteButton(state);
|
||||
const visitorsCount = state['features/visitors']?.count || 0;
|
||||
|
||||
return {
|
||||
currentRoom,
|
||||
iconColor: color,
|
||||
inviteOthersControl,
|
||||
isAddPeopleFeatureEnabled,
|
||||
isShareDialogVisible: shareDialogVisible,
|
||||
participantsCount,
|
||||
showInviteButton,
|
||||
sortedParticipantIds,
|
||||
visitorsCount
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(MeetingParticipantList);
|
||||
|
||||
@@ -12,11 +12,6 @@ import VisitorsList from './VisitorsList';
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
/**
|
||||
* Participants pane.
|
||||
*
|
||||
* @returns {React$Element<any>}
|
||||
*/
|
||||
const ParticipantsPane = () => {
|
||||
const isLocalModerator = useSelector(isLocalParticipantModerator);
|
||||
const keyExtractor
|
||||
|
||||
@@ -9,7 +9,7 @@ const participantListDescription = {
|
||||
fontSize: 15,
|
||||
fontWeight: 'bold',
|
||||
marginLeft: BaseTheme.spacing[2],
|
||||
paddingVertical: BaseTheme.spacing[2],
|
||||
marginVertical: BaseTheme.spacing[2],
|
||||
position: 'relative',
|
||||
width: '70%'
|
||||
};
|
||||
@@ -275,13 +275,11 @@ export default {
|
||||
},
|
||||
|
||||
inputContainer: {
|
||||
marginLeft: BaseTheme.spacing[3],
|
||||
marginRight: BaseTheme.spacing[3],
|
||||
marginBottom: BaseTheme.spacing[4]
|
||||
marginHorizontal: BaseTheme.spacing[3],
|
||||
marginBottom: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
centerInput: {
|
||||
paddingRight: BaseTheme.spacing[3],
|
||||
textAlign: 'center'
|
||||
},
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import { getBreakoutRooms, getCurrentRoomId, isInBreakoutRoom } from '../../../b
|
||||
import { isButtonEnabled, showOverflowDrawer } from '../../../toolbox/functions.web';
|
||||
import { iAmVisitor } from '../../../visitors/functions';
|
||||
import { getSortedParticipantIds, isCurrentRoomRenamable, shouldRenderInviteButton } from '../../functions';
|
||||
import { useParticipantDrawer } from '../../hooks';
|
||||
import { useParticipantDrawer } from '../../hooks.web';
|
||||
import RenameButton from '../breakout-rooms/components/web/RenameButton';
|
||||
|
||||
import { InviteButton } from './InviteButton';
|
||||
|
||||
@@ -41,7 +41,8 @@ const useStyles = makeStyles<{ deviceStatusType?: string; }>()((theme, { deviceS
|
||||
width: '16px',
|
||||
height: '16px',
|
||||
borderRadius: '100%',
|
||||
backgroundColor: deviceStatusType === 'ok' ? theme.palette.success01 : ColorPalette.darkGrey
|
||||
backgroundColor: deviceStatusType === 'ok' ? theme.palette.success01 : ColorPalette.darkGrey,
|
||||
flexShrink: 0
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -180,8 +180,8 @@ const LocalRecordingManager: ILocalRecordingManager = {
|
||||
this.selfRecording.withVideo = Boolean(videoTrack);
|
||||
const localTracks: MediaStreamTrack[] = [];
|
||||
|
||||
audioTrack && localTracks.push(audioTrack);
|
||||
videoTrack && localTracks.push(videoTrack);
|
||||
audioTrack && localTracks.push(audioTrack.clone());
|
||||
videoTrack && localTracks.push(videoTrack.clone());
|
||||
this.stream = new MediaStream(localTracks);
|
||||
} else {
|
||||
if (supportsCaptureHandle) {
|
||||
@@ -282,6 +282,9 @@ const LocalRecordingManager: ILocalRecordingManager = {
|
||||
this.audioContext = undefined;
|
||||
this.audioDestination = undefined;
|
||||
this.startTime = undefined;
|
||||
this.stream = undefined;
|
||||
this.selfRecording.on = false;
|
||||
this.selfRecording.withVideo = false;
|
||||
|
||||
if (this.writableStream) {
|
||||
try {
|
||||
|
||||
@@ -27,11 +27,6 @@ export interface IProps extends AbstractDialogTabProps, WithTranslation {
|
||||
*/
|
||||
classes?: Partial<Record<keyof ReturnType<typeof styles>, string>>;
|
||||
|
||||
/**
|
||||
* Whether to hide chat with permissions.
|
||||
*/
|
||||
disableChatWithPermissions: boolean;
|
||||
|
||||
/**
|
||||
* If set hides the reactions moderation setting.
|
||||
*/
|
||||
@@ -57,6 +52,11 @@ export interface IProps extends AbstractDialogTabProps, WithTranslation {
|
||||
*/
|
||||
followMeRecorderEnabled: boolean;
|
||||
|
||||
/**
|
||||
* Whether to hide chat with permissions.
|
||||
*/
|
||||
hideChatWithPermissions: boolean;
|
||||
|
||||
/**
|
||||
* Whether or not the user has selected the Start Audio Muted feature to be
|
||||
* enabled.
|
||||
@@ -210,12 +210,12 @@ class ModeratorTab extends AbstractDialogTab<IProps, any> {
|
||||
const {
|
||||
audioModerationEnabled,
|
||||
chatWithPermissionsEnabled,
|
||||
disableChatWithPermissions,
|
||||
disableReactionsModeration,
|
||||
followMeActive,
|
||||
followMeEnabled,
|
||||
followMeRecorderActive,
|
||||
followMeRecorderEnabled,
|
||||
hideChatWithPermissions,
|
||||
startAudioMuted,
|
||||
startVideoMuted,
|
||||
startReactionsMuted,
|
||||
@@ -266,7 +266,7 @@ class ModeratorTab extends AbstractDialogTab<IProps, any> {
|
||||
label = { t('settings.startReactionsMuted') }
|
||||
name = 'start-reactions-muted'
|
||||
onChange = { this._onStartReactionsMutedChanged } /> }
|
||||
{ !disableChatWithPermissions
|
||||
{ !hideChatWithPermissions
|
||||
&& <Checkbox
|
||||
checked = { chatWithPermissionsEnabled }
|
||||
className = { classes.checkbox }
|
||||
|
||||
@@ -162,12 +162,12 @@ export function getModeratorTabProps(stateful: IStateful) {
|
||||
} = state['features/base/conference'];
|
||||
const { followMeEnabled, followMeRecorderEnabled } = state['features/follow-me'];
|
||||
const { groupChatWithPermissions } = state['features/chat'];
|
||||
const { disableReactionsModeration } = state['features/base/config'];
|
||||
const { showChatPermissionsModeratorSetting, disableReactionsModeration } = state['features/base/config'];
|
||||
const followMeActive = isFollowMeActive(state);
|
||||
const followMeRecorderActive = isFollowMeRecorderActive(state);
|
||||
const showModeratorSettings = shouldShowModeratorSettings(state);
|
||||
const conferenceMetadata = conference?.getMetadataHandler()?.getMetadata();
|
||||
const disableChatWithPermissions = !conferenceMetadata?.allownersEnabled;
|
||||
const hideChatWithPermissions = !showChatPermissionsModeratorSetting || conferenceMetadata?.allownersEnabled;
|
||||
const isAudioModerationEnabled = isEnabledFromState(MEDIA_TYPE.AUDIO, state);
|
||||
const isVideoModerationEnabled = isEnabledFromState(MEDIA_TYPE.VIDEO, state);
|
||||
|
||||
@@ -177,12 +177,12 @@ export function getModeratorTabProps(stateful: IStateful) {
|
||||
videoModerationEnabled: isVideoModerationEnabled,
|
||||
chatWithPermissionsEnabled: Boolean(groupChatWithPermissions),
|
||||
showModeratorSettings: Boolean(conference && showModeratorSettings),
|
||||
disableChatWithPermissions: Boolean(disableChatWithPermissions),
|
||||
disableReactionsModeration: Boolean(disableReactionsModeration),
|
||||
followMeActive: Boolean(conference && followMeActive),
|
||||
followMeEnabled: Boolean(conference && followMeEnabled),
|
||||
followMeRecorderActive: Boolean(conference && followMeRecorderActive),
|
||||
followMeRecorderEnabled: Boolean(conference && followMeRecorderEnabled),
|
||||
hideChatWithPermissions: Boolean(hideChatWithPermissions),
|
||||
startReactionsMuted: Boolean(conference && startReactionsMuted),
|
||||
startAudioMuted: Boolean(conference && startAudioMutedPolicy),
|
||||
startVideoMuted: Boolean(conference && startVideoMutedPolicy)
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"cs": "cs-CZ",
|
||||
"da": "da-DK",
|
||||
"de": "de-DE",
|
||||
"dsb": "dsb-DE",
|
||||
"el": "el-GR",
|
||||
"enGB": "en-GB",
|
||||
"es": "es-ES",
|
||||
|
||||
@@ -203,7 +203,7 @@ function on_message(event)
|
||||
local room = get_room_by_name_and_subdomain(session.jitsi_web_query_room, session.jitsi_web_query_prefix);
|
||||
|
||||
if not room then
|
||||
module:log('warn', 'No room found found for %s/%s',
|
||||
module:log('warn', 'No room found for %s/%s',
|
||||
session.jitsi_web_query_prefix, session.jitsi_web_query_room);
|
||||
return false;
|
||||
end
|
||||
|
||||
@@ -57,7 +57,7 @@ function on_message(event)
|
||||
local room = get_room_by_name_and_subdomain(session.jitsi_web_query_room, session.jitsi_web_query_prefix);
|
||||
|
||||
if not room then
|
||||
module:log('warn', 'No room found found for %s/%s',
|
||||
module:log('warn', 'No room found for %s/%s',
|
||||
session.jitsi_web_query_prefix, session.jitsi_web_query_room);
|
||||
return false;
|
||||
end
|
||||
|
||||
@@ -19,7 +19,7 @@ local function on_message(event)
|
||||
-- this should already been through domain mapper and this should be the real room jid [tenant]name format
|
||||
local room = get_room_from_jid(stanza.attr.to);
|
||||
if not room then
|
||||
module:log('warn', 'No room found found for %s', stanza.attr.to);
|
||||
module:log('warn', 'No room found for %s', stanza.attr.to);
|
||||
return;
|
||||
end
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ function on_message(event)
|
||||
-- get room name with tenant and find room.
|
||||
local room = get_room_by_name_and_subdomain(session.jitsi_web_query_room, session.jitsi_web_query_prefix);
|
||||
if not room then
|
||||
module:log('warn', 'No room found found for %s/%s',
|
||||
module:log('warn', 'No room found for %s/%s',
|
||||
session.jitsi_web_query_prefix, session.jitsi_web_query_room);
|
||||
return;
|
||||
end
|
||||
@@ -138,7 +138,7 @@ function poll_created(event)
|
||||
-- get room name with tenant and find room.
|
||||
local room = get_room_by_name_and_subdomain(session.jitsi_web_query_room, session.jitsi_web_query_prefix);
|
||||
if not room then
|
||||
module:log('warn', 'No room found found for %s/%s',
|
||||
module:log('warn', 'No room found for %s/%s',
|
||||
session.jitsi_web_query_prefix, session.jitsi_web_query_room);
|
||||
return false;
|
||||
end
|
||||
|
||||
@@ -30,7 +30,8 @@ module:hook("muc-room-created", function(event)
|
||||
local room = event.room;
|
||||
|
||||
if room.jitsiMetadata then
|
||||
room.jitsiMetadata.allownersEnabled = true;
|
||||
-- indicates whether all participants in the room will be moderators
|
||||
room.jitsiMetadata.allownersEnabled = not is_moderated(room.jid);
|
||||
end
|
||||
end, -2); -- room_metadata should run before this module on -1
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ function on_message(event)
|
||||
-- get room name with tenant and find room
|
||||
local room = get_room_by_name_and_subdomain(session.jitsi_web_query_room, session.jitsi_web_query_prefix);
|
||||
if not room then
|
||||
module:log('warn', 'No room found found for %s/%s',
|
||||
module:log('warn', 'No room found for %s/%s',
|
||||
session.jitsi_web_query_prefix, session.jitsi_web_query_room);
|
||||
return false;
|
||||
end
|
||||
|
||||
@@ -175,7 +175,7 @@ local function filterTranscriptionResult(event)
|
||||
|
||||
local room = get_room_from_jid(stanza.attr.to);
|
||||
if not room then
|
||||
module:log('warn', 'No room found found for %s', stanza.attr.to);
|
||||
module:log('warn', 'No room found for %s', stanza.attr.to);
|
||||
return;
|
||||
end
|
||||
|
||||
|
||||
@@ -143,17 +143,15 @@ end
|
||||
|
||||
occupant = main_room:get_occupant_by_real_jid(occupant_jid);
|
||||
|
||||
if main_room._data.breakout_rooms_active then
|
||||
if main_room._data.breakout_rooms_active and not occupant then
|
||||
-- let's find is this participant in the main room or in some breakout room
|
||||
if not occupant then
|
||||
-- not in main room, let's check breakout rooms
|
||||
for breakout_room_jid, subject in pairs(main_room._data.breakout_rooms or {}) do
|
||||
local breakout_room = get_room_from_jid(breakout_room_jid);
|
||||
occupant = breakout_room:get_occupant_by_real_jid(occupant_jid);
|
||||
if occupant then
|
||||
room = breakout_room;
|
||||
break;
|
||||
end
|
||||
-- not in main room, let's check breakout rooms
|
||||
for breakout_room_jid, subject in pairs(main_room._data.breakout_rooms or {}) do
|
||||
local breakout_room = get_room_from_jid(breakout_room_jid);
|
||||
occupant = breakout_room:get_occupant_by_real_jid(occupant_jid);
|
||||
if occupant then
|
||||
room = breakout_room;
|
||||
break;
|
||||
end
|
||||
end
|
||||
else
|
||||
@@ -167,7 +165,7 @@ end
|
||||
end
|
||||
|
||||
if not room then
|
||||
module:log('warn', 'No room found found for %s %s', session.jitsi_web_query_room, session.jitsi_web_query_prefix);
|
||||
module:log('warn', 'No room found for %s %s', session.jitsi_web_query_room, session.jitsi_web_query_prefix);
|
||||
return;
|
||||
end
|
||||
|
||||
|
||||
@@ -151,7 +151,7 @@ function on_message(event)
|
||||
local room = get_room_from_jid(room_jid_match_rewrite(roomJid));
|
||||
|
||||
if not room then
|
||||
module:log('warn', 'No room found found for %s/%s',
|
||||
module:log('warn', 'No room found for %s/%s',
|
||||
session.jitsi_web_query_prefix, session.jitsi_web_query_room);
|
||||
return false;
|
||||
end
|
||||
|
||||
@@ -74,17 +74,7 @@ local function verify_user(session, stanza)
|
||||
or allowlist:contains(user_bare_jid)
|
||||
|
||||
-- allow main participants in visitor mode
|
||||
or session.type == 's2sin'
|
||||
|
||||
-- Let Jigasi or transcriber pass throw
|
||||
or util.is_sip_jigasi(stanza)
|
||||
or util.is_transcriber_jigasi(stanza)
|
||||
|
||||
-- is jibri
|
||||
or util.is_jibri(user_jid)
|
||||
|
||||
-- Let Sip Jibri pass through
|
||||
or util.is_sip_jibri_join(stanza) then
|
||||
or session.type == 's2sin' then
|
||||
if DEBUG then module:log("debug", "Token not required from user in allow list: %s", user_jid); end
|
||||
return true;
|
||||
end
|
||||
|
||||
35
tests/specs/misc/xmppConferenceRequest.spec.ts
Normal file
35
tests/specs/misc/xmppConferenceRequest.spec.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { setTestProperties } from '../../helpers/TestProperties';
|
||||
import { ensureOneParticipant } from '../../helpers/participants';
|
||||
|
||||
setTestProperties(__filename, {
|
||||
description: 'Test that conference requests work over XMPP',
|
||||
usesBrowsers: [ 'p1' ]
|
||||
});
|
||||
|
||||
describe('XMPP Conference Request', () => {
|
||||
it('join with conferenceRequestUrl disabled', async () => {
|
||||
await ensureOneParticipant({
|
||||
skipWaitToJoin: true,
|
||||
configOverwrite: {
|
||||
prejoinConfig: {
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const { p1 } = ctx;
|
||||
|
||||
// Update config before joining, because this option cannot be overridden with URL params.
|
||||
await p1.driver.execute(async () => {
|
||||
config.conferenceRequestUrl = '';
|
||||
APP.store.dispatch({
|
||||
type: 'OVERWRITE_CONFIG',
|
||||
config
|
||||
});
|
||||
});
|
||||
|
||||
await p1.getPreJoinScreen().getJoinButton().click();
|
||||
await p1.waitForMucJoinedOrError();
|
||||
expect(await p1.isInMuc()).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -110,7 +110,6 @@ describe('Lobby', () => {
|
||||
await p3.waitForSendReceiveData();
|
||||
await p3.waitForRemoteStreams(2);
|
||||
|
||||
// now check third one display name in the room, is the one set in the prejoin screen
|
||||
// now check third one display name in the room, is the one set in the prejoin screen
|
||||
const name = await p1.getFilmstrip().getRemoteDisplayName(await p3.getEndpointId());
|
||||
|
||||
|
||||
@@ -323,7 +323,7 @@ export const config: WebdriverIO.MultiremoteConfig = {
|
||||
* @param {Object} context - The context object.
|
||||
*/
|
||||
beforeTest(test, context) {
|
||||
// Use the directory under 'tests/specs' as the parent suite
|
||||
// Extract directory to use as parent suite and describe block name as suite
|
||||
const dirMatch = test.file.match(/.*\/tests\/specs\/([^\/]+)\//);
|
||||
const dir = dirMatch ? dirMatch[1] : false;
|
||||
const fileMatch = test.file.match(/.*\/tests\/specs\/(.*)/);
|
||||
@@ -337,8 +337,10 @@ export const config: WebdriverIO.MultiremoteConfig = {
|
||||
AllureReporter.addLink(`https://github.com/jitsi/jitsi-meet/blob/master/tests/specs/${file}`, 'Code');
|
||||
}
|
||||
|
||||
if (dir) {
|
||||
// For Allure v3: set directory as parent suite and describe block as suite
|
||||
if (dir && test.parent) {
|
||||
AllureReporter.addParentSuite(dir);
|
||||
AllureReporter.addSuite(test.parent);
|
||||
}
|
||||
|
||||
if (ctx.skipSuiteTests) {
|
||||
@@ -455,6 +457,34 @@ export const config: WebdriverIO.MultiremoteConfig = {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
onComplete() {
|
||||
// Clean up duplicate parentSuite labels from Allure results
|
||||
const resultsDir = `${TEST_RESULTS_DIR}/allure-results`;
|
||||
const resultFiles = fs.readdirSync(resultsDir).filter(f => f.endsWith('-result.json'));
|
||||
|
||||
resultFiles.forEach(file => {
|
||||
const filePath = path.join(resultsDir, file);
|
||||
const result = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||
|
||||
// Keep only the LAST parentSuite label (the manual one with directory name)
|
||||
// Remove the automatic one from WebDriverIO (describe block name)
|
||||
const parentSuiteLabels: any[] = [];
|
||||
const otherLabels: any[] = [];
|
||||
|
||||
result.labels.forEach((label: any) => {
|
||||
if (label.name === 'parentSuite') {
|
||||
parentSuiteLabels.push(label);
|
||||
} else {
|
||||
otherLabels.push(label);
|
||||
}
|
||||
});
|
||||
|
||||
// Keep only the last parentSuite (the directory name we manually added)
|
||||
if (parentSuiteLabels.length > 1) {
|
||||
result.labels = [ ...otherLabels, parentSuiteLabels[parentSuiteLabels.length - 1] ];
|
||||
fs.writeFileSync(filePath, JSON.stringify(result));
|
||||
}
|
||||
});
|
||||
|
||||
const reportError = new Error('Could not generate Allure report');
|
||||
const generation = allure([
|
||||
'generate', `${TEST_RESULTS_DIR}/allure-results`,
|
||||
|
||||
Reference in New Issue
Block a user