mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-01-06 06:42:28 +00:00
Compare commits
1 Commits
dependabot
...
debug-brek
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff6f0ee949 |
@@ -14,12 +14,3 @@ trim_trailing_whitespace = false
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
|
||||
[*.{java,kt}]
|
||||
indent_size = 4
|
||||
|
||||
[*.xml]
|
||||
indent_size = 2
|
||||
|
||||
[*.{swift,m,mm,h}]
|
||||
indent_size = 4
|
||||
|
||||
12
.github/dependabot.yml
vendored
12
.github/dependabot.yml
vendored
@@ -1,12 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
# Monitor GitHub Actions versions
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "github-actions"
|
||||
commit-message:
|
||||
prefix: "chore(ci)"
|
||||
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@v4
|
||||
|
||||
- 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@v4
|
||||
- uses: actions/setup-node@v4
|
||||
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@v4
|
||||
- uses: actions/setup-node@v4
|
||||
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@v4
|
||||
- uses: actions/setup-node@v4
|
||||
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@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
@@ -103,10 +103,10 @@ jobs:
|
||||
android-sdk-build:
|
||||
name: Build mobile SDK (Android)
|
||||
runs-on: ubuntu-latest
|
||||
container: reactnativecommunity/react-native-android:v15.0
|
||||
container: reactnativecommunity/react-native-android:v18.0
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
@@ -123,8 +123,8 @@ jobs:
|
||||
name: Build mobile SDK (iOS)
|
||||
runs-on: macos-15
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
@@ -173,8 +173,8 @@ jobs:
|
||||
name: Test Debian packages build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
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@v10
|
||||
- uses: actions/stale@v8
|
||||
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.'
|
||||
|
||||
@@ -37,6 +37,7 @@ public class JitsiMeetActivityDelegate {
|
||||
* React Native module.
|
||||
*/
|
||||
private static PermissionListener permissionListener;
|
||||
private static Callback permissionsCallback;
|
||||
|
||||
/**
|
||||
* Tells whether or not the permissions request is currently in progress.
|
||||
@@ -141,6 +142,11 @@ public class JitsiMeetActivityDelegate {
|
||||
if (reactInstanceManager != null) {
|
||||
reactInstanceManager.onHostResume(activity, new DefaultHardwareBackBtnHandlerImpl(activity));
|
||||
}
|
||||
|
||||
if (permissionsCallback != null) {
|
||||
permissionsCallback.invoke();
|
||||
permissionsCallback = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -163,10 +169,15 @@ public class JitsiMeetActivityDelegate {
|
||||
|
||||
public static void onRequestPermissionsResult(
|
||||
final int requestCode, final String[] permissions, final int[] grantResults) {
|
||||
// Invoke the callback immediately
|
||||
if (permissionListener != null && permissionListener.onRequestPermissionsResult(requestCode, permissions, grantResults)) {
|
||||
permissionListener = null;
|
||||
}
|
||||
permissionsCallback = new Callback() {
|
||||
@Override
|
||||
public void invoke(Object... args) {
|
||||
if (permissionListener != null
|
||||
&& permissionListener.onRequestPermissionsResult(requestCode, permissions, grantResults)) {
|
||||
permissionListener = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static void requestPermissions(Activity activity, String[] permissions, int requestCode, PermissionListener listener) {
|
||||
|
||||
@@ -99,7 +99,6 @@ public class JitsiMeetOngoingConferenceService extends Service implements Ongoin
|
||||
|
||||
public static void launch(Context context, HashMap<String, Object> extraData) {
|
||||
List<String> permissionsList = new ArrayList<>();
|
||||
Activity activity = (Activity) context;
|
||||
|
||||
PermissionListener listener = new PermissionListener() {
|
||||
@Override
|
||||
@@ -135,7 +134,7 @@ public class JitsiMeetOngoingConferenceService extends Service implements Ongoin
|
||||
|
||||
if (permissionsArray.length > 0) {
|
||||
JitsiMeetActivityDelegate.requestPermissions(
|
||||
activity,
|
||||
(Activity) context,
|
||||
permissionsArray,
|
||||
PERMISSIONS_REQUEST_CODE,
|
||||
listener
|
||||
@@ -160,20 +159,12 @@ public class JitsiMeetOngoingConferenceService extends Service implements Ongoin
|
||||
stopSelf();
|
||||
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
|
||||
} else {
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK | ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE);
|
||||
} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
|
||||
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK);
|
||||
} else {
|
||||
startForeground(NOTIFICATION_ID, notification);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Handle ForegroundServiceStartNotAllowedException when app is in background and cannot start foreground service.
|
||||
// See: https://developer.android.com/develop/background-work/services/fgs/restrictions-bg-start#wiu-restrictions
|
||||
JitsiMeetLogger.w(TAG + " Failed to start foreground service", e);
|
||||
stopSelf();
|
||||
return;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK | ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE);
|
||||
} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
|
||||
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK);
|
||||
} else {
|
||||
startForeground(NOTIFICATION_ID, notification);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1375,7 +1375,7 @@ export default {
|
||||
}
|
||||
|
||||
APP.store.dispatch(updateRemoteParticipantFeatures(user));
|
||||
logger.log(`USER ${id} connected`);
|
||||
logger.log(`USER ${id} connected:`, user);
|
||||
APP.UI.addUser(user);
|
||||
});
|
||||
|
||||
|
||||
@@ -139,9 +139,6 @@ var config = {
|
||||
// Disables polls feature.
|
||||
// disablePolls: false,
|
||||
|
||||
// Disables chat feature entirely including notifications, sounds, and private messages.
|
||||
// disableChat: false,
|
||||
|
||||
// Disables demote button from self-view
|
||||
// disableSelfDemote: false,
|
||||
|
||||
@@ -907,8 +904,6 @@ var config = {
|
||||
// alwaysVisible: false,
|
||||
// // Indicates whether the toolbar should still autohide when chat is open
|
||||
// autoHideWhileChatIsOpen: false,
|
||||
// // Default background color for the main toolbar. Accepts any valid CSS color.
|
||||
// // backgroundColor: '#ffffff',
|
||||
// },
|
||||
|
||||
// Overrides the buttons displayed in the main toolbar. Depending on the screen size the number of displayed
|
||||
|
||||
7
debian/jitsi-meet-prosody.postinst
vendored
7
debian/jitsi-meet-prosody.postinst
vendored
@@ -124,17 +124,10 @@ case "$1" in
|
||||
ln -s $PROSODY_HOST_CONFIG /etc/prosody/conf.d/$JVB_HOSTNAME.cfg.lua
|
||||
fi
|
||||
PROSODY_CREATE_JICOFO_USER="true"
|
||||
fi
|
||||
|
||||
if ! grep -q "VirtualHost \"$JVB_HOSTNAME\"" $PROSODY_CONFIG_OLD; then
|
||||
# on some distributions main prosody config doesn't include configs
|
||||
# from conf.d folder enable it as this where we put our config by default
|
||||
# also when upgrading to new prosody version from prosody repo we need to add it again
|
||||
if ! grep -q "Include \"conf\.d\/\*\.cfg.lua\"" $PROSODY_CONFIG_OLD; then
|
||||
echo -e "\nInclude \"conf.d/*.cfg.lua\"" >> $PROSODY_CONFIG_OLD
|
||||
|
||||
# trigger a restart
|
||||
PROSODY_CONFIG_PRESENT="false"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
@@ -1484,7 +1484,6 @@
|
||||
"connectionInfo": "Verbindungsinformationen",
|
||||
"demote": "Zu Gästen verschieben",
|
||||
"domute": "Stummschalten",
|
||||
"domuteDesktopOfOthers": "Bildschirm freigeben für alle beenden",
|
||||
"domuteOthers": "Alle anderen stummschalten",
|
||||
"domuteVideo": "Kamera ausschalten",
|
||||
"domuteVideoOfOthers": "Alle anderen Kameras auschalten",
|
||||
|
||||
@@ -114,9 +114,6 @@
|
||||
"error": "Errore: il tuo messaggio non è stato inviato. Motivo: {{error}}",
|
||||
"everyone": "Tutti",
|
||||
"fieldPlaceHolder": "Scrivi qui il tuo messaggio",
|
||||
"fileAccessibleTitle": "{{user}} ha caricato un file",
|
||||
"fileAccessibleTitleMe": "ho caricato un file",
|
||||
"fileDeleted": "Un file è stato eliminato",
|
||||
"guestsChatIndicator": "(ospite)",
|
||||
"lobbyChatMessageTo": "Messaggio a {{recipient}} in sala d'attesa",
|
||||
"message": "Messaggio",
|
||||
@@ -283,6 +280,7 @@
|
||||
"Submit": "Invia",
|
||||
"Understand": "Accetto, mantieni microfono e videocamera disattivati per ora",
|
||||
"UnderstandAndUnmute": "Accetto, riattiva microfono e videocamera",
|
||||
"WaitForHostMsg": "La riunione non è ancora iniziata. Se sei l'organizzatore, per favore autenticati. Altrimenti, attendi l'arrivo dell'organizzatore.",
|
||||
"WaitForHostNoAuthMsg": "La riunione non è ancora iniziata perché nessun organizzatore si è ancora collegato. Si prega di attendere.",
|
||||
"WaitingForHostButton": "Attendi l'organizzatore",
|
||||
"WaitingForHostTitle": "In attesa dell'organizzatore…",
|
||||
@@ -525,7 +523,6 @@
|
||||
"tokenAuthFailedWithReasons": "Non sei autorizzato a partecipare a questa chiamata. Possibile motivo: {{reason}}",
|
||||
"tokenAuthUnsupported": "Il token URL non è supportato.",
|
||||
"transcribing": "Trascrizione in corso",
|
||||
"unauthenticatedAccessDisabled": "Questa chiamata richiede l'autenticazione. Si prega di accedere per procedere.",
|
||||
"unlockRoom": "Rimuovi la $t(lockRoomPassword) alla riunione",
|
||||
"user": "Utente",
|
||||
"userIdentifier": "Identificatore utente",
|
||||
@@ -573,12 +570,10 @@
|
||||
"downloadStarted": "Download del file iniziato",
|
||||
"dragAndDrop": "Trascina e rilascia i file qui o da qualsiasi altra parte nella schermata",
|
||||
"fileAlreadyUploaded": "Questo file è già stato caricato nella riunione.",
|
||||
"fileRemovedByOther": "Il tuo file '{{ fileName }}' è stato rimosso",
|
||||
"fileTooLargeDescription": "Assicurati che il file non superi {{ maxFileSize }}.",
|
||||
"fileTooLargeTitle": "Il file selezionato è troppo grande",
|
||||
"fileUploadProgress": "Caricamento del file in corso",
|
||||
"fileUploadedSuccessfully": "Il file è stato caricato con successo",
|
||||
"newFileNotification": "{{ participantName }} ha condiviso '{{ fileName }}'",
|
||||
"removeFile": "Rimuovi",
|
||||
"removeFileSuccess": "File rimosso con successo",
|
||||
"uploadFailedDescription": "Si prega di riprovare.",
|
||||
@@ -753,8 +748,7 @@
|
||||
"notificationTitle": "Sala d'attesa",
|
||||
"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."
|
||||
"toggleLabel": "Attiva sala d'attesa"
|
||||
},
|
||||
"localRecording": {
|
||||
"clientState": {
|
||||
@@ -970,9 +964,6 @@
|
||||
"by": "Da {{ name }}",
|
||||
"closeButton": "Chiudi sondaggio",
|
||||
"create": {
|
||||
"accessibilityLabel": {
|
||||
"send": "Invia sondaggio"
|
||||
},
|
||||
"addOption": "Aggiungi opzione",
|
||||
"answerPlaceholder": "Opzione {{index}}",
|
||||
"cancel": "Annulla",
|
||||
@@ -981,7 +972,8 @@
|
||||
"pollQuestion": "Domanda del sondaggio",
|
||||
"questionPlaceholder": "Fai una domanda",
|
||||
"removeOption": "Elimina opzione",
|
||||
"save": "Salva"
|
||||
"save": "Salva",
|
||||
"send": "Invia"
|
||||
},
|
||||
"errors": {
|
||||
"notUniqueOption": "Le opzioni devono essere uniche"
|
||||
@@ -1625,8 +1617,6 @@
|
||||
"noMainParticipantsTitle": "La riunione non è ancora iniziata.",
|
||||
"noVisitorLobby": "Non puoi partecipare se la sala d'attesa è attiva per la riunione.",
|
||||
"notAllowedPromotion": "Un partecipante deve autorizzare la tua richiesta prima.",
|
||||
"requestToJoin": "Mano alzata",
|
||||
"requestToJoinDescription": "La tua richiesta è stata inviata ai relatori. Tieni duro!",
|
||||
"title": "Sei uno spettatore nella riunione"
|
||||
},
|
||||
"waitingMessage": "Ti unirai alla riunione quando inizierà!"
|
||||
|
||||
@@ -114,9 +114,6 @@
|
||||
"error": "Kļūda: Jūsu ziņa netika nosūtīta. Cēlonis: {{error}}",
|
||||
"everyone": "Visi",
|
||||
"fieldPlaceHolder": "Rakstiet ziņu šeit",
|
||||
"fileAccessibleTitle": "{{user}} augšuplādēja failu",
|
||||
"fileAccessibleTitleMe": "es augšuplādēju failu",
|
||||
"fileDeleted": "Fails tika dzēsts",
|
||||
"guestsChatIndicator": "(viesis)",
|
||||
"lobbyChatMessageTo": "Vestibila tērzēšanas ziņa adresātam {{recipient}}",
|
||||
"message": "Ziņa",
|
||||
@@ -126,20 +123,12 @@
|
||||
"messagebox": "Rakstiet ziņu",
|
||||
"newMessages": "Jaunas ziņas",
|
||||
"nickname": {
|
||||
"featureChat": "tērzētava",
|
||||
"featureClosedCaptions": "slēgtie subtitri",
|
||||
"featureFileSharing": "failu kopīgošana",
|
||||
"featurePolls": "aptaujas",
|
||||
"popover": "Izvēlieties segvārdu",
|
||||
"title": "Ierakstiet segvārdu, lai izmantotu tērzēšanu",
|
||||
"titleWith1Features": "Ievadiet segvārdu, lai izmantotu {{feature1}}",
|
||||
"titleWith2Features": "Ievadiet segvārdu, lai izmantotu {{feature1}} un {{feature2}}",
|
||||
"titleWith3Features": "Ievadiet segvārdu, lai izmantotu {{feature1}}, {{feature2}} un {{feature3}}",
|
||||
"titleWith4Features": "Ievadiet segvārdu, lai izmantotu {{feature1}}, {{feature2}}, {{feature3}} un {{feature4}}",
|
||||
"titleWithCC": "Ievadiet segvārdu, lai izmantotu tērzēšanu un slēgtos subtitrus",
|
||||
"titleWithPolls": "Ierakstiet segvārdu, lai izmantotu tērzēšanu un aptaujas",
|
||||
"titleWithPollsAndCC": "Ievadiet segvārdu, lai izmantotu tērzēšanu, aptaujas un slēgtos subtitrus",
|
||||
"titleWithPollsAndCCAndFileSharing": "Ievadiet segvārdu, lai izmantotu tērzēšanu, aptaujas, slēgtos subtitrus un failus"
|
||||
"popover": "Izvēlieties vārdu",
|
||||
"title": "Ierakstiet vārdu, lai izmantotu tērzēšanā",
|
||||
"titleWithCC": "Ievadiet segvārdu, lai izmantotu tērzēšanā un slēptos subtitros",
|
||||
"titleWithPolls": "Ierakstiet segvārdu, lai izmantotu tērzēšanā un aptaujās",
|
||||
"titleWithPollsAndCC": "Ievadiet segvārdu, lai izmantotu tērzēšanā, aptaujās un slēptos subtitros",
|
||||
"titleWithPollsAndCCAndFileSharing": "Ievadiet segvārdu, lai izmantotu tērzēšanā, aptaujās, slēptos subtitros un failos"
|
||||
},
|
||||
"noMessagesMessage": "Sapulcē pagaidām nav nevienas ziņas. Uzsāciet saraksti!",
|
||||
"privateNotice": "Privāta ziņa adresātam {{recipient}}",
|
||||
@@ -148,12 +137,12 @@
|
||||
"systemDisplayName": "Sistēma",
|
||||
"tabs": {
|
||||
"chat": "Tērzēšana",
|
||||
"closedCaptions": "Slēgtie subtitri",
|
||||
"closedCaptions": "Slēptie subtitri",
|
||||
"fileSharing": "Faili",
|
||||
"polls": "Aptaujas"
|
||||
},
|
||||
"title": "Tērzēšana",
|
||||
"titleWithCC": "Tērzēšana un Slēgtie subtitri",
|
||||
"titleWithCC": "Tērzēšana un Slēptie subtitri",
|
||||
"titleWithFeatures": "Tērzēšana un",
|
||||
"titleWithFileSharing": "Faili",
|
||||
"titleWithPolls": "Tērzēšana un Aptaujas",
|
||||
@@ -167,8 +156,8 @@
|
||||
"installExtensionText": "Uzstādīt spraudni Google kalendāra un Office 365 integrācijai"
|
||||
},
|
||||
"closedCaptionsTab": {
|
||||
"emptyState": "Slēgto subtitru saturs būs pieejams, tiklīdz moderators uzsāks to.",
|
||||
"startClosedCaptionsButton": "Uzsākt slēgtos subtitrus"
|
||||
"emptyState": "Slēpto subtitru saturs būs pieejams, tiklīdz moderators uzsāks to.",
|
||||
"startClosedCaptionsButton": "Uzsākt slēptos subtitrus"
|
||||
},
|
||||
"connectingOverlay": {
|
||||
"joiningRoom": "Notiek pieslēgšanās jūsu sapulcei…"
|
||||
@@ -291,6 +280,7 @@
|
||||
"Submit": "Iesniegt",
|
||||
"Understand": "Saprotu",
|
||||
"UnderstandAndUnmute": "Es saprotu, lūdzu, ieslēdziet skaņu.",
|
||||
"WaitForHostMsg": "Sapulce vēl nav sākusies, jo vēl nav ieradies neviens moderators. Lūdzu, autorizējieties, lai kļūtu par moderatoru. Pretējā gadījumā, lūdzu, uzgaidiet.",
|
||||
"WaitForHostNoAuthMsg": "Sapulce vēl nav sākusies, jo vēl nav ieradies neviens moderators. Lūdzu, uzgaidiet.",
|
||||
"WaitingForHostButton": "Gaidīt rīkotāju",
|
||||
"WaitingForHostTitle": "Gaida rīkotāju…",
|
||||
@@ -427,7 +417,7 @@
|
||||
"muteParticipantsVideoDialog": "Vai tiešām vēlaties izslēgt šī dalībnieka kameru? Jūs nevarēsiet to ieslēgt atpakaļ, taču dalībnieks pats to varēs izdarīt jebkurā laikā.",
|
||||
"muteParticipantsVideoDialogModerationOn": "Vai tiešām vēlaties izslēgt šī dalībnieka kameru? Ne Jūs, ne dalībnieks nevarēsiet to ieslēgt atpakaļ.",
|
||||
"muteParticipantsVideoTitle": "Vai izslēgt šī dalībnieka video?",
|
||||
"noDropboxToken": "Nav derīgas Dropbox pilnvaras",
|
||||
"noDropboxToken": "Nav derīga Dropbox tokena",
|
||||
"password": "Parole",
|
||||
"passwordLabel": "Dalībnieks ir aizslēdzis sapulci. Lūdzu, ievadiet $t(lockRoomPassword), lai pievienotos.",
|
||||
"passwordNotSupported": "Sapulces slēgšana ar $t(lockRoomPassword) netiek atbalstīta.",
|
||||
@@ -511,7 +501,7 @@
|
||||
"stopStreamingWarning": "Tiešām vēlaties beigt tiešraidi?",
|
||||
"streamKey": "Tiešraides atslēga",
|
||||
"thankYou": "Paldies, ka izmantojāt {{appName}}!",
|
||||
"token": "pilnvara",
|
||||
"token": "tokens",
|
||||
"tokenAuthFailed": "Atvainojiet, jums nav atļauts pievienoties šim zvanam.",
|
||||
"tokenAuthFailedReason": {
|
||||
"audInvalid": "Nederīga `aud` vērtība. Tai vajadzētu būt `jitsi`.",
|
||||
@@ -527,13 +517,12 @@
|
||||
"nbfFuture": "`nbf` vērtība ir nākotnē.",
|
||||
"nbfInvalid": "Nederīga `nbf` vērtība.",
|
||||
"payloadNotFound": "Trūkst satura.",
|
||||
"tokenExpired": "Pilnvara ir beigusies."
|
||||
"tokenExpired": "Token ir beidzies."
|
||||
},
|
||||
"tokenAuthFailedTitle": "Autentifikācijas kļūda",
|
||||
"tokenAuthFailedWithReasons": "Atvainojiet, jums nav atļauts pievienoties šim zvanam. Iespējamie iemesli: {{reason}}",
|
||||
"tokenAuthUnsupported": "Pilnvaras URL nav atbalstīts.",
|
||||
"tokenAuthUnsupported": "Token URL netiek atbalstīts.",
|
||||
"transcribing": "Notiek atšifrējuma izveide",
|
||||
"unauthenticatedAccessDisabled": "Šim zvanam nepieciešama autentifikācija. Lūdzu, piesakieties, lai turpinātu.",
|
||||
"unlockRoom": "Noņemt $t(lockRoomPassword)",
|
||||
"user": "Lietotājs",
|
||||
"userIdentifier": "Lietotājvārds",
|
||||
@@ -581,12 +570,10 @@
|
||||
"downloadStarted": "Sākta faila lejuplāde",
|
||||
"dragAndDrop": "Velciet un palaidiet failus šeit, vai jebkurā ekrāna vietā",
|
||||
"fileAlreadyUploaded": "Fails jau ir augšuplādēts šajā sanāksmē.",
|
||||
"fileRemovedByOther": "Jūsu fails '{{ fileName }}' tika noņemts",
|
||||
"fileTooLargeDescription": "Lūdzu, pārliecinieties, vai faila lielums nepārsniedz {{ maxFileSize }}.",
|
||||
"fileTooLargeTitle": "Izvēlētais fails ir pārāk liels",
|
||||
"fileUploadProgress": "Faila augšuplādes gaita",
|
||||
"fileUploadedSuccessfully": "Fails veiksmīgi augšuplādēts",
|
||||
"newFileNotification": "{{ participantName }} kopīgoja '{{ fileName }}'",
|
||||
"removeFile": "Noņemt",
|
||||
"removeFileSuccess": "Fails veiksmīgi noņemts",
|
||||
"uploadFailedDescription": "Lūdzu, mēģiniet vēlreiz.",
|
||||
@@ -761,8 +748,7 @@
|
||||
"notificationTitle": "Vestibils",
|
||||
"passwordJoinButton": "Pievienoties",
|
||||
"title": "Vestibils",
|
||||
"toggleLabel": "Iespējot vestibilu",
|
||||
"waitForModerator": "Konference vēl nav sākusies, jo vēl nav ieradušies moderatori. Ja vēlaties kļūt par moderatoru, lūdzu, piesakieties. Pretējā gadījumā, lūdzu, uzgaidiet."
|
||||
"toggleLabel": "Iespējot vestibilu"
|
||||
},
|
||||
"localRecording": {
|
||||
"clientState": {
|
||||
@@ -789,7 +775,7 @@
|
||||
"participant": "Dalībnieks",
|
||||
"participantStats": "Dalībnieku statistika",
|
||||
"selectTabTitle": "🎥 Lūdzu, atveriet šo cilni ierakstīšanai",
|
||||
"sessionToken": "Sesijas Pilnvara",
|
||||
"sessionToken": "Sessijas tokens",
|
||||
"start": "Sākt ierakstu",
|
||||
"stop": "Beigt ierakstu",
|
||||
"stopping": "Ierakstīšanas pārtraukšana",
|
||||
@@ -879,7 +865,6 @@
|
||||
"oldElectronClientDescription1": "Izskatās, ka jūs izmantojat vecu Jitsi Meet klienta versiju, kurai ir zināmas drošības ievainojamības. Lūdzu, atjauniniet uz ",
|
||||
"oldElectronClientDescription2": "jaunākā versija",
|
||||
"oldElectronClientDescription3": "tagad!",
|
||||
"openChat": "Atvērt tērzētavu",
|
||||
"participantWantsToJoin": "Vēlas pievienoties sapulcei",
|
||||
"participantsWantToJoin": "Vēlas pievienoties sapulcei",
|
||||
"passwordRemovedRemotely": "Kāds dalībnieks noņēma $t(lockRoomPasswordUppercase).",
|
||||
@@ -978,9 +963,6 @@
|
||||
"by": "Pēc {{ name }} iniciatīvas",
|
||||
"closeButton": "Slēgt aptauju",
|
||||
"create": {
|
||||
"accessibilityLabel": {
|
||||
"send": "Nosūtīt aptauju"
|
||||
},
|
||||
"addOption": "Pievienot opciju",
|
||||
"answerPlaceholder": "Opcija {{index}}",
|
||||
"cancel": "Atcelt",
|
||||
@@ -989,7 +971,8 @@
|
||||
"pollQuestion": "Aptaujas Jautājums",
|
||||
"questionPlaceholder": "Uzdod jautājumu",
|
||||
"removeOption": "Noņemt opciju",
|
||||
"save": "Saglabāt"
|
||||
"save": "Saglabāt",
|
||||
"send": "Nosūtīt"
|
||||
},
|
||||
"errors": {
|
||||
"notUniqueOption": "Iespējām jābūt unikālām"
|
||||
@@ -1108,7 +1091,7 @@
|
||||
}
|
||||
},
|
||||
"recording": {
|
||||
"authDropboxText": "Augšuplādēt uz Dropbox",
|
||||
"authDropboxText": "Augšupielādēt uz Dropbox",
|
||||
"availableSpace": "Pieejama vieta: {{spaceLeft}} MB (apmēram {{duration}} ieraksta minūtes)",
|
||||
"beta": "BETA",
|
||||
"busy": "Cenšamies nodrošināt ierakstam vairāk resursu. Lūdzu, pēc dažām minūtēm pamēģiniet vēlreiz.",
|
||||
@@ -1162,7 +1145,7 @@
|
||||
"title": "Ieraksts",
|
||||
"unavailable": "Hmm! {{serviceName}} pašlaik nav pieejams. Mēs strādājam pie problēmas risināšanas. Lūdzu, pamēģiniet vēlreiz vēlāk.",
|
||||
"unavailableTitle": "Ieraksts nav iespējams",
|
||||
"uploadToCloud": "Augšuplādēt mākonī"
|
||||
"uploadToCloud": "Augšupielādēt mākonī"
|
||||
},
|
||||
"screenshareDisplayName": "{{name}} ekrāns",
|
||||
"sectionList": {
|
||||
@@ -1317,7 +1300,7 @@
|
||||
"closeChat": "Aizvērt tērzēšanu",
|
||||
"closeMoreActions": "Aizvērt vairāk darbību izvēlni",
|
||||
"closeParticipantsPane": "Aizvērt dalībnieku paneli",
|
||||
"closedCaptions": "Slēgtie subtitri",
|
||||
"closedCaptions": "Slēptie subtitri",
|
||||
"collapse": "Sakļaut",
|
||||
"document": "Kopīgotais dokuments (iesl./izsl.)",
|
||||
"documentClose": "Aizvērt kopīgoto dokumentu",
|
||||
@@ -1395,21 +1378,7 @@
|
||||
"videomuteGUMPending": "Kameras pievienošana",
|
||||
"videounmute": "Ieslēgt kameru"
|
||||
},
|
||||
"addPeople": "Pievienot cilvēkus savam zvanam",
|
||||
"advancedAudioSettings": {
|
||||
"aec": {
|
||||
"label": "Akustiskās atbalss slāpēšana"
|
||||
},
|
||||
"agc": {
|
||||
"label": "Automātiska pastiprinājuma kontrole"
|
||||
},
|
||||
"ns": {
|
||||
"label": "Trokšņu slāpēšana"
|
||||
},
|
||||
"stereo": {
|
||||
"label": "Stereo"
|
||||
}
|
||||
},
|
||||
"addPeople": "Pievienot cilvēkus savai sesijai/zvanam",
|
||||
"audioOnlyOff": "Atspējot kanāla/trafika taupības režīmu",
|
||||
"audioOnlyOn": "Iespējot kanāla/trafika taupības režīmu",
|
||||
"audioRoute": "Izvēlēties audioierīci",
|
||||
@@ -1422,7 +1391,7 @@
|
||||
"closeChat": "Aizvērt tērzētavu",
|
||||
"closeParticipantsPane": "Aizvērt dalībnieku paneli",
|
||||
"closeReactionsMenu": "Aizvērt reakciju izvēlni",
|
||||
"closedCaptions": "Slēgtie subtitri",
|
||||
"closedCaptions": "Slēptie subtitri",
|
||||
"disableNoiseSuppression": "Atspējot trokšņu slāpēšanu",
|
||||
"disableReactionSounds": "Šai sapulcei varat atspējot reakcijas skaņas",
|
||||
"documentClose": "Aizvērt kopīgoto dokumentu",
|
||||
@@ -1437,7 +1406,6 @@
|
||||
"exitFullScreen": "Pilnekrāna režīms",
|
||||
"exitTileView": "Tuvplāna režīms",
|
||||
"feedback": "Atstāts atsauksmi",
|
||||
"fileSharing": "Failu kopīgošana",
|
||||
"giphy": "GIPHY izvēlne (rādīt/nerādīt)",
|
||||
"hangup": "Iziet no sapulces",
|
||||
"help": "Palīdzība",
|
||||
@@ -1473,19 +1441,17 @@
|
||||
"openReactionsMenu": "Atvērt reakciju izvēlni",
|
||||
"participants": "Dalībnieki",
|
||||
"pip": "Iesl. attēls attēlā (PIP) režīmu",
|
||||
"polls": "Aptaujas",
|
||||
"privateMessage": "Nosūtīt privātu ziņu",
|
||||
"profile": "Rediģēt profilu",
|
||||
"raiseHand": "Pacelt roku",
|
||||
"raiseYourHand": "Pacelt roku",
|
||||
"reactionBoo": "Sūtīt būū reakciju",
|
||||
"reactionClap": "Sūtīt aplausu reakciju",
|
||||
"reactionHeart": "Sūtīt sirds reakciju",
|
||||
"reactionLaugh": "Sūtīt smieklu reakciju",
|
||||
"reactionLike": "Sūtīt īkšķis augšup reakciju",
|
||||
"reactionLove": "Sūtīt mīlestības reakciju",
|
||||
"reactionSilence": "Sūtīt klusuma reakciju",
|
||||
"reactionSurprised": "Sūtīt pārsteiguma reakciju",
|
||||
"reactionBoo": "Nosūtīt būū reakciju",
|
||||
"reactionClap": "Nosūtīt aplausu reakciju",
|
||||
"reactionHeart": "Nosūtīt sirds reakciju",
|
||||
"reactionLaugh": "Nosūtīt smieklu reakciju",
|
||||
"reactionLike": "Nosūtīt īkšķi augšup reakciju",
|
||||
"reactionSilence": "Nosūtīt klusuma reakciju",
|
||||
"reactionSurprised": "Nosūtīt pārsteigts reakciju",
|
||||
"reactions": "Reakcijas",
|
||||
"security": "Drošības iespējas",
|
||||
"selectBackground": "Izvēlēties fonu",
|
||||
@@ -1518,7 +1484,7 @@
|
||||
"failed": "Atšifrējuma izveide neizdevās",
|
||||
"labelTooltip": "Šajā sapulcē notiek atšifrējuma izveide.",
|
||||
"labelTooltipExtra": "Turklāt vēlāk būs pieejams atšifrējums.",
|
||||
"openClosedCaptions": "Atvērt slēgtos subtitrus",
|
||||
"openClosedCaptions": "Atvērt slēptos subtitrus",
|
||||
"original": "Oriģināls",
|
||||
"sourceLanguageDesc": "Pašlaik sapulces valoda ir iestatīta uz <b>{{sourceLanguage}}</b>. <br/> Varat to mainīt no ",
|
||||
"sourceLanguageHere": "šeit",
|
||||
@@ -1616,7 +1582,7 @@
|
||||
"removeBackground": "Noņemt fonu",
|
||||
"slightBlur": "Viegli izplūdis",
|
||||
"title": "Virtuālie foni",
|
||||
"uploadedImage": "Augšuplādēts attēls {{index}}",
|
||||
"uploadedImage": "Augšupielādēts attēls {{index}}",
|
||||
"webAssemblyWarning": "WebAssembly netiek atbalstīts",
|
||||
"webAssemblyWarningDescription": "WebAssemb ir atspējots vai šī pārlūkprogramma to neatbalsta"
|
||||
},
|
||||
@@ -1635,8 +1601,6 @@
|
||||
"noMainParticipantsTitle": "Šī sapulce vēl nav sākusies.",
|
||||
"noVisitorLobby": "Jūs nevarat pievienoties, kamēr sapulcei ir iespējots vestibils.",
|
||||
"notAllowedPromotion": "Dalībniekam vispirms ir jāatļauj jūsu pieprasījums.",
|
||||
"requestToJoin": "Roka Pacelta",
|
||||
"requestToJoinDescription": "Jūsu pieprasījums tika nosūtīts moderatoriem. Uzgaidiet!",
|
||||
"title": "Jūs esat sapulces apmeklētājs"
|
||||
},
|
||||
"waitingMessage": "Jūs pievienosities sapulcei, tiklīdz tā sāksies!"
|
||||
|
||||
@@ -112,12 +112,7 @@
|
||||
"disabled": "聊天已禁用",
|
||||
"enter": "加入会议室",
|
||||
"error": "错误:你的消息未发送。原因:{{error}}",
|
||||
"everyone": "所有人",
|
||||
"fieldPlaceHolder": "在这里输入你的信息",
|
||||
"fileAccessibleTitle": "{{user}}上传了一个文件",
|
||||
"fileAccessibleTitleMe": "我上传了一个文件",
|
||||
"fileDeleted": "文件已被删除",
|
||||
"guestsChatIndicator": "(访客)",
|
||||
"lobbyChatMessageTo": "等候室聊天消息发送至{{recipient}}",
|
||||
"message": "信息",
|
||||
"messageAccessibleTitle": "{{user}}:",
|
||||
@@ -305,12 +300,6 @@
|
||||
"alreadySharedVideoTitle": "同一时间只允许一个视频分享",
|
||||
"applicationWindow": "应用程序窗口",
|
||||
"authenticationRequired": "需要身份验证",
|
||||
"cameraCaptureDialog": {
|
||||
"description": "使用手机摄像头拍照并发送",
|
||||
"ok": "打开相机",
|
||||
"reject": "暂不使用",
|
||||
"title": "拍照"
|
||||
},
|
||||
"cameraConstraintFailedError": "你的摄像头未满足某些必要条件。",
|
||||
"cameraNotFoundError": "找不到摄像头",
|
||||
"cameraNotSendingData": "我们无法访问你的摄像头,请检查是否有其他应用程序正在使用此设备,从设置菜单中选择另一个设备,或尝试重新加载应用程序。",
|
||||
@@ -386,34 +375,22 @@
|
||||
"micTimeoutError": "无法开启音频设备,发生超时!",
|
||||
"micUnknownError": "由于未知原因,无法使用麦克风。",
|
||||
"moderationAudioLabel": "允许参会者自己解除静音",
|
||||
"moderationDesktopLabel": "允许非主持人共享屏幕",
|
||||
"moderationVideoLabel": "允许参会者自己开启视频",
|
||||
"muteEveryoneDialog": "参会者可以在任何时候解除自己的静音。",
|
||||
"muteEveryoneDialogModerationOn": "参会者可以在任何时候请求发言。",
|
||||
"muteEveryoneElseDialog": "静音后,你将无法为其解除静音,但是他们可以随时解除自己的静音。",
|
||||
"muteEveryoneElseTitle": "除了{{whom}}以外的将所有人静音?",
|
||||
"muteEveryoneElsesDesktopDialog": "一旦停止共享,你将无法重新开启他们的屏幕共享,但他们可以随时重新开启。",
|
||||
"muteEveryoneElsesDesktopTitle": "停止除了{{whom}}以外所有人的屏幕共享?",
|
||||
"muteEveryoneElsesVideoDialog": "一旦关闭,你将无法重新开启他们的摄像头,但他们随时可以重新开启。",
|
||||
"muteEveryoneElsesVideoTitle": "除了{{whom}}以外,关闭所有人的摄像头?",
|
||||
"muteEveryoneSelf": "你自己",
|
||||
"muteEveryoneStartMuted": "现在所有人都已静音",
|
||||
"muteEveryoneTitle": "静音所有人?",
|
||||
"muteEveryonesDesktopDialog": "参会者可以随时共享他们的屏幕",
|
||||
"muteEveryonesDesktopDialogModerationOn": "参会者可以随时请求共享他们的屏幕",
|
||||
"muteEveryonesDesktopTitle": "停止所有人的屏幕共享?",
|
||||
"muteEveryonesVideoDialog": "参会者可以随时开启他们的摄像头",
|
||||
"muteEveryonesVideoDialogModerationOn": "参会者可以随时请求开启他们的摄像头",
|
||||
"muteEveryonesVideoDialogOk": "关闭",
|
||||
"muteEveryonesVideoTitle": "关闭所有人的摄像头?",
|
||||
"muteParticipantBody": "你将无法为他们解除静音,但是他们可以随时解除自己的静音。",
|
||||
"muteParticipantButton": "静音",
|
||||
"muteParticipantsDesktopBody": "你无法重新开启他们的屏幕共享,但他们可以随时重新开启。",
|
||||
"muteParticipantsDesktopBodyModerationOn": "你和他们都无法重新开启屏幕共享",
|
||||
"muteParticipantsDesktopButton": "停止屏幕共享",
|
||||
"muteParticipantsDesktopDialog": "你确定要停止这个参会者的屏幕共享吗?你将无法重新开启他们的屏幕共享,但他们可以随时重新开启。",
|
||||
"muteParticipantsDesktopDialogModerationOn": "你确定要停止这个参会者的屏幕共享吗?你和他们都无法重新开启屏幕共享。",
|
||||
"muteParticipantsDesktopTitle": "停止这个参会者的屏幕共享?",
|
||||
"muteParticipantsVideoBody": "你无法重新开启摄像头,但他们随时可以重新开启。",
|
||||
"muteParticipantsVideoBodyModerationOn": "你和他们都无法重新开启摄像头",
|
||||
"muteParticipantsVideoButton": "关闭摄像头",
|
||||
@@ -526,7 +503,6 @@
|
||||
"tokenAuthFailedWithReasons": "抱歉,你无法加入此通话,原因:",
|
||||
"tokenAuthUnsupported": "Token地址不支持",
|
||||
"transcribing": "转录中",
|
||||
"unauthenticatedAccessDisabled": "此会议需要身份验证,请先登录后继续。",
|
||||
"unlockRoom": "移除会议$t(lockRoomPassword)",
|
||||
"user": "用户",
|
||||
"userIdentifier": "用户ID",
|
||||
@@ -571,17 +547,11 @@
|
||||
"downloadFailedDescription": "请稍后重试",
|
||||
"downloadFailedTitle": "下载失败",
|
||||
"downloadFile": "下载",
|
||||
"downloadStarted": "文件下载已开始",
|
||||
"dragAndDrop": "拖拽文件到此处上传",
|
||||
"fileAlreadyUploaded": "文件已上传至本次会议",
|
||||
"fileRemovedByOther": "你的文件{{fileName}}已被移除",
|
||||
"fileTooLargeDescription": "请确保文件不超过 {{ maxFileSize }}",
|
||||
"fileTooLargeTitle": "文件太大",
|
||||
"fileUploadProgress": "文件上传进度",
|
||||
"fileUploadedSuccessfully": "文件上传成功",
|
||||
"newFileNotification": "{{participantName}}分享了{{fileName}}",
|
||||
"removeFile": "移除",
|
||||
"removeFileSuccess": "文件移除成功",
|
||||
"uploadFailedDescription": "请稍后重试",
|
||||
"uploadFailedTitle": "上传失败",
|
||||
"uploadFile": "文件共享"
|
||||
@@ -754,8 +724,7 @@
|
||||
"notificationTitle": "等候室",
|
||||
"passwordJoinButton": "加入",
|
||||
"title": "等候室",
|
||||
"toggleLabel": "开启等候室模式",
|
||||
"waitForModerator": "会议尚未开始,暂无主持人入会。如需成为主持人请先登录,或耐心等待会议开始。"
|
||||
"toggleLabel": "开启等候室模式"
|
||||
},
|
||||
"localRecording": {
|
||||
"clientState": {
|
||||
@@ -798,10 +767,8 @@
|
||||
"me": "我",
|
||||
"notify": {
|
||||
"OldElectronAPPTitle": "安全漏洞!",
|
||||
"allowAll": "允许全部",
|
||||
"allowAudio": "允许开启麦克风",
|
||||
"allowBoth": "允许音视频",
|
||||
"allowDesktop": "允许屏幕共享",
|
||||
"allowVideo": "允许开启摄像头",
|
||||
"allowedUnmute": "你可以解除麦克风静音、启动摄像头或共享屏幕。",
|
||||
"audioUnmuteBlockedDescription": "由于系统限制,麦克风解除静音操作被暂时阻止。",
|
||||
@@ -815,7 +782,6 @@
|
||||
"dataChannelClosedDescription": "桥接通道已断开,视频质量可能会被限制为最低设置",
|
||||
"dataChannelClosedDescriptionWithAudio": "桥接通道已断开,音视频可能会出现卡顿或中断",
|
||||
"dataChannelClosedWithAudio": "音视频质量可能受影响",
|
||||
"desktopMutedRemotelyTitle": "你的屏幕共享已被{{participantDisplayName}}停止",
|
||||
"disabledIframe": "嵌入仅用于演示,本次通话将在{{timeout}}分钟后自动断开",
|
||||
"disabledIframeSecondaryNative": "嵌入{{domain}}仅用于演示,本次通话将在{{timeout}}分钟后自动断开",
|
||||
"disabledIframeSecondaryWeb": "嵌入{{domain}}仅用于演示,本次通话将在{{timeout}}分钟后自动断开。如需在正式环境嵌入,请使用<a href='{{jaasDomain}}' rel='noopener noreferrer' target='_blank'>Jitsi服务</a>!",
|
||||
@@ -873,7 +839,6 @@
|
||||
"oldElectronClientDescription1": "你似乎正在使用存在已知安全漏洞的旧版Jitsi Meet客户端,请确保您更新到我们的",
|
||||
"oldElectronClientDescription2": "最新版本",
|
||||
"oldElectronClientDescription3": "!",
|
||||
"openChat": "打开聊天",
|
||||
"participantWantsToJoin": "想要加入会议",
|
||||
"participantsWantToJoin": "想要加入会议",
|
||||
"passwordRemovedRemotely": "其他参会者移除了$t(lockRoomPasswordUppercase)",
|
||||
@@ -897,7 +862,6 @@
|
||||
"suggestRecordingDescription": "是否需要录制本次会议?",
|
||||
"suggestRecordingTitle": "录制会议",
|
||||
"unmute": "解除静音",
|
||||
"unmuteScreen": "开始屏幕共享",
|
||||
"unmuteVideo": "开启摄像头",
|
||||
"videoMutedRemotelyDescription": "你可随时重新开启视频",
|
||||
"videoMutedRemotelyTitle": "{{participantDisplayName}}已关闭你的视频",
|
||||
@@ -917,14 +881,11 @@
|
||||
"admit": "同意加入",
|
||||
"admitAll": "全部同意加入",
|
||||
"allow": "允许参会者:",
|
||||
"allowDesktop": "允许屏幕共享",
|
||||
"allowVideo": "允许开启摄像头",
|
||||
"askDesktop": "请求共享屏幕",
|
||||
"askUnmute": "请求取消静音",
|
||||
"audioModeration": "自行解除静音",
|
||||
"blockEveryoneMicCamera": "禁用所有人的麦克风和摄像头",
|
||||
"breakoutRooms": "分组讨论室",
|
||||
"desktopModeration": "开始屏幕共享",
|
||||
"goLive": "开始直播",
|
||||
"invite": "邀请其他人",
|
||||
"lowerAllHands": "取消全部举手",
|
||||
@@ -936,8 +897,6 @@
|
||||
"muteAll": "全体静音",
|
||||
"muteEveryoneElse": "静音其他人",
|
||||
"reject": "拒绝",
|
||||
"stopDesktop": "停止屏幕共享",
|
||||
"stopEveryonesDesktop": "停止所有人的屏幕共享",
|
||||
"stopEveryonesVideo": "关闭所有人摄像头",
|
||||
"stopVideo": "关闭摄像头",
|
||||
"unblockEveryoneMicCamera": "允许所有人开启麦克风和摄像头",
|
||||
@@ -947,11 +906,9 @@
|
||||
"headings": {
|
||||
"lobby": "等候室(({{count}}人)",
|
||||
"participantsList": "会议参会者({{count}}人)",
|
||||
"viewerRequests": "观众请求({{count}}人)",
|
||||
"visitorInQueue": "(排队中:{{count}}人)",
|
||||
"visitorRequests": "(请求加入:{{count}}人)",
|
||||
"visitors": "观众(({{count}}人)",
|
||||
"visitorsList": "观众({{count}}人)",
|
||||
"waitingLobby": "在等候室等待({{count}}人)"
|
||||
},
|
||||
"search": "搜索参会者",
|
||||
@@ -972,9 +929,6 @@
|
||||
"by": "由{{ name }}发起",
|
||||
"closeButton": "结束投票",
|
||||
"create": {
|
||||
"accessibilityLabel": {
|
||||
"send": "发送投票"
|
||||
},
|
||||
"addOption": "添加选项",
|
||||
"answerPlaceholder": "选项{{index}}",
|
||||
"cancel": "取消",
|
||||
@@ -1391,20 +1345,6 @@
|
||||
"videounmute": "打开摄像头"
|
||||
},
|
||||
"addPeople": "添加成员到通话中",
|
||||
"advancedAudioSettings": {
|
||||
"aec": {
|
||||
"label": "回声消除"
|
||||
},
|
||||
"agc": {
|
||||
"label": "自动增益控制"
|
||||
},
|
||||
"ns": {
|
||||
"label": "降噪"
|
||||
},
|
||||
"stereo": {
|
||||
"label": "立体声"
|
||||
}
|
||||
},
|
||||
"audioOnlyOff": "关闭省流模式",
|
||||
"audioOnlyOn": "开启省流模式",
|
||||
"audioRoute": "选择音频设备",
|
||||
@@ -1476,7 +1416,6 @@
|
||||
"reactionHeart": "发送爱心",
|
||||
"reactionLaugh": "发送大笑",
|
||||
"reactionLike": "发送点赞",
|
||||
"reactionLove": "发送爱心",
|
||||
"reactionSilence": "发送沉默",
|
||||
"reactionSurprised": "发送惊讶",
|
||||
"reactions": "互动表情",
|
||||
@@ -1562,8 +1501,6 @@
|
||||
"connectionInfo": "连接信息",
|
||||
"demote": "设为观众",
|
||||
"domute": "静音",
|
||||
"domuteDesktop": "停止屏幕共享",
|
||||
"domuteDesktopOfOthers": "停止屏幕共享给其他人",
|
||||
"domuteOthers": "静音其他人",
|
||||
"domuteVideo": "关闭摄像头",
|
||||
"domuteVideoOfOthers": "关闭其他人摄像头",
|
||||
@@ -1628,8 +1565,6 @@
|
||||
"noMainParticipantsTitle": "会议尚未开始",
|
||||
"noVisitorLobby": "当前会议已开启等候室,暂无法加入",
|
||||
"notAllowedPromotion": "需由会议成员同意才能参与讨论",
|
||||
"requestToJoin": "举手请求",
|
||||
"requestToJoinDescription": "你的请求已发送给主持人,请稍候!",
|
||||
"title": "你当前为会议观众"
|
||||
},
|
||||
"waitingMessage": "会议开始后将自动加入"
|
||||
|
||||
@@ -112,12 +112,7 @@
|
||||
"disabled": "聊天訊息已停用",
|
||||
"enter": "加入聊天室",
|
||||
"error": "錯誤:您的訊息未被傳送。原因:{{error}}",
|
||||
"everyone": "所有人",
|
||||
"fieldPlaceHolder": "在此輸入您的訊息",
|
||||
"fileAccessibleTitle": "{{user}}上傳了一個檔案",
|
||||
"fileAccessibleTitleMe": "我上傳了一個檔案",
|
||||
"fileDeleted": "檔案已被刪除",
|
||||
"guestsChatIndicator": "(訪客)",
|
||||
"lobbyChatMessageTo": "大廳聊天訊息傳送至 {{recipient}}",
|
||||
"message": "訊息",
|
||||
"messageAccessibleTitle": "{{user}}:",
|
||||
@@ -305,12 +300,6 @@
|
||||
"alreadySharedVideoTitle": "同一時間只允許一位影像分享",
|
||||
"applicationWindow": "應用程式視窗",
|
||||
"authenticationRequired": "需要驗證",
|
||||
"cameraCaptureDialog": {
|
||||
"description": "使用手機攝影機拍照並傳送",
|
||||
"ok": "開啟相機",
|
||||
"reject": "暫不使用",
|
||||
"title": "拍照"
|
||||
},
|
||||
"cameraConstraintFailedError": "您的網路攝影機不符合要求。",
|
||||
"cameraNotFoundError": "找不到網路攝影機。",
|
||||
"cameraNotSendingData": "我們無法存取您的網路攝影機,請檢查是否有其他應用程式正在使用這個裝置,並從裝置選單裡選擇其他設備或者重新載入。",
|
||||
@@ -386,34 +375,22 @@
|
||||
"micTimeoutError": "無法啟動音訊裝置,連線逾時!",
|
||||
"micUnknownError": "不明原因造成麥克風無法使用。",
|
||||
"moderationAudioLabel": "允許與會者自我解除靜音",
|
||||
"moderationDesktopLabel": "允許非主持人共享螢幕",
|
||||
"moderationVideoLabel": "允許與會者開啟視訊",
|
||||
"muteEveryoneDialog": "與會者可以隨時解除自己的靜音狀態。",
|
||||
"muteEveryoneDialogModerationOn": "與會者可以隨時請求發言。",
|
||||
"muteEveryoneElseDialog": "靜音後,您就不能再解除對方的靜音,但對方可以隨時解除自己的靜音狀態。",
|
||||
"muteEveryoneElseTitle": "是否要讓除了 {{whom}} 以外的人靜音?",
|
||||
"muteEveryoneElsesDesktopDialog": "一旦停止共享,您將無法重新開啟他們的螢幕共享,但他們可以隨時重新開啟。",
|
||||
"muteEveryoneElsesDesktopTitle": "停止除了{{whom}}以外所有人的螢幕共享?",
|
||||
"muteEveryoneElsesVideoDialog": "一旦停用,您就不能再重新開啟對方的網路攝影機,但對方隨時能重新開啟自己的網路攝影機。",
|
||||
"muteEveryoneElsesVideoTitle": "是否要關閉除了 {{whom}} 以外的人的網路攝影機?",
|
||||
"muteEveryoneSelf": "您自己",
|
||||
"muteEveryoneStartMuted": "現在所有人皆已靜音",
|
||||
"muteEveryoneTitle": "要將所有人靜音嗎?",
|
||||
"muteEveryonesDesktopDialog": "與會者可以隨時共享他們的螢幕",
|
||||
"muteEveryonesDesktopDialogModerationOn": "與會者可以隨時請求共享他們的螢幕",
|
||||
"muteEveryonesDesktopTitle": "停止所有人的螢幕共享?",
|
||||
"muteEveryonesVideoDialog": "與會者隨時可以重新開啟自己的網路攝影機。",
|
||||
"muteEveryonesVideoDialogModerationOn": "與會者可以隨時傳送開啟視訊請求。",
|
||||
"muteEveryonesVideoDialogOk": "停用",
|
||||
"muteEveryonesVideoTitle": "要關閉所有人的網路攝影機嗎?",
|
||||
"muteParticipantBody": "您無法對他們解除靜音,但是他們自己隨時可以解除靜音。",
|
||||
"muteParticipantButton": "靜音",
|
||||
"muteParticipantsDesktopBody": "您無法重新開啟他們的螢幕共享,但他們可以隨時重新開啟。",
|
||||
"muteParticipantsDesktopBodyModerationOn": "您和他們都無法重新開啟螢幕共享",
|
||||
"muteParticipantsDesktopButton": "停止螢幕分享",
|
||||
"muteParticipantsDesktopDialog": "您確定要停止這位與會者的螢幕共享嗎?您將無法重新開啟他們的螢幕共享,但他們可以隨時重新開啟。",
|
||||
"muteParticipantsDesktopDialogModerationOn": "您確定要停止這位與會者的螢幕共享嗎?您和他們都無法重新開啟螢幕共享。",
|
||||
"muteParticipantsDesktopTitle": "停止這位與會者的螢幕共享?",
|
||||
"muteParticipantsVideoBody": "您無法重新開啟,只有對方能自己重新開啟。",
|
||||
"muteParticipantsVideoBodyModerationOn": "您和他都無法再將視訊重新開啟。",
|
||||
"muteParticipantsVideoButton": "停用網路攝影機",
|
||||
@@ -526,7 +503,6 @@
|
||||
"tokenAuthFailedWithReasons": "抱歉,您無法參加這個通話,可能原因:{{reason}}",
|
||||
"tokenAuthUnsupported": "不支援權杖網址。",
|
||||
"transcribing": "轉錄中",
|
||||
"unauthenticatedAccessDisabled": "此會議需要身份驗證,請先登入後繼續。",
|
||||
"unlockRoom": "移除會議 $t(lockRoomPassword)",
|
||||
"user": "使用者",
|
||||
"userIdentifier": "使用者 ID",
|
||||
@@ -571,17 +547,11 @@
|
||||
"downloadFailedDescription": "請重試",
|
||||
"downloadFailedTitle": "下載失敗",
|
||||
"downloadFile": "下載",
|
||||
"downloadStarted": "檔案下載已開始",
|
||||
"dragAndDrop": "將檔案拖曳至此或畫面任一處上傳",
|
||||
"fileAlreadyUploaded": "檔案已上傳至此會議",
|
||||
"fileRemovedByOther": "您的檔案「{{fileName}}」已被移除",
|
||||
"fileTooLargeDescription": "請確認檔案未超過 {{ maxFileSize }}",
|
||||
"fileTooLargeTitle": "檔案過大",
|
||||
"fileUploadProgress": "檔案上傳進度",
|
||||
"fileUploadedSuccessfully": "檔案上傳成功",
|
||||
"newFileNotification": "{{participantName}}分享了「{{fileName}}」",
|
||||
"removeFile": "移除",
|
||||
"removeFileSuccess": "檔案移除成功",
|
||||
"uploadFailedDescription": "請重試",
|
||||
"uploadFailedTitle": "上傳失敗",
|
||||
"uploadFile": "分享檔案"
|
||||
@@ -754,8 +724,7 @@
|
||||
"notificationTitle": "大廳",
|
||||
"passwordJoinButton": "加入",
|
||||
"title": "大廳",
|
||||
"toggleLabel": "啟用大廳模式",
|
||||
"waitForModerator": "會議尚未開始,暫無主持人入會。如需成為主持人請先登入,或耐心等待會議開始。"
|
||||
"toggleLabel": "啟用大廳模式"
|
||||
},
|
||||
"localRecording": {
|
||||
"clientState": {
|
||||
@@ -798,10 +767,8 @@
|
||||
"me": "我",
|
||||
"notify": {
|
||||
"OldElectronAPPTitle": "安全漏洞!",
|
||||
"allowAll": "允許全部",
|
||||
"allowAudio": "允許音訊",
|
||||
"allowBoth": "允許音訊與視訊",
|
||||
"allowDesktop": "允許螢幕分享",
|
||||
"allowVideo": "允許視訊",
|
||||
"allowedUnmute": "您可以將麥克風解除靜音、開啟視訊,或是分享您的螢幕。",
|
||||
"audioUnmuteBlockedDescription": "麥克風解除靜音操作由於系統限制而被暫時封鎖。",
|
||||
@@ -815,7 +782,6 @@
|
||||
"dataChannelClosedDescription": "橋接通道已斷開,視訊品質降至最低設定。",
|
||||
"dataChannelClosedDescriptionWithAudio": "橋接通道已斷開,音訊和視訊可能會受到影響。",
|
||||
"dataChannelClosedWithAudio": "音訊和視訊品質可能會降低。",
|
||||
"desktopMutedRemotelyTitle": "您的螢幕分享已被{{participantDisplayName}}停止",
|
||||
"disabledIframe": "嵌入僅供示範使用,此通話將於 {{timeout}} 分鐘後中斷連線。",
|
||||
"disabledIframeSecondaryNative": "嵌入 {{domain}} 僅供示範,此通話將於 {{timeout}} 分鐘後中斷。",
|
||||
"disabledIframeSecondaryWeb": "嵌入 {{domain}} 僅供示範,此通話將於 {{timeout}} 分鐘後中斷,請使用 <a href='{{jaasDomain}}' rel='noopener noreferrer' target='_blank'>Jitsi 服務</a> 來進行正式嵌入!",
|
||||
@@ -873,7 +839,6 @@
|
||||
"oldElectronClientDescription1": "您似乎正在使用存在已知安全漏洞的過時 Jitsi Meet 用戶端,請盡快更新至我們的",
|
||||
"oldElectronClientDescription2": "最新版本",
|
||||
"oldElectronClientDescription3": "!",
|
||||
"openChat": "開啟聊天",
|
||||
"participantWantsToJoin": "希望加入會議",
|
||||
"participantsWantToJoin": "希望加入會議",
|
||||
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) 已被其他與會者移除",
|
||||
@@ -897,7 +862,6 @@
|
||||
"suggestRecordingDescription": "是否要開始錄製這場會議?",
|
||||
"suggestRecordingTitle": "錄製此會議",
|
||||
"unmute": "取消靜音",
|
||||
"unmuteScreen": "開始螢幕分享",
|
||||
"unmuteVideo": "啟用視訊",
|
||||
"videoMutedRemotelyDescription": "您隨時可以再次啟用。",
|
||||
"videoMutedRemotelyTitle": "您的視訊已被 {{participantDisplayName}} 停用",
|
||||
@@ -917,14 +881,11 @@
|
||||
"admit": "準許",
|
||||
"admitAll": "準許所有人",
|
||||
"allow": "允許與會者能夠:",
|
||||
"allowDesktop": "允許螢幕分享",
|
||||
"allowVideo": "允許視訊",
|
||||
"askDesktop": "請求共享螢幕",
|
||||
"askUnmute": "要求解除靜音",
|
||||
"audioModeration": "自我解除靜音",
|
||||
"blockEveryoneMicCamera": "停用所有人的麥克風和網路攝影機",
|
||||
"breakoutRooms": "分組討論室",
|
||||
"desktopModeration": "開始螢幕分享",
|
||||
"goLive": "開始直播",
|
||||
"invite": "邀請他人",
|
||||
"lowerAllHands": "全部取消舉手",
|
||||
@@ -936,8 +897,6 @@
|
||||
"muteAll": "靜音所有人",
|
||||
"muteEveryoneElse": "靜音其他人",
|
||||
"reject": "拒絕",
|
||||
"stopDesktop": "停止螢幕分享",
|
||||
"stopEveryonesDesktop": "停止所有人的螢幕分享",
|
||||
"stopEveryonesVideo": "停用所有人的視訊",
|
||||
"stopVideo": "停用視訊",
|
||||
"unblockEveryoneMicCamera": "解除封鎖所有人的麥克風及網路攝影機",
|
||||
@@ -947,11 +906,9 @@
|
||||
"headings": {
|
||||
"lobby": "大廳({{count}} 人)",
|
||||
"participantsList": "會議與會者({{count}} 人)",
|
||||
"viewerRequests": "觀眾請求({{count}}人)",
|
||||
"visitorInQueue": "({{count}} 人等候中)",
|
||||
"visitorRequests": "{{count}} 人申請",
|
||||
"visitors": "訪客({{count}} 人)",
|
||||
"visitorsList": "觀眾({{count}}人)",
|
||||
"waitingLobby": "於大廳等候({{count}} 人)"
|
||||
},
|
||||
"search": "搜尋與會者",
|
||||
@@ -972,9 +929,6 @@
|
||||
"by": "由 {{ name }}",
|
||||
"closeButton": "結束投票",
|
||||
"create": {
|
||||
"accessibilityLabel": {
|
||||
"send": "傳送投票"
|
||||
},
|
||||
"addOption": "新增選項",
|
||||
"answerPlaceholder": "選項 {{index}}",
|
||||
"cancel": "取消",
|
||||
@@ -1391,20 +1345,6 @@
|
||||
"videounmute": "啟用網路攝影機"
|
||||
},
|
||||
"addPeople": "新增人員到您的通話中",
|
||||
"advancedAudioSettings": {
|
||||
"aec": {
|
||||
"label": "回聲消除"
|
||||
},
|
||||
"agc": {
|
||||
"label": "自動增益控制"
|
||||
},
|
||||
"ns": {
|
||||
"label": "降噪"
|
||||
},
|
||||
"stereo": {
|
||||
"label": "立體聲"
|
||||
}
|
||||
},
|
||||
"audioOnlyOff": "停用低頻寬模式",
|
||||
"audioOnlyOn": "啟用低頻寬模式",
|
||||
"audioRoute": "選擇音訊裝置",
|
||||
@@ -1476,7 +1416,6 @@
|
||||
"reactionHeart": "傳送愛心反應",
|
||||
"reactionLaugh": "傳送大笑反應",
|
||||
"reactionLike": "傳送比讚反應",
|
||||
"reactionLove": "傳送愛心",
|
||||
"reactionSilence": "傳送沉默反應",
|
||||
"reactionSurprised": "傳送驚訝反應",
|
||||
"reactions": "反應",
|
||||
@@ -1562,8 +1501,6 @@
|
||||
"connectionInfo": "連線資訊",
|
||||
"demote": "轉為訪客",
|
||||
"domute": "靜音",
|
||||
"domuteDesktop": "停止螢幕分享",
|
||||
"domuteDesktopOfOthers": "停止螢幕分享給其他人",
|
||||
"domuteOthers": "靜音其他人",
|
||||
"domuteVideo": "停用網路攝影機",
|
||||
"domuteVideoOfOthers": "停用其他人的網路攝影機",
|
||||
@@ -1628,8 +1565,6 @@
|
||||
"noMainParticipantsTitle": "會議尚未開始",
|
||||
"noVisitorLobby": "此會議啟用大廳,暫時無法加入",
|
||||
"notAllowedPromotion": "需由與會者同意您的申請",
|
||||
"requestToJoin": "舉手請求",
|
||||
"requestToJoinDescription": "您的請求已傳送給主持人,請稍候!",
|
||||
"title": "您是會議中的訪客"
|
||||
},
|
||||
"waitingMessage": "會議開始後您將自動加入!"
|
||||
|
||||
@@ -126,16 +126,8 @@
|
||||
"messagebox": "Type a message",
|
||||
"newMessages": "New messages",
|
||||
"nickname": {
|
||||
"featureChat": "chat",
|
||||
"featureClosedCaptions": "closed captions",
|
||||
"featureFileSharing": "file sharing",
|
||||
"featurePolls": "polls",
|
||||
"popover": "Choose a nickname",
|
||||
"title": "Enter a nickname to use chat",
|
||||
"titleWith1Features": "Enter a nickname to use {{feature1}}",
|
||||
"titleWith2Features": "Enter a nickname to use {{feature1}} and {{feature2}}",
|
||||
"titleWith3Features": "Enter a nickname to use {{feature1}}, {{feature2}} and {{feature3}}",
|
||||
"titleWith4Features": "Enter a nickname to use {{feature1}}, {{feature2}}, {{feature3}} and {{feature4}}",
|
||||
"titleWithCC": "Enter a nickname to use chat and closed captions",
|
||||
"titleWithPolls": "Enter a nickname to use chat and polls",
|
||||
"titleWithPollsAndCC": "Enter a nickname to use chat, polls and closed captions",
|
||||
@@ -1437,7 +1429,6 @@
|
||||
"exitFullScreen": "Exit full screen",
|
||||
"exitTileView": "Exit tile view",
|
||||
"feedback": "Leave feedback",
|
||||
"fileSharing": "File sharing",
|
||||
"giphy": "Toggle GIPHY menu",
|
||||
"hangup": "Leave the meeting",
|
||||
"help": "Help",
|
||||
@@ -1473,7 +1464,6 @@
|
||||
"openReactionsMenu": "Open reactions menu",
|
||||
"participants": "Participants",
|
||||
"pip": "Enter Picture-in-Picture mode",
|
||||
"polls": "Polls",
|
||||
"privateMessage": "Send private message",
|
||||
"profile": "Edit your profile",
|
||||
"raiseHand": "Raise your hand",
|
||||
|
||||
@@ -340,7 +340,6 @@ function initCommands() {
|
||||
|
||||
APP.store.dispatch(setAssumedBandwidthBps(value));
|
||||
},
|
||||
|
||||
'set-blurred-background': blurType => {
|
||||
const tracks = APP.store.getState()['features/base/tracks'];
|
||||
const videoTrack = getLocalVideoTrack(tracks)?.jitsiTrack;
|
||||
|
||||
@@ -158,10 +158,11 @@ const VideoLayout = {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const state = APP.store.getState();
|
||||
const currentContainer = largeVideo.getCurrentContainer();
|
||||
const currentContainerType = largeVideo.getCurrentContainerType();
|
||||
const isOnLarge = this.isCurrentlyOnLarge(id);
|
||||
const state = APP.store.getState();
|
||||
const participant = getParticipantById(state, id);
|
||||
const videoTrack = getVideoTrackByParticipant(state, participant);
|
||||
const videoStream = videoTrack?.jitsiTrack;
|
||||
|
||||
87
package-lock.json
generated
87
package-lock.json
generated
@@ -19,10 +19,10 @@
|
||||
"@giphy/react-components": "6.9.4",
|
||||
"@giphy/react-native-sdk": "4.1.0",
|
||||
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/v0.0.19/jitsi-excalidraw-0.0.19.tgz",
|
||||
"@jitsi/js-utils": "2.6.7",
|
||||
"@jitsi/js-utils": "2.2.1",
|
||||
"@jitsi/logger": "2.1.1",
|
||||
"@jitsi/rnnoise-wasm": "0.2.1",
|
||||
"@matrix-org/olm": "3.2.15",
|
||||
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
|
||||
"@microsoft/microsoft-graph-client": "3.0.1",
|
||||
"@mui/material": "5.12.1",
|
||||
"@react-native-async-storage/async-storage": "1.23.1",
|
||||
@@ -66,7 +66,7 @@
|
||||
"js-md5": "0.6.1",
|
||||
"js-sha512": "0.8.0",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2115.0.0+cc2f34c2/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2101.0.0+8061f52a/lib-jitsi-meet.tgz",
|
||||
"lodash-es": "4.17.21",
|
||||
"null-loader": "4.0.1",
|
||||
"optional-require": "1.0.3",
|
||||
@@ -4544,10 +4544,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jitsi/js-utils": {
|
||||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.6.7.tgz",
|
||||
"integrity": "sha512-r16J3CjYt325CFIpfHznND4O3b9BE7CZ7cu+Xx0uk1C+ZY6/bDPZFM/0d4t0VH+1/rmfG4I7i18qxXct3xmPrw==",
|
||||
"license": "Apache-2.0",
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.2.1.tgz",
|
||||
"integrity": "sha512-4Ia4hWO7aTMGbYftzeBr+IHIu5YxiWwTlhsSK34z6925oNAUNI863WgYYGTcXkW/1yuM6LBZrnuZBySDqosISA==",
|
||||
"dependencies": {
|
||||
"@hapi/bourne": "^3.0.0",
|
||||
"js-md5": "0.7.3",
|
||||
@@ -4690,9 +4689,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@matrix-org/olm": {
|
||||
"version": "3.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@matrix-org/olm/-/olm-3.2.15.tgz",
|
||||
"integrity": "sha512-S7lOrndAK9/8qOtaTq/WhttJC/o4GAzdfK0MUPpo8ApzsJEC0QjtwrkC3KBXdFP1cD1MXi/mlKR7aaoVMKgs6Q==",
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
|
||||
"integrity": "sha512-OhC9wwZ/ox9vputA1MR2A7QlYlvfXCV+tdbADOR7Jn7o9qoXh3HWf+AbSpXTK3daF0GIHA69Ws8XOnWqu5n53A==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@microsoft/microsoft-graph-client": {
|
||||
@@ -18259,11 +18258,11 @@
|
||||
},
|
||||
"node_modules/lib-jitsi-meet": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2115.0.0+cc2f34c2/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-/fpQChyB3jTrQbAhm1YVHmU8HcU7hZyfK+dq8NblKKdnnalzfYUelG1c3leSG3SVb6s3xPyxgmulIgTBX5y6eA==",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2101.0.0+8061f52a/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-PCMJIfFWIZtDC6UA/53mT79hiqTGNCRE04/XFgWEr7KRf2QIni2tFh3hW1IPW0OjbtMAkJ1KGQpca/3l6sa5Mw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jitsi/js-utils": "^2.6.7",
|
||||
"@jitsi/js-utils": "2.4.6",
|
||||
"@jitsi/logger": "2.1.1",
|
||||
"@jitsi/precall-test": "1.0.6",
|
||||
"@jitsi/rtcstats": "9.7.0",
|
||||
@@ -18279,6 +18278,17 @@
|
||||
"webrtc-adapter": "8.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/lib-jitsi-meet/node_modules/@jitsi/js-utils": {
|
||||
"version": "2.4.6",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.4.6.tgz",
|
||||
"integrity": "sha512-z/VbM9c0V35T8Zkhxq2gdWbMWmM/3w4BD68xJVmQNrq/NQHxH0fDkRoT/MUds9Mp6dK3AV/h15tCKxVA/0w8Kg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@hapi/bourne": "^3.0.0",
|
||||
"js-md5": "0.7.3",
|
||||
"ua-parser-js": "1.0.35"
|
||||
}
|
||||
},
|
||||
"node_modules/lib-jitsi-meet/node_modules/@jitsi/logger": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/logger/-/logger-2.0.2.tgz",
|
||||
@@ -18301,6 +18311,12 @@
|
||||
"integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lib-jitsi-meet/node_modules/js-md5": {
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.7.3.tgz",
|
||||
"integrity": "sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lie": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
|
||||
@@ -20086,10 +20102,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/node-forge": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.2.tgz",
|
||||
"integrity": "sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw==",
|
||||
"license": "(BSD-3-Clause OR GPL-2.0)",
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
|
||||
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==",
|
||||
"engines": {
|
||||
"node": ">= 6.13.0"
|
||||
}
|
||||
@@ -30001,9 +30016,9 @@
|
||||
"integrity": "sha512-8fAv3cVEuoukSyu5RBZg0YWcrGEMjSKsdeQJuMmeOL2vVXIBWo0TpaHqys4HNCGRmZKzkhYccqxtmNSTxlBgkQ=="
|
||||
},
|
||||
"@jitsi/js-utils": {
|
||||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.6.7.tgz",
|
||||
"integrity": "sha512-r16J3CjYt325CFIpfHznND4O3b9BE7CZ7cu+Xx0uk1C+ZY6/bDPZFM/0d4t0VH+1/rmfG4I7i18qxXct3xmPrw==",
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.2.1.tgz",
|
||||
"integrity": "sha512-4Ia4hWO7aTMGbYftzeBr+IHIu5YxiWwTlhsSK34z6925oNAUNI863WgYYGTcXkW/1yuM6LBZrnuZBySDqosISA==",
|
||||
"requires": {
|
||||
"@hapi/bourne": "^3.0.0",
|
||||
"js-md5": "0.7.3",
|
||||
@@ -30106,9 +30121,8 @@
|
||||
"dev": true
|
||||
},
|
||||
"@matrix-org/olm": {
|
||||
"version": "3.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@matrix-org/olm/-/olm-3.2.15.tgz",
|
||||
"integrity": "sha512-S7lOrndAK9/8qOtaTq/WhttJC/o4GAzdfK0MUPpo8ApzsJEC0QjtwrkC3KBXdFP1cD1MXi/mlKR7aaoVMKgs6Q=="
|
||||
"version": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
|
||||
"integrity": "sha512-OhC9wwZ/ox9vputA1MR2A7QlYlvfXCV+tdbADOR7Jn7o9qoXh3HWf+AbSpXTK3daF0GIHA69Ws8XOnWqu5n53A=="
|
||||
},
|
||||
"@microsoft/microsoft-graph-client": {
|
||||
"version": "3.0.1",
|
||||
@@ -39675,10 +39689,10 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2115.0.0+cc2f34c2/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-/fpQChyB3jTrQbAhm1YVHmU8HcU7hZyfK+dq8NblKKdnnalzfYUelG1c3leSG3SVb6s3xPyxgmulIgTBX5y6eA==",
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2101.0.0+8061f52a/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-PCMJIfFWIZtDC6UA/53mT79hiqTGNCRE04/XFgWEr7KRf2QIni2tFh3hW1IPW0OjbtMAkJ1KGQpca/3l6sa5Mw==",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "^2.6.7",
|
||||
"@jitsi/js-utils": "2.4.6",
|
||||
"@jitsi/logger": "2.1.1",
|
||||
"@jitsi/precall-test": "1.0.6",
|
||||
"@jitsi/rtcstats": "9.7.0",
|
||||
@@ -39694,6 +39708,16 @@
|
||||
"webrtc-adapter": "8.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@jitsi/js-utils": {
|
||||
"version": "2.4.6",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.4.6.tgz",
|
||||
"integrity": "sha512-z/VbM9c0V35T8Zkhxq2gdWbMWmM/3w4BD68xJVmQNrq/NQHxH0fDkRoT/MUds9Mp6dK3AV/h15tCKxVA/0w8Kg==",
|
||||
"requires": {
|
||||
"@hapi/bourne": "^3.0.0",
|
||||
"js-md5": "0.7.3",
|
||||
"ua-parser-js": "1.0.35"
|
||||
}
|
||||
},
|
||||
"@jitsi/logger": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/logger/-/logger-2.0.2.tgz",
|
||||
@@ -39714,6 +39738,11 @@
|
||||
"version": "10.4.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz",
|
||||
"integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="
|
||||
},
|
||||
"js-md5": {
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.7.3.tgz",
|
||||
"integrity": "sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -41021,9 +41050,9 @@
|
||||
}
|
||||
},
|
||||
"node-forge": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.2.tgz",
|
||||
"integrity": "sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw=="
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
|
||||
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA=="
|
||||
},
|
||||
"node-int64": {
|
||||
"version": "0.4.0",
|
||||
|
||||
@@ -25,10 +25,10 @@
|
||||
"@giphy/react-components": "6.9.4",
|
||||
"@giphy/react-native-sdk": "4.1.0",
|
||||
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/v0.0.19/jitsi-excalidraw-0.0.19.tgz",
|
||||
"@jitsi/js-utils": "2.6.7",
|
||||
"@jitsi/js-utils": "2.2.1",
|
||||
"@jitsi/logger": "2.1.1",
|
||||
"@jitsi/rnnoise-wasm": "0.2.1",
|
||||
"@matrix-org/olm": "3.2.15",
|
||||
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
|
||||
"@microsoft/microsoft-graph-client": "3.0.1",
|
||||
"@mui/material": "5.12.1",
|
||||
"@react-native-async-storage/async-storage": "1.23.1",
|
||||
@@ -72,7 +72,7 @@
|
||||
"js-md5": "0.6.1",
|
||||
"js-sha512": "0.8.0",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2115.0.0+cc2f34c2/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2101.0.0+8061f52a/lib-jitsi-meet.tgz",
|
||||
"lodash-es": "4.17.21",
|
||||
"null-loader": "4.0.1",
|
||||
"optional-require": "1.0.3",
|
||||
|
||||
@@ -600,6 +600,31 @@ export function createRemoteVideoMenuButtonEvent(buttonName: string, attributes
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The rtcstats websocket onclose event. We send this to amplitude in order
|
||||
* to detect trace ws prematurely closing.
|
||||
*
|
||||
* @param {Object} closeEvent - The event with which the websocket closed.
|
||||
* @returns {Object} The event in a format suitable for sending via
|
||||
* sendAnalytics.
|
||||
*/
|
||||
export function createRTCStatsTraceCloseEvent(closeEvent: { code: string; reason: string; }) {
|
||||
const event: {
|
||||
action: string;
|
||||
code?: string;
|
||||
reason?: string;
|
||||
source: string;
|
||||
} = {
|
||||
action: 'trace.onclose',
|
||||
source: 'rtcstats'
|
||||
};
|
||||
|
||||
event.code = closeEvent.code;
|
||||
event.reason = closeEvent.reason;
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event indicating that an action related to screen sharing
|
||||
* occurred (e.g. It was started or stopped).
|
||||
|
||||
@@ -139,7 +139,7 @@ function _upgradeRoleStarted(thenableWithCancel: Object) {
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function hideLoginDialog() {
|
||||
return hideDialog('LoginDialog', LoginDialog);
|
||||
return hideDialog(LoginDialog);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -199,7 +199,7 @@ export function enableModeratorLogin() {
|
||||
* @returns {Action}
|
||||
*/
|
||||
export function openWaitForOwnerDialog() {
|
||||
return openDialog('WaitForOwnerDialog', WaitForOwnerDialog);
|
||||
return openDialog(WaitForOwnerDialog);
|
||||
}
|
||||
|
||||
|
||||
@@ -240,7 +240,7 @@ export function waitForOwner() {
|
||||
* @returns {Action}
|
||||
*/
|
||||
export function openLoginDialog() {
|
||||
return openDialog('LoginDialog', LoginDialog);
|
||||
return openDialog(LoginDialog);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -65,7 +65,7 @@ export function openTokenAuthUrl(tokenAuthServiceUrl: string): any {
|
||||
|
||||
// Show warning for leaving conference only when in a conference.
|
||||
if (!browser.isElectron() && getState()['features/base/conference'].conference) {
|
||||
dispatch(openDialog('LoginQuestionDialog', LoginQuestionDialog, {
|
||||
dispatch(openDialog(LoginQuestionDialog, {
|
||||
handler: () => {
|
||||
// Give time for the dialog to close.
|
||||
setTimeout(() => redirect(), 500);
|
||||
|
||||
@@ -209,7 +209,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
|
||||
case STOP_WAIT_FOR_OWNER:
|
||||
_clearExistingWaitForOwnerTimeout(store);
|
||||
store.dispatch(hideDialog('WaitForOwnerDialog', WaitForOwnerDialog));
|
||||
store.dispatch(hideDialog(WaitForOwnerDialog));
|
||||
break;
|
||||
|
||||
case UPGRADE_ROLE_FINISHED: {
|
||||
|
||||
@@ -55,28 +55,6 @@ function getFirstGraphemeUpper(word: string) {
|
||||
return splitter.splitGraphemes(word)[0].toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips bracketed annotations from a display name. Handles multiple bracket types like (),
|
||||
* [], and {}.
|
||||
*
|
||||
* @param {string} name - The display name to clean.
|
||||
* @returns {string} The cleaned display name without bracketed annotations.
|
||||
*/
|
||||
function stripBracketedAnnotations(name: string): string {
|
||||
// Match content within any of the bracket types at the end of the string
|
||||
// This regex matches: (...) or [...] or {...} at the end
|
||||
const bracketRegex = /\s*[([{][^)\]}]*[)\]}]$/;
|
||||
|
||||
let cleaned = name;
|
||||
|
||||
// Remove all trailing bracketed annotations (handle multiple occurrences)
|
||||
while (bracketRegex.test(cleaned)) {
|
||||
cleaned = cleaned.replace(bracketRegex, '');
|
||||
}
|
||||
|
||||
return cleaned.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates initials for a simple string.
|
||||
*
|
||||
@@ -86,15 +64,7 @@ function stripBracketedAnnotations(name: string): string {
|
||||
export function getInitials(s?: string) {
|
||||
// We don't want to use the domain part of an email address, if it is one
|
||||
const initialsBasis = split(s, '@')[0];
|
||||
|
||||
// Strip bracketed annotations (e.g., "(Department)", "[Team]", "{Org}")
|
||||
// to prevent them from being considered as name parts
|
||||
const cleanedName = stripBracketedAnnotations(initialsBasis);
|
||||
|
||||
// Fallback to original if cleaned name is empty
|
||||
const nameForInitials = cleanedName || initialsBasis;
|
||||
|
||||
const [ firstWord, ...remainingWords ] = nameForInitials.split(wordSplitRegex).filter(Boolean);
|
||||
const [ firstWord, ...remainingWords ] = initialsBasis.split(wordSplitRegex).filter(Boolean);
|
||||
|
||||
return getFirstGraphemeUpper(firstWord) + getFirstGraphemeUpper(remainingWords.pop() || '');
|
||||
}
|
||||
|
||||
@@ -285,7 +285,6 @@ export interface IConfig {
|
||||
disableAudioLevels?: boolean;
|
||||
disableBeforeUnloadHandlers?: boolean;
|
||||
disableCameraTintForeground?: boolean;
|
||||
disableChat?: boolean;
|
||||
disableChatSmileys?: boolean;
|
||||
disableDeepLinking?: boolean;
|
||||
disableFilmstripAutohiding?: boolean;
|
||||
@@ -394,7 +393,6 @@ export interface IConfig {
|
||||
disabled?: boolean;
|
||||
initialWidth?: number;
|
||||
minParticipantCountForTopPanel?: number;
|
||||
stageFilmstripParticipants?: number;
|
||||
};
|
||||
flags?: {
|
||||
ssrcRewritingEnabled: boolean;
|
||||
@@ -618,10 +616,6 @@ export interface IConfig {
|
||||
toolbarConfig?: {
|
||||
alwaysVisible?: boolean;
|
||||
autoHideWhileChatIsOpen?: boolean;
|
||||
/**
|
||||
* Background color for the main toolbar. Accepts any valid CSS color.
|
||||
*/
|
||||
backgroundColor?: string;
|
||||
initialTimeout?: number;
|
||||
timeout?: number;
|
||||
};
|
||||
|
||||
@@ -94,7 +94,6 @@ export default [
|
||||
'disableAudioLevels',
|
||||
'disableBeforeUnloadHandlers',
|
||||
'disableCameraTintForeground',
|
||||
'disableChat',
|
||||
'disableChatSmileys',
|
||||
'disableDeepLinking',
|
||||
'disabledNotifications',
|
||||
|
||||
@@ -14,7 +14,6 @@ import logger from './logger';
|
||||
/**
|
||||
* Signals Dialog to close its dialog.
|
||||
*
|
||||
* @param {string|undefined} name - The name of the component for logging purposes.
|
||||
* @param {Object} [component] - The {@code Dialog} component to close/hide. If
|
||||
* {@code undefined}, closes/hides {@code Dialog} regardless of which
|
||||
* component it's rendering; otherwise, closes/hides {@code Dialog} only if
|
||||
@@ -24,8 +23,8 @@ import logger from './logger';
|
||||
* component: (React.Component | undefined)
|
||||
* }}
|
||||
*/
|
||||
export function hideDialog(name?: string, component?: ComponentType<any>) {
|
||||
logger.info(`Hide dialog: ${name}`);
|
||||
export function hideDialog(component?: ComponentType<any>) {
|
||||
logger.info(`Hide dialog: ${getComponentDisplayName(component)}`);
|
||||
|
||||
return {
|
||||
type: HIDE_DIALOG,
|
||||
@@ -49,7 +48,6 @@ export function hideSheet() {
|
||||
/**
|
||||
* Signals Dialog to open dialog.
|
||||
*
|
||||
* @param {string} name - The name of the component for logging purposes.
|
||||
* @param {Object} component - The component to display as dialog.
|
||||
* @param {Object} [componentProps] - The React {@code Component} props of the
|
||||
* specified {@code component}.
|
||||
@@ -59,8 +57,8 @@ export function hideSheet() {
|
||||
* componentProps: (Object | undefined)
|
||||
* }}
|
||||
*/
|
||||
export function openDialog(name: string, component: ComponentType<any>, componentProps?: Object) {
|
||||
logger.info(`Open dialog: ${name}`);
|
||||
export function openDialog(component: ComponentType<any>, componentProps?: Object) {
|
||||
logger.info(`Open dialog: ${getComponentDisplayName(component)}`);
|
||||
|
||||
return {
|
||||
type: OPEN_DIALOG,
|
||||
@@ -94,18 +92,35 @@ export function openSheet(component: ComponentType<any>, componentProps?: Object
|
||||
* is not already open. If it is open, then Dialog is signaled to close its
|
||||
* dialog.
|
||||
*
|
||||
* @param {string} name - The name of the component for logging purposes.
|
||||
* @param {Object} component - The component to display as dialog.
|
||||
* @param {Object} [componentProps] - The React {@code Component} props of the
|
||||
* specified {@code component}.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function toggleDialog(name: string, component: ComponentType<any>, componentProps?: Object) {
|
||||
export function toggleDialog(component: ComponentType<any>, componentProps?: Object) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
if (isDialogOpen(getState, component)) {
|
||||
dispatch(hideDialog(name, component));
|
||||
dispatch(hideDialog(component));
|
||||
} else {
|
||||
dispatch(openDialog(name, component, componentProps));
|
||||
dispatch(openDialog(component, componentProps));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a printable name for a dialog component.
|
||||
*
|
||||
* @param {Object} component - The component to extract the name for.
|
||||
*
|
||||
* @returns {string} The display name.
|
||||
*/
|
||||
function getComponentDisplayName(component?: ComponentType<any>) {
|
||||
if (!component) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const name = component.displayName ?? component.name ?? 'Component';
|
||||
|
||||
return name.replace('withI18nextTranslation(Connect(', '') // dialogs with translations
|
||||
.replace('))', ''); // dialogs with translations suffix
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ const options: i18next.InitOptions = {
|
||||
interpolation: {
|
||||
escapeValue: false // not needed for react as it escapes by default
|
||||
},
|
||||
load: 'all',
|
||||
load: 'languageOnly',
|
||||
ns: [ 'main', 'languages', 'countries', 'translation-languages' ],
|
||||
react: {
|
||||
// re-render when a new resource bundle is added
|
||||
|
||||
@@ -3,14 +3,13 @@ import { jitsiLocalStorage } from '@jitsi/js-utils/jitsi-local-storage';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { safeJsonParse } from '@jitsi/js-utils/json';
|
||||
import { pick } from 'lodash-es';
|
||||
|
||||
import { browser } from '../lib-jitsi-meet';
|
||||
import { isEmbedded } from '../util/embedUtils';
|
||||
import { parseURLParams } from '../util/parseURLParams';
|
||||
|
||||
import logger from './logger';
|
||||
import WHITELIST from './whitelist';
|
||||
|
||||
|
||||
/**
|
||||
* Handles changes of the fake local storage.
|
||||
@@ -62,7 +61,7 @@ function setupJitsiLocalStorage() {
|
||||
|
||||
if (shouldUseHostPageLocalStorage(urlParams)) {
|
||||
try {
|
||||
let localStorageContent = safeJsonParse(urlParams['appData.localStorageContent']);
|
||||
const localStorageContent = safeJsonParse(urlParams['appData.localStorageContent']);
|
||||
|
||||
// We need to disable the local storage before setting the data in case the browser local storage doesn't
|
||||
// throw exception (in some cases when this happens the local storage may be cleared for every session.
|
||||
@@ -72,10 +71,6 @@ function setupJitsiLocalStorage() {
|
||||
jitsiLocalStorage.setLocalStorageDisabled(true);
|
||||
|
||||
if (typeof localStorageContent === 'object') {
|
||||
if (!isEmbedded()) {
|
||||
localStorageContent = pick(localStorageContent, WHITELIST);
|
||||
}
|
||||
|
||||
Object.keys(localStorageContent).forEach(key => {
|
||||
jitsiLocalStorage.setItem(key, localStorageContent[key]);
|
||||
});
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
/**
|
||||
* Keys of localStorage that are used by jibri.
|
||||
*/
|
||||
export default [
|
||||
'callStatsUserName',
|
||||
'displayname',
|
||||
'email',
|
||||
'xmpp_username_override',
|
||||
'xmpp_password_override',
|
||||
'xmpp_conference_password_override'
|
||||
];
|
||||
@@ -186,9 +186,6 @@ function _initLogging({ dispatch, getState }: IStore,
|
||||
Logger.addGlobalTransport(debugLogCollector);
|
||||
JitsiMeetJS.addGlobalLogTransport(debugLogCollector);
|
||||
debugLogCollector.start();
|
||||
|
||||
Logger.removeGlobalTransport(console);
|
||||
JitsiMeetJS.removeGlobalLogTransport(console);
|
||||
}
|
||||
} else if (logCollector && loggingConfig.disableLogCollector) {
|
||||
Logger.removeGlobalTransport(logCollector);
|
||||
|
||||
@@ -26,7 +26,7 @@ MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: AnyA
|
||||
case SET_VIDEO_MUTED: {
|
||||
if (LocalRecordingManager.isRecordingLocally() && LocalRecordingManager.selfRecording.on) {
|
||||
if (action.muted && LocalRecordingManager.selfRecording.withVideo) {
|
||||
dispatch(openDialog('StopRecordingDialog', StopRecordingDialog, { localRecordingVideoStop: true }));
|
||||
dispatch(openDialog(StopRecordingDialog, { localRecordingVideoStop: true }));
|
||||
|
||||
return;
|
||||
} else if (!action.muted && !LocalRecordingManager.selfRecording.withVideo) {
|
||||
|
||||
@@ -62,65 +62,6 @@ const AVATAR_CHECKER_FUNCTIONS = [
|
||||
];
|
||||
/* eslint-enable arrow-body-style */
|
||||
|
||||
/**
|
||||
* Returns the list of active speakers that should be moved to the top of the sorted list of participants so that the
|
||||
* dominant speaker is visible always on the vertical filmstrip in stage layout.
|
||||
*
|
||||
* @param {Function | Object} stateful - The (whole) redux state, or redux's {@code getState} function to be used to
|
||||
* retrieve the state.
|
||||
* @returns {Array<string>}
|
||||
*/
|
||||
export function getActiveSpeakersToBeDisplayed(stateful: IStateful) {
|
||||
const state = toState(stateful);
|
||||
const {
|
||||
dominantSpeaker,
|
||||
fakeParticipants,
|
||||
sortedRemoteVirtualScreenshareParticipants,
|
||||
speakersList
|
||||
} = state['features/base/participants'];
|
||||
const { visibleRemoteParticipants } = state['features/filmstrip'];
|
||||
let activeSpeakers = new Map(speakersList);
|
||||
|
||||
// Do not re-sort the active speakers if dominant speaker is currently visible.
|
||||
if (dominantSpeaker && visibleRemoteParticipants.has(dominantSpeaker)) {
|
||||
return activeSpeakers;
|
||||
}
|
||||
let availableSlotsForActiveSpeakers = visibleRemoteParticipants.size;
|
||||
|
||||
if (activeSpeakers.has(dominantSpeaker ?? '')) {
|
||||
activeSpeakers.delete(dominantSpeaker ?? '');
|
||||
}
|
||||
|
||||
// Add dominant speaker to the beginning of the list (not including self) since the active speaker list is always
|
||||
// alphabetically sorted.
|
||||
if (dominantSpeaker && dominantSpeaker !== getLocalParticipant(state)?.id) {
|
||||
const updatedSpeakers = Array.from(activeSpeakers);
|
||||
|
||||
updatedSpeakers.splice(0, 0, [ dominantSpeaker, getParticipantById(state, dominantSpeaker)?.name ?? '' ]);
|
||||
activeSpeakers = new Map(updatedSpeakers);
|
||||
}
|
||||
|
||||
// Remove screenshares from the count.
|
||||
if (sortedRemoteVirtualScreenshareParticipants) {
|
||||
availableSlotsForActiveSpeakers -= sortedRemoteVirtualScreenshareParticipants.size * 2;
|
||||
for (const screenshare of Array.from(sortedRemoteVirtualScreenshareParticipants.keys())) {
|
||||
const ownerId = getVirtualScreenshareParticipantOwnerId(screenshare as string);
|
||||
|
||||
activeSpeakers.delete(ownerId);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove fake participants from the count.
|
||||
if (fakeParticipants) {
|
||||
availableSlotsForActiveSpeakers -= fakeParticipants.size;
|
||||
}
|
||||
const truncatedSpeakersList = Array.from(activeSpeakers).slice(0, availableSlotsForActiveSpeakers);
|
||||
|
||||
truncatedSpeakersList.sort((a: any, b: any) => a[1].localeCompare(b[1]));
|
||||
|
||||
return new Map(truncatedSpeakersList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the first loadable avatar URL for a participant.
|
||||
*
|
||||
|
||||
@@ -27,7 +27,6 @@ import {
|
||||
isRemoteScreenshareParticipant,
|
||||
isScreenShareParticipant
|
||||
} from './functions';
|
||||
import logger from './logger';
|
||||
import { FakeParticipant, ILocalParticipant, IParticipant, ISourceInfo } from './types';
|
||||
|
||||
/**
|
||||
@@ -77,12 +76,12 @@ const DEFAULT_STATE = {
|
||||
numberOfParticipantsNotSupportingE2EE: 0,
|
||||
overwrittenNameList: {},
|
||||
pinnedParticipant: undefined,
|
||||
previousSpeakers: new Set<string>(),
|
||||
raisedHandsQueue: [],
|
||||
remote: new Map(),
|
||||
remoteVideoSources: new Set<string>(),
|
||||
sortedRemoteVirtualScreenshareParticipants: new Map(),
|
||||
sortedRemoteParticipants: new Map(),
|
||||
speakersList: new Map()
|
||||
sortedRemoteParticipants: new Map()
|
||||
};
|
||||
|
||||
export interface IParticipantsState {
|
||||
@@ -95,12 +94,12 @@ export interface IParticipantsState {
|
||||
numberOfParticipantsNotSupportingE2EE: number;
|
||||
overwrittenNameList: { [id: string]: string; };
|
||||
pinnedParticipant?: string;
|
||||
previousSpeakers: Set<string>;
|
||||
raisedHandsQueue: Array<{ hasBeenNotified?: boolean; id: string; raisedHandTimestamp: number; }>;
|
||||
remote: Map<string, IParticipant>;
|
||||
remoteVideoSources: Set<string>;
|
||||
sortedRemoteParticipants: Map<string, string>;
|
||||
sortedRemoteVirtualScreenshareParticipants: Map<string, string>;
|
||||
speakersList: Map<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,22 +156,10 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
|
||||
const { participant } = action;
|
||||
const { id, previousSpeakers = [] } = participant;
|
||||
const { dominantSpeaker, local } = state;
|
||||
const newSpeakers = [ id, ...previousSpeakers ];
|
||||
const sortedSpeakersList: Array<Array<string>> = [];
|
||||
|
||||
for (const speaker of newSpeakers) {
|
||||
if (speaker !== local?.id) {
|
||||
const remoteParticipant = state.remote.get(speaker);
|
||||
|
||||
remoteParticipant
|
||||
&& sortedSpeakersList.push(
|
||||
[ speaker, _getDisplayName(state, remoteParticipant?.name) ]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Keep the remote speaker list sorted alphabetically.
|
||||
sortedSpeakersList.sort((a, b) => a[1].localeCompare(b[1]));
|
||||
// Build chronologically ordered Set of remote speakers (excluding local)
|
||||
const previousSpeakersSet: Set<string>
|
||||
= new Set(previousSpeakers.filter((speaker: string) => speaker !== local?.id));
|
||||
|
||||
// Only one dominant speaker is allowed.
|
||||
if (dominantSpeaker) {
|
||||
@@ -182,8 +169,8 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
|
||||
if (_updateParticipantProperty(state, id, 'dominantSpeaker', true)) {
|
||||
return {
|
||||
...state,
|
||||
dominantSpeaker: id, // @ts-ignore
|
||||
speakersList: new Map(sortedSpeakersList)
|
||||
dominantSpeaker: id,
|
||||
previousSpeakers: previousSpeakersSet
|
||||
};
|
||||
}
|
||||
|
||||
@@ -365,8 +352,6 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
|
||||
sortedRemoteVirtualScreenshareParticipants.sort((a, b) => a[1].localeCompare(b[1]));
|
||||
|
||||
state.sortedRemoteVirtualScreenshareParticipants = new Map(sortedRemoteVirtualScreenshareParticipants);
|
||||
|
||||
logger.debug('Remote screenshare participant joined', id);
|
||||
}
|
||||
|
||||
// Exclude the screenshare participant from the fake participant count to avoid duplicates.
|
||||
@@ -438,7 +423,7 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
|
||||
}
|
||||
|
||||
// Remove the participant from the list of speakers.
|
||||
state.speakersList.has(id) && state.speakersList.delete(id);
|
||||
state.previousSpeakers.delete(id);
|
||||
|
||||
if (pinnedParticipant === id) {
|
||||
state.pinnedParticipant = undefined;
|
||||
@@ -451,8 +436,6 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
|
||||
if (sortedRemoteVirtualScreenshareParticipants.has(id)) {
|
||||
sortedRemoteVirtualScreenshareParticipants.delete(id);
|
||||
state.sortedRemoteVirtualScreenshareParticipants = new Map(sortedRemoteVirtualScreenshareParticipants);
|
||||
|
||||
logger.debug('Remote screenshare participant left', id);
|
||||
}
|
||||
|
||||
if (oldParticipant && !oldParticipant.fakeParticipant && !isLocalScreenShare) {
|
||||
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
getRemoteScreensharesBasedOnPresence,
|
||||
getVirtualScreenshareParticipantOwnerId
|
||||
} from './functions';
|
||||
import logger from './logger';
|
||||
import { FakeParticipant } from './types';
|
||||
|
||||
StateListenerRegistry.register(
|
||||
@@ -70,19 +69,14 @@ function _createOrRemoveVirtualParticipants(
|
||||
const addedScreenshareSourceNames = difference(newScreenshareSourceNames, oldScreenshareSourceNames);
|
||||
|
||||
if (removedScreenshareSourceNames.length) {
|
||||
removedScreenshareSourceNames.forEach(id => {
|
||||
logger.debug('Dispatching participantLeft for virtual screenshare', id);
|
||||
dispatch(participantLeft(id, conference, {
|
||||
fakeParticipant: FakeParticipant.RemoteScreenShare
|
||||
}));
|
||||
});
|
||||
removedScreenshareSourceNames.forEach(id => dispatch(participantLeft(id, conference, {
|
||||
fakeParticipant: FakeParticipant.RemoteScreenShare
|
||||
})));
|
||||
}
|
||||
|
||||
if (addedScreenshareSourceNames.length) {
|
||||
addedScreenshareSourceNames.forEach(id => {
|
||||
logger.debug('Creating virtual screenshare participant', id);
|
||||
dispatch(createVirtualScreenshareParticipant(id, false, conference));
|
||||
});
|
||||
addedScreenshareSourceNames.forEach(id => dispatch(
|
||||
createVirtualScreenshareParticipant(id, false, conference)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { isTrackStreamingStatusActive } from '../../connection-indicator/functions';
|
||||
import { handleToggleVideoMuted } from '../../toolbox/actions.any';
|
||||
import { muteLocal } from '../../video-menu/actions.any';
|
||||
import { MEDIA_TYPE, VIDEO_TYPE } from '../media/constants';
|
||||
import { getParticipantById, isScreenShareParticipant } from '../participants/functions';
|
||||
import {
|
||||
@@ -80,43 +78,3 @@ export function isRemoteVideoReceived({ getState }: IStore, id: string): boolean
|
||||
|
||||
return Boolean(videoTrack && !videoTrack.muted && isTrackStreamingStatusActive(videoTrack));
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutes the local audio. Same as clicking the audio mute button.
|
||||
*
|
||||
* @param {IStore} store - The redux store.
|
||||
* @returns {Promise} Resolves when the action is complete.
|
||||
*/
|
||||
export function audioMute({ dispatch }: IStore) {
|
||||
return dispatch(muteLocal(true, MEDIA_TYPE.AUDIO));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmutes the local audio. Same as clicking the audio unmute button.
|
||||
*
|
||||
* @param {IStore} store - The redux store.
|
||||
* @returns {Promise} Resolves when the action is complete.
|
||||
*/
|
||||
export function audioUnmute({ dispatch }: IStore) {
|
||||
return dispatch(muteLocal(false, MEDIA_TYPE.AUDIO));
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutes the local video. Same as clicking the video mute button.
|
||||
*
|
||||
* @param {IStore} store - The redux store.
|
||||
* @returns {Promise} Resolves when the action is complete.
|
||||
*/
|
||||
export function videoMute({ dispatch }: IStore) {
|
||||
return dispatch(handleToggleVideoMuted(true, true, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmutes the local video. Same as clicking the video unmute button.
|
||||
*
|
||||
* @param {IStore} store - The redux store.
|
||||
* @returns {Promise} Resolves when the action is complete.
|
||||
*/
|
||||
export function videoUnmute({ dispatch }: IStore) {
|
||||
return dispatch(handleToggleVideoMuted(false, true, true));
|
||||
}
|
||||
|
||||
@@ -8,15 +8,11 @@ import { getJitsiMeetGlobalNS } from '../util/helpers';
|
||||
|
||||
import { setConnectionState } from './actions';
|
||||
import {
|
||||
audioMute,
|
||||
audioUnmute,
|
||||
getLocalCameraEncoding,
|
||||
getRemoteVideoType,
|
||||
isLargeVideoReceived,
|
||||
isRemoteVideoReceived,
|
||||
isTestModeEnabled,
|
||||
videoMute,
|
||||
videoUnmute
|
||||
isTestModeEnabled
|
||||
} from './functions';
|
||||
import logger from './logger';
|
||||
|
||||
@@ -89,14 +85,10 @@ function _bindTortureHelpers(store: IStore) {
|
||||
|
||||
// All torture helper methods go in here
|
||||
getJitsiMeetGlobalNS().testing = {
|
||||
audioMute: audioMute.bind(null, store),
|
||||
audioUnmute: audioUnmute.bind(null, store),
|
||||
getRemoteVideoType: getRemoteVideoType.bind(null, store),
|
||||
isLargeVideoReceived: isLargeVideoReceived.bind(null, store),
|
||||
getLocalCameraEncoding: getLocalCameraEncoding.bind(null, store),
|
||||
isRemoteVideoReceived: isRemoteVideoReceived.bind(null, store),
|
||||
videoMute: videoMute.bind(null, store),
|
||||
videoUnmute: videoUnmute.bind(null, store),
|
||||
isRemoteVideoReceived: isRemoteVideoReceived.bind(null, store)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import { setScreenshareMuted } from '../media/actions';
|
||||
|
||||
import { addLocalTrack, replaceLocalTrack } from './actions.any';
|
||||
import { getLocalDesktopTrack, getTrackState } from './functions.native';
|
||||
import logger from './logger';
|
||||
|
||||
|
||||
export * from './actions.any';
|
||||
@@ -64,6 +63,6 @@ async function _startScreenSharing(dispatch: IStore['dispatch'], state: IReduxSt
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
}
|
||||
} catch (error: any) {
|
||||
logger.error('Error creating screen-sharing stream', error);
|
||||
console.log('ERROR creating screen-sharing stream ', error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,7 +294,7 @@ export function setCameraFacingMode(facingMode: string | undefined) {
|
||||
* @returns {Object} - The open dialog action.
|
||||
*/
|
||||
export function openAllowToggleCameraDialog(onAllow: Function, initiatorId: string) {
|
||||
return openDialog('AllowToggleCameraDialog', AllowToggleCameraDialog, {
|
||||
return openDialog(AllowToggleCameraDialog, {
|
||||
onAllow,
|
||||
initiatorId
|
||||
});
|
||||
|
||||
@@ -211,21 +211,18 @@ export function getLocalJitsiAudioTrackSettings(state: IReduxState) {
|
||||
const jitsiTrack = getLocalJitsiAudioTrack(state);
|
||||
|
||||
if (!jitsiTrack) {
|
||||
const {
|
||||
audioQuality,
|
||||
disableAEC = false,
|
||||
disableAGC = false,
|
||||
disableAP = false,
|
||||
disableNS = false
|
||||
} = state['features/base/config'] || {};
|
||||
|
||||
const enableStereo = Boolean(audioQuality?.stereo);
|
||||
const config = state['features/base/config'];
|
||||
const disableAP = Boolean(config?.disableAP);
|
||||
const disableAGC = Boolean(config?.disableAGC);
|
||||
const disableAEC = Boolean(config?.disableAEC);
|
||||
const disableNS = Boolean(config?.disableNS);
|
||||
const stereo = Boolean(config?.audioQuality?.stereo);
|
||||
|
||||
return {
|
||||
autoGainControl: enableStereo ? false : !disableAP && !disableAGC,
|
||||
channelCount: enableStereo ? 2 : 1,
|
||||
echoCancellation: enableStereo ? false : !disableAP && !disableAEC,
|
||||
noiseSuppression: enableStereo ? false : !disableAP && !disableNS
|
||||
autoGainControl: !disableAP && !disableAGC,
|
||||
channelCount: stereo ? 2 : 1,
|
||||
echoCancellation: !disableAP && !disableAEC,
|
||||
noiseSuppression: !disableAP && !disableNS
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ const BreakoutRoomContextMenu = ({ room, actions = ALL_ACTIONS }: IProps) => {
|
||||
}, [ dispatch, room ]);
|
||||
|
||||
const onRenameBreakoutRoom = useCallback(() => {
|
||||
dispatch(openDialog('BreakoutRoomNamePrompt', BreakoutRoomNamePrompt, {
|
||||
dispatch(openDialog(BreakoutRoomNamePrompt, {
|
||||
breakoutRoomJid: room.jid,
|
||||
initialRoomName: room.name
|
||||
}));
|
||||
|
||||
@@ -22,7 +22,7 @@ export * from './actions.any';
|
||||
* }}
|
||||
*/
|
||||
export function openUpdateCalendarEventDialog(eventId: string) {
|
||||
return openDialog('UpdateCalendarEventDialog', UpdateCalendarEventDialog, { eventId });
|
||||
return openDialog(UpdateCalendarEventDialog, { eventId });
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -220,34 +220,6 @@ export function openCCPanel() {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the chat panel with polls tab active.
|
||||
*
|
||||
* @returns {Object} The redux action.
|
||||
*/
|
||||
export function openPollsPanel() {
|
||||
return async (dispatch: IStore['dispatch']) => {
|
||||
dispatch(setFocusedTab(ChatTabs.POLLS));
|
||||
dispatch({
|
||||
type: OPEN_CHAT
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the chat panel with file sharing tab active.
|
||||
*
|
||||
* @returns {Object} The redux action.
|
||||
*/
|
||||
export function openFileSharingPanel() {
|
||||
return async (dispatch: IStore['dispatch']) => {
|
||||
dispatch(setFocusedTab(ChatTabs.FILE_SHARING));
|
||||
dispatch({
|
||||
type: OPEN_CHAT
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initiates the sending of messages between a moderator and a lobby attendee.
|
||||
|
||||
@@ -1,34 +1,30 @@
|
||||
import { IStore } from '../app/types';
|
||||
import { IParticipant } from '../base/participants/types';
|
||||
import { navigate } from '../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
import { screen } from '../mobile/navigation/routes';
|
||||
|
||||
import { OPEN_CHAT } from './actionTypes';
|
||||
import { setFocusedTab } from './actions.any';
|
||||
import { ChatTabs } from './constants';
|
||||
|
||||
export * from './actions.any';
|
||||
|
||||
/**
|
||||
* Displays the chat panel with the CHAT tab active.
|
||||
* Displays the chat panel.
|
||||
*
|
||||
* @param {Object} participant - The recipient for the private chat.
|
||||
* @param {boolean} disablePolls - Checks if polls are disabled.
|
||||
*
|
||||
* @returns {Function}
|
||||
* @returns {{
|
||||
* participant: participant,
|
||||
* type: OPEN_CHAT
|
||||
* }}
|
||||
*/
|
||||
export function openChat(participant?: IParticipant | undefined | Object, disablePolls?: boolean) {
|
||||
return (dispatch: IStore['dispatch']) => {
|
||||
if (disablePolls) {
|
||||
navigate(screen.conference.chat);
|
||||
} else {
|
||||
navigate(screen.conference.chatandpolls.main);
|
||||
}
|
||||
if (disablePolls) {
|
||||
navigate(screen.conference.chat);
|
||||
}
|
||||
navigate(screen.conference.chatandpolls.main);
|
||||
|
||||
dispatch(setFocusedTab(ChatTabs.CHAT));
|
||||
dispatch({
|
||||
participant,
|
||||
type: OPEN_CHAT
|
||||
});
|
||||
return {
|
||||
participant,
|
||||
type: OPEN_CHAT
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,13 +8,12 @@ import {
|
||||
SET_CHAT_WIDTH,
|
||||
SET_USER_CHAT_WIDTH
|
||||
} from './actionTypes';
|
||||
import { closeChat, setFocusedTab } from './actions.any';
|
||||
import { ChatTabs } from './constants';
|
||||
import { closeChat } from './actions.any';
|
||||
|
||||
export * from './actions.any';
|
||||
|
||||
/**
|
||||
* Displays the chat panel with the CHAT tab active.
|
||||
* Displays the chat panel.
|
||||
*
|
||||
* @param {Object} participant - The recipient for the private chat.
|
||||
* @param {Object} _disablePolls - Used on native.
|
||||
@@ -25,7 +24,6 @@ export * from './actions.any';
|
||||
*/
|
||||
export function openChat(participant?: Object, _disablePolls?: boolean) {
|
||||
return function(dispatch: IStore['dispatch']) {
|
||||
dispatch(setFocusedTab(ChatTabs.CHAT));
|
||||
dispatch({
|
||||
participant,
|
||||
type: OPEN_CHAT
|
||||
|
||||
@@ -10,7 +10,7 @@ import { arePollsDisabled } from '../../../conference/functions.any';
|
||||
import { navigate } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
import { screen } from '../../../mobile/navigation/routes';
|
||||
import { getUnreadPollCount } from '../../../polls/functions';
|
||||
import { getUnreadCount, getUnreadFilesCount, isChatDisabled } from '../../functions';
|
||||
import { getUnreadCount, getUnreadFilesCount } from '../../functions';
|
||||
|
||||
interface IProps extends AbstractButtonProps {
|
||||
|
||||
@@ -65,7 +65,7 @@ class ChatButton extends AbstractButton<IProps> {
|
||||
* @returns {IProps}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
const enabled = getFeatureFlag(state, CHAT_ENABLED, true) && !isChatDisabled(state);
|
||||
const enabled = getFeatureFlag(state, CHAT_ENABLED, true);
|
||||
const { visible = enabled } = ownProps;
|
||||
|
||||
return {
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
toggleChat
|
||||
} from '../../actions.web';
|
||||
import { CHAT_SIZE, ChatTabs, OPTION_GROUPCHAT, SMALL_WIDTH_THRESHOLD } from '../../constants';
|
||||
import { getChatMaxSize, getFocusedTab, isChatDisabled } from '../../functions';
|
||||
import { getChatMaxSize } from '../../functions';
|
||||
import { IChatProps as AbstractProps } from '../../types';
|
||||
|
||||
import ChatHeader from './ChatHeader';
|
||||
@@ -41,18 +41,13 @@ interface IProps extends AbstractProps {
|
||||
/**
|
||||
* The currently focused tab.
|
||||
*/
|
||||
_focusedTab?: ChatTabs;
|
||||
_focusedTab: ChatTabs;
|
||||
|
||||
/**
|
||||
* True if the CC tab is enabled and false otherwise.
|
||||
*/
|
||||
_isCCTabEnabled: boolean;
|
||||
|
||||
/**
|
||||
* True if chat is disabled.
|
||||
*/
|
||||
_isChatDisabled: boolean;
|
||||
|
||||
/**
|
||||
* True if file sharing tab is enabled.
|
||||
*/
|
||||
@@ -222,7 +217,6 @@ const Chat = ({
|
||||
_isOpen,
|
||||
_isPollsEnabled,
|
||||
_isCCTabEnabled,
|
||||
_isChatDisabled,
|
||||
_isFileSharingTabEnabled,
|
||||
_focusedTab,
|
||||
_isResizing,
|
||||
@@ -235,11 +229,6 @@ const Chat = ({
|
||||
dispatch,
|
||||
t
|
||||
}: IProps) => {
|
||||
// If no tabs are available, don't render the chat panel at all.
|
||||
if (_isChatDisabled && !_isPollsEnabled && !_isCCTabEnabled && !_isFileSharingTabEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { classes, cx } = useStyles({ _isResizing, width: _width });
|
||||
const [ isMouseDown, setIsMouseDown ] = useState(false);
|
||||
const [ mousePosition, setMousePosition ] = useState<number | null>(null);
|
||||
@@ -298,6 +287,8 @@ const Chat = ({
|
||||
|
||||
// Disable text selection during resize
|
||||
document.body.style.userSelect = 'none';
|
||||
|
||||
console.log('Chat resize: Mouse down', { clientX: e.clientX, initialWidth: _width });
|
||||
}, [ _width, dispatch ]);
|
||||
|
||||
/**
|
||||
@@ -313,6 +304,8 @@ const Chat = ({
|
||||
// Restore cursor and text selection
|
||||
document.body.style.cursor = '';
|
||||
document.body.style.userSelect = '';
|
||||
|
||||
console.log('Chat resize: Mouse up');
|
||||
}
|
||||
}, [ isMouseDown, dispatch ]);
|
||||
|
||||
@@ -323,6 +316,7 @@ const Chat = ({
|
||||
* @returns {void}
|
||||
*/
|
||||
const onChatResize = useCallback(throttle((e: MouseEvent) => {
|
||||
// console.log('Chat resize: Mouse move', { clientX: e.clientX, isMouseDown, mousePosition, _width });
|
||||
if (isMouseDown && mousePosition !== null && dragChatWidth !== null) {
|
||||
// For chat panel resizing on the left edge:
|
||||
// - Dragging left (decreasing X coordinate) should make the panel wider
|
||||
@@ -422,7 +416,7 @@ const Chat = ({
|
||||
return (
|
||||
<>
|
||||
{renderTabs()}
|
||||
{!_isChatDisabled && (<div
|
||||
<div
|
||||
aria-labelledby = { ChatTabs.CHAT }
|
||||
className = { cx(
|
||||
classes.chatPanel,
|
||||
@@ -448,7 +442,7 @@ const Chat = ({
|
||||
)}
|
||||
<ChatInput
|
||||
onSend = { onSendMessage } />
|
||||
</div>) }
|
||||
</div>
|
||||
{ _isPollsEnabled && (
|
||||
<>
|
||||
<div
|
||||
@@ -490,18 +484,8 @@ const Chat = ({
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
function renderTabs() {
|
||||
// The only way focused tab will be undefined is when no tab is enabled. Therefore this function won't be
|
||||
// executed because Chat component won't render anything. This should never happen but adding the check
|
||||
// here to make TS happy (when passing the _focusedTab in the selected prop for Tabs).
|
||||
if (!_focusedTab) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let tabs = [];
|
||||
|
||||
// Only add chat tab if chat is not disabled.
|
||||
if (!_isChatDisabled) {
|
||||
tabs.push({
|
||||
let tabs = [
|
||||
{
|
||||
accessibilityLabel: t('chat.tabs.chat'),
|
||||
countBadge:
|
||||
_focusedTab !== ChatTabs.CHAT && _unreadMessagesCount > 0 ? _unreadMessagesCount : undefined,
|
||||
@@ -509,8 +493,8 @@ const Chat = ({
|
||||
controlsId: `${ChatTabs.CHAT}-panel`,
|
||||
icon: IconMessage,
|
||||
title: t('chat.tabs.chat')
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
if (_isPollsEnabled) {
|
||||
tabs.push({
|
||||
@@ -580,8 +564,6 @@ const Chat = ({
|
||||
{_showNamePrompt
|
||||
? <DisplayNameForm
|
||||
isCCTabEnabled = { _isCCTabEnabled }
|
||||
isChatDisabled = { _isChatDisabled }
|
||||
isFileSharingEnabled = { _isFileSharingTabEnabled }
|
||||
isPollsEnabled = { _isPollsEnabled } />
|
||||
: renderChat()}
|
||||
<div
|
||||
@@ -620,7 +602,7 @@ const Chat = ({
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState, _ownProps: any) {
|
||||
const { isOpen, messages, unreadMessagesCount, unreadFilesCount, width, isResizing } = state['features/chat'];
|
||||
const { isOpen, focusedTab, messages, unreadMessagesCount, unreadFilesCount, width, isResizing } = state['features/chat'];
|
||||
const { unreadPollsCount } = state['features/polls'];
|
||||
const _localParticipant = getLocalParticipant(state);
|
||||
|
||||
@@ -629,9 +611,8 @@ function _mapStateToProps(state: IReduxState, _ownProps: any) {
|
||||
_isOpen: isOpen,
|
||||
_isPollsEnabled: !arePollsDisabled(state),
|
||||
_isCCTabEnabled: isCCTabEnabled(state),
|
||||
_isChatDisabled: isChatDisabled(state),
|
||||
_isFileSharingTabEnabled: isFileSharingEnabled(state),
|
||||
_focusedTab: getFocusedTab(state),
|
||||
_focusedTab: focusedTab,
|
||||
_messages: messages,
|
||||
_unreadMessagesCount: unreadMessagesCount,
|
||||
_unreadPollsCount: unreadPollsCount,
|
||||
|
||||
@@ -9,7 +9,6 @@ import { IconMessage } from '../../../base/icons/svg';
|
||||
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
|
||||
import { closeOverflowMenuIfOpen } from '../../../toolbox/actions.web';
|
||||
import { toggleChat } from '../../actions.web';
|
||||
import { isChatDisabled } from '../../functions';
|
||||
|
||||
import ChatCounter from './ChatCounter';
|
||||
|
||||
@@ -92,8 +91,7 @@ class ChatButton extends AbstractButton<IProps> {
|
||||
*/
|
||||
const mapStateToProps = (state: IReduxState) => {
|
||||
return {
|
||||
_chatOpen: state['features/chat'].isOpen,
|
||||
visible: !isChatDisabled(state)
|
||||
_chatOpen: state['features/chat'].isOpen
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -2,12 +2,12 @@ import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import Icon from '../../../base/icons/components/Icon';
|
||||
import { IconCloseLarge } from '../../../base/icons/svg';
|
||||
import { isFileSharingEnabled } from '../../../file-sharing/functions.any';
|
||||
import { toggleChat } from '../../actions.web';
|
||||
import { ChatTabs } from '../../constants';
|
||||
import { getFocusedTab, isChatDisabled } from '../../functions';
|
||||
|
||||
interface IProps {
|
||||
|
||||
@@ -40,8 +40,7 @@ interface IProps {
|
||||
function ChatHeader({ className, isCCTabEnabled, isPollsEnabled }: IProps) {
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
const _isChatDisabled = useSelector(isChatDisabled);
|
||||
const focusedTab = useSelector(getFocusedTab);
|
||||
const { focusedTab } = useSelector((state: IReduxState) => state['features/chat']);
|
||||
const fileSharingTabEnabled = useSelector(isFileSharingEnabled);
|
||||
|
||||
const onCancel = useCallback(() => {
|
||||
@@ -57,7 +56,7 @@ function ChatHeader({ className, isCCTabEnabled, isPollsEnabled }: IProps) {
|
||||
|
||||
let title = 'chat.title';
|
||||
|
||||
if (!_isChatDisabled && focusedTab === ChatTabs.CHAT) {
|
||||
if (focusedTab === ChatTabs.CHAT) {
|
||||
title = 'chat.tabs.chat';
|
||||
} else if (isPollsEnabled && focusedTab === ChatTabs.POLLS) {
|
||||
title = 'chat.tabs.polls';
|
||||
@@ -65,11 +64,6 @@ function ChatHeader({ className, isCCTabEnabled, isPollsEnabled }: IProps) {
|
||||
title = 'chat.tabs.closedCaptions';
|
||||
} else if (fileSharingTabEnabled && focusedTab === ChatTabs.FILE_SHARING) {
|
||||
title = 'chat.tabs.fileSharing';
|
||||
} else {
|
||||
// If the focused tab is not enabled, don't render the header.
|
||||
// This should not happen in normal circumstances since Chat.tsx already checks
|
||||
// if any tabs are available before rendering.
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -25,16 +25,6 @@ interface IProps extends WithTranslation {
|
||||
*/
|
||||
isCCTabEnabled: boolean;
|
||||
|
||||
/**
|
||||
* Whether chat is disabled.
|
||||
*/
|
||||
isChatDisabled: boolean;
|
||||
|
||||
/**
|
||||
* Whether file sharing is enabled.
|
||||
*/
|
||||
isFileSharingEnabled: boolean;
|
||||
|
||||
/**
|
||||
* Whether the polls feature is enabled or not.
|
||||
*/
|
||||
@@ -84,31 +74,18 @@ class DisplayNameForm extends Component<IProps, IState> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
override render() {
|
||||
const { isCCTabEnabled, isChatDisabled, isFileSharingEnabled, isPollsEnabled, t } = this.props;
|
||||
const { isCCTabEnabled, isPollsEnabled, t } = this.props;
|
||||
|
||||
// Build array of enabled feature names (translated).
|
||||
const features = [
|
||||
!isChatDisabled ? t('chat.nickname.featureChat') : '',
|
||||
isPollsEnabled ? t('chat.nickname.featurePolls') : '',
|
||||
isFileSharingEnabled ? t('chat.nickname.featureFileSharing') : '',
|
||||
isCCTabEnabled ? t('chat.nickname.featureClosedCaptions') : ''
|
||||
].filter(Boolean);
|
||||
let title = 'chat.nickname.title';
|
||||
|
||||
// Return null if no features available - component won't render.
|
||||
if (features.length === 0) {
|
||||
return null;
|
||||
if (isCCTabEnabled && isPollsEnabled) {
|
||||
title = 'chat.nickname.titleWithPollsAndCC';
|
||||
} else if (isCCTabEnabled) {
|
||||
title = 'chat.nickname.titleWithCC';
|
||||
} else if (isPollsEnabled) {
|
||||
title = 'chat.nickname.titleWithPolls';
|
||||
}
|
||||
|
||||
// Build translation arguments dynamically: { feature1: "chat", feature2: "polls", ... }
|
||||
const translationArgs = features.reduce((acc, feature, index) => {
|
||||
acc[`feature${index + 1}`] = feature;
|
||||
|
||||
return acc;
|
||||
}, {} as Record<string, string>);
|
||||
|
||||
// Use dynamic translation key: "titleWith1Features", "titleWith2Features", etc.
|
||||
const title = t(`chat.nickname.titleWith${features.length}Features`, translationArgs);
|
||||
|
||||
return (
|
||||
<div id = 'nickname'>
|
||||
<form onSubmit = { this._onSubmit }>
|
||||
|
||||
@@ -12,7 +12,6 @@ import Button from '../../../base/ui/components/web/Button';
|
||||
import { BUTTON_TYPES } from '../../../base/ui/constants.any';
|
||||
import { copyText } from '../../../base/util/copyText.web';
|
||||
import { handleLobbyChatInitialized, openChat } from '../../actions.web';
|
||||
import logger from '../../logger';
|
||||
|
||||
export interface IProps {
|
||||
className?: string;
|
||||
@@ -126,11 +125,11 @@ const MessageMenu = ({ message, participantId, isFromVisitor, isLobbyMessage, en
|
||||
setShowCopiedMessage(false);
|
||||
}, 2000);
|
||||
} else {
|
||||
logger.error('Failed to copy text');
|
||||
console.error('Failed to copy text');
|
||||
}
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
logger.error('Error copying text', error);
|
||||
.catch(error => {
|
||||
console.error('Error copying text:', error);
|
||||
});
|
||||
handleClose();
|
||||
}, [ message ]);
|
||||
|
||||
@@ -12,14 +12,11 @@ import { isJwtFeatureEnabled } from '../base/jwt/functions';
|
||||
import { getParticipantById, isPrivateChatEnabled } from '../base/participants/functions';
|
||||
import { IParticipant } from '../base/participants/types';
|
||||
import { escapeRegexp } from '../base/util/helpers';
|
||||
import { arePollsDisabled } from '../conference/functions.any';
|
||||
import { isFileSharingEnabled } from '../file-sharing/functions.any';
|
||||
import { getParticipantsPaneWidth } from '../participants-pane/functions';
|
||||
import { isCCTabEnabled } from '../subtitles/functions.any';
|
||||
import { VIDEO_SPACE_MIN_SIZE } from '../video-layout/constants';
|
||||
import { IVisitorChatParticipant } from '../visitors/types';
|
||||
|
||||
import { ChatTabs, MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL, TIMESTAMP_FORMAT } from './constants';
|
||||
import { MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL, TIMESTAMP_FORMAT } from './constants';
|
||||
import { IMessage } from './types';
|
||||
|
||||
/**
|
||||
@@ -156,53 +153,6 @@ export function areSmileysDisabled(state: IReduxState) {
|
||||
return disableChatSmileys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the chat feature is disabled.
|
||||
*
|
||||
* @param {IReduxState} state - The redux state.
|
||||
* @returns {boolean} True if chat is disabled, false otherwise.
|
||||
*/
|
||||
export function isChatDisabled(state: IReduxState): boolean {
|
||||
return Boolean(state['features/base/config']?.disableChat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default focused tab based on what features are enabled.
|
||||
* Returns the first available tab in priority order: CHAT -> POLLS -> FILE_SHARING -> CLOSED_CAPTIONS.
|
||||
*
|
||||
* @param {IReduxState} state - The redux state.
|
||||
* @returns {ChatTabs | undefined} The default focused tab.
|
||||
*/
|
||||
export function getDefaultFocusedTab(state: IReduxState): ChatTabs | undefined {
|
||||
if (!isChatDisabled(state)) {
|
||||
return ChatTabs.CHAT;
|
||||
}
|
||||
|
||||
if (!arePollsDisabled(state)) {
|
||||
return ChatTabs.POLLS;
|
||||
}
|
||||
|
||||
if (isFileSharingEnabled(state)) {
|
||||
return ChatTabs.FILE_SHARING;
|
||||
}
|
||||
|
||||
if (isCCTabEnabled(state)) {
|
||||
return ChatTabs.CLOSED_CAPTIONS;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently focused tab or the default focused tab if none is set.
|
||||
*
|
||||
* @param {IReduxState} state - The redux state.
|
||||
* @returns {ChatTabs | undefined} The focused tab or undefined if no tabs are available.
|
||||
*/
|
||||
export function getFocusedTab(state: IReduxState): ChatTabs | undefined {
|
||||
return state['features/chat'].focusedTab || getDefaultFocusedTab(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the timestamp to display for the message.
|
||||
*
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import ChatButton from './components/web/ChatButton';
|
||||
import { isChatDisabled } from './functions';
|
||||
|
||||
const chat = {
|
||||
key: 'chat',
|
||||
Content: ChatButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
/**
|
||||
* A hook that returns the chat button if chat is not disabled.
|
||||
*
|
||||
* @returns {Object | undefined} - The chat button object or undefined.
|
||||
*/
|
||||
export function useChatButton() {
|
||||
const _isChatDisabled = useSelector(isChatDisabled);
|
||||
|
||||
if (!_isChatDisabled) {
|
||||
return chat;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
import { getLogger } from '../base/logging/functions';
|
||||
|
||||
export default getLogger('app:chat');
|
||||
@@ -25,8 +25,6 @@ import { IParticipant } from '../base/participants/types';
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
|
||||
import { playSound, registerSound, unregisterSound } from '../base/sounds/actions';
|
||||
import { arePollsDisabled } from '../conference/functions.any';
|
||||
import { isFileSharingEnabled } from '../file-sharing/functions.any';
|
||||
import { addGif } from '../gifs/actions';
|
||||
import { extractGifURL, getGifDisplayMode, isGifEnabled, isGifMessage } from '../gifs/function.any';
|
||||
import { showMessageNotification } from '../notifications/actions';
|
||||
@@ -36,7 +34,6 @@ import { ADD_REACTION_MESSAGE } from '../reactions/actionTypes';
|
||||
import { pushReactions } from '../reactions/actions.any';
|
||||
import { ENDPOINT_REACTION_NAME } from '../reactions/constants';
|
||||
import { getReactionMessageFromBuffer, isReactionsEnabled } from '../reactions/functions.any';
|
||||
import { isCCTabEnabled } from '../subtitles/functions.any';
|
||||
import { showToolbox } from '../toolbox/actions';
|
||||
import { getDisplayName } from '../visitors/functions';
|
||||
|
||||
@@ -69,9 +66,7 @@ import {
|
||||
} from './constants';
|
||||
import {
|
||||
getDisplayNameSuffix,
|
||||
getFocusedTab,
|
||||
getUnreadCount,
|
||||
isChatDisabled,
|
||||
isSendGroupChatDisabled,
|
||||
isVisitorChatParticipant
|
||||
} from './functions';
|
||||
@@ -186,28 +181,23 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
|
||||
case SET_FOCUSED_TAB:
|
||||
case OPEN_CHAT: {
|
||||
const state = store.getState();
|
||||
const focusedTab = action.tabId || getFocusedTab(state);
|
||||
const focusedTab = action.tabId || getState()['features/chat'].focusedTab;
|
||||
|
||||
if (focusedTab === ChatTabs.CHAT) {
|
||||
// Don't allow opening chat if it's disabled AND user is trying to open the CHAT tab.
|
||||
if (isChatDisabled(state)) {
|
||||
return next(action);
|
||||
}
|
||||
unreadCount = 0;
|
||||
|
||||
if (typeof APP !== 'undefined') {
|
||||
APP.API.notifyChatUpdated(unreadCount, true);
|
||||
}
|
||||
|
||||
const { privateMessageRecipient } = state['features/chat'];
|
||||
const { privateMessageRecipient } = store.getState()['features/chat'];
|
||||
|
||||
if (
|
||||
isSendGroupChatDisabled(state)
|
||||
isSendGroupChatDisabled(store.getState())
|
||||
&& privateMessageRecipient
|
||||
&& !action.participant
|
||||
) {
|
||||
const participant = getParticipantById(state, privateMessageRecipient.id);
|
||||
const participant = getParticipantById(store.getState(), privateMessageRecipient.id);
|
||||
|
||||
if (participant) {
|
||||
action.participant = participant;
|
||||
@@ -217,21 +207,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
}
|
||||
}
|
||||
} else if (focusedTab === ChatTabs.POLLS) {
|
||||
// Don't allow opening chat panel if polls are disabled AND user is trying to open the POLLS tab.
|
||||
if (arePollsDisabled(state)) {
|
||||
return next(action);
|
||||
}
|
||||
dispatch(resetUnreadPollsCount());
|
||||
|
||||
// Don't allow opening chat panel if file sharing is disabled AND user is trying to open the
|
||||
// FILE_SHARING tab.
|
||||
} else if (focusedTab === ChatTabs.FILE_SHARING && !isFileSharingEnabled(state)) {
|
||||
return next(action);
|
||||
|
||||
// Don't allow opening chat panel if closed captions are disabled AND user is trying to open the
|
||||
// CLOSED_CAPTIONS tab.
|
||||
} else if (focusedTab === ChatTabs.CLOSED_CAPTIONS && !isCCTabEnabled(state)) {
|
||||
return next(action);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -263,7 +239,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
const participantExists = getParticipantById(state, shouldSendPrivateMessageTo.id);
|
||||
|
||||
if (participantExists || shouldSendPrivateMessageTo.isFromVisitor) {
|
||||
dispatch(openDialog('ChatPrivacyDialog', ChatPrivacyDialog, {
|
||||
dispatch(openDialog(ChatPrivacyDialog, {
|
||||
message: action.message,
|
||||
participantID: shouldSendPrivateMessageTo.id,
|
||||
isFromVisitor: shouldSendPrivateMessageTo.isFromVisitor,
|
||||
@@ -600,11 +576,6 @@ function _handleReceivedMessage({ dispatch, getState }: IStore,
|
||||
const { isOpen: isChatOpen } = state['features/chat'];
|
||||
const { soundsIncomingMessage: soundEnabled, userSelectedNotifications } = state['features/base/settings'];
|
||||
|
||||
// Don't play sound or show notifications if chat is disabled.
|
||||
if (isChatDisabled(state)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (soundEnabled && shouldPlaySound && !isChatOpen) {
|
||||
dispatch(playSound(INCOMING_MSG_SOUND_ID));
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ const DEFAULT_STATE = {
|
||||
privateMessageRecipient: undefined,
|
||||
lobbyMessageRecipient: undefined,
|
||||
isLobbyChatActive: false,
|
||||
focusedTab: undefined,
|
||||
focusedTab: ChatTabs.CHAT,
|
||||
isResizing: false,
|
||||
width: {
|
||||
current: CHAT_SIZE,
|
||||
@@ -44,7 +44,7 @@ const DEFAULT_STATE = {
|
||||
};
|
||||
|
||||
export interface IChatState {
|
||||
focusedTab?: ChatTabs;
|
||||
focusedTab: ChatTabs;
|
||||
groupChatWithPermissions: boolean;
|
||||
isLobbyChatActive: boolean;
|
||||
isOpen: boolean;
|
||||
|
||||
@@ -22,7 +22,7 @@ export function notifyKickedOut(participant: any, submit?: Function) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(openDialog('AlertDialog', AlertDialog, {
|
||||
dispatch(openDialog(AlertDialog, {
|
||||
contentKey: {
|
||||
key: participant ? 'dialog.kickTitle' : 'dialog.kickSystemTitle',
|
||||
params: {
|
||||
@@ -52,7 +52,7 @@ export function notifyConferenceFailed(reasonKey: string, submit?: Function) {
|
||||
// we have to push the opening of the dialog to the queue
|
||||
// so that we make sure it will be visible after the events
|
||||
// of conference destroyed are done
|
||||
setTimeout(() => dispatch(openDialog('AlertDialog', AlertDialog, {
|
||||
setTimeout(() => dispatch(openDialog(AlertDialog, {
|
||||
contentKey: {
|
||||
key: reasonKey
|
||||
},
|
||||
@@ -60,7 +60,7 @@ export function notifyConferenceFailed(reasonKey: string, submit?: Function) {
|
||||
},
|
||||
onSubmit: () => {
|
||||
submit?.();
|
||||
dispatch(hideDialog('AlertDialog', AlertDialog));
|
||||
dispatch(hideDialog(AlertDialog));
|
||||
}
|
||||
})));
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@ import logger from './logger';
|
||||
*/
|
||||
export function openLeaveReasonDialog(title?: string) {
|
||||
return (dispatch: IStore['dispatch']): Promise<void> => new Promise(resolve => {
|
||||
dispatch(openDialog('LeaveReasonDialog', LeaveReasonDialog, {
|
||||
dispatch(openDialog(LeaveReasonDialog, {
|
||||
onClose: resolve,
|
||||
title
|
||||
}));
|
||||
|
||||
@@ -26,7 +26,7 @@ function SpeakerStatsLabel() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const onClick = () => {
|
||||
dispatch(openDialog('SpeakerStats', SpeakerStats, { conference }));
|
||||
dispatch(openDialog(SpeakerStats, { conference }));
|
||||
};
|
||||
|
||||
if (count <= 2 || _isSpeakerStatsDisabled) {
|
||||
|
||||
@@ -327,7 +327,7 @@ export function _mapDispatchToProps(dispatch: IStore['dispatch']) {
|
||||
* @returns {void}
|
||||
*/
|
||||
_onOpenBandwidthDialog() {
|
||||
dispatch(openDialog('BandwidthSettingsDialog', BandwidthSettingsDialog));
|
||||
dispatch(openDialog(BandwidthSettingsDialog));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ type Options = {
|
||||
export function showDesktopPicker(options: Options = {}, onSourceChoose: Function) {
|
||||
const { desktopSharingSources } = options;
|
||||
|
||||
return openDialog('DesktopPicker', DesktopPicker, {
|
||||
return openDialog(DesktopPicker, {
|
||||
desktopSharingSources,
|
||||
onSourceChoose
|
||||
});
|
||||
|
||||
@@ -259,29 +259,6 @@ class AudioDevicesSelection extends AbstractDialogTab<IProps, {}> {
|
||||
|
||||
const newValue = name === 'channelCount' ? (checked ? 2 : 1) : checked;
|
||||
|
||||
if (name === 'channelCount' && newValue === 2) {
|
||||
super._onChange({
|
||||
audioSettings: {
|
||||
autoGainControl: false,
|
||||
channelCount: 2,
|
||||
echoCancellation: false,
|
||||
noiseSuppression: false
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
} else if (name !== 'channelCount' && newValue === true) {
|
||||
super._onChange({
|
||||
audioSettings: {
|
||||
...audioSettings,
|
||||
[name]: newValue,
|
||||
channelCount: 1
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
super._onChange({
|
||||
audioSettings: {
|
||||
...audioSettings,
|
||||
|
||||
@@ -14,7 +14,7 @@ export function openDisplayNamePrompt({ onPostSubmit, validateInput }: {
|
||||
onPostSubmit?: Function;
|
||||
validateInput?: Function;
|
||||
}) {
|
||||
return openDialog('DisplayNamePrompt', DisplayNamePrompt, {
|
||||
return openDialog(DisplayNamePrompt, {
|
||||
onPostSubmit,
|
||||
validateInput
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||
case SETTINGS_UPDATED: {
|
||||
if (action.settings.displayName
|
||||
&& isDialogOpen(getState, DisplayNamePrompt)) {
|
||||
dispatch(hideDialog('DisplayNamePrompt', DisplayNamePrompt));
|
||||
dispatch(hideDialog(DisplayNamePrompt));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ StateListenerRegistry.register(
|
||||
});
|
||||
|
||||
conference.on(JitsiConferenceEvents.E2EE_VERIFICATION_READY, (pId: string, sas: object) => {
|
||||
dispatch(openDialog('ParticipantVerificationDialog', ParticipantVerificationDialog, { pId,
|
||||
dispatch(openDialog(ParticipantVerificationDialog, { pId,
|
||||
sas }));
|
||||
});
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ class EmbedMeetingButton extends AbstractButton<AbstractButtonProps> {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
sendAnalytics(createToolbarEvent('embed.meeting'));
|
||||
dispatch(openDialog('EmbedMeetingDialog', EmbedMeetingDialog));
|
||||
dispatch(openDialog(EmbedMeetingDialog));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ export function maybeOpenFeedbackDialog(conference: IJitsiConference, title?: st
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function openFeedbackDialog(conference?: IJitsiConference, title?: string, onClose?: Function) {
|
||||
return openDialog('FeedbackDialog', FeedbackDialog, {
|
||||
return openDialog(FeedbackDialog, {
|
||||
conference,
|
||||
onClose,
|
||||
title
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { IconShareDoc } from '../../../base/icons/svg';
|
||||
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
|
||||
import { openFileSharingPanel } from '../../../chat/actions.any';
|
||||
import { isFileSharingEnabled } from '../../functions.any';
|
||||
|
||||
/**
|
||||
* Component that renders a button to open the file sharing panel.
|
||||
*
|
||||
* @augments AbstractButton
|
||||
*/
|
||||
class FileSharingButton extends AbstractButton<AbstractButtonProps> {
|
||||
override icon = IconShareDoc;
|
||||
override label = 'toolbar.fileSharing';
|
||||
override tooltip = 'toolbar.fileSharing';
|
||||
|
||||
/**
|
||||
* Handles clicking the button to open the file sharing panel.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
override _handleClick() {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(openFileSharingPanel());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the Redux state to the props of this component.
|
||||
*
|
||||
* @param {IReduxState} state - The Redux state.
|
||||
* @returns {Object} - Mapped props.
|
||||
*/
|
||||
function mapStateToProps(state: IReduxState) {
|
||||
return {
|
||||
visible: isFileSharingEnabled(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(mapStateToProps)(FileSharingButton));
|
||||
@@ -1,23 +0,0 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import FileSharingButton from './components/web/FileSharingButton';
|
||||
import { isFileSharingEnabled } from './functions.any';
|
||||
|
||||
const fileSharing = {
|
||||
key: 'filesharing',
|
||||
Content: FileSharingButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
/**
|
||||
* A hook that returns the file sharing button if it is enabled and undefined otherwise.
|
||||
*
|
||||
* @returns {Object | undefined} - The file sharing button object or undefined.
|
||||
*/
|
||||
export function useFileSharingButton() {
|
||||
const isEnabled = useSelector(isFileSharingEnabled);
|
||||
|
||||
if (isEnabled) {
|
||||
return fileSharing;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,28 @@
|
||||
import { IReduxState, IStore } from '../app/types';
|
||||
import {
|
||||
getActiveSpeakersToBeDisplayed,
|
||||
getVirtualScreenshareParticipantOwnerId
|
||||
} from '../base/participants/functions';
|
||||
import { getParticipantById, getVirtualScreenshareParticipantOwnerId } from '../base/participants/functions';
|
||||
|
||||
import { setRemoteParticipants } from './actions';
|
||||
import { isFilmstripScrollVisible } from './functions';
|
||||
|
||||
/**
|
||||
* Returns an array containing the first `count` items from a set.
|
||||
*
|
||||
* @param {Set<T>} set - The set from which to take items.
|
||||
* @param {number} count - The number of items to take.
|
||||
* @returns {T[]} An array containing the taken items.
|
||||
* @private
|
||||
*/
|
||||
function _takeFirstN<T>(set: Set<T>, count: number): T[] {
|
||||
const result: T[] = [];
|
||||
|
||||
for (const item of set) {
|
||||
if (result.length >= count) break;
|
||||
result.push(item);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the reorderd list of the remote participants.
|
||||
*
|
||||
@@ -16,7 +32,7 @@ import { isFilmstripScrollVisible } from './functions';
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
export function updateRemoteParticipants(store: IStore, force?: boolean, participantId?: string) {
|
||||
export function updateRemoteParticipants(store: IStore, force?: boolean, participantId?: string): void {
|
||||
const state = store.getState();
|
||||
let reorderedParticipants = [];
|
||||
const { sortedRemoteVirtualScreenshareParticipants } = state['features/base/participants'];
|
||||
@@ -33,14 +49,26 @@ export function updateRemoteParticipants(store: IStore, force?: boolean, partici
|
||||
}
|
||||
|
||||
const {
|
||||
dominantSpeaker,
|
||||
fakeParticipants,
|
||||
previousSpeakers,
|
||||
sortedRemoteParticipants
|
||||
} = state['features/base/participants'];
|
||||
const { visibleRemoteParticipants } = state['features/filmstrip'];
|
||||
const remoteParticipants = new Map(sortedRemoteParticipants);
|
||||
const screenShareParticipants = sortedRemoteVirtualScreenshareParticipants
|
||||
? [ ...sortedRemoteVirtualScreenshareParticipants.keys() ] : [];
|
||||
const sharedVideos = fakeParticipants ? Array.from(fakeParticipants.keys()) : [];
|
||||
const speakers = getActiveSpeakersToBeDisplayed(state);
|
||||
const speakers: Set<string> = new Set();
|
||||
const dominant = dominantSpeaker ? getParticipantById(state, dominantSpeaker) : undefined;
|
||||
const config = state['features/base/config'];
|
||||
const defaultRemoteDisplayName = config?.defaultRemoteDisplayName ?? 'Fellow Jitster';
|
||||
|
||||
// Generate the remote active speakers list.
|
||||
if (dominant && !dominant.local) {
|
||||
speakers.add(dominant.id);
|
||||
}
|
||||
previousSpeakers.forEach(id => speakers.add(id));
|
||||
|
||||
for (const screenshare of screenShareParticipants) {
|
||||
const ownerId = getVirtualScreenshareParticipantOwnerId(screenshare);
|
||||
@@ -57,7 +85,16 @@ export function updateRemoteParticipants(store: IStore, force?: boolean, partici
|
||||
remoteParticipants.delete(speaker);
|
||||
}
|
||||
|
||||
// Always update the order of the thubmnails.
|
||||
// Calculate the number of slots available for active speakers and then sort them alphabetically to ensure
|
||||
// consistent order.
|
||||
const numberOfActiveSpeakerSlots
|
||||
= visibleRemoteParticipants.size - (screenShareParticipants.length * 2) - sharedVideos.length;
|
||||
const activeSpeakersDisplayed = _takeFirstN(speakers, numberOfActiveSpeakerSlots)
|
||||
.sort((a: string, b: string) => {
|
||||
return (getParticipantById(state, a)?.name ?? defaultRemoteDisplayName)
|
||||
.localeCompare(getParticipantById(state, b)?.name ?? defaultRemoteDisplayName);
|
||||
});
|
||||
|
||||
const participantsWithScreenShare = screenShareParticipants.reduce<string[]>((acc, screenshare) => {
|
||||
const ownerId = getVirtualScreenshareParticipantOwnerId(screenshare);
|
||||
|
||||
@@ -67,10 +104,11 @@ export function updateRemoteParticipants(store: IStore, force?: boolean, partici
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
// Always update the order of the thumbnails.
|
||||
reorderedParticipants = [
|
||||
...participantsWithScreenShare,
|
||||
...sharedVideos,
|
||||
...Array.from(speakers.keys()),
|
||||
...activeSpeakersDisplayed,
|
||||
...Array.from(remoteParticipants.keys())
|
||||
];
|
||||
|
||||
|
||||
@@ -559,7 +559,7 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<IProps, IState> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_showFailedInviteAlert() {
|
||||
this.props.dispatch(openDialog('AlertDialog', AlertDialog, {
|
||||
this.props.dispatch(openDialog(AlertDialog, {
|
||||
contentKey: {
|
||||
key: 'inviteDialog.alertText'
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ class DialInSummary extends PureComponent<IProps> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_onError() {
|
||||
this.props.dispatch(openDialog('DialInSummaryErrorDialog', DialInSummaryErrorDialog));
|
||||
this.props.dispatch(openDialog(DialInSummaryErrorDialog));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -41,7 +41,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
function _beginAddPeople({ dispatch }: IStore, next: Function, action: AnyAction) {
|
||||
const result = next(action);
|
||||
|
||||
dispatch(openDialog('AddPeopleDialog', AddPeopleDialog));
|
||||
dispatch(openDialog(AddPeopleDialog));
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -60,7 +60,7 @@ function _beginAddPeople({ dispatch }: IStore, next: Function, action: AnyAction
|
||||
* @returns {*} The value returned by {@code next(action)}.
|
||||
*/
|
||||
function _hideAddPeopleDialog({ dispatch }: IStore, next: Function, action: AnyAction) {
|
||||
dispatch(hideDialog('AddPeopleDialog', AddPeopleDialog));
|
||||
dispatch(hideDialog(AddPeopleDialog));
|
||||
|
||||
return next(action);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import { isFeatureDisabled } from './functions';
|
||||
export function maybeShowPremiumFeatureDialog(feature: ParticipantFeaturesKey) {
|
||||
return function(dispatch: IStore['dispatch'], getState: IStore['getState']) {
|
||||
if (isFeatureDisabled(getState(), feature)) {
|
||||
dispatch(openDialog('PremiumFeatureDialog', PremiumFeatureDialog));
|
||||
dispatch(openDialog(PremiumFeatureDialog));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
getVirtualScreenshareParticipantByOwnerId
|
||||
} from '../base/participants/functions';
|
||||
import { toState } from '../base/redux/functions';
|
||||
import { isStageFilmstripAvailable } from '../filmstrip/functions';
|
||||
import { getAutoPinSetting } from '../video-layout/functions';
|
||||
|
||||
import {
|
||||
@@ -19,6 +18,7 @@ import {
|
||||
SET_LARGE_VIDEO_DIMENSIONS,
|
||||
UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION
|
||||
} from './actionTypes';
|
||||
import { shouldHideLargeVideo } from './functions';
|
||||
|
||||
/**
|
||||
* Action to select the participant to be displayed in LargeVideo based on the
|
||||
@@ -34,12 +34,8 @@ export function selectParticipantInLargeVideo(participant?: string) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const state = getState();
|
||||
|
||||
if (isStageFilmstripAvailable(state, 2)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Keep Etherpad open.
|
||||
if (state['features/etherpad'].editing) {
|
||||
// Skip large video updates when the large video container is hidden.
|
||||
if (shouldHideLargeVideo(state)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -165,18 +161,26 @@ function _electParticipantInLargeVideo(state: IReduxState) {
|
||||
}
|
||||
}
|
||||
|
||||
// Next, pick the dominant speaker (other than self).
|
||||
// Next, pick the dominant speaker or the last active speaker if the dominant speaker is local.
|
||||
participant = getDominantSpeakerParticipant(state);
|
||||
if (participant && !participant.local) {
|
||||
// Return the screensharing participant id associated with this endpoint if multi-stream is enabled and
|
||||
// auto_pin_latest_screen_share setting is disabled.
|
||||
const screenshareParticipant = getVirtualScreenshareParticipantByOwnerId(state, participant.id);
|
||||
let speakerId: string | undefined;
|
||||
|
||||
return screenshareParticipant?.id ?? participant.id;
|
||||
if (participant?.local) {
|
||||
const { previousSpeakers } = state['features/base/participants'];
|
||||
|
||||
if (previousSpeakers?.size) {
|
||||
speakerId = previousSpeakers.keys().next().value;
|
||||
}
|
||||
} else if (participant) {
|
||||
speakerId = participant.id;
|
||||
}
|
||||
|
||||
// In case this is the local participant.
|
||||
participant = undefined;
|
||||
// Return the screensharing participant id associated with this endpoint.
|
||||
if (speakerId) {
|
||||
const screenshareParticipant = getVirtualScreenshareParticipantByOwnerId(state, speakerId);
|
||||
|
||||
return screenshareParticipant?.id ?? speakerId;
|
||||
}
|
||||
|
||||
// Next, pick the most recent participant with video.
|
||||
const lastVisibleRemoteParticipant = _electLastVisibleRemoteParticipant(state);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { IReduxState } from '../app/types';
|
||||
import { getParticipantById } from '../base/participants/functions';
|
||||
import { isStageFilmstripAvailable } from '../filmstrip/functions';
|
||||
import { shouldDisplayTileView } from '../video-layout/functions.any';
|
||||
|
||||
/**
|
||||
* Selector for the participant currently displaying on the large video.
|
||||
@@ -12,3 +14,17 @@ export function getLargeVideoParticipant(state: IReduxState) {
|
||||
|
||||
return getParticipantById(state, participantId ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the large video container should be hidden.
|
||||
* Large video is hidden in tile view, stage filmstrip mode (with multiple participants),
|
||||
* or when editing etherpad.
|
||||
*
|
||||
* @param {IReduxState} state - The Redux state.
|
||||
* @returns {boolean} True if large video should be hidden, false otherwise.
|
||||
*/
|
||||
export function shouldHideLargeVideo(state: IReduxState): boolean {
|
||||
return shouldDisplayTileView(state)
|
||||
|| isStageFilmstripAvailable(state, 2)
|
||||
|| Boolean(state['features/etherpad']?.editing);
|
||||
}
|
||||
|
||||
26
react/features/large-video/subscriber.any.ts
Normal file
26
react/features/large-video/subscriber.any.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
|
||||
|
||||
import { SELECT_LARGE_VIDEO_PARTICIPANT } from './actionTypes';
|
||||
import { selectParticipantInLargeVideo } from './actions.any';
|
||||
import { shouldHideLargeVideo } from './functions';
|
||||
|
||||
/**
|
||||
* Updates the large video when transitioning from a hidden state to visible state.
|
||||
* This ensures the large video is properly updated when exiting tile view, stage filmstrip,
|
||||
* whiteboard, or etherpad editing modes.
|
||||
*/
|
||||
StateListenerRegistry.register(
|
||||
/* selector */ state => shouldHideLargeVideo(state),
|
||||
/* listener */ (isHidden, { dispatch }) => {
|
||||
// When transitioning from hidden to visible state, select participant (because currently it is undefined).
|
||||
// Otherwise set it to undefined because we don't show the large video.
|
||||
if (!isHidden) {
|
||||
dispatch(selectParticipantInLargeVideo());
|
||||
} else {
|
||||
dispatch({
|
||||
type: SELECT_LARGE_VIDEO_PARTICIPANT,
|
||||
participantId: undefined
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
import './subscriber.any';
|
||||
|
||||
@@ -4,6 +4,7 @@ import StateListenerRegistry from '../base/redux/StateListenerRegistry';
|
||||
import { getVideoTrackByParticipant } from '../base/tracks/functions.web';
|
||||
|
||||
import { getLargeVideoParticipant } from './functions';
|
||||
import './subscriber.any';
|
||||
|
||||
/**
|
||||
* Updates the on stage participant video.
|
||||
|
||||
@@ -467,7 +467,7 @@ export function _mapStateToProps(state: IReduxState) {
|
||||
_knocking: knocking,
|
||||
_lobbyChatMessages: messages,
|
||||
_lobbyMessageRecipient: lobbyMessageRecipient?.name,
|
||||
_login: showModeratorLogin && !state['features/base/jwt'].jwt,
|
||||
_login: showModeratorLogin,
|
||||
_hangUp: showHangUp,
|
||||
_isLobbyChatActive: isLobbyChatActive,
|
||||
_meetingName: getConferenceName(state),
|
||||
|
||||
@@ -4,6 +4,7 @@ import { createMaterialTopTabNavigator } from '@react-navigation/material-top-ta
|
||||
import React from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../../../app/types';
|
||||
import {
|
||||
getClientHeight,
|
||||
getClientWidth
|
||||
@@ -11,7 +12,6 @@ import {
|
||||
import { setFocusedTab } from '../../../../../chat/actions.any';
|
||||
import Chat from '../../../../../chat/components/native/Chat';
|
||||
import { ChatTabs } from '../../../../../chat/constants';
|
||||
import { getFocusedTab } from '../../../../../chat/functions';
|
||||
import { resetUnreadPollsCount } from '../../../../../polls/actions';
|
||||
import PollsPane from '../../../../../polls/components/native/PollsPane';
|
||||
import { screen } from '../../../routes';
|
||||
@@ -23,8 +23,8 @@ const ChatAndPolls = () => {
|
||||
const clientHeight = useSelector(getClientHeight);
|
||||
const clientWidth = useSelector(getClientWidth);
|
||||
const dispatch = useDispatch();
|
||||
const currentFocusedTab = useSelector(getFocusedTab);
|
||||
const initialRouteName = currentFocusedTab === ChatTabs.POLLS
|
||||
const { focusedTab } = useSelector((state: IReduxState) => state['features/chat']);
|
||||
const initialRouteName = focusedTab === ChatTabs.POLLS
|
||||
? screen.conference.chatandpolls.tab.polls
|
||||
: screen.conference.chatandpolls.tab.chat;
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { querySelector, querySelectorAll } from '@jitsi/js-utils/polyfills/querySelectorPolyfill';
|
||||
import { DOMParser } from '@xmldom/xmldom';
|
||||
import { atob, btoa } from 'abab';
|
||||
import { NativeModules, Platform } from 'react-native';
|
||||
@@ -9,6 +8,7 @@ import 'promise.withresolvers/auto'; // Promise.withResolvers.
|
||||
import 'react-native-url-polyfill/auto'; // Complete URL polyfill.
|
||||
|
||||
import Storage from './Storage';
|
||||
import { querySelector, querySelectorAll } from './querySelectorPolyfill';
|
||||
|
||||
const { AppInfo } = NativeModules;
|
||||
|
||||
@@ -193,7 +193,7 @@ const { AppInfo } = NativeModules;
|
||||
// - lib-jitsi-meet -> XMLUtils.ts -> parseXML
|
||||
if (typeof document.querySelector === 'undefined') {
|
||||
document.querySelector = function(selectors) {
|
||||
return querySelector(this, selectors);
|
||||
return this.documentElement ? querySelector(this.documentElement, selectors) : null;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -203,7 +203,7 @@ const { AppInfo } = NativeModules;
|
||||
// - lib-jitsi-meet -> XMLUtils.ts -> parseXML
|
||||
if (typeof document.querySelectorAll === 'undefined') {
|
||||
document.querySelectorAll = function(selectors) {
|
||||
return querySelectorAll(this, selectors);
|
||||
return this.documentElement ? querySelectorAll(this.documentElement, selectors) : [];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -217,7 +217,7 @@ const { AppInfo } = NativeModules;
|
||||
// - lib-jitsi-meet -> XMLUtils.ts -> parseXML
|
||||
if (typeof documentPrototype.querySelector === 'undefined') {
|
||||
documentPrototype.querySelector = function(selectors) {
|
||||
return querySelector(this, selectors);
|
||||
return this.documentElement ? querySelector(this.documentElement, selectors) : null;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -227,7 +227,7 @@ const { AppInfo } = NativeModules;
|
||||
// - lib-jitsi-meet -> XMLUtils.ts -> parseXML
|
||||
if (typeof documentPrototype.querySelectorAll === 'undefined') {
|
||||
documentPrototype.querySelectorAll = function(selectors) {
|
||||
return querySelectorAll(this, selectors);
|
||||
return this.documentElement ? querySelectorAll(this.documentElement, selectors) : [];
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
282
react/features/mobile/polyfills/querySelectorPolyfill.js
Normal file
282
react/features/mobile/polyfills/querySelectorPolyfill.js
Normal file
@@ -0,0 +1,282 @@
|
||||
// Regex constants for efficient reuse across selector parsing
|
||||
const SIMPLE_TAG_NAME_REGEX = /^[a-zA-Z][\w-]*$/;
|
||||
const MULTI_ATTRIBUTE_SELECTOR_REGEX = /^([a-zA-Z][\w-]*)?(\[(?:\*\|)?([^=\]]+)=["']?([^"'\]]+)["']?\])+$/;
|
||||
const SINGLE_ATTRIBUTE_REGEX = /\[(?:\*\|)?([^=\]]+)=["']?([^"'\]]+)["']?\]/g;
|
||||
const WHITESPACE_AROUND_COMBINATOR_REGEX = /\s*>\s*/g;
|
||||
|
||||
/**
|
||||
* Parses a CSS selector into reusable components.
|
||||
*
|
||||
* @param {string} selector - The CSS selector to parse.
|
||||
* @returns {Object} - Object with tagName and attrConditions properties.
|
||||
*/
|
||||
function _parseSelector(selector) {
|
||||
// Wildcard selector
|
||||
if (selector === '*') {
|
||||
return {
|
||||
tagName: null, // null means match all tag names
|
||||
attrConditions: []
|
||||
};
|
||||
}
|
||||
|
||||
// Simple tag name
|
||||
if (SIMPLE_TAG_NAME_REGEX.test(selector)) {
|
||||
return {
|
||||
tagName: selector,
|
||||
attrConditions: []
|
||||
};
|
||||
}
|
||||
|
||||
// Attribute selector: tagname[attr="value"] or
|
||||
// tagname[attr1="value1"][attr2="value2"] (with optional wildcard namespace)
|
||||
const multiAttrMatch = selector.match(MULTI_ATTRIBUTE_SELECTOR_REGEX);
|
||||
|
||||
if (multiAttrMatch) {
|
||||
const tagName = multiAttrMatch[1];
|
||||
const attrConditions = [];
|
||||
let attrMatch;
|
||||
|
||||
while ((attrMatch = SINGLE_ATTRIBUTE_REGEX.exec(selector)) !== null) {
|
||||
attrConditions.push({
|
||||
name: attrMatch[1], // This properly strips the *| prefix
|
||||
value: attrMatch[2]
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
tagName,
|
||||
attrConditions
|
||||
};
|
||||
}
|
||||
|
||||
// Unsupported selector
|
||||
throw new SyntaxError(`Unsupported selector pattern: '${selector}'`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters elements by selector pattern and handles findFirst logic.
|
||||
*
|
||||
* @param {Element[]} elements - Array of elements to filter.
|
||||
* @param {string} selector - CSS selector to match against.
|
||||
* @param {boolean} findFirst - If true, return after finding the first match.
|
||||
* @returns {Element[]|Element|null} - Filtered results with proper return type.
|
||||
*/
|
||||
function _filterAndMatchElements(elements, selector, findFirst) {
|
||||
const { tagName, attrConditions } = _parseSelector(selector);
|
||||
|
||||
const results = [];
|
||||
|
||||
for (const element of elements) {
|
||||
// Check tag name if specified
|
||||
if (tagName && !(element.localName === tagName || element.tagName === tagName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if all attribute conditions match
|
||||
const allMatch = attrConditions.every(condition =>
|
||||
element.getAttribute(condition.name) === condition.value
|
||||
);
|
||||
|
||||
if (allMatch) {
|
||||
results.push(element);
|
||||
if (findFirst) {
|
||||
return element;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return findFirst ? null : results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles direct child traversal for selectors with > combinators.
|
||||
* This is the shared logic used by both scope selectors and regular direct child selectors.
|
||||
*
|
||||
* @param {Element[]} startElements - Array of starting elements to traverse from.
|
||||
* @param {string[]} selectorParts - Array of selector parts split by '>'.
|
||||
* @param {boolean} findFirst - If true, return after finding the first match.
|
||||
* @returns {Element[]|Element|null} - Array of Elements for querySelectorAll,
|
||||
* single Element or null for querySelector.
|
||||
*/
|
||||
function _traverseDirectChildren(startElements, selectorParts, findFirst) {
|
||||
let currentElements = startElements;
|
||||
|
||||
for (const part of selectorParts) {
|
||||
const nextElements = [];
|
||||
|
||||
currentElements.forEach(el => {
|
||||
// Get direct children
|
||||
const directChildren = Array.from(el.children || []);
|
||||
|
||||
// Use same helper as handlers
|
||||
const matchingChildren = _filterAndMatchElements(directChildren, part, false);
|
||||
|
||||
nextElements.push(...matchingChildren);
|
||||
});
|
||||
|
||||
currentElements = nextElements;
|
||||
|
||||
// If we have no results, we can stop early (applies to both querySelector and querySelectorAll)
|
||||
if (currentElements.length === 0) {
|
||||
return findFirst ? null : [];
|
||||
}
|
||||
}
|
||||
|
||||
return findFirst ? currentElements[0] || null : currentElements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles :scope pseudo-selector cases with direct child combinators.
|
||||
*
|
||||
* @param {Node} node - The Node which is the root of the tree to query.
|
||||
* @param {string} selector - The CSS selector.
|
||||
* @param {boolean} findFirst - If true, return after finding the first match.
|
||||
* @returns {Element[]|Element|null} - Array of Elements for querySelectorAll,
|
||||
* single Element or null for querySelector.
|
||||
*/
|
||||
function _handleScopeSelector(node, selector, findFirst) {
|
||||
let searchSelector = selector.substring(6);
|
||||
|
||||
// Handle :scope > tagname (direct children)
|
||||
if (searchSelector.startsWith('>')) {
|
||||
searchSelector = searchSelector.substring(1);
|
||||
|
||||
// Split by > and use shared traversal logic
|
||||
const parts = searchSelector.split('>');
|
||||
|
||||
// Start from the node itself (scope)
|
||||
return _traverseDirectChildren([ node ], parts, findFirst);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles nested > selectors (direct child combinators).
|
||||
*
|
||||
* @param {Node} node - The Node which is the root of the tree to query.
|
||||
* @param {string} selector - The CSS selector.
|
||||
* @param {boolean} findFirst - If true, return after finding the first match.
|
||||
* @returns {Element[]|Element|null} - Array of Elements for querySelectorAll,
|
||||
* single Element or null for querySelector.
|
||||
*/
|
||||
function _handleDirectChildSelectors(node, selector, findFirst) {
|
||||
const parts = selector.split('>');
|
||||
|
||||
// First find elements matching the first part (this could be descendants, not just direct children)
|
||||
const startElements = _querySelectorInternal(node, parts[0], false);
|
||||
|
||||
// If no starting elements found, return early
|
||||
if (startElements.length === 0) {
|
||||
return findFirst ? null : [];
|
||||
}
|
||||
|
||||
// Use shared traversal logic for the remaining parts
|
||||
return _traverseDirectChildren(startElements, parts.slice(1), findFirst);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles simple tag name selectors.
|
||||
*
|
||||
* @param {Node} node - The Node which is the root of the tree to query.
|
||||
* @param {string} selector - The CSS selector.
|
||||
* @param {boolean} findFirst - If true, return after finding the first match.
|
||||
* @returns {Element[]|Element|null} - Array of Elements for querySelectorAll,
|
||||
* single Element or null for querySelector.
|
||||
*/
|
||||
function _handleSimpleTagSelector(node, selector, findFirst) {
|
||||
const elements = Array.from(node.getElementsByTagName(selector));
|
||||
|
||||
if (findFirst) {
|
||||
return elements[0] || null;
|
||||
}
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles attribute selectors: tagname[attr="value"] or tagname[attr1="value1"][attr2="value2"].
|
||||
* Supports single or multiple attributes with optional wildcard namespace (*|).
|
||||
*
|
||||
* @param {Node} node - The Node which is the root of the tree to query.
|
||||
* @param {string} selector - The CSS selector.
|
||||
* @param {boolean} findFirst - If true, return after finding the first match.
|
||||
* @returns {Element[]|Element|null} - Array of Elements for querySelectorAll,
|
||||
* single Element or null for querySelector.
|
||||
*/
|
||||
function _handleAttributeSelector(node, selector, findFirst) {
|
||||
const { tagName } = _parseSelector(selector); // Just to get tagName for optimization
|
||||
|
||||
// Handler's job: find the right elements to search
|
||||
const elementsToCheck = tagName
|
||||
? Array.from(node.getElementsByTagName(tagName))
|
||||
: Array.from(node.getElementsByTagName('*'));
|
||||
|
||||
// Common helper does the matching
|
||||
return _filterAndMatchElements(elementsToCheck, selector, findFirst);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal function that implements the core selector matching logic for both
|
||||
* querySelector and querySelectorAll. Supports :scope pseudo-selector, direct
|
||||
* child selectors, and common CSS selectors.
|
||||
*
|
||||
* @param {Node} node - The Node which is the root of the tree to query.
|
||||
* @param {string} selector - The CSS selector to match elements against.
|
||||
* @param {boolean} findFirst - If true, return after finding the first match.
|
||||
* @returns {Element[]|Element|null} - Array of Elements for querySelectorAll,
|
||||
* single Element or null for querySelector.
|
||||
*/
|
||||
function _querySelectorInternal(node, selector, findFirst = false) {
|
||||
// Normalize whitespace around > combinators first
|
||||
const normalizedSelector = selector.replace(WHITESPACE_AROUND_COMBINATOR_REGEX, '>');
|
||||
|
||||
// Handle :scope pseudo-selector
|
||||
if (normalizedSelector.startsWith(':scope')) {
|
||||
return _handleScopeSelector(node, normalizedSelector, findFirst);
|
||||
}
|
||||
|
||||
// Handle nested > selectors (direct child combinators)
|
||||
if (normalizedSelector.includes('>')) {
|
||||
return _handleDirectChildSelectors(node, normalizedSelector, findFirst);
|
||||
}
|
||||
|
||||
// Fast path: simple tag name
|
||||
if (normalizedSelector === '*' || SIMPLE_TAG_NAME_REGEX.test(normalizedSelector)) {
|
||||
return _handleSimpleTagSelector(node, normalizedSelector, findFirst);
|
||||
}
|
||||
|
||||
// Attribute selector: tagname[attr="value"] or
|
||||
// tagname[attr1="value1"][attr2="value2"] (with optional wildcard namespace)
|
||||
if (normalizedSelector.match(MULTI_ATTRIBUTE_SELECTOR_REGEX)) {
|
||||
return _handleAttributeSelector(node, normalizedSelector, findFirst);
|
||||
}
|
||||
|
||||
// Unsupported selector - throw SyntaxError to match browser behavior
|
||||
throw new SyntaxError(`Failed to execute 'querySelector${
|
||||
findFirst ? '' : 'All'}' on 'Element': '${selector}' is not a valid selector.`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements querySelector functionality using the shared internal logic.
|
||||
* Supports the same selectors as querySelectorAll but returns only the first match.
|
||||
*
|
||||
* @param {Node} node - The Node which is the root of the tree to query.
|
||||
* @param {string} selectors - The CSS selector to match elements against.
|
||||
* @returns {Element|null} - The first Element which matches the selector, or null.
|
||||
*/
|
||||
export function querySelector(node, selectors) {
|
||||
return _querySelectorInternal(node, selectors, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements querySelectorAll functionality using the shared internal logic.
|
||||
* Supports :scope pseudo-selector, direct child selectors, and common CSS selectors.
|
||||
*
|
||||
* @param {Node} node - The Node which is the root of the tree to query.
|
||||
* @param {string} selector - The CSS selector to match elements against.
|
||||
* @returns {Element[]} - Array of Elements matching the selector.
|
||||
*/
|
||||
export function querySelectorAll(node, selector) {
|
||||
return _querySelectorInternal(node, selector, false);
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import PageReloadDialog from '../base/dialog/components/native/PageReloadDialog'
|
||||
*/
|
||||
export function openPageReloadDialog(
|
||||
conferenceError?: Error, configError?: Error, connectionError?: ConnectionFailedError) {
|
||||
return openDialog('PageReloadDialog', PageReloadDialog, {
|
||||
return openDialog(PageReloadDialog, {
|
||||
conferenceError,
|
||||
configError,
|
||||
connectionError
|
||||
|
||||
@@ -34,7 +34,7 @@ export default function RenameButton({ breakoutRoomJid, name }: IProps) {
|
||||
const dispatch = useDispatch();
|
||||
const { classes, cx } = useStyles();
|
||||
const onRename = useCallback(() => {
|
||||
dispatch(openDialog('BreakoutRoomNamePrompt', BreakoutRoomNamePrompt, {
|
||||
dispatch(openDialog(BreakoutRoomNamePrompt, {
|
||||
breakoutRoomJid,
|
||||
initialRoomName: name
|
||||
}));
|
||||
|
||||
@@ -68,7 +68,7 @@ export const RoomContextMenu = ({
|
||||
}, [ dispatch, room ]);
|
||||
|
||||
const onRenameBreakoutRoom = useCallback(() => {
|
||||
dispatch(openDialog('BreakoutRoomNamePrompt', BreakoutRoomNamePrompt, {
|
||||
dispatch(openDialog(BreakoutRoomNamePrompt, {
|
||||
breakoutRoomJid: room?.jid,
|
||||
initialRoomName: room?.name
|
||||
}));
|
||||
|
||||
@@ -32,7 +32,7 @@ import styles from './styles';
|
||||
export const ContextMenuMore = () => {
|
||||
const dispatch = useDispatch();
|
||||
const muteAllVideo = useCallback(() => {
|
||||
dispatch(openDialog('MuteEveryonesVideoDialog', MuteEveryonesVideoDialog));
|
||||
dispatch(openDialog(MuteEveryonesVideoDialog));
|
||||
dispatch(hideSheet());
|
||||
}, [ dispatch ]);
|
||||
const conference = useSelector(getCurrentConference);
|
||||
|
||||
@@ -42,7 +42,7 @@ const ParticipantsPaneFooter = (): JSX.Element => {
|
||||
getFeatureFlag(state, BREAKOUT_ROOMS_BUTTON_ENABLED, true)
|
||||
);
|
||||
const openMoreMenu = useCallback(() => dispatch(openSheet(ContextMenuMore)), [ dispatch ]);
|
||||
const muteAll = useCallback(() => dispatch(openDialog('MuteEveryoneDialog', MuteEveryoneDialog)),
|
||||
const muteAll = useCallback(() => dispatch(openDialog(MuteEveryoneDialog)),
|
||||
[ dispatch ]);
|
||||
const showMoreActions = useSelector(isMoreActionsVisible);
|
||||
const showMuteAll = useSelector(isMuteAllVisible);
|
||||
|
||||
@@ -112,10 +112,10 @@ export const FooterContextMenu = ({ isOpen, onDrawerClose, onMouseLeave }: IProp
|
||||
const { classes } = useStyles();
|
||||
|
||||
const muteAllVideo = useCallback(
|
||||
() => dispatch(openDialog('MuteEveryonesVideoDialog', MuteEveryonesVideoDialog)), [ dispatch ]);
|
||||
() => dispatch(openDialog(MuteEveryonesVideoDialog)), [ dispatch ]);
|
||||
|
||||
const muteAllDesktop = useCallback(
|
||||
() => dispatch(openDialog('MuteEveryonesDesktopDialog', MuteEveryonesDesktopDialog)), [ dispatch ]);
|
||||
() => dispatch(openDialog(MuteEveryonesDesktopDialog)), [ dispatch ]);
|
||||
|
||||
const openModeratorSettings = () => dispatch(openSettingsDialog(SETTINGS_TABS.MODERATOR));
|
||||
|
||||
|
||||
@@ -181,7 +181,7 @@ const ParticipantsPane = () => {
|
||||
}, []);
|
||||
|
||||
const onMuteAll = useCallback(() => {
|
||||
dispatch(openDialog('MuteEveryoneDialog', MuteEveryoneDialog));
|
||||
dispatch(openDialog(MuteEveryoneDialog));
|
||||
}, []);
|
||||
|
||||
const onToggleContext = useCallback(() => {
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { IconInfo } from '../../../base/icons/svg';
|
||||
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
|
||||
import { openPollsPanel } from '../../../chat/actions.any';
|
||||
import { arePollsDisabled } from '../../../conference/functions.any';
|
||||
|
||||
/**
|
||||
* Component that renders a button to open the polls panel.
|
||||
*
|
||||
* @augments AbstractButton
|
||||
*/
|
||||
class PollsButton extends AbstractButton<AbstractButtonProps> {
|
||||
override icon = IconInfo;
|
||||
override label = 'toolbar.polls';
|
||||
override tooltip = 'toolbar.polls';
|
||||
|
||||
/**
|
||||
* Handles clicking the button to open the polls panel.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
override _handleClick() {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(openPollsPanel());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the Redux state to the props of this component.
|
||||
*
|
||||
* @param {IReduxState} state - The Redux state.
|
||||
* @returns {Object} - Mapped props.
|
||||
*/
|
||||
function mapStateToProps(state: IReduxState) {
|
||||
return {
|
||||
visible: !arePollsDisabled(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(mapStateToProps)(PollsButton));
|
||||
@@ -1,24 +0,0 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { arePollsDisabled } from '../conference/functions.any';
|
||||
|
||||
import PollsButton from './components/web/PollsButton';
|
||||
|
||||
const polls = {
|
||||
key: 'polls',
|
||||
Content: PollsButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
/**
|
||||
* A hook that returns the polls button if it is enabled and undefined otherwise.
|
||||
*
|
||||
* @returns {Object | undefined} - The polls button object or undefined.
|
||||
*/
|
||||
export function usePollsButton() {
|
||||
const isPollsDisabled = useSelector(arePollsDisabled);
|
||||
|
||||
if (!isPollsDisabled) {
|
||||
return polls;
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
RESET_UNREAD_POLLS_COUNT,
|
||||
SAVE_POLL
|
||||
} from './actionTypes';
|
||||
import logger from './logger';
|
||||
import { IIncomingAnswerData, IPollData } from './types';
|
||||
|
||||
const INITIAL_STATE = {
|
||||
@@ -88,7 +87,7 @@ ReducerRegistry.register<IPollsState>(STORE_NAME, (state = INITIAL_STATE, action
|
||||
|
||||
// if the poll doesn't exist
|
||||
if (!(pollId in state.polls)) {
|
||||
logger.warn('Requested poll does not exist', { pollId });
|
||||
console.warn('requested poll does not exist: pollId ', pollId);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ class ReactionMenuDialog extends PureComponent<IProps> {
|
||||
*/
|
||||
_onCancel() {
|
||||
if (this.props._isOpen) {
|
||||
this.props.dispatch(hideDialog('ReactionMenu_', ReactionMenu_));
|
||||
this.props.dispatch(hideDialog(ReactionMenu_));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ class ReactionsMenuButton extends AbstractButton<IProps> {
|
||||
* @returns {void}
|
||||
*/
|
||||
override _handleClick() {
|
||||
this.props.dispatch(openDialog('ReactionMenuDialog', ReactionMenuDialog));
|
||||
this.props.dispatch(openDialog(ReactionMenuDialog));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -76,7 +76,7 @@ export function showRecordingLimitNotification(streamType: string) {
|
||||
*/
|
||||
export function showStartRecordingNotification() {
|
||||
return (dispatch: IStore['dispatch']) => {
|
||||
const openDialogCallback = () => dispatch(openDialog('StartRecordingDialog', StartRecordingDialog));
|
||||
const openDialogCallback = () => dispatch(openDialog(StartRecordingDialog));
|
||||
|
||||
dispatch(showStartRecordingNotificationWithCallback(openDialogCallback));
|
||||
};
|
||||
|
||||
@@ -34,7 +34,7 @@ class LiveStreamButton extends AbstractLiveStreamButton<Props> {
|
||||
const { _isLiveStreamRunning, dispatch } = this.props;
|
||||
|
||||
if (_isLiveStreamRunning) {
|
||||
dispatch(openDialog('StopLiveStreamDialog', StopLiveStreamDialog));
|
||||
dispatch(openDialog(StopLiveStreamDialog));
|
||||
} else {
|
||||
navigate(screen.conference.liveStream);
|
||||
}
|
||||
|
||||
@@ -26,10 +26,10 @@ class LiveStreamButton extends AbstractLiveStreamButton<IProps> {
|
||||
*/
|
||||
override _onHandleClick() {
|
||||
const { _isLiveStreamRunning, dispatch } = this.props;
|
||||
const dialogComponent = _isLiveStreamRunning ? StopLiveStreamDialog : StartLiveStreamDialog;
|
||||
const dialogName = _isLiveStreamRunning ? 'StopLiveStreamDialog' : 'StartLiveStreamDialog';
|
||||
|
||||
dispatch(openDialog(dialogName, dialogComponent));
|
||||
dispatch(openDialog(
|
||||
_isLiveStreamRunning ? StopLiveStreamDialog : StartLiveStreamDialog
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ export default class AbstractHighlightButton<P extends IProps, S={}> extends Com
|
||||
const dialogShown = dispatch(maybeShowPremiumFeatureDialog(MEET_FEATURES.RECORDING));
|
||||
|
||||
if (!dialogShown) {
|
||||
dispatch(openDialog('StartRecordingDialog', StartRecordingDialog));
|
||||
dispatch(openDialog(StartRecordingDialog));
|
||||
}
|
||||
} ],
|
||||
appearance: NOTIFICATION_TYPE.NORMAL
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user