mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-05-19 08:47:47 +00:00
Compare commits
32 Commits
5711
...
saghul-pat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e3463735b | ||
|
|
7dd9785e48 | ||
|
|
315c884f0d | ||
|
|
8590315244 | ||
|
|
6b260c27a5 | ||
|
|
197dbfbbcb | ||
|
|
847dc2a7bb | ||
|
|
7f26e7f927 | ||
|
|
6247d45b9e | ||
|
|
04f9ad32e1 | ||
|
|
348414cc84 | ||
|
|
804573e9aa | ||
|
|
9dd3941388 | ||
|
|
f341a4e4eb | ||
|
|
1c462996f5 | ||
|
|
1316c48964 | ||
|
|
1080404de9 | ||
|
|
55ef73875e | ||
|
|
afeb3e0e69 | ||
|
|
ca227df7ec | ||
|
|
fb843a7ecf | ||
|
|
af985d8fd8 | ||
|
|
8d62e010e0 | ||
|
|
1c4283eeca | ||
|
|
c64d1a97c1 | ||
|
|
6159504478 | ||
|
|
f2efdfcbc1 | ||
|
|
6682167800 | ||
|
|
b084aae212 | ||
|
|
a436a889a9 | ||
|
|
9816be4745 | ||
|
|
b9e182b7cc |
88
config.js
88
config.js
@@ -615,41 +615,61 @@ var config = {
|
||||
// alwaysVisible: false
|
||||
// },
|
||||
|
||||
// Toolbar buttons which have their click event exposed through the API on
|
||||
// `toolbarButtonClicked` event instead of executing the normal click routine.
|
||||
// Toolbar buttons which have their click/tap event exposed through the API on
|
||||
// `toolbarButtonClicked`. Passing a string for the button key will
|
||||
// prevent execution of the click/tap routine; passing an object with `key` and
|
||||
// `preventExecution` flag on false will not prevent execution of the click/tap
|
||||
// routine. Below array with mixed mode for passing the buttons.
|
||||
// buttonsWithNotifyClick: [
|
||||
// 'camera',
|
||||
// 'chat',
|
||||
// 'closedcaptions',
|
||||
// 'desktop',
|
||||
// 'download',
|
||||
// 'embedmeeting',
|
||||
// 'etherpad',
|
||||
// 'feedback',
|
||||
// 'filmstrip',
|
||||
// 'fullscreen',
|
||||
// 'hangup',
|
||||
// 'help',
|
||||
// 'invite',
|
||||
// 'livestreaming',
|
||||
// 'microphone',
|
||||
// 'mute-everyone',
|
||||
// 'mute-video-everyone',
|
||||
// 'participants-pane',
|
||||
// 'profile',
|
||||
// 'raisehand',
|
||||
// 'recording',
|
||||
// 'security',
|
||||
// 'select-background',
|
||||
// 'settings',
|
||||
// 'shareaudio',
|
||||
// 'sharedvideo',
|
||||
// 'shortcuts',
|
||||
// 'stats',
|
||||
// 'tileview',
|
||||
// 'toggle-camera',
|
||||
// 'videoquality',
|
||||
// '__end'
|
||||
// 'camera',
|
||||
// {
|
||||
// key: 'chat',
|
||||
// preventExecution: false
|
||||
// },
|
||||
// {
|
||||
// key: 'closedcaptions',
|
||||
// preventExecution: true
|
||||
// },
|
||||
// 'desktop',
|
||||
// 'download',
|
||||
// 'embedmeeting',
|
||||
// 'etherpad',
|
||||
// 'feedback',
|
||||
// 'filmstrip',
|
||||
// 'fullscreen',
|
||||
// 'hangup',
|
||||
// 'help',
|
||||
// {
|
||||
// key: 'invite',
|
||||
// preventExecution: false
|
||||
// },
|
||||
// 'livestreaming',
|
||||
// 'microphone',
|
||||
// 'mute-everyone',
|
||||
// 'mute-video-everyone',
|
||||
// 'participants-pane',
|
||||
// 'profile',
|
||||
// {
|
||||
// key: 'raisehand',
|
||||
// preventExecution: true
|
||||
// },
|
||||
// 'recording',
|
||||
// 'security',
|
||||
// 'select-background',
|
||||
// 'settings',
|
||||
// 'shareaudio',
|
||||
// 'sharedvideo',
|
||||
// 'shortcuts',
|
||||
// 'stats',
|
||||
// 'tileview',
|
||||
// 'toggle-camera',
|
||||
// 'videoquality',
|
||||
// // The add passcode button from the security dialog.
|
||||
// {
|
||||
// key: 'add-passcode',
|
||||
// preventExecution: false
|
||||
// }
|
||||
// '__end'
|
||||
// ],
|
||||
|
||||
// List of pre meeting screens buttons to hide. The values must be one or more of the 5 allowed buttons:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.subject {
|
||||
color: #fff;
|
||||
transition: opacity .3s ease-in;
|
||||
transition: opacity .6s ease-in-out;
|
||||
z-index: $zindex3;
|
||||
margin-top: 20px;
|
||||
opacity: 0;
|
||||
@@ -11,14 +11,14 @@
|
||||
|
||||
&#autoHide.with-always-on {
|
||||
overflow: hidden;
|
||||
animation: hideSubject forwards .3s ease-out;
|
||||
animation: hideSubject forwards .6s ease-out;
|
||||
|
||||
& > .subject-info-container {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
&.visible {
|
||||
animation: showSubject forwards .3s ease-out;
|
||||
animation: showSubject forwards .6s ease-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,4 +32,12 @@ denied-peer-ip=198.18.0.0-198.19.255.255
|
||||
denied-peer-ip=198.51.100.0-198.51.100.255
|
||||
denied-peer-ip=203.0.113.0-203.0.113.255
|
||||
denied-peer-ip=240.0.0.0-255.255.255.255
|
||||
denied-peer-ip=::1
|
||||
denied-peer-ip=64:ff9b::-64:ff9b::ffff:ffff
|
||||
denied-peer-ip=::ffff:0.0.0.0-::ffff:255.255.255.255
|
||||
denied-peer-ip=100::-100::ffff:ffff:ffff:ffff
|
||||
denied-peer-ip=2001::-2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff
|
||||
denied-peer-ip=2002::-2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff
|
||||
denied-peer-ip=fc00::-fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
|
||||
denied-peer-ip=fe80::-febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff
|
||||
syslog
|
||||
|
||||
@@ -1,33 +1,55 @@
|
||||
{
|
||||
"en": "angielski",
|
||||
"af": "afrykanerski",
|
||||
"af": "Afrikaans",
|
||||
"ar": "arabski",
|
||||
"bg": "bułgarski",
|
||||
"ca": "kataloński",
|
||||
"cs": "czeski",
|
||||
"da": "duński",
|
||||
"de": "niemiecki",
|
||||
"el": "grecki",
|
||||
"enGB": "angielski (Zjednoczone Królestwo)",
|
||||
"enGB": "Angielski (Zjednoczone Królestwo)",
|
||||
"eo": "esperanto",
|
||||
"es": "hiszpański",
|
||||
"esUS": "hiszpański (Ameryka Łacińska)",
|
||||
"et": "estoński",
|
||||
"eu": "baskijski",
|
||||
"fi": "fiński",
|
||||
"fr": "francuski",
|
||||
"frCA": "francuski (kanadyjski)",
|
||||
"he": "hebrajski",
|
||||
"hi": "hindi",
|
||||
"mr":"Marathi",
|
||||
"hr": "chorwacki",
|
||||
"hu": "węgierski",
|
||||
"hy": "ormiański",
|
||||
"id": "indonezyjski",
|
||||
"it": "włoski",
|
||||
"ja": "japoński",
|
||||
"kab": "Kabyle",
|
||||
"ko": "koreański",
|
||||
"lt": "litewski",
|
||||
"ml": "malajalam",
|
||||
"lv": "łotewski",
|
||||
"nl": "holenderski",
|
||||
"oc": "oksytański",
|
||||
"oc": "prowansalski",
|
||||
"fa": "perski",
|
||||
"pl": "polski",
|
||||
"ptBR": "portugalski (brazylijski)",
|
||||
"pt": "portugalski",
|
||||
"ptBR": "portugalski (Brazylia)",
|
||||
"ru": "rosyjski",
|
||||
"ro": "rumuński",
|
||||
"sc": "sardyński",
|
||||
"sk": "słowacki",
|
||||
"sl": "słoweński",
|
||||
"sr": "serbski",
|
||||
"sq": "albański",
|
||||
"sv": "szwedzki",
|
||||
"te": "telugu",
|
||||
"th": "tajski",
|
||||
"tr": "turecki",
|
||||
"uk": "ukraiński",
|
||||
"vi": "wietnamski",
|
||||
"zhCN": "chiński (Chiny)",
|
||||
"zhTW": "chiński (Tajwan)"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -581,10 +581,12 @@
|
||||
"allowedUnmute": "يمكنك إعادة صوت الميكروفون و بدء تشغيل الكاميرا أو مشاركة شاشتك.",
|
||||
"audioUnmuteBlockedTitle": "تم حظر إعادة صوت الميكروفون!",
|
||||
"audioUnmuteBlockedDescription": "تم حظر عملية إلغاء كتم صوت الميكروفون مؤقتًا بسبب قيود النظام.",
|
||||
"chatMessages": "رسائل الدردشة",
|
||||
"connectedOneMember": "انضم {{name}} للاجتماع",
|
||||
"connectedThreePlusMembers": "انضم {{name}} وعدد {{count}} غيره إلى الاجتماع",
|
||||
"connectedTwoMembers": "انضم {{first}} و {{second}} إلى الاجتماع",
|
||||
"disconnected": "انقطع الاتصال",
|
||||
"displayNotifications": "عرض الإخطارات لـ",
|
||||
"focus": "التركيز على المؤتمر",
|
||||
"focusFail": "إنَّ {{component}} غير متاح. ستعاد المحاولة مرة أخرى خلال {{ms}} ثانية.",
|
||||
"hostAskedUnmute": "The moderator would like you to speak",
|
||||
@@ -857,6 +859,7 @@
|
||||
"selectAudioOutput": "خرج الصوت",
|
||||
"selectCamera": "الكاميرا",
|
||||
"selectMic": "المجهار (المايكروفون)",
|
||||
"selfView": "عرض ذاتي",
|
||||
"sounds": "اصوات",
|
||||
"speakers": "المذياع (مكبر الصوت)",
|
||||
"startAudioMuted": "بدء الجميع مكتومي الصوت",
|
||||
@@ -949,8 +952,8 @@
|
||||
"mute": "اظهِر/اخفِ كتم الصوت",
|
||||
"muteEveryone": "كتم الجميع",
|
||||
"muteEveryoneElse": "كتم صوت الآخرين",
|
||||
"muteEveryonesVideo": "تعطيل كاميرا الجميع",
|
||||
"muteEveryoneElsesVideo": "تعطيل كاميرا الآخرين",
|
||||
"muteEveryonesVideoStream": "وقّف فيديو الجميع",
|
||||
"muteEveryoneElsesVideoStream": "وقّف فيديو أي شخص آخر",
|
||||
"participants": "مشاركون",
|
||||
"pip": "استعمل/اخرج من وضع صورة-في-صورة",
|
||||
"privateMessage": "أرسل رسالة خاصة",
|
||||
|
||||
@@ -581,10 +581,12 @@
|
||||
"allowedUnmute": "Sie können die Stummschaltung aufheben, Ihre Kamera einschalten oder Ihren Bildschirm teilen.",
|
||||
"audioUnmuteBlockedTitle": "Stummschaltung kann nicht aufgehoben werden!",
|
||||
"audioUnmuteBlockedDescription": "Díe Stummschaltung kann aus Überlastungsschutzgründen temporär nicht aufgehoben werden.",
|
||||
"chatMessages": "Chatnachrichten",
|
||||
"connectedOneMember": "{{name}} nimmt am Meeting teil",
|
||||
"connectedThreePlusMembers": "{{name}} und {{count}} andere Personen nehmen am Meeting teil",
|
||||
"connectedTwoMembers": "{{first}} und {{second}} nehmen am Meeting teil",
|
||||
"disconnected": "getrennt",
|
||||
"displayNotifications": "Benachrichtigungen anzeigen für",
|
||||
"focus": "Konferenzleitung",
|
||||
"focusFail": "{{component}} ist im Moment nicht verfügbar – wiederholen in {{ms}} Sekunden",
|
||||
"hostAskedUnmute": "Die Moderation bittet Sie, das Mikrofon zu aktivieren",
|
||||
@@ -782,6 +784,7 @@
|
||||
"title": "Profil"
|
||||
},
|
||||
"raisedHand": "Ich möchte sprechen",
|
||||
"raisedHandsLabel": "Anzahl gehobener Hände",
|
||||
"recording": {
|
||||
"limitNotificationDescriptionWeb": "Wegen hoher Nachfrage ist Ihre Aufnahme auf {{limit}} Min. begrenzt. Für unlimitierte Aufnahmen nutzen Sie bitte <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
|
||||
"limitNotificationDescriptionNative": "Wegen hoher Nachfrage ist Ihre Aufnahme auf {{limit}} Min. begrenzt. Für unlimitierte Aufnahmen nutzen Sie bitte <3>{{app}}</3>.",
|
||||
@@ -857,6 +860,7 @@
|
||||
"selectAudioOutput": "Audioausgabe",
|
||||
"selectCamera": "Kamera",
|
||||
"selectMic": "Mikrofon",
|
||||
"selfView": "Eigene Ansicht",
|
||||
"sounds": "Hinweistöne",
|
||||
"speakers": "Lautsprecher",
|
||||
"startAudioMuted": "Alle Personen treten stummgeschaltet bei",
|
||||
|
||||
@@ -40,25 +40,25 @@
|
||||
"audioOnly": "Bande passante faible"
|
||||
},
|
||||
"blankPage": {
|
||||
"meetingEnded": "Réunion terminée."
|
||||
"meetingEnded": "Réunion terminée."
|
||||
},
|
||||
"breakoutRooms": {
|
||||
"defaultName": "Salle annexe #{{index}}",
|
||||
"mainRoom": "Salle principale",
|
||||
"actions": {
|
||||
"add": "Ajouter salle annexe",
|
||||
"autoAssign": "Assigner automatiquement aux salles annexes",
|
||||
"close": "Fermer",
|
||||
"join": "Rejoindre",
|
||||
"leaveBreakoutRoom": "Quitter la salle annexe",
|
||||
"more": "Plus",
|
||||
"remove": "Supprimer",
|
||||
"sendToBreakoutRoom": "Envoyer le participant dans:"
|
||||
"add": "Ajouter salle annexe",
|
||||
"autoAssign": "Assigner automatiquement aux salles annexes",
|
||||
"close": "Fermer",
|
||||
"join": "Rejoindre",
|
||||
"leaveBreakoutRoom": "Quitter la salle annexe",
|
||||
"more": "Plus",
|
||||
"remove": "Supprimer",
|
||||
"sendToBreakoutRoom": "Envoyer le participant dans:"
|
||||
},
|
||||
"notifications": {
|
||||
"joinedTitle": "Salles annexes",
|
||||
"joined": "Entrée en salle annexe \"{{name}}\"",
|
||||
"joinedMainRoom": "Retour à la salle principalem"
|
||||
"joinedTitle": "Salles annexes",
|
||||
"joined": "Entrée en salle annexe \"{{name}}\"",
|
||||
"joinedMainRoom": "Retour à la salle principalem"
|
||||
}
|
||||
},
|
||||
"calendarSync": {
|
||||
@@ -180,7 +180,8 @@
|
||||
"joinInApp": "Rejoindre la réunion en utilisant l'application",
|
||||
"launchWebButton": "Lancer dans le navigateur",
|
||||
"title": "Lancement de votre réunion dans {{app}} en cours ...",
|
||||
"tryAgainButton": "Réessayez sur le bureau"
|
||||
"tryAgainButton": "Réessayez sur le bureau",
|
||||
"unsupportedBrowser": "Il semble que vous utilisez un navigateur non supporté."
|
||||
},
|
||||
"defaultLink": "ex. {{url}}",
|
||||
"defaultNickname": "ex. Jean Dupont",
|
||||
@@ -244,8 +245,8 @@
|
||||
"gracefulShutdown": "Notre service est actuellement en maintenance. Veuillez réessayer plus tard.",
|
||||
"grantModeratorDialog": "Êtes-vous sûr de vouloir rendre ce participant modérateur ?",
|
||||
"grantModeratorTitle": "Nommer modérateur",
|
||||
"IamHost": "Je suis l'hôte",
|
||||
"hideShareAudioHelper": "Ne pas montrer ce dialogue à nouveau",
|
||||
"IamHost": "Je suis l'hôte",
|
||||
"incorrectRoomLockPassword": "Mot de passe incorrect",
|
||||
"incorrectPassword": "Nom d'utilisateur ou mot de passe incorrect",
|
||||
"internalError": "Oups ! Quelque chose s'est mal passée. L'erreur suivante s'est produite : {{error}}",
|
||||
@@ -261,8 +262,8 @@
|
||||
"lockMessage": "Impossible de verrouiller la conférence.",
|
||||
"lockRoom": "Ajouter un $t(lockRoomPassword) à la réunion ",
|
||||
"lockTitle": "Échec du verrouillage",
|
||||
"login": "Connexion",
|
||||
"logoutQuestion": "Voulez-vous vraiment vous déconnecter et arrêter la conférence ?",
|
||||
"login": "Connexion",
|
||||
"logoutTitle": "Déconnexion",
|
||||
"maxUsersLimitReached": "Le nombre maximal de participants est atteint. Le conférence est complète. Merci de contacter l'organisateur de la réunion ou réessayer plus tard !",
|
||||
"maxUsersLimitReachedTitle": "Le nombre maximal de participants est atteint",
|
||||
@@ -340,7 +341,7 @@
|
||||
"sessionRestarted": "L'appel est relancé par la passerelle",
|
||||
"Share": "Partager",
|
||||
"shareAudio": "Continuer",
|
||||
"shareAudioTitle" : "Comment partager le son",
|
||||
"shareAudioTitle": "Comment partager le son",
|
||||
"shareAudioWarningTitle": "Vous devez cesser de partager l'écran avant de partager l'audio",
|
||||
"shareAudioWarningH1": "Si vous voulez partager uniquement de l'audio:",
|
||||
"shareAudioWarningD1": "vous devez cesser le partage d'écran avant de partager votre son.",
|
||||
@@ -408,16 +409,17 @@
|
||||
"none": "Rien",
|
||||
"uploadedImage": "Image téléversée {{index}}",
|
||||
"deleteImage": "Supprimer l'image",
|
||||
"image1" : "Plage",
|
||||
"image2" : "Mur blanc neutre",
|
||||
"image3" : "Pièce vide blanche",
|
||||
"image4" : "Lampadaire noir",
|
||||
"image5" : "Montagne",
|
||||
"image6" : "Forêt ",
|
||||
"image7" : "Lever de soleil",
|
||||
"image1": "Plage",
|
||||
"image2": "Mur blanc neutre",
|
||||
"image3": "Pièce vide blanche",
|
||||
"image4": "Lampadaire noir",
|
||||
"image5": "Montagne",
|
||||
"image6": "Forêt ",
|
||||
"image7": "Lever de soleil",
|
||||
"desktopShareError": "Impossible de créer le partage de bureau",
|
||||
"desktopShare":"Partage de bureau",
|
||||
"desktopShare": "Partage de bureau",
|
||||
"webAssemblyWarning": "WebAssembly non supporté",
|
||||
"webAssemblyWarningDescription": "WebAssembly invalidé ou non supporté par ce navigateur",
|
||||
"backgroundEffectError": "Erreur dans l'application de l'effet d'arrière-plan."
|
||||
},
|
||||
"feedback": {
|
||||
@@ -445,7 +447,7 @@
|
||||
"country": "Pays",
|
||||
"dialANumber": "Pour rejoindre votre réunion, composez l'un de ces numéros, puis saisissez le code confidentiel.",
|
||||
"dialInConferenceID": "PIN :",
|
||||
"copyNumber":"Copier le numéro",
|
||||
"copyNumber": "Copier le numéro",
|
||||
"dialInNotSupported": "Désolé, l'accès par téléphone n'est pas pris en charge pour l'instant.",
|
||||
"dialInNumber": "Composer :",
|
||||
"dialInSummaryError": "Erreur lors de la récupération des informations de numérotation. Veuillez réessayer plus tard.",
|
||||
@@ -579,10 +581,12 @@
|
||||
"allowedUnmute": "Vous pouvez réactiver votre écran, votre caméra ou partager votre écran.",
|
||||
"audioUnmuteBlockedTitle": "Rétablissement du son bloqué !",
|
||||
"audioUnmuteBlockedDescription": "Le rétablissement du son a été bloqué temporairement en raison de limites système.",
|
||||
"chatMessages": "Messages de chat",
|
||||
"connectedOneMember": "{{name}} a rejoint la réunion",
|
||||
"connectedThreePlusMembers": "{{name}} et {{count}} autres personnes ont rejoint la réunion",
|
||||
"connectedTwoMembers": "{{first}} et {{second}} ont rejoint la réunion",
|
||||
"disconnected": "déconnecté",
|
||||
"displayNotifications": "Afficher les notifications pour",
|
||||
"focus": "Focus de conférence",
|
||||
"focusFail": "{{component}} n'est pas disponible - réessayez dans {{ms}} sec",
|
||||
"hostAskedUnmute": "Le modérateur souhaite vous donner la parole",
|
||||
@@ -604,9 +608,10 @@
|
||||
"passwordRemovedRemotely": "Le $t(lockRoomPassword) a été supprimé par un autre participant",
|
||||
"passwordSetRemotely": "Un $t(lockRoomPassword) a été défini par un autre participant",
|
||||
"raisedHand": "{{name}} aimerait prendre la parole.",
|
||||
"raisedHands": "{{participantName}} et {{raisedHands}} autres personnes",
|
||||
"raisedHands": "{{participantName}} et {{raisedHands}} autres personnes",
|
||||
"screenShareNoAudio": " La case Partager l'audio n'a pas été cochée dans l'écran de sélection de la fenêtre.",
|
||||
"screenShareNoAudioTitle": "La case Partager l'audio n'a pas été cochée",
|
||||
"selfViewTitle": "Vous pouvez toujours rétablir l'affichage de votre propre vidéo dans les paramètres",
|
||||
"somebody": "Quelqu'un",
|
||||
"startSilentTitle": "Vous avez rejoint sans sortie audio !",
|
||||
"startSilentDescription": "Rejoignez la réunion de nouveau pour activer l'audio",
|
||||
@@ -633,7 +638,7 @@
|
||||
"moderationToggleDescription": "par {{participantDisplayName}}",
|
||||
"raiseHandAction": "Lever la main",
|
||||
"reactionSounds": "Bloquer les réactions sonores",
|
||||
"reactionSoundsForAll": "Bloquer les réactions sonores pour tous",
|
||||
"reactionSoundsForAll": "Bloquer les réactions sonores pour tous",
|
||||
"groupTitle": "Notifications",
|
||||
"videoUnmuteBlockedTitle": "Rétablissement de la caméra bloqué !",
|
||||
"videoUnmuteBlockedDescription": "Le rétablissement de la vidéo a été bloqué temporairement en raison de limites système."
|
||||
@@ -649,7 +654,7 @@
|
||||
"actions": {
|
||||
"allow": "Autoriser les participants à:",
|
||||
"allowVideo": "permettre la vidéo",
|
||||
"audioModeration": "réouvrir leur micro",
|
||||
"audioModeration": "Rouvrir leur micro",
|
||||
"blockEveryoneMicCamera": "Bloquer tous les micros et caméras",
|
||||
"invite": "Inviter quelqu'un",
|
||||
"askUnmute": "Demander de réactiver le micro",
|
||||
@@ -662,7 +667,7 @@
|
||||
"stopVideo": "Couper la vidéo",
|
||||
"unblockEveryoneMicCamera": "Débloquer tous les micros et caméras",
|
||||
"videoModeration": "Démarrer leur vidéo",
|
||||
"moreModerationControls": "Options de modération supplémentaires"
|
||||
"moreModerationControls": "Options de modération supplémentaires"
|
||||
},
|
||||
"search": "Rechercher des participants"
|
||||
},
|
||||
@@ -675,8 +680,8 @@
|
||||
"answerPlaceholder": "Option {{index}}",
|
||||
"create": "Créer un sondage",
|
||||
"cancel": "Annuler",
|
||||
"pollOption" : "Option {{index}}",
|
||||
"pollQuestion" : "Question du sondage",
|
||||
"pollOption": "Option {{index}}",
|
||||
"pollQuestion": "Question du sondage",
|
||||
"questionPlaceholder": "Poser une question",
|
||||
"removeOption": "Supprimer l'option",
|
||||
"send": "Envoyer"
|
||||
@@ -750,8 +755,8 @@
|
||||
"or": "ou",
|
||||
"premeeting": "Pré-séance",
|
||||
"showScreen": "Activer l'écran de pré-séance",
|
||||
"keyboardShortcuts": "Activer les raccourcis clavier",
|
||||
"startWithPhone": "Commencez avec l'audio du téléphone",
|
||||
"keyboardShortcuts" : "Activer les raccourcis clavier",
|
||||
"screenSharingError": "Erreur de partage d'écran:",
|
||||
"videoOnlyError": "Erreur vidéo:",
|
||||
"videoTrackError": "Impossible de créer une piste vidéo.",
|
||||
@@ -779,6 +784,7 @@
|
||||
"title": "Profil"
|
||||
},
|
||||
"raisedHand": "Aimerait prendre la parole",
|
||||
"raisedHandsLabel": "Nombre de mains levées",
|
||||
"recording": {
|
||||
"limitNotificationDescriptionWeb": "En raison d'une forte demande, votre enregistrement sera limité à {{limit}} min. Pour des enregistrements illimités, essayez <a href={{url}} rel='noopener noreferrer' target='_blank'> {{app}} </a>.",
|
||||
"limitNotificationDescriptionNative": "En raison d'une forte demande, votre enregistrement sera limité à {{limit}} min. Pour des enregistrements illimités, essayez <3> {{app}} </3>.",
|
||||
@@ -821,8 +827,8 @@
|
||||
"security": {
|
||||
"about": "Vous pouvez ajouter un mot de passe à votre réunion. Les participants devront fournir le mot de passe avant de pouvoir rejoindre la réunion.",
|
||||
"aboutReadOnly": "Les modérateurs peuvent ajouter un mot de passe à la réunion. Les participants devront fournir le mot de passe avant de pouvoir rejoindre la réunion.",
|
||||
"insecureRoomNameWarning": "Le nom de la salle est peu sûr. Des participants non désirés peuvent rejoindre votre réunion. Pensez à sécuriser votre réunion en cliquant sur le bouton de sécurité.",
|
||||
"securityOptions": "Options de sécurité"
|
||||
"header": "Options de sécurité",
|
||||
"insecureRoomNameWarning": "Le nom de la salle est peu sûr. Des participants non désirés peuvent rejoindre votre réunion. Pensez à sécuriser votre réunion en cliquant sur le bouton de sécurité."
|
||||
},
|
||||
"settings": {
|
||||
"calendar": {
|
||||
@@ -854,10 +860,11 @@
|
||||
"selectAudioOutput": "Sortie audio",
|
||||
"selectCamera": "Caméra",
|
||||
"selectMic": "Microphone",
|
||||
"selfView": "Affichage de votre propre vidéo",
|
||||
"sounds": "Sons",
|
||||
"speakers": "Haut-parleurs",
|
||||
"startAudioMuted": "Tout le monde commence en muet",
|
||||
"startReactionsMuted": "Tout le monde commence avec les réactions sonores bloquées",
|
||||
"startReactionsMuted": "Tout le monde commence avec les réactions sonores bloquées",
|
||||
"startVideoMuted": "Tout le monde commence sans vidéo",
|
||||
"talkWhileMuted": "vous parlez en étant muet",
|
||||
"title": "Paramètres"
|
||||
@@ -890,20 +897,20 @@
|
||||
},
|
||||
"speaker": "Haut-parleur",
|
||||
"speakerStats": {
|
||||
"search": "Recherche",
|
||||
"angry": "En colère",
|
||||
"disgusted": "Dégoûté",
|
||||
"fearful": "Effrayé",
|
||||
"happy": "Content",
|
||||
"hours": "{{count}}h",
|
||||
"minutes": "{{count}}m",
|
||||
"name": "Nom",
|
||||
"seconds": "{{count}}s",
|
||||
"speakerStats": "Statistiques de l'interlocuteur",
|
||||
"speakerTime": "Temps de l'interlocuteur",
|
||||
"happy": "Content",
|
||||
"neutral": "Indifférent",
|
||||
"sad": "Triste",
|
||||
"surprised": "Surpris",
|
||||
"angry": "En colère",
|
||||
"fearful": "Effrayé",
|
||||
"disgusted": "Dégoûté"
|
||||
"search": "Recherche",
|
||||
"seconds": "{{count}}s",
|
||||
"speakerTime": "Temps de l'interlocuteur",
|
||||
"speakerStats": "Statistiques de l'interlocuteur",
|
||||
"surprised": "Surpris"
|
||||
},
|
||||
"startupoverlay": {
|
||||
"policyText": " ",
|
||||
@@ -946,8 +953,8 @@
|
||||
"mute": "Activer / Désactiver l'audio",
|
||||
"muteEveryone": "Couper le micro de tout le monde",
|
||||
"muteEveryoneElse": "Couper le micro de tous les autres",
|
||||
"muteEveryonesVideo": "Couper la caméra de tout le monde",
|
||||
"muteEveryoneElsesVideo": "Couper la caméra de tout les autres",
|
||||
"muteEveryonesVideoStream": "Couper la caméra de tout le monde",
|
||||
"muteEveryoneElsesVideoStream": "Couper la caméra de tous les autres",
|
||||
"participants": "Participants",
|
||||
"pip": "Activer / Désactiver le mode Picture in Picture",
|
||||
"privateMessage": "Envoyer un message privé",
|
||||
@@ -1123,6 +1130,7 @@
|
||||
"domuteVideoOfOthers": "Couper la caméra des autres",
|
||||
"flip": "Balancer",
|
||||
"grantModerator": "Donner des droits de modérateur",
|
||||
"hideSelfView": "Cacher l'affichage de votre propre vidéo",
|
||||
"kick": "Exclure",
|
||||
"moderator": "Modérateur",
|
||||
"mute": "Un participant a coupé son micro",
|
||||
@@ -1171,13 +1179,13 @@
|
||||
"startMeeting": "Démarrer la conférence",
|
||||
"terms": "Termes",
|
||||
"title": "Système de vidéoconférence sécurisé, riche en fonctionnalités et gratuit",
|
||||
"logo":{
|
||||
"calendar":"Logo Calendar",
|
||||
"microsoftLogo":"Logo Microsoft",
|
||||
"logoDeepLinking":"Logo Jitsi meet",
|
||||
"desktopPreviewThumbnail":"Miniature d'aperçu du bureau",
|
||||
"googleLogo":"Logo Google",
|
||||
"policyLogo":"Logo de la politique"
|
||||
"logo": {
|
||||
"calendar": "Logo Calendar",
|
||||
"microsoftLogo": "Logo Microsoft",
|
||||
"logoDeepLinking": "Logo Jitsi meet",
|
||||
"desktopPreviewThumbnail": "Miniature d'aperçu du bureau",
|
||||
"googleLogo": "Logo Google",
|
||||
"policyLogo": "Logo de la politique"
|
||||
}
|
||||
},
|
||||
"lonelyMeetingExperience": {
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
"copyStream": "Link naar livestream kopiëren",
|
||||
"countryNotSupported": "Deze bestemming wordt nog niet ondersteund.",
|
||||
"countryReminder": "Belt u buiten de Verenigde Staten? Vergeet dan niet met de landcode te beginnen.",
|
||||
"defaultEmail": "Uw Standaard Email",
|
||||
"defaultEmail": "Uw standaard e-mail",
|
||||
"disabled": "U kunt geen personen uitnodigen.",
|
||||
"failedToAdd": "Het toevoegen van deelnemers is mislukt",
|
||||
"footerText": "Uitgaande oproepen zijn uitgeschakeld.",
|
||||
"googleEmail": "Google Email",
|
||||
"googleEmail": "Google e-mail",
|
||||
"inviteMoreHeader": "U bent de enige in de vergadering",
|
||||
"inviteMoreMailSubject": "Deelnemen aan {{appName}}-vergadering",
|
||||
"inviteMorePrompt": "Nodig meer personen uit",
|
||||
@@ -21,7 +21,7 @@
|
||||
"loadingPeople": "Personen om uit te nodigen aan het zoeken",
|
||||
"noResults": "Geen resultaten die overeenkomen met de zoekopdracht",
|
||||
"noValidNumbers": "Voer een telefoonnummer in",
|
||||
"outlookEmail": "Outlook Email",
|
||||
"outlookEmail": "Outlook e-mail",
|
||||
"searchNumbers": "Telefoonnummers toevoegen",
|
||||
"searchPeople": "Personen zoeken",
|
||||
"searchPeopleAndNumbers": "Personen zoeken of hun telefoonnummers toevoegen",
|
||||
@@ -30,7 +30,7 @@
|
||||
"shareStream": "Deel de link naar de livestream",
|
||||
"telephone": "Telefoon: {{number}}",
|
||||
"title": "Personen uitnodigen voor deze vergadering",
|
||||
"yahooEmail": "Yahoo Email"
|
||||
"yahooEmail": "Yahoo e-mail"
|
||||
},
|
||||
"audioDevices": {
|
||||
"bluetooth": "Bluetooth",
|
||||
@@ -73,9 +73,17 @@
|
||||
"titleWithPolls": "Voer een bijnaam in om chat te gebruiken"
|
||||
},
|
||||
"privateNotice": "Privébericht aan {{recipient}}",
|
||||
"title": "Chat",
|
||||
"titleWithPolls": "Chat",
|
||||
"you": "u"
|
||||
"message": "Bericht",
|
||||
"messageAccessibleTitle": "{{user}} zegt:",
|
||||
"messageAccessibleTitleMe": "ik zeg:",
|
||||
"smileysPanel": "Smiley paneel",
|
||||
"tabs": {
|
||||
"chat": "Gesprek",
|
||||
"polls": "Peilingen"
|
||||
},
|
||||
"title": "Gesprek",
|
||||
"titleWithPolls": "Gesprek",
|
||||
"you": "U"
|
||||
},
|
||||
"chromeExtensionBanner": {
|
||||
"installExtensionText": "Installeer de extensie voor Google Calendar en Office 365 integratie",
|
||||
@@ -526,6 +534,35 @@
|
||||
},
|
||||
"passwordSetRemotely": "ingesteld door een andere deelnemer",
|
||||
"passwordDigitsOnly": "Maximaal {{number}} cijfers",
|
||||
"polls": {
|
||||
"by": "Door {{ name }}",
|
||||
"create": {
|
||||
"addOption": "Voeg optie toe",
|
||||
"answerPlaceholder": "Optie {{index}}",
|
||||
"create": "Creeër een peiling",
|
||||
"cancel": "Annuleer",
|
||||
"pollOption" : "Peiling optie {{index}}",
|
||||
"pollQuestion" : "Peiling vraag",
|
||||
"questionPlaceholder": "Stel een vraag",
|
||||
"removeOption": "Verwijder optie",
|
||||
"send": "Verstuur"
|
||||
},
|
||||
"answer": {
|
||||
"skip": "Sla over",
|
||||
"submit": "Verzend"
|
||||
},
|
||||
"results": {
|
||||
"vote": "Stem",
|
||||
"changeVote": "Verander stem",
|
||||
"empty": "Er zijn nog geen peilingen in deze vergadering. Start hier een peiling!",
|
||||
"hideDetailedResults": "Verberg details",
|
||||
"showDetailedResults": "Toon details"
|
||||
},
|
||||
"notification": {
|
||||
"title": "Een nieuwe peiling is aangemaakt in deze vergadering",
|
||||
"description": "Open het peilingen tabblad om te stemmen"
|
||||
}
|
||||
},
|
||||
"poweredby": "mogelijk gemaakt door",
|
||||
"prejoin": {
|
||||
"audioAndVideoError": "Audio- en videofout:",
|
||||
@@ -586,8 +623,8 @@
|
||||
},
|
||||
"profile": {
|
||||
"setDisplayNameLabel": "Uw weergavenaam instellen",
|
||||
"setEmailInput": "Voer emailadres in",
|
||||
"setEmailLabel": "Uw Gravatar email instellen",
|
||||
"setEmailInput": "Voer e-mailadres in",
|
||||
"setEmailLabel": "Uw Gravatar e-mail instellen",
|
||||
"title": "Profiel"
|
||||
},
|
||||
"raisedHand": "Zou graag willen spreken",
|
||||
@@ -935,7 +972,7 @@
|
||||
"dialogTitle": "Lobby-modus",
|
||||
"disableDialogContent": "Lobby-modus is momenteel ingeschakeld. Deze functie zorgt ervoor dat ongewenste deelnemers niet aan uw vergadering kunnen deelnemen. Wilt u het uitschakelen?",
|
||||
"disableDialogSubmit": "Uitschakelen",
|
||||
"emailField": "Voer uw emailadres in",
|
||||
"emailField": "Voer uw e-mailadres in",
|
||||
"enableDialogPasswordField": "Stel wachtwoord in (optioneel)",
|
||||
"enableDialogSubmit": "Inschakelen",
|
||||
"enableDialogText": "Met de lobby-modus kunt u uw vergadering beveiligen, door deelnemers alleen toe te laten na een formele goedkeuring van een moderator.",
|
||||
|
||||
@@ -158,7 +158,8 @@
|
||||
"joinInApp": "Rejónher la conferéncia en utilizant l’aplicacion",
|
||||
"launchWebButton": "Lançar del navigador",
|
||||
"title": "Aviada de vòstra conferéncia dins {{app}}…",
|
||||
"tryAgainButton": "Tornar ensajar del burèu"
|
||||
"tryAgainButton": "Tornar ensajar del burèu",
|
||||
"unsupportedBrowser": "Sembla qu’utilizatz un navigador que prenèm pas en carga."
|
||||
},
|
||||
"defaultLink": "ex. {{url}}",
|
||||
"defaultNickname": "ex. Joan Delpuèch",
|
||||
@@ -421,7 +422,7 @@
|
||||
"noPassword": "Pas cap",
|
||||
"noRoom": "Cap de sala pas donada per la jónher.",
|
||||
"numbers": "Sonar de numèros",
|
||||
"password": "$t(lockRoomPasswordUppercase) :",
|
||||
"password": "$t(lockRoomPasswordUppercase): ",
|
||||
"title": "Partejar",
|
||||
"tooltip": "Partejar lo ligam e las informacions d’aquesta conferéncia",
|
||||
"copyNumber": "Copiar lo numèro",
|
||||
@@ -627,9 +628,17 @@
|
||||
"moderationInEffectCSDescription": "Volgatz levar la man se volètz partejar vòstre ecran.",
|
||||
"moderationInEffectVideoDescription": "Volgatz levar la man se volètz aviar vòstra camèra.",
|
||||
"audioUnmuteBlockedTitle": "Restabliment del son del microfòn blocat !",
|
||||
"videoUnmuteBlockedTitle": "Restabliment de la camèra blocat !",
|
||||
"videoUnmuteBlockedTitle": "Restabliment de la camèra e del partiment de burèu blocat !",
|
||||
"audioUnmuteBlockedDescription": "Las operacion de restabliment del son microfòn son estadas blocadas pel moment a causa de limitas sistèma.",
|
||||
"videoUnmuteBlockedDescription": "Las operacion de restabliment de la camèra son estadas blocadas pel moment a causa de limitas sistèma."
|
||||
"videoUnmuteBlockedDescription": "Las operacion de restabliment de la camèra e del partiment del burèu son estadas blocadas pel moment a causa de limitas sistèma.",
|
||||
"chatMessages": "Messatges del chat",
|
||||
"displayNotifications": "Afichar las notificacions per",
|
||||
"leftOneMember": "{{name}} a quitat la conferéncia",
|
||||
"leftThreePlusMembers": "{{name}} e un molon d’autres an quitat la conferéncia",
|
||||
"leftTwoMembers": "{{first}} e {{second}} an quitat la conferéncia",
|
||||
"raisedHands": "{{participantName}} e {{raisedHands}} autres",
|
||||
"selfViewTitle": "Podètz totjorn quitar d’amagar vòstra pròpria vista a partir dels paramètres",
|
||||
"reactionSoundsForAll": "Desactivar los sons per totes"
|
||||
},
|
||||
"passwordDigitsOnly": "Fins a {{number}} chifras",
|
||||
"passwordSetRemotely": "causit per qualqu'un mai",
|
||||
@@ -758,7 +767,7 @@
|
||||
"about": "Podètz ajustar un $t(lockRoomPassword) per rejónher una conferéncia. Los participants deuràn fornir lo $t(lockRoomPassword) abans d’obténer l’autorizacion de dintrar dins la conferéncia.",
|
||||
"aboutReadOnly": "Los participants que son moderators pòdon ajustar un $t(lockRoomPassword) a la conferéncia. Los participants deuràn fornir lo $t(lockRoomPassword) abans d’aver l’autorizacion de rejónher la conferéncia.",
|
||||
"insecureRoomNameWarning": "Lo nom de la sala es pas segur. De monde indesirables poirián rejónher vòstra conferéncia.",
|
||||
"securityOptions": "Opcions de seguretat"
|
||||
"header": "Opcions de seguretat"
|
||||
},
|
||||
"settings": {
|
||||
"calendar": {
|
||||
@@ -795,7 +804,9 @@
|
||||
"talkWhileMuted": "Parlar en mut",
|
||||
"desktopShareHighFpsWarning": "Una frequéncia d’imatge mai nauta pel partiment burèu pòt afectar la benda passanta. Devètz reaviar lo partiment d’ecran per aplicar los paramètres novèls.",
|
||||
"desktopShareWarning": "Devètz reaviar lo partiment d’ecran per prendre en compte las modificacions.",
|
||||
"incomingMessage": "Messatge dintrant"
|
||||
"incomingMessage": "Messatge dintrant",
|
||||
"selfView": "Vista de se",
|
||||
"startReactionsMuted": "Començan totes amb las reaccions sonòras amudidas"
|
||||
},
|
||||
"settingsView": {
|
||||
"advanced": "Avançat",
|
||||
@@ -898,7 +909,6 @@
|
||||
"clap": "Picar de las mans",
|
||||
"laugh": "Rire",
|
||||
"like": "Levar lo det gròs",
|
||||
"muteEveryonesVideo": "Copar la vidèo del monde",
|
||||
"muteEveryoneElsesVideo": "Copar la vidèo de los demai",
|
||||
"participants": "Participants",
|
||||
"remoteVideoMute": "Copar la camèra del participant",
|
||||
@@ -910,7 +920,9 @@
|
||||
"collapse": "Plegar",
|
||||
"muteEveryoneElse": "Copar lo microfòn dels autres",
|
||||
"reactionsMenu": "Dobrir / Tampar lo menú de reaccions",
|
||||
"breakoutRoom": "Rejónher/quitar la sala de reünion"
|
||||
"breakoutRoom": "Rejónher/quitar la sala de reünion",
|
||||
"muteEveryoneElsesVideoStream": "Arrestar la vidèo de totes los autres",
|
||||
"muteEveryonesVideoStream": "Arrestar la vidèo de tot lo monde"
|
||||
},
|
||||
"addPeople": "Ajustar de monde a vòstra sonada",
|
||||
"audioOnlyOff": "Desactivar lo mòde connexion febla",
|
||||
@@ -1064,7 +1076,8 @@
|
||||
"videomute": "Lo participant a arrestat la camèra",
|
||||
"domuteVideo": "Desactivar la camèra",
|
||||
"domuteVideoOfOthers": "Desactivar la camèra dels demai",
|
||||
"videoMuted": "Camèra desactivada"
|
||||
"videoMuted": "Camèra desactivada",
|
||||
"hideSelfView": "Amagar pròpria vista"
|
||||
},
|
||||
"welcomepage": {
|
||||
"accessibilityLabel": {
|
||||
@@ -1135,7 +1148,8 @@
|
||||
"image6": "Forèst ",
|
||||
"desktopShareError": "Creacion impossibla d’un partiment de burèu",
|
||||
"desktopShare": "Partiment de burèu",
|
||||
"webAssemblyWarning": "WebAssembly pas pres en carga"
|
||||
"webAssemblyWarning": "WebAssembly pas pres en carga",
|
||||
"webAssemblyWarningDescription": "WebAssembly es desactivat o pas pres en carga per aqueste navigador"
|
||||
},
|
||||
"participantsPane": {
|
||||
"headings": {
|
||||
@@ -1160,7 +1174,8 @@
|
||||
"videoModeration": "Aviar lor vidèo",
|
||||
"allowVideo": "Autorizar la vidèo",
|
||||
"moreModerationActions": "Mai d’opcions de moderacion",
|
||||
"moreParticipantOptions": "Mai d’opcions de participant"
|
||||
"moreParticipantOptions": "Mai d’opcions de participant",
|
||||
"moreModerationControls": "Opcions de moderacion suplementàrias"
|
||||
},
|
||||
"search": "Cercar participants"
|
||||
},
|
||||
@@ -1207,7 +1222,12 @@
|
||||
"autoAssign": "Atribucion auto a las salas de reünion"
|
||||
},
|
||||
"defaultName": "Sala de reünion #{{index}}",
|
||||
"mainRoom": "Sala principala"
|
||||
"mainRoom": "Sala principala",
|
||||
"notifications": {
|
||||
"joinedMainRoom": "Retorn a la sala principala",
|
||||
"joinedTitle": "Salas suplementàrias",
|
||||
"joined": "Dintrada a la sala suplementària « {{name}} »"
|
||||
}
|
||||
},
|
||||
"privacyView": {
|
||||
"header": "Confidencialitat"
|
||||
@@ -1217,5 +1237,6 @@
|
||||
},
|
||||
"blankPage": {
|
||||
"meetingEnded": "Conferéncia acabada."
|
||||
}
|
||||
},
|
||||
"raisedHandsLabel": "Nombre de mans levadas"
|
||||
}
|
||||
|
||||
@@ -39,6 +39,28 @@
|
||||
"audioOnly": {
|
||||
"audioOnly": "Niska przepustowość"
|
||||
},
|
||||
"blankPage": {
|
||||
"meetingEnded": "Spotkanie zakończone."
|
||||
},
|
||||
"breakoutRooms": {
|
||||
"defaultName": "Pokój podgrupy #{{index}}",
|
||||
"mainRoom": "Główny pokój",
|
||||
"actions": {
|
||||
"add": "Dodaj pokój podgrupy",
|
||||
"autoAssign": "Automatycznie przypisuj do pokoi podgrup",
|
||||
"close": "Blisko",
|
||||
"join": "Dołącz",
|
||||
"leaveBreakoutRoom": "Opuść pokój spotkań",
|
||||
"more": "Więcej",
|
||||
"remove": "Usuń",
|
||||
"sendToBreakoutRoom": "Wyślij uczestnika do:"
|
||||
},
|
||||
"notifications": {
|
||||
"joinedTitle": "Pokoje podgrup",
|
||||
"joined": "Dołączanie do pokoju podgrup \"{{name}}\" ",
|
||||
"joinedMainRoom": "Dołączanie do głównego pokoju"
|
||||
}
|
||||
},
|
||||
"calendarSync": {
|
||||
"addMeetingURL": "Dodaj odnośnik do spotkania",
|
||||
"confirmAddLink": "Czy chcesz dodać odnośnik Jitsi do tego wydarzenia?",
|
||||
@@ -158,7 +180,8 @@
|
||||
"joinInApp": "Dołącz do spotkania używając aplikacji",
|
||||
"launchWebButton": "Uruchom przez przeglądarkę",
|
||||
"title": "Trwa uruchamianie Twojego spotkania w {{app}}…",
|
||||
"tryAgainButton": "Spróbuj ponownie w aplikacji stacjonarnej"
|
||||
"tryAgainButton": "Spróbuj ponownie w aplikacji stacjonarnej",
|
||||
"unsupportedBrowser": "Wygląda na to, że używasz przeglądarki, której nie wspieramy."
|
||||
},
|
||||
"defaultLink": "np. {{url}}",
|
||||
"defaultNickname": "np. Jan Kowalski",
|
||||
@@ -206,14 +229,16 @@
|
||||
"connectErrorWithMsg": "Upsss! Coś poszło nie tak i nie możemy podłączyć się do tej konferencji: {{msg}}",
|
||||
"connecting": "Nawiązywanie połączenia",
|
||||
"contactSupport": "Skontaktuj się ze wsparciem",
|
||||
"copy": "Kopiuj",
|
||||
"copied": "Skopiowano",
|
||||
"copy": "Kopiuj",
|
||||
"dismiss": "Odrzuć",
|
||||
"displayNameRequired": "Cześć! Jak się nazywasz?",
|
||||
"done": "Zrobione",
|
||||
"e2eeDescription": "Szyfrowanie End-to-End jest aktualnie w fazie EKSPERYMENTALNEJ. Proszę mieć na uwadze fakt, że szyfrowanie end-to-end wyłączy oferowane przez serwer usługi takie jak: nagrywanie, streaming na żywo i dołączanie uczestników przez telefon. Proszę mieć również na uwadze fakt, że takie spotkanie będą działać tylko dla uczestników korzystających z przeglądarek wspierających wstawiane strumienie.",
|
||||
"e2eeLabel": "Klucz E2EE",
|
||||
"e2eeLabel": "Włącz szyfrowanie E2EE",
|
||||
"e2eeDisabledDueToMaxModeDescription": "Nie można włączyć szyfrowania End-to-End z powodu dużej liczby uczestników konferencji.",
|
||||
"e2eeWarning": "UWAGA: Niektórzy uczestnicy tego spotkania nie mają włączonej obsługi szyfrowania E2EE. Jeśli włączysz tą funkcję ci uczestnicy nie będą mieli z tobą kontaktu.",
|
||||
"e2eeWillDisableDueToMaxModeDescription": "UWAGA: Szyfrowanie typu end-to-end zostanie automatycznie wyłączone, jeśli do konferencji dołączy więcej uczestników.",
|
||||
"enterDisplayName": "Wpisz tutaj swoje imię",
|
||||
"embedMeeting": "Osadź spotkanie",
|
||||
"error": "Błąd",
|
||||
@@ -249,25 +274,29 @@
|
||||
"micPermissionDeniedError": "Nie udzieliłeś pozwolenia na użycie twojego mikrofonu. Nadal możesz uczestniczyc w konferencji ale inni nie będą cię słyszeli. Użyj przycisku kamera aby to naprawić.",
|
||||
"micTimeoutError": "Nie udało się uruchomić źródła dźwięku. Przekroczono limit czasu",
|
||||
"micUnknownError": "Z nieznanej przyczyny nie można użyć mikrofonu.",
|
||||
"moderationAudioLabel": "Zezwalaj uczestnikom na wyłączanie wyciszenia",
|
||||
"moderationVideoLabel": "Zezwalaj uczestnikom na rozpoczęcie wideo",
|
||||
"muteEveryoneElseDialog": "Gdy wyciszysz wszystkich nie będziesz miał możliwości wyłączyć ich wyciszenia, ale oni będą mogli samodzielnie wyłączyć wyciszenie w dowolnym momencie.",
|
||||
"muteEveryoneElseTitle": "Wyciszyć wszystkich za wyjątkiem {{whom}}?",
|
||||
"muteEveryoneElsesVideoDialog": "Po dezaktywacji kamery nie można jej ponownie aktywować, ale uczestnicy mogą to zmienić samodzielnie w dowolnym momencie.",
|
||||
"muteEveryoneDialog": "Uczestnicy mogą w dowolnym momencie wyłączyć wyciszenie.",
|
||||
"muteEveryoneDialogModerationOn": "Uczestnicy mogą w każdej chwili wysłać prośbę o zabranie głosu.",
|
||||
"muteEveryoneTitle": "Wyciszyć wszystkich?",
|
||||
"muteEveryoneElsesVideoDialog": "Po wyłączeniu kamery nie będzie można go ponownie włączyć, ale można go ponownie włączyć w dowolnym momencie.",
|
||||
"muteEveryoneElsesVideoTitle": "Wyłączyć kamerę wszystkim oprócz {{whom}}?",
|
||||
"muteEveryonesVideoDialog": "Czy na pewno chcesz wyłączyć kamery wszystkich uczestników? Nie możesz ponownie włączyć kamer, ale uczestnicy mogą to zmienić samodzielnie w dowolnym momencie.",
|
||||
"muteEveryonesVideoDialog": "Uczestnicy mogą w każdej chwili włączyć swoje wideo.",
|
||||
"muteEveryonesVideoDialogModerationOn": "Uczestnicy mogą w dowolnym momencie wysłać prośbę o włączenie ich wideo.",
|
||||
"muteEveryonesVideoDialogOk": "Wyłącz",
|
||||
"muteEveryonesVideoTitle": "Wyłączyć kamery pozostałych uczestników?",
|
||||
"muteEveryoneDialog": "Czy na pewno wyciszyć wszystkich? Nie będziesz miał możliwości wyłączyć ich wyciszenia, ale oni będą mogli samodzielnie wyłączyć wyciszenie w dowolnym momencie.",
|
||||
"muteEveryoneTitle": "Wyciszyć wszystkich?",
|
||||
"muteEveryoneSelf": "siebie",
|
||||
"muteEveryoneStartMuted": "Od tego momentu wszyscy są wyciszeni",
|
||||
"muteParticipantBody": "Nie możesz wyłączyć ich wyciszenia, ale oni mogą samodzielnie wyłączyć wyciszenie w dowolnym momencie.",
|
||||
"muteParticipantButton": "Wycisz",
|
||||
"muteParticipantDialog": "Czy na pewno wyciszyć tego uczestnika? Nie będziesz mógł wyłączyć wyciszenia uczestników, ale oni mogą samodzielnie wyłączyć wyciszenie w dowolnym momencie.",
|
||||
"muteParticipantsVideoDialog": "Czy na pewno chcesz wyłączyć kamerę tego uczestnika? Nie będziesz mógł ponownie włączyć jego kamery, ale będzie on mógł samodzielnie włączyć kamerę w dowolnym momencie.",
|
||||
"muteParticipantTitle": "Wyciszyć tego uczestnika?",
|
||||
"muteParticipantsVideoDialogModerationOn": "Czy na pewno chcesz wyłączyć kamerę tego uczestnika? Nie będziesz w stanie ponownie włączyć aparatu i oni też nie.",
|
||||
"muteParticipantsVideoButton": "Wyłącz kamerę",
|
||||
"muteParticipantsVideoTitle": "Wyłączyć kamerę tego uczestnika?",
|
||||
"muteParticipantsVideoBody": "Nie będziesz mógł włączyć jego kamery ponownie, ale uczestnik samodzielnie może włączyć kamerę w dowolnym momencie.",
|
||||
"muteParticipantsVideoBodyModerationOn": "Nie będziesz w stanie ponownie włączyć kamery i oni też nie.",
|
||||
"noDropboxToken": "Brak poprawnego tokenu Dropbox",
|
||||
"Ok": "OK",
|
||||
"password": "$t(lockRoomPasswordUppercase)",
|
||||
@@ -329,6 +358,7 @@
|
||||
"shareScreenWarningH1": "Kiedy chcesz udostępniać wyłącznie swój ekran:",
|
||||
"shareScreenWarningD1": "musisz zatrzymać udostępnianie dźwięku przed udostępnieniem ekranu.",
|
||||
"shareScreenWarningD2": "musisz zatrzymać udostępnianie dźwięku, rozpocząć udostępnianie ekranu i zaznaczyć opcję \"udostępnij dźwięk\".",
|
||||
"sharedVideoLinkPlaceholder": "Link do YouTube lub bezpośredni link do wideo",
|
||||
"stopLiveStreaming": "Zatrzymaj transmisję na żywo",
|
||||
"stopRecording": "Zatrzymaj nagrywanie",
|
||||
"stopRecordingWarning": "Naprawdę chcesz zatrzymać nagrywanie?",
|
||||
@@ -388,7 +418,9 @@
|
||||
"image7": "Wschód słońca",
|
||||
"desktopShareError": "Nie można udostępnić pulpitu",
|
||||
"desktopShare": "Udostępnianie pulpitu",
|
||||
"webAssemblyWarning": "WebAssembly nie jest obsługiwany"
|
||||
"webAssemblyWarning": "WebAssembly nie jest obsługiwany",
|
||||
"webAssemblyWarningDescription": "WebAssembly wyłączony lub nieobsługiwany przez tę przeglądarkę",
|
||||
"backgroundEffectError": "Nie udało się zastosować efektu tła."
|
||||
},
|
||||
"feedback": {
|
||||
"average": "Średnio",
|
||||
@@ -493,6 +525,7 @@
|
||||
"expandedPending": "Transmisja na żywo rozpoczyna się…",
|
||||
"failedToStart": "Transmitowanie na żywo nie uruchomiło się",
|
||||
"getStreamKeyManually": "Nie byliśmy w stanie pobrać żadnych transmisji na żywo. Spróbuj uzyskać klucz do transmisji na żywo z YouTube.",
|
||||
"inProgress": "Trwa nagrywanie lub transmisja na żywo",
|
||||
"invalidStreamKey": "Klucz transmisji na żywo może być nieprawidłowy.",
|
||||
"off": "Transmitowanie na żywo zostało zatrzymane",
|
||||
"offBy": "{{name}} zatrzymał transmisję na żywo",
|
||||
@@ -500,6 +533,7 @@
|
||||
"onBy": "{{name}} rozpoczął transmisję na żywo",
|
||||
"pending": "Start strumieniowania live…",
|
||||
"serviceName": "Usługa transmisji na żywo",
|
||||
"sessionAlreadyActive": "Ta sesja jest już nagrywana lub transmitowana na żywo.",
|
||||
"signedInAs": "Jesteś obecnie zalogowany jako:",
|
||||
"signIn": "Zaloguj się z Google",
|
||||
"signInCTA": "Zaloguj się lub wpisz swój klucz do transmisji na żywo YouTube.",
|
||||
@@ -543,18 +577,24 @@
|
||||
"lockRoomPasswordUppercase": "Hasło",
|
||||
"me": "to ja",
|
||||
"notify": {
|
||||
"allowAction": "Zezwól",
|
||||
"allowedUnmute": "Możesz wyłączyć wyciszenie mikrofonu, uruchomić aparat lub udostępnić ekran.",
|
||||
"audioUnmuteBlockedTitle": "Zablokowano wyciszenie mikrofonu!",
|
||||
"audioUnmuteBlockedDescription": "Operacja wyłączania wyciszenia mikrofonu została tymczasowo zablokowana z powodu ograniczeń systemu.",
|
||||
"connectedOneMember": "{{name}} dołączył do spotkania",
|
||||
"connectedThreePlusMembers": "{{name}} i {{count}} innych osób dołączyło do spotkania",
|
||||
"connectedTwoMembers": "{{first}} i {{second}} dołączyli do spotkania",
|
||||
"disconnected": "Rozłączono",
|
||||
"focus": "Fokus konferencji",
|
||||
"focusFail": "{{component}} jest niedostępny - ponowienie w ciągu {{ms}} sec",
|
||||
"grantedTo": "Prawa moderatora przyznane dla {{to}}!",
|
||||
"hostAskedUnmute": "Gospodarz prosi Cię o wyłączenie wyciszenia",
|
||||
"invitedOneMember": "{{name}} został zaproszony",
|
||||
"invitedThreePlusMembers": "{{name}} i {{count}} innych osób zostało zaproszone",
|
||||
"invitedTwoMembers": "{{first}} i {{second}} zostali zaproszeni",
|
||||
"kickParticipant": "{{kicked}} został usunięty przez {{kicker}}",
|
||||
"leftOneMember": "{{name}} opuścił spotkanie",
|
||||
"leftThreePlusMembers": "{{name}} i wielu innych opuściło spotkanie",
|
||||
"leftTwoMembers": "{{first}} i {{second}} opuścił spotkanie",
|
||||
"me": "To ja",
|
||||
"moderator": "Prawa moderatora przydzielone!",
|
||||
"muted": "Rozpoczęto wyciszenie konwersacji.",
|
||||
@@ -566,8 +606,10 @@
|
||||
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) usunięte przez innego uczestnika",
|
||||
"passwordSetRemotely": "$t(lockRoomPasswordUppercase) ustawiony przez innego uczestnika",
|
||||
"raisedHand": "{{name}} chce mówić.",
|
||||
"raisedHands": "{{participantName}} i {{raisedHands}} więcej osób",
|
||||
"screenShareNoAudio": "Opcja \"Udostępnij dźwięk\" nie została zaznaczona podczas wyboru okna.",
|
||||
"screenShareNoAudioTitle": "Nie można udostępnić dźwięku",
|
||||
"selfViewTitle": "Zawsze możesz odkryć własny podgląd w ustawieniach",
|
||||
"somebody": "Ktoś",
|
||||
"startSilentTitle": "Dołączyłeś bez wyjścia dźwiękowego!",
|
||||
"startSilentDescription": "Ponownie dołącz do spotkania, aby włączyć dźwięk",
|
||||
@@ -594,7 +636,10 @@
|
||||
"moderationToggleDescription": "przez {{participantDisplayName}}",
|
||||
"raiseHandAction": "Podnieś rękę",
|
||||
"reactionSounds": "Wyłącz dźwięki",
|
||||
"groupTitle": "Powiadomienia"
|
||||
"reactionSoundsForAll": "Wyłącz dźwięki dla wszystkich",
|
||||
"groupTitle": "Powiadomienia",
|
||||
"videoUnmuteBlockedTitle": "Wyłączenie wyciszenia kamery i udostępnianie pulpitu zablokowane!",
|
||||
"videoUnmuteBlockedDescription": "Operacje wyłączania wyciszenia kamery i udostępniania pulpitu zostały tymczasowo zablokowane z powodu ograniczeń systemu."
|
||||
},
|
||||
"participantsPane": {
|
||||
"close": "Zamknij",
|
||||
@@ -606,21 +651,28 @@
|
||||
},
|
||||
"actions": {
|
||||
"allow": "Zezwól uczestnikom na:",
|
||||
"allowVideo": "Zezwól na wideo",
|
||||
"audioModeration": "Wyłącz wyciszenie",
|
||||
"blockEveryoneMicCamera": "Zablokuj wszystkim kamerę i mikrofon",
|
||||
"invite": "Zaproś",
|
||||
"askUnmute": "Poproś o wyłączenie wyciszenia",
|
||||
"moreModerationActions": "Więcej opcji moderatora",
|
||||
"moreParticipantOptions": "Więcej opcji dla uczestników",
|
||||
"mute": "Wycisz",
|
||||
"muteAll": "Wycisz wszystkich",
|
||||
"muteEveryoneElse": "Wycisz pozostałych",
|
||||
"startModeration": "Wyłącz wyciszenie lub rozpocznij wideo",
|
||||
"stopEveryonesVideo": "Wyłącz wszystkie kamery",
|
||||
"stopVideo": "Wyłącz kamerę",
|
||||
"unblockEveryoneMicCamera": "Odblokuj wszystkim kamerę i mikrofon"
|
||||
}
|
||||
"unblockEveryoneMicCamera": "Odblokuj wszystkim kamerę i mikrofon",
|
||||
"videoModeration": "Włącz kamerę",
|
||||
"moreModerationControls": "Więcej kontroli moderacji"
|
||||
},
|
||||
"search": "Wyszukaj uczestników"
|
||||
},
|
||||
"passwordSetRemotely": "wybrane przez innego uczestnika",
|
||||
"passwordSetRemotely": "Ustawione przez innego uczestnika",
|
||||
"passwordDigitsOnly": "Do {{number}} cyfr",
|
||||
"polls": {
|
||||
"by": "Przez {{ name }}",
|
||||
"create": {
|
||||
"addOption": "Dodaj opcję",
|
||||
"answerPlaceholder": "Opcja {{index}}",
|
||||
@@ -688,6 +740,7 @@
|
||||
"errorDialOutFailed": "Nie udało się wybrać numeru. Połączenie nieudane",
|
||||
"errorDialOutStatus": "Błąd podczas uzyskiwania stanu połączenia",
|
||||
"errorMissingName": "Podaj imię, aby dołączyć do spotkania",
|
||||
"errorNoPermissions": "Musisz włączyć dostęp do mikrofonu i kamery",
|
||||
"errorStatusCode": "Błąd wybierania, kod statusu: {{status}}",
|
||||
"errorValidation": "Weryfikacja numeru zakończona niepowodzeniem",
|
||||
"iWantToDialIn": "Chcę się wdzwonić",
|
||||
@@ -745,6 +798,7 @@
|
||||
"expandedPending": "Nagrywanie się rozpoczyna…",
|
||||
"failedToStart": "Nagrywanie nie jest możliwe",
|
||||
"fileSharingdescription": "Udostępnij nagranie uczestnikom spotkania",
|
||||
"inProgress": "Trwa nagrywanie lub transmisja na żywo",
|
||||
"linkGenerated": "Wygenerowano link do nagrania.",
|
||||
"live": "NA ŻYWO",
|
||||
"loggedIn": "Zalogowano jako {{userName}}",
|
||||
@@ -757,6 +811,7 @@
|
||||
"serviceDescription": "Twoje nagranie zostanie zapisane przez usługę nagrywania",
|
||||
"serviceDescriptionCloud": "Nagrywanie w chmurze",
|
||||
"serviceName": "Usługa nagrywania",
|
||||
"sessionAlreadyActive": "Ta sesja jest już nagrywana lub transmitowana na żywo.",
|
||||
"signIn": "Zaloguj się",
|
||||
"signOut": "Wyloguj się",
|
||||
"unavailable": "Ups! {{serviceName}} w tej chwili niedostępny. Próbujemy rozwiązać ten problem. Spróbuj ponownie później.",
|
||||
@@ -769,8 +824,8 @@
|
||||
"security": {
|
||||
"about": "Możesz dodać $t(lockRoomPassword) do spotkania. Uczestnicy będą musieli wprowadzić $t(lockRoomPassword) przed dołączeniem do spotkania.",
|
||||
"aboutReadOnly": "Uczestnicy posiadający uprawnienia do moderacji mogą ustawić $t(lockRoomPassword) do spotkania. Uczestnicy będą musieli wprowadzić $t(lockRoomPassword) zanim zostaną dołączeni do spotkania.",
|
||||
"insecureRoomNameWarning": "Nazwa pokoju nie jest bezpieczna. Niepowołaniu uczestnicy mogą dołączyć do spotkania. Proszę rozważyć ustawienie hasła spotkania używając przycisku Opcje zabezpieczeń.",
|
||||
"securityOptions": "Opcje zabezpieczeń"
|
||||
"header": "Opcje zabezpieczeń",
|
||||
"insecureRoomNameWarning": "Nazwa pokoju nie jest bezpieczna. Niepowołaniu uczestnicy mogą dołączyć do spotkania. Proszę rozważyć ustawienie hasła spotkania używając przycisku Opcje zabezpieczeń."
|
||||
},
|
||||
"settings": {
|
||||
"calendar": {
|
||||
@@ -790,7 +845,6 @@
|
||||
"language": "Język",
|
||||
"loggedIn": "Zalogowano jako {{name}}",
|
||||
"microphones": "Mikrofony",
|
||||
"sounds": "Dźwięki",
|
||||
"moderator": "Moderacja",
|
||||
"more": "Więcej",
|
||||
"name": "Nazwa",
|
||||
@@ -803,8 +857,11 @@
|
||||
"selectAudioOutput": "Wyjście audio",
|
||||
"selectCamera": "Kamera",
|
||||
"selectMic": "Mikrofon",
|
||||
"selfView": "Własnego podgląd",
|
||||
"sounds": "Dźwięki",
|
||||
"speakers": "Głośniki",
|
||||
"startAudioMuted": "Wycisz wszystkich dołączających",
|
||||
"startReactionsMuted": "Wycisz dźwięki reakcji dla wszystkich",
|
||||
"startVideoMuted": "Ukryj wszystkich dołączających",
|
||||
"talkWhileMuted": "Jesteś wyciszony",
|
||||
"title": "Ustawienia"
|
||||
@@ -837,13 +894,20 @@
|
||||
},
|
||||
"speaker": "Mówca",
|
||||
"speakerStats": {
|
||||
"search": "Wyszukaj",
|
||||
"angry": "Zły",
|
||||
"disgusted": "Oburzony",
|
||||
"fearful": "Przerażony",
|
||||
"happy": "Szczęśliwy",
|
||||
"hours": "{{count}} godz.",
|
||||
"minutes": "{{count}} min.",
|
||||
"name": "Nazwa",
|
||||
"neutral": "Neutralny",
|
||||
"sad": "Smutny",
|
||||
"search": "Wyszukaj",
|
||||
"seconds": "{{count}} sek.",
|
||||
"speakerTime": "Czas mówcy",
|
||||
"speakerStats": "Statystyki mówców",
|
||||
"speakerTime": "Czas mówcy"
|
||||
"surprised": "Zdziwiony"
|
||||
},
|
||||
"startupoverlay": {
|
||||
"policyText": " ",
|
||||
@@ -860,6 +924,7 @@
|
||||
"audioOnly": "Przełączanie tylko audio",
|
||||
"audioRoute": "Wybierz urządzenie dźwiękowe",
|
||||
"boo": "Buczenie",
|
||||
"breakoutRoom": "Dołącz/opuść pokój podgrupy",
|
||||
"callQuality": "Zarządzanie jakością obrazu",
|
||||
"cc": "Przełączanie napisów",
|
||||
"chat": "Przełączanie okna rozmowy",
|
||||
@@ -943,7 +1008,9 @@
|
||||
"hangup": "Opuść spotkanie",
|
||||
"help": "Pomoc",
|
||||
"invite": "Zaproś uczestników",
|
||||
"joinBreakoutRoom": "Dołącz do pokoju podgrupy",
|
||||
"laugh": "Śmiech",
|
||||
"leaveBreakoutRoom": "Opuść pokój spotkań",
|
||||
"like": "Kciuk w górę",
|
||||
"lobbyButtonDisable": "Wyłącz tryb lobby",
|
||||
"lobbyButtonEnable": "Włącz tryb lobby",
|
||||
@@ -1033,7 +1100,10 @@
|
||||
"pending": "{{displayName}} został zaproszony"
|
||||
},
|
||||
"videoStatus": {
|
||||
"adjustFor": "Dostosuj do:",
|
||||
"audioOnly": "DŹW",
|
||||
"bestPerformance": "Najlepsza wydajność",
|
||||
"highestQuality": "Najwyższa jakość",
|
||||
"audioOnlyExpanded": "Jesteś w trybie słabego łącza. W tym trybie będziesz otrzymywać tylko dźwięk i udostępnianie ekranu.",
|
||||
"callQuality": "Jakość obrazu",
|
||||
"hd": "HD",
|
||||
@@ -1044,6 +1114,7 @@
|
||||
"ld": "LD",
|
||||
"ldTooltip": "Podgląd obrazu w niskiej rozdzielczości",
|
||||
"lowDefinition": "Niska rozdzielczość",
|
||||
"performanceSettings": "Ustawienia wydajności",
|
||||
"sd": "SD",
|
||||
"sdTooltip": "Podgląd obrazu w standardowej rozdzielczości",
|
||||
"standardDefinition": "Standardowa rozdzielczość"
|
||||
@@ -1056,14 +1127,15 @@
|
||||
"domuteVideoOfOthers": "Wyłącz kamerę pozostałym",
|
||||
"flip": "Odwrócenie",
|
||||
"grantModerator": "Przyznaj prawa moderatora",
|
||||
"hideSelfView": "Ukryj widok własnego podglądu",
|
||||
"kick": "Wyrzuć",
|
||||
"moderator": "Moderator",
|
||||
"mute": "Uczestnik ma wyciszone audio",
|
||||
"muted": "Wyciszony",
|
||||
"videoMuted": "Kamera wyłączona",
|
||||
"remoteControl": "Kontrola zdalna",
|
||||
"show": "Pokaż na scenie",
|
||||
"videomute": "Uczestnik zatrzymał kamerę",
|
||||
"videoMuted": "Kamera wyłączona"
|
||||
"videomute": "Uczestnik zatrzymał kamerę"
|
||||
},
|
||||
"welcomepage": {
|
||||
"addMeetingName": "Dodaj nazwę spotkania",
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"failedToAdd": "Falha ao adicionar participantes",
|
||||
"footerText": "A marcação está desactivada.",
|
||||
"googleEmail": "Email do Google",
|
||||
"inviteMoreHeader": "Você é o único na reunião",
|
||||
"inviteMoreHeader": "É o único na reunião",
|
||||
"inviteMoreMailSubject": "Participar na reunião {{appName}}",
|
||||
"inviteMorePrompt": "Convidar mais pessoas",
|
||||
"linkCopied": "Link copiado para a área de transferência",
|
||||
@@ -94,7 +94,7 @@
|
||||
"privateNotice": "Mensagem privada para {{recipient}}",
|
||||
"message": "Mensagem",
|
||||
"messageAccessibleTitle": "{{user}} disse:",
|
||||
"messageAccessibleTitleMe": "Você disse:",
|
||||
"messageAccessibleTitleMe": "Eu disse:",
|
||||
"smileysPanel": "Painel de Emojis",
|
||||
"tabs": {
|
||||
"chat": "Chat",
|
||||
@@ -515,7 +515,7 @@
|
||||
"busyTitle": "Todas as transmissões estão atualmente ocupadas",
|
||||
"changeSignIn": "Alternar contas.",
|
||||
"choose": "Escolha uma transmissão em direto",
|
||||
"chooseCTA": "Escolha uma opção de transmissão. Você está conectado atualmente como {{email}}.",
|
||||
"chooseCTA": "Escolha uma opção de transmissão. Está conectado atualmente como {{email}}.",
|
||||
"enterStreamKey": "Insira sua chave de transmissão em direto do YouTube aqui.",
|
||||
"error": "Falha na transmissão em direto. Tente de novo.",
|
||||
"errorAPI": "Ocorreu um erro ao acessar suas transmissões do YouTube. Por favor tente logar novamente.",
|
||||
@@ -534,7 +534,7 @@
|
||||
"pending": "Iniciando Transmissão em Direto...",
|
||||
"serviceName": "Serviço de Transmissão em Direto",
|
||||
"sessionAlreadyActive": "Esta sessão já está a ser gravada ou transmitida em direto.",
|
||||
"signedInAs": "Você está conectado como:",
|
||||
"signedInAs": "Está conectado como:",
|
||||
"signIn": "Faça login no Google",
|
||||
"signInCTA": "Faça login ou insira sua chave de transmissão em Direto do YouTube.",
|
||||
"signOut": "Sair",
|
||||
@@ -562,7 +562,7 @@
|
||||
"engaged": "Gravação local iniciada.",
|
||||
"finished": "Sessão de gravação {{token}} terminada. Por favor, envie o arquivo gravado para o moderador.",
|
||||
"finishedModerator": "Sessão de gravação {{token}} terminada. A gravação da faixa local foi salva. Por favor, peça aos outros participantes para enviar suas gravações.",
|
||||
"notModerator": "Você não é o moderador. Você não pode iniciar ou parar a gravação local."
|
||||
"notModerator": "Não é o moderador. Não pode iniciar ou parar a gravação local."
|
||||
},
|
||||
"moderator": "Moderador",
|
||||
"no": "Não",
|
||||
@@ -581,10 +581,12 @@
|
||||
"allowedUnmute": "Pode ligar o seu microfone, ligar a sua câmara ou partilhar o seu ecrã.",
|
||||
"audioUnmuteBlockedTitle": "Ligar microfone bloqueado!",
|
||||
"audioUnmuteBlockedDescription": "A operação de ligar o microfone foi temporariamente bloqueada devido aos limites do sistema.",
|
||||
"chatMessages": "Mensagens de chat",
|
||||
"connectedOneMember": "{{name}} entrou na reunião",
|
||||
"connectedThreePlusMembers": "{{name}} e muitos outros entraram na reunião",
|
||||
"connectedTwoMembers": "{{first}} e {{second}} entraram na reunião",
|
||||
"disconnected": "desconectado",
|
||||
"displayNotifications": "Mostrar notificações para",
|
||||
"focus": "Foco da conferência",
|
||||
"focusFail": "{{component}} não disponĩvel - tente em {{ms}} seg.",
|
||||
"hostAskedUnmute": "O moderador gostaria que você falasse",
|
||||
@@ -597,8 +599,8 @@
|
||||
"leftTwoMembers": "{{first}} e {{second}} deixaram a reunião",
|
||||
"me": "Eu",
|
||||
"moderator": "É agora um moderador",
|
||||
"muted": "Você iniciou uma conversa com o microfone desativado.",
|
||||
"mutedTitle": "Você está silenciado!",
|
||||
"muted": "Iniciou uma conversa com o microfone desativado.",
|
||||
"mutedTitle": "Está silenciado!",
|
||||
"mutedRemotelyTitle": "Foi silenciado pelo {{participantDisplayName}}",
|
||||
"mutedRemotelyDescription": "Pode sempre voltar a ativar o microfone quando estiver pronto para falar. Silencie de volta quando estiver pronto para manter o barulho afastado da reunião.",
|
||||
"videoMutedRemotelyTitle": "A sua câmara foi desligada pelo {{participantDisplayName}}.",
|
||||
@@ -611,7 +613,7 @@
|
||||
"screenShareNoAudioTitle": "Não foi possível partilhar o áudio do sistema!",
|
||||
"selfViewTitle": "Pode sempre reexibir a autovisualização a partir das definições",
|
||||
"somebody": "Alguém",
|
||||
"startSilentTitle": "Você entrou sem saída de áudio!",
|
||||
"startSilentTitle": "Entrou sem saída de áudio!",
|
||||
"startSilentDescription": "Volte à reunião para habilitar o áudio",
|
||||
"suboptimalBrowserWarning": "Tememos que sua experiência de reunião não seja tão boa aqui. Estamos procurando maneiras de melhorar isso, mas até então, tente usar um dos <a href='{{recommendedBrowserPageLink}}' target='_blank'>navegadores completamente suportados</a>.",
|
||||
"suboptimalExperienceTitle": "Alerta do navegador",
|
||||
@@ -652,7 +654,7 @@
|
||||
"actions": {
|
||||
"allow": "Permitir aos participantes:",
|
||||
"allowVideo": "Permitir vídeo",
|
||||
"audioModeration": "Ativarem o microfone deles",
|
||||
"audioModeration": "Ativar o microfone deles",
|
||||
"blockEveryoneMicCamera": "Bloquear o microfone e a câmara de todos",
|
||||
"invite": "Convidar alguém",
|
||||
"askUnmute": "Pedir para ligar o microfone",
|
||||
@@ -664,7 +666,7 @@
|
||||
"stopEveryonesVideo": "Desligar a câmara de todos",
|
||||
"stopVideo": "Desligar a câmara",
|
||||
"unblockEveryoneMicCamera": "Desbloquear o microfone e a câmara de todos",
|
||||
"videoModeration": "Ligarem a câmara deles",
|
||||
"videoModeration": "Ligar a câmara deles",
|
||||
"moreModerationControls": "Mais controlos de moderação"
|
||||
},
|
||||
"search": "Pesquisar participantes"
|
||||
@@ -782,6 +784,7 @@
|
||||
"title": "Perfil"
|
||||
},
|
||||
"raisedHand": "Gostaria de falar",
|
||||
"raisedHandsLabel": "Número de mãos levantadas",
|
||||
"recording": {
|
||||
"limitNotificationDescriptionWeb": "Devido à grande procura, a sua gravação será limitada a {{limit}} min. For unlimited recordings try <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
|
||||
"limitNotificationDescriptionNative": "Due to high demand your recording will be limited to {{limit}} min. Para gravações ilimitadas tente <3>{{app}}</3>.",
|
||||
@@ -857,6 +860,7 @@
|
||||
"selectAudioOutput": "Saída de áudio",
|
||||
"selectCamera": "Câmara",
|
||||
"selectMic": "Microfone",
|
||||
"selfView": "Autovisualização",
|
||||
"sounds": "Sons",
|
||||
"speakers": "Participantes",
|
||||
"startAudioMuted": "Todos começam com microfone desligado",
|
||||
@@ -1142,7 +1146,7 @@
|
||||
"join": "Toque para entrar",
|
||||
"roomname": "Digite o nome da sala"
|
||||
},
|
||||
"appDescription": "Vá em frente, converse por vídeo com toda a equipa. Na verdade, convide todos os que conhece. {{app}} é uma solução de videoconferência totalmente criptografada e 100% de código aberto que você pode usar todos os dias, a cada dia, gratuitamente — sem necessidade de conta.",
|
||||
"appDescription": "Vá em frente, converse por vídeo com toda a equipa. Na verdade, convide todos os que conhece. {{app}} é uma solução de videoconferência totalmente criptografada e 100% de código aberto que pode usar todos os dias, a cada dia, gratuitamente — sem necessidade de conta.",
|
||||
"audioVideoSwitch": {
|
||||
"audio": "Voz",
|
||||
"video": "Vídeo"
|
||||
@@ -1166,7 +1170,7 @@
|
||||
"privacy": "Política de Privacidade",
|
||||
"recentList": "Recente",
|
||||
"recentListDelete": "Remover",
|
||||
"recentListEmpty": "Sua lista recente está vazia. As reuniões que você realizar serão exibidas aqui.",
|
||||
"recentListEmpty": "A sua lista recente está atualmente vazia. Converse com a sua equipa e encontrará aqui todas as suas reuniões recentes.",
|
||||
"reducedUIText": "Bem-vindo ao {{app}}!",
|
||||
"roomNameAllowedChars": "Nome da reunião não deve conter qualquer um destes caracteres: ?. &, :, ', \", %, #.",
|
||||
"roomname": "Digite o nome da sala",
|
||||
|
||||
@@ -102,10 +102,13 @@
|
||||
},
|
||||
"connectionindicator": {
|
||||
"address": "Адрес:",
|
||||
"audio_ssrc": "Аудио SSRC:",
|
||||
"bandwidth": "Средняя скорость:",
|
||||
"bitrate": "Битрейт:",
|
||||
"bridgeCount": "Количество серверов: ",
|
||||
"codecs": "Кодеки (A/V): ",
|
||||
"connectedTo": "Подключен к:",
|
||||
"e2e_rtt": "E2E RTT:",
|
||||
"framerate": "Частота кадров:",
|
||||
"less": "Краткая информация",
|
||||
"localaddress_0": "Локальный адрес:",
|
||||
@@ -114,6 +117,7 @@
|
||||
"localport_0": "Локальный порт:",
|
||||
"localport_1": "Локальных порта:",
|
||||
"localport_2": "Локальных портов:",
|
||||
"maxEnabledResolution": "Максимальное разрешение",
|
||||
"more": "Подробная информация",
|
||||
"packetloss": "Потери пакетов:",
|
||||
"quality": {
|
||||
@@ -130,10 +134,13 @@
|
||||
"remoteport_1": "Удаленных порта:",
|
||||
"remoteport_2": "Удаленных портов:",
|
||||
"resolution": "Разрешение:",
|
||||
"savelogs": "Сохранить логи",
|
||||
"participant_id": "id участника:",
|
||||
"status": "Связь:",
|
||||
"transport_0": "Метод отправки:",
|
||||
"transport_1": "Метода отправки:",
|
||||
"transport_2": "Методов отправки:"
|
||||
"transport_2": "Методов отправки:",
|
||||
"video_ssrc": "Видео SSRC:"
|
||||
},
|
||||
"dateUtils": {
|
||||
"earlier": "Ранее",
|
||||
@@ -559,6 +566,30 @@
|
||||
"suboptimalExperienceTitle": "Предупреждение браузера",
|
||||
"unmute": "Включить микрофон"
|
||||
},
|
||||
"participantsPane": {
|
||||
"close": "Закрыть",
|
||||
"header": "Участники",
|
||||
"headings": {
|
||||
"lobby": "Лобби ({{count}})",
|
||||
"participantsList": "Список участников ({{count}})",
|
||||
"waitingLobby": "Ожидают в лобби ({{count}})"
|
||||
},
|
||||
"actions": {
|
||||
"allow": "Разрешить",
|
||||
"allowVideo": "Разрешить видео",
|
||||
"audioModeration": "Разрешить выключить микрофон",
|
||||
"blockEveryoneMicCamera": "Заблокировать у всех микрофон и камеру",
|
||||
"invite": "Пригласить",
|
||||
"askUnmute": "Попросить разрешение включить микрофон",
|
||||
"mute": "Выключить звук",
|
||||
"muteAll": "Выключить звук у всех",
|
||||
"muteEveryoneElse": "Выключить микрофон у остальных",
|
||||
"stopEveryonesVideo": "Выключить у всех камеру",
|
||||
"stopVideo": "Остановить видео",
|
||||
"unblockEveryoneMicCamera": "Разблокировать у всех микрофон и камеру",
|
||||
"videoModeration": "Разрешить видео"
|
||||
}
|
||||
},
|
||||
"passwordDigitsOnly": "До {{number}} цифр",
|
||||
"passwordSetRemotely": "установлен другим участником",
|
||||
"poweredby": "работает на",
|
||||
@@ -744,7 +775,7 @@
|
||||
"hangup": "Завершить звонок",
|
||||
"help": "Справка",
|
||||
"invite": "Пригласить",
|
||||
"kick": "Выкинуть участника",
|
||||
"kick": "Отключить участника",
|
||||
"lobbyButton": "Вкл/Выкл режим лобби",
|
||||
"localRecording": "Вкл/Выкл кнопки записи",
|
||||
"lockRoom": "Установить пароль",
|
||||
@@ -774,13 +805,18 @@
|
||||
"videomute": "Вкл/Выкл видео"
|
||||
},
|
||||
"addPeople": "Добавить людей к вашему сеансу связи",
|
||||
"audioSettings": "Настройка звука",
|
||||
"videoSettings": "Настройка видео",
|
||||
"audioOnlyOff": "Отключить режим экономии пропускной способности",
|
||||
"audioOnlyOn": "Включить режим экономии пропускной способности",
|
||||
"audioRoute": "Выбрать аудиоустройство",
|
||||
"authenticate": "Аутентифицировать",
|
||||
"boo": "Освистывать",
|
||||
"callQuality": "Качество связи",
|
||||
"chat": "Чат",
|
||||
"clap": "Аплодисменты",
|
||||
"closeChat": "Закрыть чат",
|
||||
"closeReactionsMenu": "Закрыть меню реакций",
|
||||
"documentClose": "Закрыть общий документ",
|
||||
"documentOpen": "Открыть общий документ",
|
||||
"download": "Скачать приложение",
|
||||
@@ -794,6 +830,8 @@
|
||||
"hangup": "Выход",
|
||||
"help": "Справка",
|
||||
"invite": "Пригласить",
|
||||
"laugh": "Смеяться",
|
||||
"like": "Мне нравится",
|
||||
"lobbyButtonDisable": "Отключить режим лобби",
|
||||
"lobbyButtonEnable": "Включить режим лобби",
|
||||
"login": "Войти",
|
||||
@@ -812,6 +850,7 @@
|
||||
"noisyAudioInputTitle": "Похоже, ваш микрофон создает шум!",
|
||||
"noisyAudioInputDesc": "Возможно, ваш микрофон создает шум. Вы можете выключить его или смените устройство.",
|
||||
"openChat": "Открыть чат",
|
||||
"openReactionsMenu": "Открыть меню реакций",
|
||||
"participants": "Участники",
|
||||
"pip": "Вкл режим Картинка-в-картинке",
|
||||
"privateMessage": "Отправить личное сообщение",
|
||||
@@ -819,9 +858,12 @@
|
||||
"raiseHand": "Хочу говорить",
|
||||
"raiseYourHand": "Поднять руку",
|
||||
"security": "Настройки безопасности",
|
||||
"selectBackground": "Выбрать фоновое изображение",
|
||||
"shareRoom": "Отправить приглашение",
|
||||
"shareaudio": "Предоставить доступ к звуку",
|
||||
"sharedvideo": "Видео YouTube",
|
||||
"shortcuts": "Комбинации клавиш",
|
||||
"silence": "Молчание",
|
||||
"speakerStats": "Статистика",
|
||||
"startScreenSharing": "Начать трансляцию с экрана",
|
||||
"startSubtitles": "Включить субтитры",
|
||||
@@ -830,6 +872,7 @@
|
||||
"stopSharedVideo": "Остановить видео на YouTube",
|
||||
"stopSubtitles": "Отключить субтитры",
|
||||
"stopvideoblur": "Отключить размытие фона",
|
||||
"surprised": "Удивиться",
|
||||
"talkWhileMutedPopup": "Пытаетесь говорить? У вас отключен звук.",
|
||||
"tileViewToggle": "Вкл/выкл плитку",
|
||||
"toggleCamera": "Переключить камеру",
|
||||
@@ -887,17 +930,21 @@
|
||||
"standardDefinition": "Стандартное качество (SD)"
|
||||
},
|
||||
"videothumbnail": {
|
||||
"connectionInfo": "Информация о соединении",
|
||||
"domute": "Выключить звук",
|
||||
"domuteOthers": "Выключить остальных",
|
||||
"domuteVideo": "Выключить видео",
|
||||
"domuteOthers": "Выключить звук остальным",
|
||||
"domuteVideoOfOthers": "Выключить видео остальным",
|
||||
"flip": "Отразить",
|
||||
"grantModerator": "Сделать модератором",
|
||||
"kick": "Выкинуть",
|
||||
"kick": "Отключить",
|
||||
"moderator": "Модератор",
|
||||
"mute": "Без звука",
|
||||
"muted": "Звук выключен",
|
||||
"videoMuted": "Камера выключена",
|
||||
"remoteControl": "Начать / Остановить дистанционный контроль",
|
||||
"show": "Показать крупным планом",
|
||||
"videomute": "Участник отключил камеру"
|
||||
"videomute": "Участник выключил камеру"
|
||||
},
|
||||
"welcomepage": {
|
||||
"accessibilityLabel": {
|
||||
@@ -934,4 +981,4 @@
|
||||
"terms": "Условия",
|
||||
"title": "Защищенная, полнофункциональная и совершенно бесплатная система видеоконференций"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1521,12 +1521,14 @@ class API {
|
||||
* Notify external application ( if API is enabled) that a toolbar button was clicked.
|
||||
*
|
||||
* @param {string} key - The key of the toolbar button.
|
||||
* @param {boolean} preventExecution - Whether execution of the button click was prevented or not.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyToolbarButtonClicked(key: string) {
|
||||
notifyToolbarButtonClicked(key: string, preventExecution: boolean) {
|
||||
this._sendEvent({
|
||||
name: 'toolbar-button-clicked',
|
||||
key
|
||||
key,
|
||||
preventExecution
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
12
package-lock.json
generated
12
package-lock.json
generated
@@ -66,7 +66,7 @@
|
||||
"jquery-i18next": "1.2.1",
|
||||
"js-md5": "0.6.1",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#63c3e1f869c69c6a17d7dc881570a1731478ac8c",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#55a03ac1b52f85dcbd9bfe339690ad88436ac029",
|
||||
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.1",
|
||||
@@ -12519,8 +12519,8 @@
|
||||
},
|
||||
"node_modules/lib-jitsi-meet": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "git+ssh://git@github.com/jitsi/lib-jitsi-meet.git#63c3e1f869c69c6a17d7dc881570a1731478ac8c",
|
||||
"integrity": "sha512-EqkKpdudZ4ZkRPpUbqXeX0pF5s0kh2UJ6iT6qLyYYWCFlOiE27cTbKb5E85oj0Zzj9NnDGTgYHhCLn2q6y7JrA==",
|
||||
"resolved": "git+ssh://git@github.com/jitsi/lib-jitsi-meet.git#55a03ac1b52f85dcbd9bfe339690ad88436ac029",
|
||||
"integrity": "sha512-0ZNhG4ZPzcH+2R7K5xa5tSNVK8CKrKVCGP/bjr07XtiV3pcY65OWI2mH+QzlMIMDOXqgqQtry9RHv4vmzy5pIg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@@ -29993,9 +29993,9 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "git+ssh://git@github.com/jitsi/lib-jitsi-meet.git#63c3e1f869c69c6a17d7dc881570a1731478ac8c",
|
||||
"integrity": "sha512-EqkKpdudZ4ZkRPpUbqXeX0pF5s0kh2UJ6iT6qLyYYWCFlOiE27cTbKb5E85oj0Zzj9NnDGTgYHhCLn2q6y7JrA==",
|
||||
"from": "lib-jitsi-meet@github:jitsi/lib-jitsi-meet#63c3e1f869c69c6a17d7dc881570a1731478ac8c",
|
||||
"version": "git+ssh://git@github.com/jitsi/lib-jitsi-meet.git#55a03ac1b52f85dcbd9bfe339690ad88436ac029",
|
||||
"integrity": "sha512-0ZNhG4ZPzcH+2R7K5xa5tSNVK8CKrKVCGP/bjr07XtiV3pcY65OWI2mH+QzlMIMDOXqgqQtry9RHv4vmzy5pIg==",
|
||||
"from": "lib-jitsi-meet@github:jitsi/lib-jitsi-meet#55a03ac1b52f85dcbd9bfe339690ad88436ac029",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "2.0.0",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
"jquery-i18next": "1.2.1",
|
||||
"js-md5": "0.6.1",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#63c3e1f869c69c6a17d7dc881570a1731478ac8c",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#55a03ac1b52f85dcbd9bfe339690ad88436ac029",
|
||||
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.1",
|
||||
|
||||
@@ -23,6 +23,5 @@ export default class HangupButton extends AbstractHangupButton<Props, *> {
|
||||
*/
|
||||
_doHangup() {
|
||||
api.executeCommand('hangup');
|
||||
window.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ const styles = theme => {
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
maxWidth: 292,
|
||||
marginRight: 16,
|
||||
marginRight: theme.spacing(3),
|
||||
|
||||
'&.selected': {
|
||||
fontWeight: 600
|
||||
@@ -186,8 +186,8 @@ function CopyButton({ classes, className, displayedText, textToCopy, textOnHover
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className = { `${classes.copyButton}-content` }>
|
||||
{ isHovered ? textOnHover : displayedText }
|
||||
<div className = { clsx(classes.content) }>
|
||||
<span> { isHovered ? textOnHover : displayedText } </span>
|
||||
</div>
|
||||
<Icon src = { IconCopy } />
|
||||
</>
|
||||
|
||||
@@ -289,6 +289,12 @@ StateListenerRegistry.register(
|
||||
})),
|
||||
'raisedHand': (participant, value) =>
|
||||
_raiseHandUpdated(store, conference, participant.getId(), value),
|
||||
'region': (participant, value) =>
|
||||
store.dispatch(participantUpdated({
|
||||
conference,
|
||||
id: participant.getId(),
|
||||
region: value
|
||||
})),
|
||||
'remoteControlSessionStatus': (participant, value) =>
|
||||
store.dispatch(participantUpdated({
|
||||
conference,
|
||||
|
||||
@@ -23,14 +23,6 @@ export default class AbstractAudioMuteButton<P: Props, S: *>
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
const { handleClick } = this.props;
|
||||
|
||||
if (handleClick) {
|
||||
handleClick();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this._setAudioMuted(!this._isAudioMuted());
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { NOTIFY_CLICK_MODE } from '../../../toolbox/constants';
|
||||
import { combineStyles } from '../../styles';
|
||||
|
||||
import type { Styles } from './AbstractToolboxItem';
|
||||
@@ -14,6 +15,11 @@ export type Props = {
|
||||
*/
|
||||
afterClick: ?Function,
|
||||
|
||||
/**
|
||||
* The button's key.
|
||||
*/
|
||||
buttonKey?: string,
|
||||
|
||||
/**
|
||||
* Extra styles which will be applied in conjunction with `styles` or
|
||||
* `toggledStyles` when the button is disabled;.
|
||||
@@ -25,6 +31,12 @@ export type Props = {
|
||||
*/
|
||||
handleClick?: Function,
|
||||
|
||||
/**
|
||||
* Notify mode for `toolbarButtonClicked` event -
|
||||
* whether to only notify or to also prevent button click routine.
|
||||
*/
|
||||
notifyMode?: string,
|
||||
|
||||
/**
|
||||
* Whether to show the label or not.
|
||||
*/
|
||||
@@ -51,6 +63,8 @@ export type Props = {
|
||||
visible: boolean
|
||||
};
|
||||
|
||||
declare var APP: Object;
|
||||
|
||||
/**
|
||||
* Default style for disabled buttons.
|
||||
*/
|
||||
@@ -134,6 +148,17 @@ export default class AbstractButton<P: Props, S: *> extends Component<P, S> {
|
||||
this._onClick = this._onClick.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to be implemented by subclasses, which should be used
|
||||
* to handle a key being down.
|
||||
*
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
_onKeyDown() {
|
||||
// To be implemented by subclass.
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to be implemented by subclasses, which should be used
|
||||
* to handle the button being clicked / pressed.
|
||||
@@ -248,17 +273,29 @@ export default class AbstractButton<P: Props, S: *> extends Component<P, S> {
|
||||
_onClick: (*) => void;
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button, and toggles the audio mute state
|
||||
* accordingly.
|
||||
* Handles clicking / pressing the button.
|
||||
*
|
||||
* @param {Object} e - Event.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onClick(e) {
|
||||
const { afterClick } = this.props;
|
||||
const { afterClick, handleClick, notifyMode, buttonKey } = this.props;
|
||||
|
||||
if (typeof APP !== 'undefined' && notifyMode) {
|
||||
APP.API.notifyToolbarButtonClicked(
|
||||
buttonKey, notifyMode === NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY
|
||||
);
|
||||
}
|
||||
|
||||
if (notifyMode !== NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY) {
|
||||
if (handleClick) {
|
||||
handleClick();
|
||||
}
|
||||
|
||||
this._handleClick();
|
||||
}
|
||||
|
||||
this._handleClick();
|
||||
afterClick && afterClick(e);
|
||||
|
||||
// blur after click to release focus from button to allow PTT.
|
||||
@@ -288,6 +325,7 @@ export default class AbstractButton<P: Props, S: *> extends Component<P, S> {
|
||||
<ToolboxItem
|
||||
disabled = { this._isDisabled() }
|
||||
onClick = { this._onClick }
|
||||
onKeyDown = { this._onKeyDown }
|
||||
{ ...props } />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,14 +22,6 @@ export default class AbstractVideoMuteButton<P : Props, S : *>
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
const { handleClick } = this.props;
|
||||
|
||||
if (handleClick) {
|
||||
handleClick();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this._setVideoMuted(!this._isVideoMuted());
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,15 @@ import { Icon } from '../../icons';
|
||||
import { Tooltip } from '../../tooltip';
|
||||
|
||||
import AbstractToolboxItem from './AbstractToolboxItem';
|
||||
import type { Props } from './AbstractToolboxItem';
|
||||
import type { Props as AbstractToolboxItemProps } from './AbstractToolboxItem';
|
||||
|
||||
type Props = AbstractToolboxItemProps & {
|
||||
|
||||
/**
|
||||
* On key down handler.
|
||||
*/
|
||||
onKeyDown: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* Web implementation of {@code AbstractToolboxItem}.
|
||||
@@ -53,6 +61,7 @@ export default class ToolboxItem extends AbstractToolboxItem<Props> {
|
||||
disabled,
|
||||
elementAfter,
|
||||
onClick,
|
||||
onKeyDown,
|
||||
showLabel,
|
||||
tooltipPosition,
|
||||
toggled
|
||||
@@ -64,6 +73,7 @@ export default class ToolboxItem extends AbstractToolboxItem<Props> {
|
||||
'aria-label': this.accessibilityLabel,
|
||||
className: className + (disabled ? ' disabled' : ''),
|
||||
onClick: disabled ? undefined : onClick,
|
||||
onKeyDown: disabled ? undefined : onKeyDown,
|
||||
onKeyPress: this._onKeyPress,
|
||||
tabIndex: 0,
|
||||
role: showLabel ? 'menuitem' : 'button'
|
||||
|
||||
@@ -2,11 +2,17 @@
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { NOTIFY_CLICK_MODE } from '../../../../toolbox/constants';
|
||||
import { Icon } from '../../../icons';
|
||||
import { Tooltip } from '../../../tooltip';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The button's key.
|
||||
*/
|
||||
buttonKey?: string,
|
||||
|
||||
/**
|
||||
* The decorated component (ToolboxButton).
|
||||
*/
|
||||
@@ -22,6 +28,12 @@ type Props = {
|
||||
*/
|
||||
iconDisabled: boolean,
|
||||
|
||||
/**
|
||||
* Notify mode for `toolbarButtonClicked` event -
|
||||
* whether to only notify or to also prevent button click routine.
|
||||
*/
|
||||
notifyMode?: string,
|
||||
|
||||
/**
|
||||
* Click handler for the small icon.
|
||||
*/
|
||||
@@ -68,6 +80,8 @@ type Props = {
|
||||
iconId: string
|
||||
};
|
||||
|
||||
declare var APP: Object;
|
||||
|
||||
/**
|
||||
* Displays the `ToolboxButtonWithIcon` component.
|
||||
*
|
||||
@@ -80,6 +94,8 @@ export default function ToolboxButtonWithIcon(props: Props) {
|
||||
icon,
|
||||
iconDisabled,
|
||||
iconTooltip,
|
||||
buttonKey,
|
||||
notifyMode,
|
||||
onIconClick,
|
||||
onIconKeyDown,
|
||||
styles,
|
||||
@@ -97,7 +113,17 @@ export default function ToolboxButtonWithIcon(props: Props) {
|
||||
= 'settings-button-small-icon settings-button-small-icon--disabled';
|
||||
} else {
|
||||
iconProps.className = 'settings-button-small-icon';
|
||||
iconProps.onClick = onIconClick;
|
||||
iconProps.onClick = () => {
|
||||
if (typeof APP !== 'undefined' && notifyMode) {
|
||||
APP.API.notifyToolbarButtonClicked(
|
||||
buttonKey, notifyMode === NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY
|
||||
);
|
||||
}
|
||||
|
||||
if (notifyMode !== NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY) {
|
||||
onIconClick();
|
||||
}
|
||||
};
|
||||
iconProps.onKeyDown = onIconKeyDown;
|
||||
iconProps.role = 'button';
|
||||
iconProps.tabIndex = 0;
|
||||
|
||||
@@ -216,7 +216,10 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
StateListenerRegistry.register(
|
||||
state => getCurrentConference(state),
|
||||
(conference, { dispatch, getState }, prevConference) => {
|
||||
if (prevConference && !conference) {
|
||||
|
||||
// conference keep flipping while we are authenticating, skip clearing while we are in that process
|
||||
if (prevConference && !conference && !getState()['features/base/conference'].authRequired) {
|
||||
|
||||
// Clear all tracks.
|
||||
const remoteTracks = getState()['features/base/tracks'].filter(t => !t.local);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useDispatch } from 'react-redux';
|
||||
|
||||
import { openDialog } from '../../../base/dialog';
|
||||
import { Icon, IconArrowDown, IconArrowUp } from '../../../base/icons';
|
||||
import { participantMatchesSearch } from '../../../participants-pane/functions';
|
||||
|
||||
import BreakoutRoomContextMenu from './BreakoutRoomContextMenu';
|
||||
import BreakoutRoomParticipantItem from './BreakoutRoomParticipantItem';
|
||||
@@ -17,7 +18,12 @@ type Props = {
|
||||
/**
|
||||
* Room to display.
|
||||
*/
|
||||
room: Object
|
||||
room: Object,
|
||||
|
||||
/**
|
||||
* Participants search string.
|
||||
*/
|
||||
searchString: string
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -31,7 +37,7 @@ function _keyExtractor(item: Object) {
|
||||
}
|
||||
|
||||
|
||||
export const CollapsibleRoom = ({ room }: Props) => {
|
||||
export const CollapsibleRoom = ({ room, searchString }: Props) => {
|
||||
const dispatch = useDispatch();
|
||||
const [ collapsed, setCollapsed ] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
@@ -65,7 +71,8 @@ export const CollapsibleRoom = ({ room }: Props) => {
|
||||
horizontal = { false }
|
||||
keyExtractor = { _keyExtractor }
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
renderItem = { ({ item: participant }) => <BreakoutRoomParticipantItem item = { participant } /> }
|
||||
renderItem = { ({ item: participant }) => participantMatchesSearch(participant, searchString)
|
||||
&& <BreakoutRoomParticipantItem item = { participant } /> }
|
||||
showsHorizontalScrollIndicator = { false }
|
||||
windowSize = { 2 } />}
|
||||
</View>
|
||||
|
||||
@@ -10,6 +10,7 @@ import { ListItem } from '../../../base/components';
|
||||
import { Icon, IconArrowDown, IconArrowUp } from '../../../base/icons';
|
||||
import ParticipantItem from '../../../participants-pane/components/web/ParticipantItem';
|
||||
import { ACTION_TRIGGER } from '../../../participants-pane/constants';
|
||||
import { participantMatchesSearch } from '../../../participants-pane/functions';
|
||||
|
||||
type Props = {
|
||||
|
||||
@@ -42,6 +43,11 @@ type Props = {
|
||||
* Room reference.
|
||||
*/
|
||||
room: Object,
|
||||
|
||||
/**
|
||||
* Participants search string.
|
||||
*/
|
||||
searchString: string
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(theme => {
|
||||
@@ -78,7 +84,8 @@ export const CollapsibleRoom = ({
|
||||
isHighlighted,
|
||||
onRaiseMenu,
|
||||
onLeave,
|
||||
room
|
||||
room,
|
||||
searchString
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const styles = useStyles();
|
||||
@@ -116,13 +123,14 @@ export const CollapsibleRoom = ({
|
||||
textChildren = { roomName }
|
||||
trigger = { actionsTrigger } />
|
||||
{!collapsed && room?.participants
|
||||
&& Object.values(room?.participants || {}).map((p: Object) => (
|
||||
<ParticipantItem
|
||||
displayName = { p.displayName || defaultRemoteDisplayName }
|
||||
key = { p.jid }
|
||||
local = { false }
|
||||
participantID = { p.jid } />
|
||||
))
|
||||
&& Object.values(room?.participants || {}).map((p: Object) =>
|
||||
participantMatchesSearch(p, searchString) && (
|
||||
<ParticipantItem
|
||||
displayName = { p.displayName || defaultRemoteDisplayName }
|
||||
key = { p.jid }
|
||||
local = { false }
|
||||
participantID = { p.jid } />
|
||||
))
|
||||
}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -16,7 +16,15 @@ import { LeaveButton } from './LeaveButton';
|
||||
import RoomActionEllipsis from './RoomActionEllipsis';
|
||||
import { RoomContextMenu } from './RoomContextMenu';
|
||||
|
||||
export const RoomList = () => {
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Participants search string.
|
||||
*/
|
||||
searchString: string
|
||||
}
|
||||
|
||||
export const RoomList = ({ searchString }: Props) => {
|
||||
const currentRoomId = useSelector(getCurrentRoomId);
|
||||
const rooms = Object.values(useSelector(getBreakoutRooms, equals))
|
||||
.filter((room: Object) => room.id !== currentRoomId)
|
||||
@@ -44,7 +52,8 @@ export const RoomList = () => {
|
||||
isHighlighted = { raiseContext.entity === room }
|
||||
onLeave = { lowerMenu }
|
||||
onRaiseMenu = { onRaiseMenu(room) }
|
||||
room = { room }>
|
||||
room = { room }
|
||||
searchString = { searchString }>
|
||||
{!_overflowDrawer && <>
|
||||
<JoinActionButton room = { room } />
|
||||
{isLocalModerator && !room.isMainRoom
|
||||
|
||||
@@ -4,19 +4,3 @@
|
||||
* Key for this feature.
|
||||
*/
|
||||
export const FEATURE_KEY = 'features/breakout-rooms';
|
||||
|
||||
/**
|
||||
* The type of json-message which indicates that json carries
|
||||
* a request for a participant to move to a specified room.
|
||||
*/
|
||||
export const JSON_TYPE_MOVE_TO_ROOM_REQUEST = `${FEATURE_KEY}/move-to-room`;
|
||||
|
||||
/**
|
||||
* The type of json-message which indicates that json carries a request to remove a specified breakout room.
|
||||
*/
|
||||
export const JSON_TYPE_REMOVE_BREAKOUT_ROOM = `${FEATURE_KEY}/remove`;
|
||||
|
||||
/**
|
||||
* The type of json-message which indicates that json carries breakout rooms data.
|
||||
*/
|
||||
export const JSON_TYPE_UPDATE_BREAKOUT_ROOMS = `${FEATURE_KEY}/update`;
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
// @flow
|
||||
|
||||
import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
|
||||
import { StateListenerRegistry } from '../base/redux';
|
||||
import { getParticipantById } from '../base/participants';
|
||||
import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
|
||||
import { editMessage, MESSAGE_TYPE_REMOTE } from '../chat';
|
||||
|
||||
import { UPDATE_BREAKOUT_ROOMS } from './actionTypes';
|
||||
import { moveToRoom } from './actions';
|
||||
import { getBreakoutRooms } from './functions';
|
||||
import logger from './logger';
|
||||
|
||||
/**
|
||||
@@ -30,3 +33,37 @@ StateListenerRegistry.register(
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||
const { type } = action;
|
||||
const result = next(action);
|
||||
|
||||
switch (type) {
|
||||
case UPDATE_BREAKOUT_ROOMS: {
|
||||
const { messages } = getState()['features/chat'];
|
||||
|
||||
// edit the chat history to match names for participants in breakout rooms
|
||||
messages && messages.forEach(m => {
|
||||
if (m.messageType === MESSAGE_TYPE_REMOTE && !getParticipantById(getState(), m.id)) {
|
||||
const rooms = getBreakoutRooms(getState);
|
||||
|
||||
for (const room of Object.values(rooms)) {
|
||||
// $FlowExpectedError
|
||||
const participants = room.participants || {};
|
||||
const matchedJid = Object.keys(participants).find(jid => jid.endsWith(m.id));
|
||||
|
||||
if (matchedJid) {
|
||||
m.displayName = participants[matchedJid].displayName;
|
||||
|
||||
dispatch(editMessage(m));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
@@ -33,6 +33,16 @@ export const CLEAR_MESSAGES = 'CLEAR_MESSAGES';
|
||||
*/
|
||||
export const CLOSE_CHAT = 'CLOSE_CHAT';
|
||||
|
||||
/**
|
||||
* The type of the action which signals to edit chat message.
|
||||
*
|
||||
* {
|
||||
* type: EDIT_MESSAGE,
|
||||
* message: Object
|
||||
* }
|
||||
*/
|
||||
export const EDIT_MESSAGE = 'EDIT_MESSAGE';
|
||||
|
||||
/**
|
||||
* The type of the action which signals to display the chat panel.
|
||||
*
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
ADD_MESSAGE,
|
||||
CLEAR_MESSAGES,
|
||||
CLOSE_CHAT,
|
||||
EDIT_MESSAGE,
|
||||
SEND_MESSAGE,
|
||||
SET_PRIVATE_MESSAGE_RECIPIENT,
|
||||
SET_IS_POLL_TAB_FOCUSED
|
||||
@@ -41,6 +42,23 @@ export function addMessage(messageDetails: Object) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits an existing chat message.
|
||||
*
|
||||
* @param {Object} message - The chat message to edit/override. The messages will be matched from the state
|
||||
* comparing the messageId.
|
||||
* @returns {{
|
||||
* type: EDIT_MESSAGE,
|
||||
* message: Object
|
||||
* }}
|
||||
*/
|
||||
export function editMessage(message: Object) {
|
||||
return {
|
||||
type: EDIT_MESSAGE,
|
||||
message
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the chat messages in Redux.
|
||||
*
|
||||
|
||||
@@ -49,22 +49,6 @@ class ChatButton extends AbstractButton<Props, *> {
|
||||
// Unused.
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button, and opens the appropriate dialog.
|
||||
*
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
const { handleClick } = this.props;
|
||||
|
||||
if (handleClick) {
|
||||
handleClick();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this button is in toggled state or not.
|
||||
*
|
||||
|
||||
@@ -314,12 +314,16 @@ function _handleReceivedMessage({ dispatch, getState },
|
||||
// Provide a default for for the case when a message is being
|
||||
// backfilled for a participant that has left the conference.
|
||||
const participant = getParticipantById(state, id) || {};
|
||||
|
||||
const localParticipant = getLocalParticipant(getState);
|
||||
const displayName = getParticipantDisplayName(state, id);
|
||||
const hasRead = participant.local || isChatOpen;
|
||||
const timestampToDate = timestamp ? new Date(timestamp) : new Date();
|
||||
const millisecondsTimestamp = timestampToDate.getTime();
|
||||
const shouldShowNotification = userSelectedNotifications['notify.chatMessages'] && !hasRead && !isReaction;
|
||||
|
||||
// skip message notifications on join (the messages having timestamp - coming from the history)
|
||||
const shouldShowNotification = userSelectedNotifications['notify.chatMessages']
|
||||
&& !hasRead && !isReaction && !timestamp;
|
||||
|
||||
dispatch(addMessage({
|
||||
displayName,
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
// @flow
|
||||
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { ReducerRegistry } from '../base/redux';
|
||||
|
||||
import {
|
||||
ADD_MESSAGE,
|
||||
CLEAR_MESSAGES,
|
||||
CLOSE_CHAT,
|
||||
EDIT_MESSAGE,
|
||||
OPEN_CHAT,
|
||||
SET_PRIVATE_MESSAGE_RECIPIENT,
|
||||
SET_IS_POLL_TAB_FOCUSED
|
||||
@@ -29,6 +32,7 @@ ReducerRegistry.register('features/chat', (state = DEFAULT_STATE, action) => {
|
||||
error: action.error,
|
||||
id: action.id,
|
||||
isReaction: action.isReaction,
|
||||
messageId: uuidv4(),
|
||||
messageType: action.messageType,
|
||||
message: action.message,
|
||||
privateMessage: action.privateMessage,
|
||||
@@ -63,6 +67,30 @@ ReducerRegistry.register('features/chat', (state = DEFAULT_STATE, action) => {
|
||||
messages: []
|
||||
};
|
||||
|
||||
case EDIT_MESSAGE: {
|
||||
let found = false;
|
||||
const newMessage = action.message;
|
||||
const messages = state.messages.map(m => {
|
||||
if (m.messageId === newMessage.messageId) {
|
||||
found = true;
|
||||
|
||||
return newMessage;
|
||||
}
|
||||
|
||||
return m;
|
||||
});
|
||||
|
||||
// no change
|
||||
if (!found) {
|
||||
return state;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
messages
|
||||
};
|
||||
}
|
||||
|
||||
case SET_PRIVATE_MESSAGE_RECIPIENT:
|
||||
return {
|
||||
...state,
|
||||
|
||||
@@ -91,6 +91,11 @@ type Props = AbstractProps & {
|
||||
*/
|
||||
_onSaveLogs: Function,
|
||||
|
||||
/**
|
||||
* The region reported by the participant.
|
||||
*/
|
||||
_region: String,
|
||||
|
||||
/**
|
||||
* The video SSRC of this client.
|
||||
*/
|
||||
@@ -171,7 +176,6 @@ class ConnectionIndicatorContent extends AbstractConnectionIndicator<Props, Stat
|
||||
framerate,
|
||||
maxEnabledResolution,
|
||||
packetLoss,
|
||||
region,
|
||||
resolution,
|
||||
serverRegion,
|
||||
transport
|
||||
@@ -195,7 +199,7 @@ class ConnectionIndicatorContent extends AbstractConnectionIndicator<Props, Stat
|
||||
onShowMore = { this._onToggleShowMore }
|
||||
packetLoss = { packetLoss }
|
||||
participantId = { this.props.participantId }
|
||||
region = { region }
|
||||
region = { this.props._region }
|
||||
resolution = { resolution }
|
||||
serverRegion = { serverRegion }
|
||||
shouldShowMore = { this.state.showMoreStats }
|
||||
@@ -310,7 +314,8 @@ export function _mapStateToProps(state: Object, ownProps: Props) {
|
||||
_connectionStatus: participant?.connectionStatus,
|
||||
_enableSaveLogs: state['features/base/config'].enableSaveLogs,
|
||||
_disableShowMoreStats: state['features/base/config'].disableShowMoreStats,
|
||||
_isLocalVideo: participant?.local
|
||||
_isLocalVideo: participant?.local,
|
||||
_region: participant?.region
|
||||
};
|
||||
|
||||
if (conference) {
|
||||
|
||||
@@ -36,13 +36,7 @@ class EmbedMeetingButton extends AbstractButton<Props, *> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
const { dispatch, handleClick } = this.props;
|
||||
|
||||
if (handleClick) {
|
||||
handleClick();
|
||||
|
||||
return;
|
||||
}
|
||||
const { dispatch } = this.props;
|
||||
|
||||
sendAnalytics(createToolbarEvent('embed.meeting'));
|
||||
dispatch(openDialog(EmbedMeetingDialog));
|
||||
|
||||
@@ -9,7 +9,6 @@ import { connect } from '../../base/redux';
|
||||
import { AbstractButton, type AbstractButtonProps } from '../../base/toolbox/components';
|
||||
import { toggleDocument } from '../../etherpad/actions';
|
||||
|
||||
|
||||
type Props = AbstractButtonProps & {
|
||||
|
||||
/**
|
||||
@@ -59,13 +58,7 @@ class SharedDocumentButton extends AbstractButton<Props, *> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
const { _editing, dispatch, handleClick } = this.props;
|
||||
|
||||
if (handleClick) {
|
||||
handleClick();
|
||||
|
||||
return;
|
||||
}
|
||||
const { _editing, dispatch } = this.props;
|
||||
|
||||
sendAnalytics(createToolbarEvent(
|
||||
'toggle.etherpad',
|
||||
|
||||
@@ -39,3 +39,21 @@ export const START_FACIAL_RECOGNITION = 'START_FACIAL_RECOGNITION';
|
||||
* }
|
||||
*/
|
||||
export const STOP_FACIAL_RECOGNITION = 'STOP_FACIAL_RECOGNITION';
|
||||
|
||||
/**
|
||||
* Redux action type dispatched in order to clear the facial expressions buffer in the state.
|
||||
*
|
||||
* {
|
||||
* type: CLEAR_FACIAL_EXPRESSIONS_BUFFER
|
||||
* }
|
||||
*/
|
||||
export const CLEAR_FACIAL_EXPRESSIONS_BUFFER = 'CLEAR_FACIAL_EXPRESSIONS_BUFFER';
|
||||
|
||||
/**
|
||||
* Redux action type dispatched in order to add a expression to the facial expressions buffer.
|
||||
*
|
||||
* {
|
||||
* type: ADD_TO_FACIAL_EXPRESSIONS_BUFFER
|
||||
* }
|
||||
*/
|
||||
export const ADD_TO_FACIAL_EXPRESSIONS_BUFFER = 'ADD_TO_FACIAL_EXPRESSIONS_BUFFER ';
|
||||
|
||||
@@ -6,23 +6,20 @@ import './createImageBitmap';
|
||||
|
||||
import {
|
||||
ADD_FACIAL_EXPRESSION,
|
||||
ADD_TO_FACIAL_EXPRESSIONS_BUFFER,
|
||||
CLEAR_FACIAL_EXPRESSIONS_BUFFER,
|
||||
SET_DETECTION_TIME_INTERVAL,
|
||||
START_FACIAL_RECOGNITION,
|
||||
STOP_FACIAL_RECOGNITION
|
||||
} from './actionTypes';
|
||||
import { sendDataToWorker } from './functions';
|
||||
import {
|
||||
CPU_TIME_INTERVAL,
|
||||
WEBGL_TIME_INTERVAL,
|
||||
WEBHOOK_SEND_TIME_INTERVAL
|
||||
} from './constants';
|
||||
import { sendDataToWorker, sendFacialExpressionsWebhook } from './functions';
|
||||
import logger from './logger';
|
||||
|
||||
/**
|
||||
* Time used for detection interval when facial expressions worker uses webgl backend.
|
||||
*/
|
||||
const WEBGL_TIME_INTERVAL = 1000;
|
||||
|
||||
/**
|
||||
* Time used for detection interval when facial expression worker uses cpu backend.
|
||||
*/
|
||||
const CPU_TIME_INTERVAL = 6000;
|
||||
|
||||
/**
|
||||
* Object containing a image capture of the local track.
|
||||
*/
|
||||
@@ -38,12 +35,22 @@ let worker;
|
||||
*/
|
||||
let lastFacialExpression;
|
||||
|
||||
/**
|
||||
* The last facial expression timestamp.
|
||||
*/
|
||||
let lastFacialExpressionTimestamp;
|
||||
|
||||
/**
|
||||
* How many duplicate consecutive expression occurred.
|
||||
* If a expression that is not the same as the last one it is reset to 0.
|
||||
*/
|
||||
let duplicateConsecutiveExpressions = 0;
|
||||
|
||||
/**
|
||||
* Variable that keeps the interval for sending expressions to webhook.
|
||||
*/
|
||||
let sendInterval;
|
||||
|
||||
/**
|
||||
* Loads the worker that predicts the facial expression.
|
||||
*
|
||||
@@ -95,9 +102,17 @@ export function loadWorker() {
|
||||
if (value === lastFacialExpression) {
|
||||
duplicateConsecutiveExpressions++;
|
||||
} else {
|
||||
lastFacialExpression
|
||||
&& dispatch(addFacialExpression(lastFacialExpression, duplicateConsecutiveExpressions + 1));
|
||||
if (lastFacialExpression && lastFacialExpressionTimestamp) {
|
||||
dispatch(
|
||||
addFacialExpression(
|
||||
lastFacialExpression,
|
||||
duplicateConsecutiveExpressions + 1,
|
||||
lastFacialExpressionTimestamp
|
||||
)
|
||||
);
|
||||
}
|
||||
lastFacialExpression = value;
|
||||
lastFacialExpressionTimestamp = Date.now();
|
||||
duplicateConsecutiveExpressions = 0;
|
||||
}
|
||||
}
|
||||
@@ -143,9 +158,15 @@ export function startFacialRecognition() {
|
||||
|
||||
// $FlowFixMe
|
||||
imageCapture = new ImageCapture(firstVideoTrack);
|
||||
|
||||
sendDataToWorker(worker, imageCapture);
|
||||
sendInterval = setInterval(async () => {
|
||||
const result = await sendFacialExpressionsWebhook(getState());
|
||||
|
||||
if (result) {
|
||||
dispatch(clearFacialExpressionBuffer());
|
||||
}
|
||||
}
|
||||
, WEBHOOK_SEND_TIME_INTERVAL);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -169,9 +190,21 @@ export function stopFacialRecognition() {
|
||||
id: 'CLEAR_TIMEOUT'
|
||||
});
|
||||
|
||||
lastFacialExpression
|
||||
&& dispatch(addFacialExpression(lastFacialExpression, duplicateConsecutiveExpressions + 1));
|
||||
if (lastFacialExpression && lastFacialExpressionTimestamp) {
|
||||
dispatch(
|
||||
addFacialExpression(
|
||||
lastFacialExpression,
|
||||
duplicateConsecutiveExpressions + 1,
|
||||
lastFacialExpressionTimestamp
|
||||
)
|
||||
);
|
||||
}
|
||||
duplicateConsecutiveExpressions = 0;
|
||||
|
||||
if (sendInterval) {
|
||||
clearInterval(sendInterval);
|
||||
sendInterval = null;
|
||||
}
|
||||
dispatch({ type: STOP_FACIAL_RECOGNITION });
|
||||
logger.log('Stop face recognition');
|
||||
};
|
||||
@@ -215,9 +248,10 @@ export function changeTrack(track: Object) {
|
||||
*
|
||||
* @param {string} facialExpression - Facial expression to be added.
|
||||
* @param {number} duration - Duration in seconds of the facial expression.
|
||||
* @param {number} timestamp - Duration in seconds of the facial expression.
|
||||
* @returns {Object}
|
||||
*/
|
||||
function addFacialExpression(facialExpression: string, duration: number) {
|
||||
function addFacialExpression(facialExpression: string, duration: number, timestamp: number) {
|
||||
return function(dispatch: Function, getState: Function) {
|
||||
const { detectionTimeInterval } = getState()['features/facial-recognition'];
|
||||
let finalDuration = duration;
|
||||
@@ -228,7 +262,8 @@ function addFacialExpression(facialExpression: string, duration: number) {
|
||||
dispatch({
|
||||
type: ADD_FACIAL_EXPRESSION,
|
||||
facialExpression,
|
||||
duration: finalDuration
|
||||
duration: finalDuration,
|
||||
timestamp
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -245,3 +280,27 @@ function setDetectionTimeInterval(time: number) {
|
||||
time
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a facial expression with its timestamp to the facial expression buffer.
|
||||
*
|
||||
* @param {Object} facialExpression - Object containing facial expression string and its timestamp.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function addToFacialExpressionsBuffer(facialExpression: Object) {
|
||||
return {
|
||||
type: ADD_TO_FACIAL_EXPRESSIONS_BUFFER,
|
||||
facialExpression
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the facial expressions array in the state.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
function clearFacialExpressionBuffer() {
|
||||
return {
|
||||
type: CLEAR_FACIAL_EXPRESSIONS_BUFFER
|
||||
};
|
||||
}
|
||||
|
||||
@@ -9,3 +9,18 @@ export const FACIAL_EXPRESSION_EMOJIS = {
|
||||
fearful: '😨',
|
||||
disgusted: '🤢'
|
||||
};
|
||||
|
||||
/**
|
||||
* Time used for detection interval when facial expressions worker uses webgl backend.
|
||||
*/
|
||||
export const WEBGL_TIME_INTERVAL = 1000;
|
||||
|
||||
/**
|
||||
* Time used for detection interval when facial expression worker uses cpu backend.
|
||||
*/
|
||||
export const CPU_TIME_INTERVAL = 6000;
|
||||
|
||||
/**
|
||||
* Time is ms used for sending expression.
|
||||
*/
|
||||
export const WEBHOOK_SEND_TIME_INTERVAL = 15000;
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
// @flow
|
||||
import { getLocalParticipant } from '../base/participants';
|
||||
import { extractFqnFromPath } from '../dynamic-branding';
|
||||
|
||||
import logger from './logger';
|
||||
|
||||
/**
|
||||
@@ -49,6 +52,60 @@ export function sendFacialExpressionToServer(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends facial expression to backend.
|
||||
*
|
||||
* @param {Object} state - Redux state.
|
||||
* @returns {boolean} - True if sent, false otherwise.
|
||||
*/
|
||||
export async function sendFacialExpressionsWebhook(state: Object) {
|
||||
const { webhookProxyUrl: url } = state['features/base/config'];
|
||||
const { conference } = state['features/base/conference'];
|
||||
const { jwt } = state['features/base/jwt'];
|
||||
const { connection } = state['features/base/connection'];
|
||||
const jid = connection.getJid();
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
const { facialExpressionsBuffer } = state['features/facial-recognition'];
|
||||
|
||||
if (facialExpressionsBuffer.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const headers = {
|
||||
...jwt ? { 'Authorization': `Bearer ${jwt}` } : {},
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
|
||||
const reqBody = {
|
||||
meetingFqn: extractFqnFromPath(),
|
||||
sessionId: conference.sessionId,
|
||||
submitted: Date.now(),
|
||||
emotions: facialExpressionsBuffer,
|
||||
participantId: localParticipant.jwtId,
|
||||
participantName: localParticipant.name,
|
||||
participantJid: jid
|
||||
};
|
||||
|
||||
if (url) {
|
||||
try {
|
||||
const res = await fetch(`${url}/emotions`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify(reqBody)
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
return true;
|
||||
}
|
||||
logger.error('Status error:', res.status);
|
||||
} catch (err) {
|
||||
logger.error('Could not send request', err);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the image data a canvas from the track in the image capture to the facial expression worker.
|
||||
*
|
||||
|
||||
@@ -12,6 +12,7 @@ import { VIRTUAL_BACKGROUND_TRACK_CHANGED } from '../virtual-background/actionTy
|
||||
|
||||
import { ADD_FACIAL_EXPRESSION } from './actionTypes';
|
||||
import {
|
||||
addToFacialExpressionsBuffer,
|
||||
changeTrack,
|
||||
loadWorker,
|
||||
resetTrack,
|
||||
@@ -96,6 +97,10 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||
sendFacialExpressionToParticipants(conference, action.facialExpression, action.duration);
|
||||
}
|
||||
sendFacialExpressionToServer(conference, action.facialExpression, action.duration);
|
||||
dispatch(addToFacialExpressionsBuffer({
|
||||
emotion: action.facialExpression,
|
||||
timestamp: action.timestamp
|
||||
}));
|
||||
|
||||
return next(action);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import { ReducerRegistry } from '../base/redux';
|
||||
|
||||
import {
|
||||
ADD_FACIAL_EXPRESSION,
|
||||
ADD_TO_FACIAL_EXPRESSIONS_BUFFER,
|
||||
CLEAR_FACIAL_EXPRESSIONS_BUFFER,
|
||||
SET_DETECTION_TIME_INTERVAL,
|
||||
START_FACIAL_RECOGNITION,
|
||||
STOP_FACIAL_RECOGNITION
|
||||
@@ -19,6 +21,7 @@ const defaultState = {
|
||||
disgusted: 0,
|
||||
sad: 0
|
||||
},
|
||||
facialExpressionsBuffer: [],
|
||||
detectionTimeInterval: -1,
|
||||
recognitionActive: false
|
||||
};
|
||||
@@ -30,6 +33,18 @@ ReducerRegistry.register('features/facial-recognition', (state = defaultState, a
|
||||
|
||||
return state;
|
||||
}
|
||||
case ADD_TO_FACIAL_EXPRESSIONS_BUFFER: {
|
||||
return {
|
||||
...state,
|
||||
facialExpressionsBuffer: [ ...state.facialExpressionsBuffer, action.facialExpression ]
|
||||
};
|
||||
}
|
||||
case CLEAR_FACIAL_EXPRESSIONS_BUFFER: {
|
||||
return {
|
||||
...state,
|
||||
facialExpressionsBuffer: []
|
||||
};
|
||||
}
|
||||
case SET_DETECTION_TIME_INTERVAL: {
|
||||
return {
|
||||
...state,
|
||||
|
||||
@@ -40,13 +40,7 @@ class FeedbackButton extends AbstractButton<Props, *> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
const { _conference, dispatch, handleClick } = this.props;
|
||||
|
||||
if (handleClick) {
|
||||
handleClick();
|
||||
|
||||
return;
|
||||
}
|
||||
const { _conference, dispatch } = this.props;
|
||||
|
||||
sendAnalytics(createToolbarEvent('feedback'));
|
||||
dispatch(openFeedbackDialog(_conference));
|
||||
|
||||
@@ -34,13 +34,7 @@ class InviteButton extends AbstractButton<Props, *> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
const { dispatch, handleClick } = this.props;
|
||||
|
||||
if (handleClick) {
|
||||
handleClick();
|
||||
|
||||
return;
|
||||
}
|
||||
const { dispatch } = this.props;
|
||||
|
||||
sendAnalytics(createToolbarEvent('invite'));
|
||||
dispatch(beginAddPeople());
|
||||
|
||||
@@ -34,13 +34,7 @@ class KeyboardShortcutsButton extends AbstractButton<Props, *> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
const { dispatch, handleClick } = this.props;
|
||||
|
||||
if (handleClick) {
|
||||
handleClick();
|
||||
|
||||
return;
|
||||
}
|
||||
const { dispatch } = this.props;
|
||||
|
||||
sendAnalytics(createToolbarEvent('shortcuts'));
|
||||
dispatch(openKeyboardShortcutsDialog());
|
||||
|
||||
@@ -36,13 +36,7 @@ class LocalRecording extends AbstractButton<Props, *> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
const { dispatch, handleClick } = this.props;
|
||||
|
||||
if (handleClick) {
|
||||
handleClick();
|
||||
|
||||
return;
|
||||
}
|
||||
const { dispatch } = this.props;
|
||||
|
||||
sendAnalytics(createToolbarEvent('local.recording'));
|
||||
dispatch(openDialog(LocalRecordingInfoDialog));
|
||||
|
||||
@@ -8,10 +8,9 @@ import { translate } from '../../../base/i18n';
|
||||
import { Icon, IconInviteMore } from '../../../base/icons';
|
||||
import { getLocalParticipant, getParticipantCountWithFake, getRemoteParticipants } from '../../../base/participants';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { normalizeAccents } from '../../../base/util/strings';
|
||||
import { getBreakoutRooms, getCurrentRoomId } from '../../../breakout-rooms/functions';
|
||||
import { doInvitePeople } from '../../../invite/actions.native';
|
||||
import { shouldRenderInviteButton } from '../../functions';
|
||||
import { participantMatchesSearch, shouldRenderInviteButton } from '../../functions';
|
||||
|
||||
import ClearableInput from './ClearableInput';
|
||||
import MeetingParticipantItem from './MeetingParticipantItem';
|
||||
@@ -55,6 +54,16 @@ type Props = {
|
||||
*/
|
||||
dispatch: Function,
|
||||
|
||||
/**
|
||||
* Participants search string.
|
||||
*/
|
||||
searchString: string,
|
||||
|
||||
/**
|
||||
* Function to update the search string.
|
||||
*/
|
||||
setSearchString: Function,
|
||||
|
||||
/**
|
||||
* Translation function.
|
||||
*/
|
||||
@@ -66,14 +75,10 @@ type Props = {
|
||||
theme: Object
|
||||
}
|
||||
|
||||
type State = {
|
||||
searchString: string
|
||||
};
|
||||
|
||||
/**
|
||||
* The meeting participant list component.
|
||||
*/
|
||||
class MeetingParticipantList extends PureComponent<Props, State> {
|
||||
class MeetingParticipantList extends PureComponent<Props> {
|
||||
|
||||
/**
|
||||
* Creates new MeetingParticipantList instance.
|
||||
@@ -83,10 +88,6 @@ class MeetingParticipantList extends PureComponent<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
searchString: ''
|
||||
};
|
||||
|
||||
this._keyExtractor = this._keyExtractor.bind(this);
|
||||
this._onInvite = this._onInvite.bind(this);
|
||||
this._renderParticipant = this._renderParticipant.bind(this);
|
||||
@@ -139,27 +140,10 @@ class MeetingParticipantList extends PureComponent<Props, State> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderParticipant({ item/* , index, separators */ }) {
|
||||
const { _localParticipant, _remoteParticipants } = this.props;
|
||||
const { searchString } = this.state;
|
||||
const { _localParticipant, _remoteParticipants, searchString } = this.props;
|
||||
const participant = item === _localParticipant?.id ? _localParticipant : _remoteParticipants.get(item);
|
||||
const displayName = participant?.name || '';
|
||||
|
||||
if (displayName) {
|
||||
const names = normalizeAccents(displayName)
|
||||
.toLowerCase()
|
||||
.split(' ');
|
||||
const lowerCaseSearch = normalizeAccents(searchString).toLowerCase();
|
||||
|
||||
for (const name of names) {
|
||||
if (lowerCaseSearch === '' || name.startsWith(lowerCaseSearch)) {
|
||||
return (
|
||||
<MeetingParticipantItem
|
||||
key = { item }
|
||||
participant = { participant } />
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if (displayName === '' && searchString === '') {
|
||||
if (participantMatchesSearch(participant, searchString)) {
|
||||
return (
|
||||
<MeetingParticipantItem
|
||||
key = { item }
|
||||
@@ -179,9 +163,7 @@ class MeetingParticipantList extends PureComponent<Props, State> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_onSearchStringChange(text: string) {
|
||||
this.setState({
|
||||
searchString: text
|
||||
});
|
||||
this.props.setSearchString(text);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ScrollView, View } from 'react-native';
|
||||
import { Button } from 'react-native-paper';
|
||||
@@ -36,6 +36,7 @@ import styles from './styles';
|
||||
*/
|
||||
const ParticipantsPane = () => {
|
||||
const dispatch = useDispatch();
|
||||
const [ searchString, setSearchString ] = useState('');
|
||||
const openMoreMenu = useCallback(() => dispatch(openDialog(ContextMenuMore)), [ dispatch ]);
|
||||
const isLocalModerator = useSelector(isLocalParticipantModerator);
|
||||
const muteAll = useCallback(() => dispatch(openDialog(MuteEveryoneDialog)),
|
||||
@@ -59,7 +60,9 @@ const ParticipantsPane = () => {
|
||||
style = { styles.participantsPane }>
|
||||
<ScrollView bounces = { false }>
|
||||
<LobbyParticipantList />
|
||||
<MeetingParticipantList />
|
||||
<MeetingParticipantList
|
||||
searchString = { searchString }
|
||||
setSearchString = { setSearchString } />
|
||||
{!inBreakoutRoom
|
||||
&& isLocalModerator
|
||||
&& participantsCount > 2
|
||||
@@ -69,7 +72,8 @@ const ParticipantsPane = () => {
|
||||
{_isBreakoutRoomsSupported
|
||||
&& rooms.map(room => (<CollapsibleRoom
|
||||
key = { room.id }
|
||||
room = { room } />))}
|
||||
room = { room }
|
||||
searchString = { searchString } />))}
|
||||
{_isBreakoutRoomsSupported && !hideAddRoomButton && isLocalModerator
|
||||
&& <AddBreakoutRoomButton />}
|
||||
</ScrollView>
|
||||
|
||||
@@ -19,12 +19,12 @@ import {
|
||||
isParticipantAudioMuted,
|
||||
isParticipantVideoMuted
|
||||
} from '../../../base/tracks';
|
||||
import { normalizeAccents } from '../../../base/util/strings';
|
||||
import { ACTION_TRIGGER, type MediaState, MEDIA_STATE } from '../../constants';
|
||||
import {
|
||||
getParticipantAudioMediaState,
|
||||
getParticipantVideoMediaState,
|
||||
getQuickActionButtonType
|
||||
getQuickActionButtonType,
|
||||
participantMatchesSearch
|
||||
} from '../../functions';
|
||||
|
||||
import ParticipantActionEllipsis from './ParticipantActionEllipsis';
|
||||
@@ -300,22 +300,7 @@ function _mapStateToProps(state, ownProps): Object {
|
||||
|
||||
const _displayName = getParticipantDisplayName(state, participant?.id);
|
||||
|
||||
let _matchesSearch = false;
|
||||
const names = normalizeAccents(_displayName)
|
||||
.toLowerCase()
|
||||
.split(' ');
|
||||
const lowerCaseSearchString = searchString.toLowerCase();
|
||||
|
||||
if (lowerCaseSearchString === '') {
|
||||
_matchesSearch = true;
|
||||
} else {
|
||||
for (const name of names) {
|
||||
if (name.startsWith(lowerCaseSearchString)) {
|
||||
_matchesSearch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
const _matchesSearch = participantMatchesSearch(participant, searchString);
|
||||
|
||||
const _isAudioMuted = isParticipantAudioMuted(participant, state);
|
||||
const _isVideoMuted = isParticipantVideoMuted(participant, state);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import { makeStyles } from '@material-ui/styles';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
@@ -45,8 +45,10 @@ const useStyles = makeStyles(theme => {
|
||||
type Props = {
|
||||
currentRoom: ?Object,
|
||||
participantsCount: number,
|
||||
showInviteButton: boolean,
|
||||
overflowDrawer: boolean,
|
||||
searchString: string,
|
||||
setSearchString: Function,
|
||||
showInviteButton: boolean,
|
||||
sortedParticipantIds: Array<string>
|
||||
};
|
||||
|
||||
@@ -64,11 +66,12 @@ function MeetingParticipants({
|
||||
currentRoom,
|
||||
overflowDrawer,
|
||||
participantsCount,
|
||||
searchString,
|
||||
setSearchString,
|
||||
showInviteButton,
|
||||
sortedParticipantIds = []
|
||||
}: Props) {
|
||||
const dispatch = useDispatch();
|
||||
const [ searchString, setSearchString ] = useState('');
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [ lowerMenu, , toggleMenu, menuEnter, menuLeave, raiseContext ] = useContextMenu();
|
||||
@@ -104,7 +107,8 @@ function MeetingParticipants({
|
||||
{showInviteButton && <InviteButton />}
|
||||
<ClearableInput
|
||||
onChange = { setSearchString }
|
||||
placeholder = { t('participantsPane.search') } />
|
||||
placeholder = { t('participantsPane.search') }
|
||||
value = { searchString } />
|
||||
<div>
|
||||
<MeetingParticipantItems
|
||||
askUnmuteText = { askUnmuteText }
|
||||
|
||||
@@ -75,6 +75,11 @@ type State = {
|
||||
* Indicates if the footer context menu is open.
|
||||
*/
|
||||
contextOpen: boolean,
|
||||
|
||||
/**
|
||||
* Participants search string.
|
||||
*/
|
||||
searchString: string
|
||||
};
|
||||
|
||||
const styles = theme => {
|
||||
@@ -152,7 +157,8 @@ class ParticipantsPane extends Component<Props, State> {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
contextOpen: false
|
||||
contextOpen: false,
|
||||
searchString: ''
|
||||
};
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
@@ -162,6 +168,7 @@ class ParticipantsPane extends Component<Props, State> {
|
||||
this._onMuteAll = this._onMuteAll.bind(this);
|
||||
this._onToggleContext = this._onToggleContext.bind(this);
|
||||
this._onWindowClickListener = this._onWindowClickListener.bind(this);
|
||||
this.setSearchString = this.setSearchString.bind(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -197,7 +204,7 @@ class ParticipantsPane extends Component<Props, State> {
|
||||
classes,
|
||||
t
|
||||
} = this.props;
|
||||
const { contextOpen } = this.state;
|
||||
const { contextOpen, searchString } = this.state;
|
||||
|
||||
// when the pane is not open optimize to not
|
||||
// execute the MeetingParticipantList render for large list of participants
|
||||
@@ -224,8 +231,10 @@ class ParticipantsPane extends Component<Props, State> {
|
||||
<div className = { classes.container }>
|
||||
<LobbyParticipants />
|
||||
<br className = { classes.antiCollapse } />
|
||||
<MeetingParticipants />
|
||||
{_isBreakoutRoomsSupported && <RoomList />}
|
||||
<MeetingParticipants
|
||||
searchString = { searchString }
|
||||
setSearchString = { this.setSearchString } />
|
||||
{_isBreakoutRoomsSupported && <RoomList searchString = { searchString } />}
|
||||
{_showAddRoomButton && <AddBreakoutRoomButton />}
|
||||
</div>
|
||||
{_showFooter && (
|
||||
@@ -255,6 +264,20 @@ class ParticipantsPane extends Component<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
setSearchString: (string) => void;
|
||||
|
||||
/**
|
||||
* Sets the search string.
|
||||
*
|
||||
* @param {string} newSearchString - The new search string.
|
||||
* @returns {void}
|
||||
*/
|
||||
setSearchString(newSearchString) {
|
||||
this.setState({
|
||||
searchString: newSearchString
|
||||
});
|
||||
}
|
||||
|
||||
_onClosePane: () => void;
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,22 +25,6 @@ class ParticipantsPaneButton extends AbstractButton<Props, *> {
|
||||
label = 'toolbar.participants';
|
||||
tooltip = 'toolbar.participants';
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button, and opens the appropriate dialog.
|
||||
*
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
const { handleClick } = this.props;
|
||||
|
||||
if (handleClick) {
|
||||
handleClick();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this button is in toggled state or not.
|
||||
*
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
getRaiseHandsQueue
|
||||
} from '../base/participants/functions';
|
||||
import { toState } from '../base/redux';
|
||||
import { normalizeAccents } from '../base/util/strings';
|
||||
import { isInBreakoutRoom } from '../breakout-rooms/functions';
|
||||
|
||||
import { QUICK_ACTION_BUTTON, REDUCER_KEY, MEDIA_STATE } from './constants';
|
||||
@@ -242,3 +243,28 @@ export function getSortedParticipantIds(stateful: Object | Function): Array<stri
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a participant matches the search string.
|
||||
*
|
||||
* @param {Object} participant - The participant to be checked.
|
||||
* @param {string} searchString - The participants search string.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function participantMatchesSearch(participant: Object, searchString: string) {
|
||||
if (searchString === '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
const names = normalizeAccents(participant?.name || participant?.displayName || '')
|
||||
.toLowerCase()
|
||||
.split(' ');
|
||||
const lowerCaseSearchString = searchString.toLowerCase();
|
||||
|
||||
for (const name of names) {
|
||||
if (name.startsWith(lowerCaseSearchString)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ StateListenerRegistry.register(
|
||||
appearance: NOTIFICATION_TYPE.NORMAL,
|
||||
titleKey: 'polls.notification.title',
|
||||
descriptionKey: 'polls.notification.description'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.SHORT));
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
73
react/features/reactions/components/web/RaiseHandButton.js
Normal file
73
react/features/reactions/components/web/RaiseHandButton.js
Normal file
@@ -0,0 +1,73 @@
|
||||
// @flow
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { IconRaisedHand } from '../../../base/icons';
|
||||
import { getLocalParticipant, hasRaisedHand } from '../../../base/participants';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
|
||||
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link RaiseHandButton}.
|
||||
*/
|
||||
type Props = AbstractButtonProps & {
|
||||
|
||||
/**
|
||||
* Whether or not the hand is raised.
|
||||
*/
|
||||
raisedHand: boolean,
|
||||
};
|
||||
|
||||
/**
|
||||
* Implementation of a button for raising hand.
|
||||
*/
|
||||
class RaiseHandButton extends AbstractButton<Props, *> {
|
||||
accessibilityLabel = 'toolbar.accessibilityLabel.raiseHand';
|
||||
icon = IconRaisedHand;
|
||||
label = 'toolbar.raiseHand';
|
||||
toggledLabel = 'toolbar.raiseHand';
|
||||
|
||||
/**
|
||||
* Retrieves tooltip dynamically.
|
||||
*/
|
||||
get tooltip() {
|
||||
return 'toolbar.raiseHand';
|
||||
}
|
||||
|
||||
/**
|
||||
* Required by linter due to AbstractButton overwritten prop being writable.
|
||||
*
|
||||
* @param {string} _value - The value.
|
||||
*/
|
||||
set tooltip(_value) {
|
||||
// Unused.
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this button is in toggled state or not.
|
||||
*
|
||||
* @override
|
||||
* @protected
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_isToggled() {
|
||||
return this.props.raisedHand;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Function that maps parts of Redux state tree into component props.
|
||||
*
|
||||
* @param {Object} state - Redux state.
|
||||
* @returns {Object}
|
||||
*/
|
||||
const mapStateToProps = state => {
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
|
||||
return {
|
||||
raisedHand: hasRaisedHand(localParticipant)
|
||||
};
|
||||
};
|
||||
|
||||
export default translate(connect(mapStateToProps)(RaiseHandButton));
|
||||
@@ -4,16 +4,15 @@ import React, { useCallback } from 'react';
|
||||
|
||||
import { isMobileBrowser } from '../../../base/environment/utils';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { IconArrowUp, IconRaisedHand } from '../../../base/icons';
|
||||
import { getLocalParticipant, hasRaisedHand } from '../../../base/participants';
|
||||
import { IconArrowUp } from '../../../base/icons';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { ToolboxButtonWithIcon } from '../../../base/toolbox/components';
|
||||
import ToolbarButton from '../../../toolbox/components/web/ToolbarButton';
|
||||
import { toggleReactionsMenuVisibility } from '../../actions.web';
|
||||
import { type ReactionEmojiProps } from '../../constants';
|
||||
import { getReactionsQueue, isReactionsEnabled } from '../../functions.any';
|
||||
import { getReactionsMenuVisibility } from '../../functions.web';
|
||||
|
||||
import RaiseHandButton from './RaiseHandButton';
|
||||
import ReactionEmoji from './ReactionEmoji';
|
||||
import ReactionsMenuPopup from './ReactionsMenuPopup';
|
||||
|
||||
@@ -24,6 +23,11 @@ type Props = {
|
||||
*/
|
||||
_reactionsEnabled: Boolean,
|
||||
|
||||
/**
|
||||
* The button's key.
|
||||
*/
|
||||
buttonKey?: string,
|
||||
|
||||
/**
|
||||
* Redux dispatch function.
|
||||
*/
|
||||
@@ -45,9 +49,10 @@ type Props = {
|
||||
isMobile: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the local participant's hand is raised.
|
||||
* Notify mode for `toolbarButtonClicked` event -
|
||||
* whether to only notify or to also prevent button click routine.
|
||||
*/
|
||||
raisedHand: boolean,
|
||||
notifyMode?: string,
|
||||
|
||||
/**
|
||||
* The array of reactions to be displayed.
|
||||
@@ -70,11 +75,12 @@ declare var APP: Object;
|
||||
*/
|
||||
function ReactionsMenuButton({
|
||||
_reactionsEnabled,
|
||||
buttonKey,
|
||||
dispatch,
|
||||
handleClick,
|
||||
isOpen,
|
||||
isMobile,
|
||||
raisedHand,
|
||||
notifyMode,
|
||||
reactionsQueue,
|
||||
t
|
||||
}: Props) {
|
||||
@@ -82,30 +88,31 @@ function ReactionsMenuButton({
|
||||
dispatch(toggleReactionsMenuVisibility());
|
||||
}, [ dispatch ]);
|
||||
|
||||
const raiseHandButton = (<ToolbarButton
|
||||
accessibilityLabel = { t('toolbar.accessibilityLabel.raiseHand') }
|
||||
icon = { IconRaisedHand }
|
||||
key = 'raise-hand'
|
||||
onClick = { handleClick }
|
||||
toggled = { raisedHand }
|
||||
tooltip = { t('toolbar.raiseHand') } />);
|
||||
|
||||
return (
|
||||
<div className = 'reactions-menu-popup-container'>
|
||||
<ReactionsMenuPopup>
|
||||
{!_reactionsEnabled || isMobile ? raiseHandButton
|
||||
{!_reactionsEnabled || isMobile ? (
|
||||
<RaiseHandButton
|
||||
buttonKey = { buttonKey }
|
||||
handleClick = { handleClick }
|
||||
notifyMode = { notifyMode } />)
|
||||
: (
|
||||
<ToolboxButtonWithIcon
|
||||
ariaControls = 'reactions-menu-dialog'
|
||||
ariaExpanded = { isOpen }
|
||||
ariaHasPopup = { true }
|
||||
ariaLabel = { t('toolbar.accessibilityLabel.reactionsMenu') }
|
||||
buttonKey = { buttonKey }
|
||||
icon = { IconArrowUp }
|
||||
iconDisabled = { false }
|
||||
iconId = 'reactions-menu-button'
|
||||
iconTooltip = { t(`toolbar.${isOpen ? 'closeReactionsMenu' : 'openReactionsMenu'}`) }
|
||||
notifyMode = { notifyMode }
|
||||
onIconClick = { toggleReactionsMenu }>
|
||||
{raiseHandButton}
|
||||
<RaiseHandButton
|
||||
buttonKey = { buttonKey }
|
||||
handleClick = { handleClick }
|
||||
notifyMode = { notifyMode } />
|
||||
</ToolboxButtonWithIcon>
|
||||
)}
|
||||
</ReactionsMenuPopup>
|
||||
@@ -125,14 +132,11 @@ function ReactionsMenuButton({
|
||||
* @returns {Object}
|
||||
*/
|
||||
function mapStateToProps(state) {
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
|
||||
return {
|
||||
_reactionsEnabled: isReactionsEnabled(state),
|
||||
isOpen: getReactionsMenuVisibility(state),
|
||||
isMobile: isMobileBrowser(),
|
||||
reactionsQueue: getReactionsQueue(state),
|
||||
raisedHand: hasRaisedHand(localParticipant)
|
||||
reactionsQueue: getReactionsQueue(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -77,13 +77,7 @@ export default class AbstractLiveStreamButton<P: Props> extends AbstractButton<P
|
||||
* @returns {void}
|
||||
*/
|
||||
async _handleClick() {
|
||||
const { _isLiveStreamRunning, dispatch, handleClick } = this.props;
|
||||
|
||||
if (handleClick) {
|
||||
handleClick();
|
||||
|
||||
return;
|
||||
}
|
||||
const { _isLiveStreamRunning, dispatch } = this.props;
|
||||
|
||||
const dialogShown = await dispatch(maybeShowPremiumFeatureDialog(FEATURES.RECORDING));
|
||||
|
||||
|
||||
@@ -78,13 +78,7 @@ export default class AbstractRecordButton<P: Props> extends AbstractButton<P, *>
|
||||
* @returns {void}
|
||||
*/
|
||||
async _handleClick() {
|
||||
const { _isRecordingRunning, dispatch, handleClick } = this.props;
|
||||
|
||||
if (handleClick) {
|
||||
handleClick();
|
||||
|
||||
return;
|
||||
}
|
||||
const { _isRecordingRunning, dispatch } = this.props;
|
||||
|
||||
sendAnalytics(createToolbarEvent(
|
||||
'recording.button',
|
||||
|
||||
@@ -78,10 +78,16 @@ export default class AbstractStopRecordingDialog<P: Props>
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* To be overwritten by web component.
|
||||
*/
|
||||
_toggleScreenshotCapture: () => void;
|
||||
|
||||
/**
|
||||
* Toggles screenshot capture feature.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_toggleScreenshotCapture() {
|
||||
// To be implemented by subclass.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -47,13 +47,7 @@ class ShareAudioButton extends AbstractButton<Props, *> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
const { dispatch, handleClick } = this.props;
|
||||
|
||||
if (handleClick) {
|
||||
handleClick();
|
||||
|
||||
return;
|
||||
}
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(startAudioScreenShareFlow());
|
||||
dispatch(setOverflowMenuVisible(false));
|
||||
|
||||
@@ -56,13 +56,7 @@ export default class AbstractSecurityDialogButton<P: Props, S:*>
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
const { _locked, handleClick } = this.props;
|
||||
|
||||
if (handleClick) {
|
||||
handleClick();
|
||||
|
||||
return;
|
||||
}
|
||||
const { _locked } = this.props;
|
||||
|
||||
sendAnalytics(createToolbarEvent('toggle.security', { enable: !_locked }));
|
||||
this._handleClickSecurityButton();
|
||||
|
||||
@@ -6,11 +6,19 @@ import React, { useRef } from 'react';
|
||||
|
||||
import { translate } from '../../../../base/i18n';
|
||||
import { copyText } from '../../../../base/util';
|
||||
import { NOTIFY_CLICK_MODE } from '../../../../toolbox/constants';
|
||||
|
||||
import PasswordForm from './PasswordForm';
|
||||
|
||||
const KEY = 'add-passcode';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Toolbar buttons which have their click exposed through the API.
|
||||
*/
|
||||
buttonsWithNotifyClick: Array<string | Object>,
|
||||
|
||||
/**
|
||||
* Whether or not the current user can modify the current password.
|
||||
*/
|
||||
@@ -59,12 +67,15 @@ type Props = {
|
||||
t: Function
|
||||
};
|
||||
|
||||
declare var APP: Object;
|
||||
|
||||
/**
|
||||
* Component that handles the password manipulation from the invite dialog.
|
||||
*
|
||||
* @returns {React$Element<any>}
|
||||
*/
|
||||
function PasswordSection({
|
||||
buttonsWithNotifyClick,
|
||||
canEditPassword,
|
||||
conference,
|
||||
locked,
|
||||
@@ -97,7 +108,31 @@ function PasswordSection({
|
||||
* @returns {void}
|
||||
*/
|
||||
function onTogglePasswordEditState() {
|
||||
setPasswordEditEnabled(!passwordEditEnabled);
|
||||
if (typeof APP === 'undefined' || !buttonsWithNotifyClick?.length) {
|
||||
setPasswordEditEnabled(!passwordEditEnabled);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let notifyMode;
|
||||
const notify = buttonsWithNotifyClick.find(
|
||||
(btn: string | Object) =>
|
||||
(typeof btn === 'string' && btn === KEY)
|
||||
|| (typeof btn === 'object' && btn.key === KEY)
|
||||
);
|
||||
|
||||
if (notify) {
|
||||
notifyMode = typeof notify === 'string' || notify.preventExecution
|
||||
? NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY
|
||||
: NOTIFY_CLICK_MODE.ONLY_NOTIFY;
|
||||
APP.API.notifyToolbarButtonClicked(
|
||||
KEY, notifyMode === NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY
|
||||
);
|
||||
}
|
||||
|
||||
if (notifyMode === NOTIFY_CLICK_MODE.ONLY_NOTIFY) {
|
||||
setPasswordEditEnabled(!passwordEditEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,6 +13,11 @@ import PasswordSection from './PasswordSection';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Toolbar buttons which have their click exposed through the API.
|
||||
*/
|
||||
_buttonsWithNotifyClick: Array<string | Object>,
|
||||
|
||||
/**
|
||||
* Whether or not the current user can modify the current password.
|
||||
*/
|
||||
@@ -57,6 +62,7 @@ type Props = {
|
||||
* @returns {React$Element<any>}
|
||||
*/
|
||||
function SecurityDialog({
|
||||
_buttonsWithNotifyClick,
|
||||
_canEditPassword,
|
||||
_conference,
|
||||
_locked,
|
||||
@@ -82,6 +88,7 @@ function SecurityDialog({
|
||||
<div className = 'security-dialog'>
|
||||
<LobbySection />
|
||||
<PasswordSection
|
||||
buttonsWithNotifyClick = { _buttonsWithNotifyClick }
|
||||
canEditPassword = { _canEditPassword }
|
||||
conference = { _conference }
|
||||
locked = { _locked }
|
||||
@@ -117,11 +124,12 @@ function mapStateToProps(state) {
|
||||
locked,
|
||||
password
|
||||
} = state['features/base/conference'];
|
||||
const { roomPasswordNumberOfDigits } = state['features/base/config'];
|
||||
const { roomPasswordNumberOfDigits, buttonsWithNotifyClick } = state['features/base/config'];
|
||||
|
||||
const showE2ee = Boolean(e2eeSupported) && isLocalParticipantModerator(state);
|
||||
|
||||
return {
|
||||
_buttonsWithNotifyClick: buttonsWithNotifyClick,
|
||||
_canEditPassword: isLocalParticipantModerator(state),
|
||||
_conference: conference,
|
||||
_dialIn: state['features/invite'],
|
||||
|
||||
@@ -40,17 +40,7 @@ class SettingsButton extends AbstractButton<Props, *> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
const {
|
||||
defaultTab = SETTINGS_TABS.DEVICES,
|
||||
dispatch,
|
||||
handleClick
|
||||
} = this.props;
|
||||
|
||||
if (handleClick) {
|
||||
handleClick();
|
||||
|
||||
return;
|
||||
}
|
||||
const { defaultTab = SETTINGS_TABS.DEVICES, dispatch } = this.props;
|
||||
|
||||
sendAnalytics(createToolbarEvent('settings'));
|
||||
dispatch(openSettingsDialog(defaultTab));
|
||||
|
||||
@@ -66,14 +66,6 @@ class SharedVideoButton extends AbstractButton<Props, *> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
const { handleClick } = this.props;
|
||||
|
||||
if (handleClick) {
|
||||
handleClick();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this._doToggleSharedVideo();
|
||||
}
|
||||
|
||||
|
||||
@@ -20,13 +20,7 @@ class SpeakerStatsButton extends AbstractSpeakerStatsButton {
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
const { dispatch, handleClick } = this.props;
|
||||
|
||||
if (handleClick) {
|
||||
handleClick();
|
||||
|
||||
return;
|
||||
}
|
||||
const { dispatch } = this.props;
|
||||
|
||||
sendAnalytics(createToolbarEvent('speaker.stats'));
|
||||
dispatch(openDialog(SpeakerStats));
|
||||
|
||||
@@ -38,13 +38,7 @@ export class AbstractClosedCaptionButton
|
||||
* @returns {void}
|
||||
*/
|
||||
async _handleClick() {
|
||||
const { _requestingSubtitles, dispatch, handleClick } = this.props;
|
||||
|
||||
if (handleClick) {
|
||||
handleClick();
|
||||
|
||||
return;
|
||||
}
|
||||
const { _requestingSubtitles, dispatch } = this.props;
|
||||
|
||||
sendAnalytics(createToolbarEvent('transcribing.ccButton',
|
||||
{
|
||||
|
||||
@@ -10,7 +10,7 @@ import { getFeatureFlag, AUDIO_MUTE_BUTTON_ENABLED } from '../../base/flags';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { MEDIA_TYPE } from '../../base/media';
|
||||
import { connect } from '../../base/redux';
|
||||
import { AbstractAudioMuteButton } from '../../base/toolbox/components';
|
||||
import { AbstractAudioMuteButton, AbstractButton } from '../../base/toolbox/components';
|
||||
import type { AbstractButtonProps } from '../../base/toolbox/components';
|
||||
import { isLocalTrackMuted } from '../../base/tracks';
|
||||
import { muteLocal } from '../../video-menu/actions';
|
||||
@@ -120,7 +120,7 @@ class AudioMuteButton extends AbstractAudioMuteButton<Props, *> {
|
||||
ACTION_SHORTCUT_TRIGGERED,
|
||||
{ enable: !this._isAudioMuted() }));
|
||||
|
||||
super._handleClick();
|
||||
AbstractButton.prototype._onClick.call(this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -32,13 +32,7 @@ class DownloadButton extends AbstractButton<Props, *> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
const { _downloadAppsUrl, handleClick } = this.props;
|
||||
|
||||
if (handleClick) {
|
||||
handleClick();
|
||||
|
||||
return;
|
||||
}
|
||||
const { _downloadAppsUrl } = this.props;
|
||||
|
||||
sendAnalytics(createToolbarEvent('download.pressed'));
|
||||
openURLInBrowser(_downloadAppsUrl);
|
||||
|
||||
@@ -33,13 +33,7 @@ class HelpButton extends AbstractButton<Props, *> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
const { _userDocumentationURL, handleClick } = this.props;
|
||||
|
||||
if (handleClick) {
|
||||
handleClick();
|
||||
|
||||
return;
|
||||
}
|
||||
const { _userDocumentationURL } = this.props;
|
||||
|
||||
sendAnalytics(createToolbarEvent('help.pressed'));
|
||||
openURLInBrowser(_userDocumentationURL);
|
||||
|
||||
@@ -39,13 +39,7 @@ class MuteEveryoneButton extends AbstractButton<Props, *> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
const { dispatch, localParticipantId, handleClick } = this.props;
|
||||
|
||||
if (handleClick) {
|
||||
handleClick();
|
||||
|
||||
return;
|
||||
}
|
||||
const { dispatch, localParticipantId } = this.props;
|
||||
|
||||
sendAnalytics(createToolbarEvent('mute.everyone.pressed'));
|
||||
dispatch(openDialog(MuteEveryoneDialog, {
|
||||
|
||||
@@ -39,13 +39,7 @@ class MuteEveryonesVideoButton extends AbstractButton<Props, *> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
const { dispatch, localParticipantId, handleClick } = this.props;
|
||||
|
||||
if (handleClick) {
|
||||
handleClick();
|
||||
|
||||
return;
|
||||
}
|
||||
const { dispatch, localParticipantId } = this.props;
|
||||
|
||||
sendAnalytics(createToolbarEvent('mute.everyone.pressed'));
|
||||
dispatch(openDialog(MuteEveryonesVideoDialog, {
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
setVideoMuted
|
||||
} from '../../base/media';
|
||||
import { connect } from '../../base/redux';
|
||||
import { AbstractVideoMuteButton } from '../../base/toolbox/components';
|
||||
import { AbstractButton, AbstractVideoMuteButton } from '../../base/toolbox/components';
|
||||
import type { AbstractButtonProps } from '../../base/toolbox/components';
|
||||
import { getLocalVideoType, isLocalCameraTrackMuted } from '../../base/tracks';
|
||||
import { isVideoMuteButtonDisabled } from '../functions';
|
||||
@@ -146,7 +146,7 @@ class VideoMuteButton extends AbstractVideoMuteButton<Props, *> {
|
||||
ACTION_SHORTCUT_TRIGGERED,
|
||||
{ enable: !this._isVideoMuted() }));
|
||||
|
||||
super._handleClick();
|
||||
AbstractButton.prototype._onClick.call(this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,6 +15,11 @@ import AudioMuteButton from '../AudioMuteButton';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The button's key.
|
||||
*/
|
||||
buttonKey?: string,
|
||||
|
||||
/**
|
||||
* External handler for click action.
|
||||
*/
|
||||
@@ -35,6 +40,12 @@ type Props = {
|
||||
*/
|
||||
isDisabled: boolean,
|
||||
|
||||
/**
|
||||
* Notify mode for `toolbarButtonClicked` event -
|
||||
* whether to only notify or to also prevent button click routine.
|
||||
*/
|
||||
notifyMode?: string,
|
||||
|
||||
/**
|
||||
* Used for translation.
|
||||
*/
|
||||
@@ -94,13 +105,7 @@ class AudioSettingsButton extends Component<Props> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_onClick() {
|
||||
const { handleClick, onAudioOptionsClick } = this.props;
|
||||
|
||||
if (handleClick) {
|
||||
handleClick();
|
||||
|
||||
return;
|
||||
}
|
||||
const { onAudioOptionsClick } = this.props;
|
||||
|
||||
onAudioOptionsClick();
|
||||
}
|
||||
@@ -111,7 +116,7 @@ class AudioSettingsButton extends Component<Props> {
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { handleClick, hasPermissions, isDisabled, visible, isOpen, t } = this.props;
|
||||
const { hasPermissions, isDisabled, visible, isOpen, buttonKey, notifyMode, t } = this.props;
|
||||
const settingsDisabled = !hasPermissions
|
||||
|| isDisabled
|
||||
|| !JitsiMeetJS.mediaDevices.isMultipleAudioInputSupported();
|
||||
@@ -123,16 +128,22 @@ class AudioSettingsButton extends Component<Props> {
|
||||
ariaExpanded = { isOpen }
|
||||
ariaHasPopup = { true }
|
||||
ariaLabel = { t('toolbar.audioSettings') }
|
||||
buttonKey = { buttonKey }
|
||||
icon = { IconArrowUp }
|
||||
iconDisabled = { settingsDisabled }
|
||||
iconId = 'audio-settings-button'
|
||||
iconTooltip = { t('toolbar.audioSettings') }
|
||||
notifyMode = { notifyMode }
|
||||
onIconClick = { this._onClick }
|
||||
onIconKeyDown = { this._onEscClick }>
|
||||
<AudioMuteButton handleClick = { handleClick } />
|
||||
<AudioMuteButton
|
||||
buttonKey = { buttonKey }
|
||||
notifyMode = { notifyMode } />
|
||||
</ToolboxButtonWithIcon>
|
||||
</AudioSettingsPopup>
|
||||
) : <AudioMuteButton handleClick = { handleClick } />;
|
||||
) : <AudioMuteButton
|
||||
buttonKey = { buttonKey }
|
||||
notifyMode = { notifyMode } />;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,22 +61,6 @@ class FullscreenButton extends AbstractButton<Props, *> {
|
||||
// Unused.
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button, and opens the appropriate dialog.
|
||||
*
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
const { handleClick } = this.props;
|
||||
|
||||
if (handleClick) {
|
||||
handleClick();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this button is in toggled state or not.
|
||||
*
|
||||
|
||||
@@ -5,7 +5,6 @@ import React, { Component } from 'react';
|
||||
|
||||
import { createToolbarEvent, sendAnalytics } from '../../../analytics';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { IconHorizontalPoints } from '../../../base/icons';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { ReactionEmoji, ReactionsMenu } from '../../../reactions/components';
|
||||
import { type ReactionEmojiProps } from '../../../reactions/constants';
|
||||
@@ -13,7 +12,7 @@ import { getReactionsQueue } from '../../../reactions/functions.any';
|
||||
|
||||
import Drawer from './Drawer';
|
||||
import JitsiPortal from './JitsiPortal';
|
||||
import ToolbarButton from './ToolbarButton';
|
||||
import OverflowToggleButton from './OverflowToggleButton';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link OverflowMenuButton}.
|
||||
@@ -78,8 +77,8 @@ class OverflowMenuButton extends Component<Props> {
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onCloseDialog = this._onCloseDialog.bind(this);
|
||||
this._onToggleDialogVisibility
|
||||
= this._onToggleDialogVisibility.bind(this);
|
||||
this._toggleDialogVisibility
|
||||
= this._toggleDialogVisibility.bind(this);
|
||||
this._onEscClick = this._onEscClick.bind(this);
|
||||
}
|
||||
|
||||
@@ -113,7 +112,10 @@ class OverflowMenuButton extends Component<Props> {
|
||||
{
|
||||
overflowDrawer ? (
|
||||
<>
|
||||
{this._renderToolbarButton()}
|
||||
<OverflowToggleButton
|
||||
handleClick = { this._toggleDialogVisibility }
|
||||
isOpen = { isOpen }
|
||||
onKeyDown = { this._onEscClick } />
|
||||
<JitsiPortal>
|
||||
<Drawer
|
||||
isOpen = { isOpen }
|
||||
@@ -136,7 +138,10 @@ class OverflowMenuButton extends Component<Props> {
|
||||
isOpen = { isOpen }
|
||||
onClose = { this._onCloseDialog }
|
||||
placement = 'top-end'>
|
||||
{this._renderToolbarButton()}
|
||||
<OverflowToggleButton
|
||||
handleClick = { this._toggleDialogVisibility }
|
||||
isOpen = { isOpen }
|
||||
onKeyDown = { this._onEscClick } />
|
||||
</InlineDialog>
|
||||
)
|
||||
}
|
||||
@@ -144,30 +149,6 @@ class OverflowMenuButton extends Component<Props> {
|
||||
);
|
||||
}
|
||||
|
||||
_renderToolbarButton: () => React$Node;
|
||||
|
||||
/**
|
||||
* Renders the actual toolbar overflow menu button.
|
||||
*
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderToolbarButton() {
|
||||
const { ariaControls, isOpen, t } = this.props;
|
||||
|
||||
return (
|
||||
<ToolbarButton
|
||||
accessibilityLabel =
|
||||
{ t('toolbar.accessibilityLabel.moreActions') }
|
||||
aria-controls = { ariaControls }
|
||||
aria-haspopup = 'true'
|
||||
icon = { IconHorizontalPoints }
|
||||
onClick = { this._onToggleDialogVisibility }
|
||||
onKeyDown = { this._onEscClick }
|
||||
toggled = { isOpen }
|
||||
tooltip = { t('toolbar.moreActions') } />
|
||||
);
|
||||
}
|
||||
|
||||
_onCloseDialog: () => void;
|
||||
|
||||
/**
|
||||
@@ -181,7 +162,7 @@ class OverflowMenuButton extends Component<Props> {
|
||||
this.props.onVisibilityChange(false);
|
||||
}
|
||||
|
||||
_onToggleDialogVisibility: () => void;
|
||||
_toggleDialogVisibility: () => void;
|
||||
|
||||
/**
|
||||
* Callback invoked to signal that an event has occurred that should change
|
||||
@@ -190,7 +171,7 @@ class OverflowMenuButton extends Component<Props> {
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onToggleDialogVisibility() {
|
||||
_toggleDialogVisibility() {
|
||||
sendAnalytics(createToolbarEvent('overflow'));
|
||||
|
||||
this.props.onVisibilityChange(!this.props.isOpen);
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
// @flow
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { IconHorizontalPoints } from '../../../base/icons';
|
||||
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
|
||||
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link OverflowToggleButton}.
|
||||
*/
|
||||
type Props = AbstractButtonProps & {
|
||||
|
||||
/**
|
||||
* Whether the more options menu is open.
|
||||
*/
|
||||
isOpen: boolean,
|
||||
|
||||
/**
|
||||
* External handler for key down action.
|
||||
*/
|
||||
onKeyDown: Function,
|
||||
};
|
||||
|
||||
/**
|
||||
* Implementation of a button for toggleing the overflow menu.
|
||||
*/
|
||||
class OverflowToggleButton extends AbstractButton<Props, *> {
|
||||
accessibilityLabel = 'toolbar.accessibilityLabel.moreActions';
|
||||
icon = IconHorizontalPoints;
|
||||
label = 'toolbar.moreActions';
|
||||
toggledLabel = 'toolbar.moreActions';
|
||||
|
||||
/**
|
||||
* Retrieves tooltip dynamically.
|
||||
*/
|
||||
get tooltip() {
|
||||
return 'toolbar.moreActions';
|
||||
}
|
||||
|
||||
/**
|
||||
* Required by linter due to AbstractButton overwritten prop being writable.
|
||||
*
|
||||
* @param {string} _value - The value.
|
||||
*/
|
||||
set tooltip(_value) {
|
||||
// Unused.
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this button is in toggled state or not.
|
||||
*
|
||||
* @override
|
||||
* @protected
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_isToggled() {
|
||||
return this.props.isOpen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether a key was pressed.
|
||||
*
|
||||
* @override
|
||||
* @protected
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_onKeyDown() {
|
||||
this.props.onKeyDown();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default translate(OverflowToggleButton);
|
||||
@@ -95,13 +95,7 @@ class ProfileButton extends AbstractButton<Props, *> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
const { dispatch, _unclickable, handleClick } = this.props;
|
||||
|
||||
if (handleClick) {
|
||||
handleClick();
|
||||
|
||||
return;
|
||||
}
|
||||
const { dispatch, _unclickable } = this.props;
|
||||
|
||||
if (!_unclickable) {
|
||||
sendAnalytics(createToolbarEvent('profile'));
|
||||
|
||||
@@ -67,22 +67,6 @@ class ShareDesktopButton extends AbstractButton<Props, *> {
|
||||
// Unused.
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button, and opens the appropriate dialog.
|
||||
*
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
const { handleClick } = this.props;
|
||||
|
||||
if (handleClick) {
|
||||
handleClick();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this button is in toggled state or not.
|
||||
*
|
||||
|
||||
@@ -41,13 +41,7 @@ class ToggleCameraButton extends AbstractButton<Props, any> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
const { dispatch, handleClick } = this.props;
|
||||
|
||||
if (handleClick) {
|
||||
handleClick();
|
||||
|
||||
return;
|
||||
}
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(toggleCamera());
|
||||
}
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Icon } from '../../../base/icons';
|
||||
import { Tooltip } from '../../../base/tooltip';
|
||||
import AbstractToolbarButton from '../AbstractToolbarButton';
|
||||
import type { Props as AbstractToolbarButtonProps }
|
||||
from '../AbstractToolbarButton';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link ToolbarButton}.
|
||||
*/
|
||||
export type Props = AbstractToolbarButtonProps & {
|
||||
|
||||
/**
|
||||
* The text to display in the tooltip.
|
||||
*/
|
||||
tooltip: string,
|
||||
|
||||
/**
|
||||
* From which direction the tooltip should appear, relative to the
|
||||
* button.
|
||||
*/
|
||||
tooltipPosition: string,
|
||||
|
||||
/**
|
||||
* KeyDown handler.
|
||||
*/
|
||||
onKeyDown?: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a button in the toolbar.
|
||||
*
|
||||
* @augments AbstractToolbarButton
|
||||
*/
|
||||
class ToolbarButton extends AbstractToolbarButton<Props> {
|
||||
/**
|
||||
* Default values for {@code ToolbarButton} component's properties.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static defaultProps = {
|
||||
tooltipPosition: 'top'
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a new {@code ToolbarButton} instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this._onKeyPress = this._onKeyPress.bind(this);
|
||||
this._onClick = this._onClick.bind(this);
|
||||
}
|
||||
|
||||
_onKeyPress: (Object) => void;
|
||||
|
||||
/**
|
||||
* Handles 'Enter' and Space key on the button to trigger onClick for accessibility.
|
||||
*
|
||||
* @param {Object} event - The key event.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onKeyPress(event) {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
this.props.onClick();
|
||||
}
|
||||
}
|
||||
_onClick: (Object) => void;
|
||||
|
||||
/**
|
||||
* Handles button click.
|
||||
*
|
||||
* @param {Object} e - The key event.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onClick(e) {
|
||||
this.props.onClick(e);
|
||||
|
||||
// blur after click to release focus from button to allow PTT.
|
||||
e && e.currentTarget && e.currentTarget.blur();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the button of this {@code ToolbarButton}.
|
||||
*
|
||||
* @param {Object} children - The children, if any, to be rendered inside
|
||||
* the button. Presumably, contains the icon of this {@code ToolbarButton}.
|
||||
* @protected
|
||||
* @returns {ReactElement} The button of this {@code ToolbarButton}.
|
||||
*/
|
||||
_renderButton(children) {
|
||||
return (
|
||||
<div
|
||||
aria-label = { this.props.accessibilityLabel }
|
||||
aria-pressed = { this.props.toggled }
|
||||
className = 'toolbox-button'
|
||||
onClick = { this._onClick }
|
||||
onKeyDown = { this.props.onKeyDown }
|
||||
onKeyPress = { this._onKeyPress }
|
||||
role = 'button'
|
||||
tabIndex = { 0 }>
|
||||
{ this.props.tooltip
|
||||
? <Tooltip
|
||||
content = { this.props.tooltip }
|
||||
position = { this.props.tooltipPosition }>
|
||||
{ children }
|
||||
</Tooltip>
|
||||
: children }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the icon of this {@code ToolbarButton}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
_renderIcon() {
|
||||
return (
|
||||
<div className = { `toolbox-icon ${this.props.toggled ? 'toggled' : ''}` }>
|
||||
<Icon src = { this.props.icon } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ToolbarButton;
|
||||
@@ -76,7 +76,7 @@ import {
|
||||
setToolbarHovered,
|
||||
showToolbox
|
||||
} from '../../actions';
|
||||
import { THRESHOLDS, NOT_APPLICABLE, DRAWER_MAX_HEIGHT } from '../../constants';
|
||||
import { THRESHOLDS, NOT_APPLICABLE, DRAWER_MAX_HEIGHT, NOTIFY_CLICK_MODE } from '../../constants';
|
||||
import { isToolboxVisible } from '../../functions';
|
||||
import DownloadButton from '../DownloadButton';
|
||||
import HangupButton from '../HangupButton';
|
||||
@@ -106,7 +106,7 @@ type Props = {
|
||||
/**
|
||||
* Toolbar buttons which have their click exposed through the API.
|
||||
*/
|
||||
_buttonsWithNotifyClick: Array<string>,
|
||||
_buttonsWithNotifyClick: Array<string | Object>,
|
||||
|
||||
/**
|
||||
* Whether or not the chat feature is currently displayed.
|
||||
@@ -819,22 +819,31 @@ class Toolbox extends Component<Props> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrites click handlers for buttons in case click is exposed through the iframe API.
|
||||
* Sets the notify click mode for the buttons.
|
||||
*
|
||||
* @param {Object} buttons - The list of toolbar buttons.
|
||||
* @returns {void}
|
||||
*/
|
||||
_overwriteButtonsClickHandlers(buttons) {
|
||||
_setButtonsNotifyClickMode(buttons) {
|
||||
if (typeof APP === 'undefined' || !this.props._buttonsWithNotifyClick?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object.values(buttons).forEach((button: any) => {
|
||||
if (
|
||||
typeof button === 'object'
|
||||
&& this.props._buttonsWithNotifyClick.includes(button.key)
|
||||
) {
|
||||
button.handleClick = () => APP.API.notifyToolbarButtonClicked(button.key);
|
||||
if (typeof button === 'object') {
|
||||
const notify = this.props._buttonsWithNotifyClick.find(
|
||||
(btn: string | Object) =>
|
||||
(typeof btn === 'string' && btn === button.key)
|
||||
|| (typeof btn === 'object' && btn.key === button.key)
|
||||
);
|
||||
|
||||
if (notify) {
|
||||
const notifyMode = typeof notify === 'string' || notify.preventExecution
|
||||
? NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY
|
||||
: NOTIFY_CLICK_MODE.ONLY_NOTIFY;
|
||||
|
||||
button.notifyMode = notifyMode;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -854,7 +863,7 @@ class Toolbox extends Component<Props> {
|
||||
|
||||
const buttons = this._getAllButtons();
|
||||
|
||||
this._overwriteButtonsClickHandlers(buttons);
|
||||
this._setButtonsNotifyClickMode(buttons);
|
||||
const isHangupVisible = isToolbarButtonEnabled('hangup', _toolbarButtons);
|
||||
const { order } = THRESHOLDS.find(({ width }) => _clientWidth > width)
|
||||
|| THRESHOLDS[THRESHOLDS.length - 1];
|
||||
@@ -1265,6 +1274,7 @@ class Toolbox extends Component<Props> {
|
||||
{mainMenuButtons.map(({ Content, key, ...rest }) => Content !== Separator && (
|
||||
<Content
|
||||
{ ...rest }
|
||||
buttonKey = { key }
|
||||
key = { key } />))}
|
||||
|
||||
{Boolean(overflowMenuButtons.length) && (
|
||||
@@ -1292,6 +1302,7 @@ class Toolbox extends Component<Props> {
|
||||
{showSeparator && <Separator key = { `hr${group}` } />}
|
||||
<Content
|
||||
{ ...rest }
|
||||
buttonKey = { key }
|
||||
key = { key }
|
||||
showLabel = { true } />
|
||||
</Fragment>
|
||||
|
||||
@@ -16,6 +16,11 @@ import VideoMuteButton from '../VideoMuteButton';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The button's key.
|
||||
*/
|
||||
buttonKey?: string,
|
||||
|
||||
/**
|
||||
* External handler for click action.
|
||||
*/
|
||||
@@ -41,6 +46,12 @@ type Props = {
|
||||
*/
|
||||
isDisabled: boolean,
|
||||
|
||||
/**
|
||||
* Notify mode for `toolbarButtonClicked` event -
|
||||
* whether to only notify or to also prevent button click routine.
|
||||
*/
|
||||
notifyMode?: string,
|
||||
|
||||
/**
|
||||
* Flag controlling the visibility of the button.
|
||||
* VideoSettings popup is currently disabled on mobile browsers
|
||||
@@ -112,13 +123,7 @@ class VideoSettingsButton extends Component<Props> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_onClick() {
|
||||
const { handleClick, onVideoOptionsClick } = this.props;
|
||||
|
||||
if (handleClick) {
|
||||
handleClick();
|
||||
|
||||
return;
|
||||
}
|
||||
const { onVideoOptionsClick } = this.props;
|
||||
|
||||
onVideoOptionsClick();
|
||||
}
|
||||
@@ -129,7 +134,7 @@ class VideoSettingsButton extends Component<Props> {
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { handleClick, t, visible, isOpen } = this.props;
|
||||
const { t, visible, isOpen, buttonKey, notifyMode } = this.props;
|
||||
|
||||
return visible ? (
|
||||
<VideoSettingsPopup>
|
||||
@@ -138,16 +143,22 @@ class VideoSettingsButton extends Component<Props> {
|
||||
ariaExpanded = { isOpen }
|
||||
ariaHasPopup = { true }
|
||||
ariaLabel = { this.props.t('toolbar.videoSettings') }
|
||||
buttonKey = { buttonKey }
|
||||
icon = { IconArrowUp }
|
||||
iconDisabled = { this._isIconDisabled() }
|
||||
iconId = 'video-settings-button'
|
||||
iconTooltip = { t('toolbar.videoSettings') }
|
||||
notifyMode = { notifyMode }
|
||||
onIconClick = { this._onClick }
|
||||
onIconKeyDown = { this._onEscClick }>
|
||||
<VideoMuteButton handleClick = { handleClick } />
|
||||
<VideoMuteButton
|
||||
buttonKey = { buttonKey }
|
||||
notifyMode = { notifyMode } />
|
||||
</ToolboxButtonWithIcon>
|
||||
</VideoSettingsPopup>
|
||||
) : <VideoMuteButton handleClick = { handleClick } />;
|
||||
) : <VideoMuteButton
|
||||
buttonKey = { buttonKey }
|
||||
notifyMode = { notifyMode } />;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
export { default as AudioSettingsButton } from './AudioSettingsButton';
|
||||
export { default as VideoSettingsButton } from './VideoSettingsButton';
|
||||
export { default as ToolbarButton } from './ToolbarButton';
|
||||
export { default as Toolbox } from './Toolbox';
|
||||
export { default as Drawer } from './Drawer';
|
||||
export { default as JitsiPortal } from './JitsiPortal';
|
||||
|
||||
@@ -33,3 +33,8 @@ export const NOT_APPLICABLE = 'N/A';
|
||||
export const TOOLBAR_TIMEOUT = 4000;
|
||||
|
||||
export const DRAWER_MAX_HEIGHT = '80vh - 64px';
|
||||
|
||||
export const NOTIFY_CLICK_MODE = {
|
||||
ONLY_NOTIFY: 'ONLY_NOTIFY',
|
||||
PREVENT_AND_NOTIFY: 'PREVENT_AND_NOTIFY'
|
||||
};
|
||||
|
||||
@@ -52,13 +52,7 @@ class TileViewButton<P: Props> extends AbstractButton<P, *> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
const { _tileViewEnabled, dispatch, handleClick } = this.props;
|
||||
|
||||
if (handleClick) {
|
||||
handleClick();
|
||||
|
||||
return;
|
||||
}
|
||||
const { _tileViewEnabled, dispatch } = this.props;
|
||||
|
||||
const value = !_tileViewEnabled;
|
||||
|
||||
|
||||
@@ -39,24 +39,6 @@ class VideoQualityButton extends AbstractButton<Props, *> {
|
||||
label = 'videoStatus.performanceSettings';
|
||||
tooltip = 'videoStatus.performanceSettings';
|
||||
icon = IconGauge;
|
||||
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button.
|
||||
*
|
||||
* @override
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
const { handleClick } = this.props;
|
||||
|
||||
if (handleClick) {
|
||||
handleClick();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(VideoQualityButton);
|
||||
|
||||
@@ -43,13 +43,7 @@ class VideoBackgroundButton extends AbstractButton<Props, *> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
const { dispatch, handleClick } = this.props;
|
||||
|
||||
if (handleClick) {
|
||||
handleClick();
|
||||
|
||||
return;
|
||||
}
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(openDialog(VirtualBackgroundDialog));
|
||||
}
|
||||
|
||||
@@ -213,7 +213,7 @@ end
|
||||
function destroy_breakout_room(room_jid, message)
|
||||
local main_room, main_room_jid = get_main_room(room_jid);
|
||||
|
||||
if room_jid == main_room_jid then
|
||||
if room_jid == main_room_jid or not main_room then
|
||||
return;
|
||||
end
|
||||
|
||||
@@ -339,7 +339,7 @@ function on_occupant_joined(event)
|
||||
|
||||
local main_room = get_main_room(room.jid);
|
||||
|
||||
if main_room._data.breakout_rooms_active then
|
||||
if main_room and main_room._data.breakout_rooms_active then
|
||||
if jid_node(event.occupant.jid) ~= 'focus' then
|
||||
broadcast_breakout_rooms(room.jid);
|
||||
end
|
||||
@@ -388,6 +388,10 @@ function on_occupant_left(event)
|
||||
|
||||
local main_room = get_main_room(room_jid);
|
||||
|
||||
if not main_room then
|
||||
return;
|
||||
end
|
||||
|
||||
if main_room._data.breakout_rooms_active and jid_node(event.occupant.jid) ~= 'focus' then
|
||||
broadcast_breakout_rooms(room_jid);
|
||||
end
|
||||
@@ -477,7 +481,7 @@ function process_breakout_rooms_muc_loaded(breakout_rooms_muc, host_module)
|
||||
event.formdata['muc#roominfo_breakout_main_room'] = main_room_jid;
|
||||
|
||||
-- If the main room has a lobby, make it so this breakout room also uses it.
|
||||
if (main_room._data.lobbyroom and main_room:get_members_only()) then
|
||||
if (main_room and main_room._data.lobbyroom and main_room:get_members_only()) then
|
||||
table.insert(event.form, {
|
||||
name = 'muc#roominfo_lobbyroom';
|
||||
label = 'Lobby room jid';
|
||||
|
||||
@@ -380,10 +380,13 @@ process_host_module(main_muc_component_config, function(host_module, host)
|
||||
local invitee = event.stanza.attr.from;
|
||||
local affiliation = room:get_affiliation(invitee);
|
||||
if not affiliation or affiliation == 'none' then
|
||||
local reply = st.error_reply(stanza, 'auth', 'registration-required'):up();
|
||||
local reply = st.error_reply(stanza, 'auth', 'registration-required');
|
||||
reply.tags[1].attr.code = '407';
|
||||
reply:tag('x', {xmlns = MUC_NS}):up();
|
||||
reply:tag('lobbyroom'):text(room._data.lobbyroom);
|
||||
reply:tag('lobbyroom', { xmlns = 'http://jitsi.org/jitmeet' }):text(room._data.lobbyroom):up():up();
|
||||
|
||||
-- TODO: Drop this tag at some point (when all mobile clients and jigasi are updated), as this violates the rfc
|
||||
reply:tag('lobbyroom'):text(room._data.lobbyroom):up();
|
||||
|
||||
event.origin.send(reply:tag('x', {xmlns = MUC_NS}));
|
||||
return true;
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user