diff --git a/config.js b/config.js index c6dc2b68c55c..41f1ab78feb8 100644 --- a/config.js +++ b/config.js @@ -927,6 +927,9 @@ var config = { // [ 'microphone', 'camera' ] // ], + // Enable reduced UI on web. + // reducedUIEnabled: true, + // Overrides the buttons displayed in the main toolbar for reduced UI. // When there isn't an override for a certain configuration the default jitsi-meet configuration will be used. // The order of the buttons in the array is preserved. diff --git a/css/_base.scss b/css/_base.scss index 5ac310f86256..058413b31f30 100644 --- a/css/_base.scss +++ b/css/_base.scss @@ -45,7 +45,7 @@ body { .jitsi-icon { &-default svg { - fill: white; + fill: var(--icon-default-color, white); } } diff --git a/css/_mini_toolbox.scss b/css/_mini_toolbox.scss index 14f8737d6f38..11c20e83b8b3 100644 --- a/css/_mini_toolbox.scss +++ b/css/_mini_toolbox.scss @@ -1,5 +1,5 @@ .always-on-top-toolbox { - background-color: $newToolbarBackgroundColor; + background-color: var(--toolbox-background-color, $newToolbarBackgroundColor); border-radius: 3px; display: flex; z-index: $toolbarZ; diff --git a/css/_toolbars.scss b/css/_toolbars.scss index d1327a9a000e..90c7525c205b 100644 --- a/css/_toolbars.scss +++ b/css/_toolbars.scss @@ -2,7 +2,7 @@ * Round badge. */ .badge-round { - background-color: #165ECC; + background-color: var(--toolbar-badge-background, #165ECC); border-radius: 50%; box-sizing: border-box; color: #FFFFFF; @@ -93,7 +93,7 @@ .toolbox-content-wrapper::after { content: ''; - background: $newToolbarBackgroundColor; + background: var(--toolbox-background-color, $newToolbarBackgroundColor); padding-bottom: env(safe-area-inset-bottom, 0); } diff --git a/css/premeeting/_lobby.scss b/css/premeeting/_lobby.scss index bfaf9b6e4a40..22c026f614eb 100644 --- a/css/premeeting/_lobby.scss +++ b/css/premeeting/_lobby.scss @@ -60,7 +60,7 @@ } #notification-participant-list { - background-color: $newToolbarBackgroundColor; + background-color: var(--toolbox-background-color, $newToolbarBackgroundColor); border: 1px solid rgba(255, 255, 255, .4); border-radius: 8px; left: 0; diff --git a/ios/Podfile.lock b/ios/Podfile.lock index b2fd038f7076..290f5ff229dd 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1407,7 +1407,7 @@ PODS: - Yoga - react-native-performance (5.1.2): - React-Core - - react-native-safe-area-context (5.5.2): + - react-native-safe-area-context (5.6.1): - React-Core - react-native-slider (4.5.6): - DoubleConversion @@ -2271,7 +2271,7 @@ SPEC CHECKSUMS: react-native-orientation-locker: dbd3f6ddbe9e62389cb0807dc2af63f6c36dec36 react-native-pager-view: 11662c698c8f11d39e05891316d2a144fa00adc4 react-native-performance: 125a96c145e29918b55b45ce25cbba54f1e24dcd - react-native-safe-area-context: 0f7bf11598f9a61b7ceac8dc3f59ef98697e99e1 + react-native-safe-area-context: 2243039f43d10cb1ea30ec5ac57fc6d1448413f4 react-native-slider: 1205801a8d29b28cacc14eef08cb120015cdafcb react-native-video: eb861d67a71dfef1bbf6086a811af5f338b13781 react-native-webrtc: e8f0ce746353adc2744a2b933645e1aeb41eaa74 diff --git a/lang/main-fr.json b/lang/main-fr.json index cf7b23891ab6..a7ae558cb152 100644 --- a/lang/main-fr.json +++ b/lang/main-fr.json @@ -114,6 +114,9 @@ "error": "Erreur : votre message n'a pas été envoyé. Raison : {{error}}", "everyone": "Tout le monde", "fieldPlaceHolder": "Tapez votre message ici", + "fileAccessibleTitle": "{{user}} a téléversé un fichier", + "fileAccessibleTitleMe": "j’ai téléversé un fichier", + "fileDeleted": "Un fichier a été supprimé", "guestsChatIndicator": "(invité)", "lobbyChatMessageTo": "Message de salle d'attente à {{recipient}}", "message": "Message", @@ -123,8 +126,16 @@ "messagebox": "Envoyer un message", "newMessages": "Nouveaux messages", "nickname": { + "featureChat": "chat", + "featureClosedCaptions": "sous-titres", + "featureFileSharing": "partage de fichiers", + "featurePolls": "sondages", "popover": "Choisissez un pseudonyme", "title": "Entrez un pseudonyme pour utiliser le chat", + "titleWith1Features": "Entrez un pseudonyme pour utiliser {{feature1}}", + "titleWith2Features": "Entrez un pseudonyme pour utiliser {{feature1}} et {{feature2}}", + "titleWith3Features": "Entrez un pseudonyme pour utiliser {{feature1}}, {{feature2}} et {{feature3}}", + "titleWith4Features": "Entrez un pseudonyme pour utiliser {{feature1}}, {{feature2}}, {{feature3}} et {{feature4}}", "titleWithCC": "Entrez un pseudonyme pour utiliser le chat et les sous-titres", "titleWithPolls": "Entrez un pseudonyme pour utiliser le chat et les sondages", "titleWithPollsAndCC": "Entrez un pseudonyme pour utiliser le chat, les sondages et les sous-titres", @@ -216,6 +227,9 @@ "video_ssrc": "Video SSRC :", "yes": "oui" }, + "customPanel": { + "close": "Fermer" + }, "dateUtils": { "earlier": "Plus tôt", "today": "Aujourd'hui", @@ -522,6 +536,7 @@ "tokenAuthFailedWithReasons": "Désolé, vous n’êtes pas autorisé à rejoindre l’appel. La raison possible : {{reason}}.", "tokenAuthUnsupported": "Token URL n'est pas supporté.", "transcribing": "Transcription", + "unauthenticatedAccessDisabled": "Cet appel nécessite une authentification. Veuillez vous connecter pour continuer.", "unlockRoom": "Supprimer le $t(lockRoomPassword) de la réunion", "user": "Utilisateur", "userIdentifier": "Identifiant utilisateur", @@ -569,10 +584,12 @@ "downloadStarted": "Téléchargement de fichier démarré", "dragAndDrop": "Glissez et déposez des fichiers ici ou n'importe où sur l'écran", "fileAlreadyUploaded": "Le fichier a déjà été téléchargé vers cette réunion.", + "fileRemovedByOther": "Votre fichier « {{ fileName }} » a été supprimé", "fileTooLargeDescription": "Veuillez vous assurer que le fichier ne dépasse pas {{ maxFileSize }}.", "fileTooLargeTitle": "Le fichier sélectionné est trop volumineux", "fileUploadProgress": "Progression du téléchargement de fichier", "fileUploadedSuccessfully": "Fichier téléchargé avec succès", + "newFileNotification": "{{ participantName }} a partagé « {{ fileName }} »", "removeFile": "Supprimer", "removeFileSuccess": "Fichier supprimé avec succès", "uploadFailedDescription": "Veuillez réessayer.", @@ -964,6 +981,9 @@ "by": "Par {{ name }}", "closeButton": "Fermer le sondage", "create": { + "accessibilityLabel": { + "send": "Envoyer le sondage" + }, "addOption": "Ajouter une option", "answerPlaceholder": "Option {{index}}", "cancel": "Annuler", @@ -972,8 +992,7 @@ "pollQuestion": "Question du sondage", "questionPlaceholder": "Poser une question", "removeOption": "Supprimer l'option", - "save": "Enregistrer", - "send": "Envoyer" + "save": "Enregistrer" }, "errors": { "notUniqueOption": "Les options doivent être uniques" @@ -1299,6 +1318,7 @@ "chat": "Afficher / Masquer la discussion instantanée", "clap": "Applaudir", "closeChat": "Fermer la discussion instantanée", + "closeCustomPanel": "Fermer", "closeMoreActions": "Fermer le menu plus d'actions", "closeParticipantsPane": "Fermer le panneau des participants", "closedCaptions": "Sous-titres", @@ -1404,9 +1424,11 @@ "chat": "Ouvrir / Fermer le chat", "clap": "Applaudir", "closeChat": "Fermer le chat", + "closeCustomPanel": "Fermer", "closeParticipantsPane": "Fermer le panneau des participants", "closeReactionsMenu": "Fermer le menu réactions", "closedCaptions": "Sous-titres", + "copilot": "Copilot", "disableNoiseSuppression": "Arrêter la suppression du bruit", "disableReactionSounds": "Vous pouvez interdire les réactions sonores à cette réunion", "documentClose": "Fermer le document partagé", @@ -1421,6 +1443,7 @@ "exitFullScreen": "Quitter le mode plein écran", "exitTileView": "Quitter le mode mosaïque", "feedback": "Laisser des commentaires", + "fileSharing": "Partage de fichiers", "giphy": "Activer/désactiver le menu GIPHY", "hangup": "Quitter", "help": "Aide", @@ -1456,6 +1479,7 @@ "openReactionsMenu": "Ouvrir le menu Réactions", "participants": "Participants", "pip": "Entrer en mode Picture-in-Picture", + "polls": "Sondages", "privateMessage": "Envoyer un message privé", "profile": "Éditer votre profil", "raiseHand": "Lever / Baisser la main", diff --git a/lang/main-nl.json b/lang/main-nl.json index f57c1cdbf46f..253c8a6bf84f 100644 --- a/lang/main-nl.json +++ b/lang/main-nl.json @@ -1,5 +1,8 @@ { "addPeople": { + "accessibilityLabel": { + "meetingLink": "Meeting link: {{url}}" + }, "add": "Uitnodigen", "addContacts": "Nodig uw contacten uit", "contacts": "contacten", @@ -11,7 +14,6 @@ "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 e-mail", "inviteMoreHeader": "U bent de enige in de vergadering", "inviteMoreMailSubject": "Deelnemen aan {{appName}}-vergadering", @@ -24,40 +26,59 @@ "shareInvite": "Uitnodiging voor vergadering delen", "shareLink": "Deel de link naar de vergadering om anderen uit te nodigen", "shareStream": "Deel de link naar de livestream", + "sipAddresses": "sip-adressen", "telephone": "Telefoon: {{number}}", "title": "Personen uitnodigen voor deze vergadering", "yahooEmail": "Yahoo e-mail" }, "audioDevices": { "bluetooth": "Bluetooth", + "car": "Car Audio", "headphones": "Hoofdtelefoon", "none": "Geen audioapparaten beschikbaar", "phone": "Telefoon", - "speaker": "Speaker" + "speaker": "Luidspreker" }, "audioOnly": { - "audioOnly": "Lage bandbreedte" + "audioOnly": "Beperkte bandbreedte" }, - "blankPage": { - "meetingEnded": "Vergadering beëindigd." + "bandwidthSettings": { + "assumedBandwidthBps": "bijv. 10000000 voor 10 Mbps", + "assumedBandwidthBpsWarning": "Hogere waarden kunnen netwerkproblemen veroorzaken.", + "customValue": "aangepaste waarde", + "customValueEffect": "om de daadwerkelijke bps-waarde in te stellen", + "leaveEmpty": "laat leeg", + "leaveEmptyEffect": "om schattingen mogelijk te maken", + "possibleValues": "Mogelijke waarden", + "setAssumedBandwidthBps": "Aangenomen bandbreedte (bps)", + "title": "Bandbreedte-instellingen", + "zeroEffect": "om video uit te schakelen" }, "breakoutRooms": { "actions": { "add": "Aparte vergaderruimte toevoegen", + "autoAssign": "Aparte vergaderruimtes automatisch toewijzen", "close": "Sluiten", "join": "Deelnemen", - "leaveBreakoutRoom": "Verlaat aparte vergaderruimte", + "leaveBreakoutRoom": "Aparte vergaderruimte verlaten", "more": "Meer", "remove": "Verwijderen", + "rename": "Hernoemen", + "renameBreakoutRoom": "Aparte vergaderruimte hernoemen", "sendToBreakoutRoom": "Stuur deelnemer naar:" }, + "breakoutList": "Breakout-lijst", + "buttonLabel": "Aparte vergaderruimtes", "defaultName": "Aparte vergaderruimte #{{index}}", + "hideParticipantList": "Deelnemerslijst verbergen", "mainRoom": "Hoofdruimte", "notifications": { - "joined": "Aparte vergaderruimte \"{{name}}\" binnentreden", - "joinedMainRoom": "Hoofdruimte binnentreden", + "joined": "Aparte vergaderruimte \"{{name}}\" betreden", + "joinedMainRoom": "Hoofdruimte betreden", "joinedTitle": "Aparte vergaderruimtes" - } + }, + "showParticipantList": "Deelnemerslijst weergeven", + "title": "Aparte vergaderruimtes" }, "calendarSync": { "addMeetingURL": "Een link naar een vergadering toevoegen", @@ -77,37 +98,77 @@ "refresh": "Agenda vernieuwen", "today": "Vandaag" }, + "carmode": { + "actions": { + "selectSoundDevice": "Geluidsapparaat selecteren" + }, + "labels": { + "buttonLabel": "Auto-modus", + "title": "Auto-modus", + "videoStopped": "Uw video is gestopt" + } + }, "chat": { - "enter": "Chat openen", + "disabled": "Het verzenden van chatberichten is uitgeschakeld.", + "enter": "Vertrek betreden", "error": "Fout: uw bericht \"{{originalText}}\" is niet verzonden. Reden: {{error}}", + "everyone": "Iedereen", "fieldPlaceHolder": "Typ hier uw bericht", + "fileAccessibleTitle": "{{user}} heeft een bestand geüploadet", + "fileAccessibleTitleMe": "ik heb een bestand geüploadet", + "fileDeleted": "Een bestand werd verwijderd", + "guestsChatIndicator": "(gast)", + "lobbyChatMessageTo": "Wachtruimte-chatbericht naar {{recipient}}", "message": "Bericht", "messageAccessibleTitle": "{{user}} zegt:", "messageAccessibleTitleMe": "ik zeg:", "messageTo": "Privébericht aan {{recipient}}", "messagebox": "Typ een bericht", + "newMessages": "Nieuwe berichten", "nickname": { + "featureChat": "chat", + "featureClosedCaptions": "ondertiteling", + "featureFileSharing": "bestandsdeling", + "featurePolls": "peilingen", "popover": "Kies een bijnaam", "title": "Voer een bijnaam in om chat te gebruiken", - "titleWithPolls": "Voer een bijnaam in om chat te gebruiken" + "titleWith1Features": "Voer een bijnaam in om {{feature1}} te gebruiken", + "titleWith2Features": "Voer een bijnaam in om {{feature1}} en {{feature2}} te gebruiken", + "titleWith3Features": "Voer een bijnaam in om {{feature1}}, {{feature2}} en {{feature3}} te gebruiken", + "titleWith4Features": "Voer een bijnaam in om {{feature1}}, {{feature2}}, {{feature3}} en {{feature4}} te gebruiken", + "titleWithCC": "Voer een bijnaam in om chat en ondertiteling te gebruiken", + "titleWithPolls": "Voer een bijnaam in om chat en peilingen te gebruiken", + "titleWithPollsAndCC": "Voer een bijnaam in om chat, peilingen en ondertiteling te gebruiken", + "titleWithPollsAndCCAndFileSharing": "Voer een bijnaam in om chat, peilingen, ondertiteling en bestanden te gebruiken" }, "noMessagesMessage": "Er zijn nog geen berichten in de vergadering. Begin hier een gesprek!", "privateNotice": "Privébericht aan {{recipient}}", "sendButton": "Verzenden", - "smileysPanel": "Smiley paneel", + "smileysPanel": "Emoji-paneel", + "systemDisplayName": "Systeem", "tabs": { "chat": "Gesprek", + "closedCaptions": "OT", + "fileSharing": "Bestanden", "polls": "Peilingen" }, "title": "Gesprek", - "titleWithPolls": "Gesprek", + "titleWithCC": "OT", + "titleWithFeatures": "Gesprek en", + "titleWithFileSharing": "Bestanden", + "titleWithPolls": "Peilingen", "you": "U" }, "chromeExtensionBanner": { - "buttonText": "Installeer Chrome Extensie", + "buttonText": "Chrome Extensie installeren", + "buttonTextEdge": "Edge Extensie installeren", "close": "Sluiten", "dontShowAgain": "Laat me dit niet meer zien", - "installExtensionText": "Installeer de extensie voor Google Calendar en Office 365 integratie" + "installExtensionText": "Installeer de extensie voor integratie met Google Agenda en Office 365" + }, + "closedCaptionsTab": { + "emptyState": "De inhoud met ondertiteling zal beschikbaar zijn zodra een moderator deze start", + "startClosedCaptionsButton": "Ondertiteling starten" }, "connectingOverlay": { "joiningRoom": "U wordt verbonden met uw vergadering…" @@ -129,12 +190,13 @@ }, "connectionindicator": { "address": "Adres:", - "audio_ssrc": "Audio SSRC:", + "audio_ssrc": "Audio-SSRC:", "bandwidth": "Geschatte bandbreedte:", "bitrate": "Bitrate:", "bridgeCount": "Aantal servers: ", "codecs": "Codecs (A/V): ", "connectedTo": "Verbonden met:", + "e2eeVerified": "E2EE geverifieerd:", "framerate": "Framesnelheid:", "less": "Minder weergeven", "localaddress": "Lokaal adres:", @@ -143,6 +205,7 @@ "localport_plural": "Lokale poorten:", "maxEnabledResolution": "verstuur max", "more": "Meer weergeven", + "no": "nee", "packetloss": "Pakketverlies:", "participant_id": "Deelnemer id:", "quality": { @@ -161,7 +224,11 @@ "status": "Verbinding:", "transport": "Transport:", "transport_plural": "Transporten:", - "video_ssrc": "Video SSRC:" + "video_ssrc": "Video-SSRC:", + "yes": "ja" + }, + "customPanel": { + "close": "Sluiten" }, "dateUtils": { "earlier": "Eerder", @@ -171,13 +238,23 @@ "deepLinking": { "appNotInstalled": "U hebt de mobiele app {{app}} nodig om aan deze vergadering deel te nemen via uw telefoon.", "description": "Gebeurt er niets? Er is geprobeerd uw vergadering te starten in de desktop-app {{app}}. Probeer het opnieuw of start de vergadering in de web-app {{app}}.", + "descriptionNew": "Niets gebeurd? We hebben geprobeerd uw vergadering te starten in de desktop-app {{app}}.

U kunt het opnieuw proberen of starten op internet.", "descriptionWithoutWeb": "Gebeurt er niets? Er is geprobeerd om uw vergadering te starten in de desktop-app {{app}}", "downloadApp": "Download de app", + "downloadMobileApp": "Download van de App Store", "ifDoNotHaveApp": "Als u de app nog niet hebt:", "ifHaveApp": "Als u de app al hebt:", "joinInApp": "Deelnemen aan deze vergadering met de app", + "joinInAppNew": "Deelnemen in app", + "joinInBrowser": "Deelnemen in browser", + "launchMeetingLabel": "Hoe wilt u deelnemen aan deze vergadering?", "launchWebButton": "Starten in web", + "noDesktopApp": "U hebt de app niet?", + "noMobileApp": "U hebt de app niet?", + "or": "OF", + "termsAndConditions": "Door verder te gaan, gaat u akkoord met onze gebruiksvoorwaarden.", "title": "Uw vergadering wordt gestart in {{app}}…", + "titleNew": "Uw vergadering wordt gestart…", "tryAgainButton": "Opnieuw proberen in desktop", "unsupportedBrowser": "Het lijkt erop dat u een browser gebruikt die wij niet ondersteunen." }, @@ -190,38 +267,65 @@ "microphonePermission": "Fout bij verkrijgen toegangsrecht microfoon" }, "deviceSelection": { + "hid": { + "callControl": "Gesprekscontrole", + "connectedDevices": "Verbonden apparaten:", + "deleteDevice": "Apparaat verwijderen", + "pairDevice": "Apparaat koppelen" + }, "noPermission": "Geen toestemming verleend", "previewUnavailable": "Voorbeeld niet beschikbaar", "selectADevice": "Selecteer een apparaat", "testAudio": "Een testgeluid afspelen" }, + "dialIn": { + "screenTitle": "Inbeloverzicht" + }, "dialOut": { "statusMessage": "is nu {{status}}" }, "dialog": { "Back": "Terug", "Cancel": "Annuleren", - "IamHost": "Ik ben de host", + "IamHost": "Inloggen", "Ok": "OK", "Remove": "Verwijderen", "Share": "Delen", "Submit": "Verzenden", - "WaitForHostMsg": "De vergadering is nog niet gestart. Authenticeer uzelf als u de host bent. Anders wacht u tot de host aanwezig is.", + "Understand": "Ik begrijp het, houd mij voorlopig gedempt", + "UnderstandAndUnmute": "Ik begrijp het, zet de microfoon aan", + "WaitForHostNoAuthMsg": "De conferentie is nog niet gestart. Authenticeer uzelf als u de host bent. Anders wacht u tot de host aanwezig is.", + "WaitingForHostButton": "Wachten op moderator", + "WaitingForHostTitle": "Wachten op een moderator…", "Yes": "Ja", "accessibilityLabel": { - "liveStreaming": "Livestream" + "Cancel": "Annuleren (dialoog verlaten)", + "Ok": "OK (opslaan en dialoog verlaten)", + "close": "Dialoog sluiten", + "liveStreaming": "Livestream", + "sharingTabs": "Opties voor delen" }, "add": "Toevoegen", + "addMeetingNote": "Voeg een opmerking toe over deze bijeenkomst", + "addOptionalNote": "Een opmerking toevoegen (optioneel):", "allow": "Toestaan", - "alreadySharedVideoMsg": "Er wordt al een video gedeeld door een andere deelnemer. Deze vergadering staat slechts één gedeelde video tegelijkertijd toe.", + "allowToggleCameraDialog": "Wilt u {{initiatorName}} toestaan om uw camera (voor/achter) te wisselen?", + "allowToggleCameraTitle": "Camera wisselen toestaan?", + "alreadySharedVideoMsg": "Er wordt al een video gedeeld door een andere deelnemer. Deze conferentie staat slechts één gedeelde video tegelijkertijd toe.", "alreadySharedVideoTitle": "Slechts één gedeelde video tegelijkertijd is toegestaan", "applicationWindow": "Toepassingsvenster", "authenticationRequired": "Authenticatie vereist", + "cameraCaptureDialog": { + "description": "Foto maken en versturen met uw mobiele camera", + "ok": "Camera openen", + "reject": "Niet nu", + "title": "Maak een foto" + }, "cameraConstraintFailedError": "Uw camera voldoet niet aan alle vereiste beperkingen.", "cameraNotFoundError": "Camera niet gevonden.", "cameraNotSendingData": "Er is geen toegang tot uw camera verkregen. Controleer of dit apparaat wordt gebruikt door een andere toepassing, selecteer een ander apparaat vanuit de instellingen of probeer de toepassing te herladen.", "cameraNotSendingDataTitle": "Geen toegang tot camera", - "cameraPermissionDeniedError": "U hebt geen toestemming verleend om uw camera te gebruiken. U kunt wel deelnemen aan de vergadering, maar anderen kunnen u niet zien. Gebruik de cameraknop in de adresbalk om dit op te lossen.", + "cameraPermissionDeniedError": "U hebt geen toestemming verleend om uw camera te gebruiken. U kunt wel deelnemen aan de conferentie, maar anderen kunnen u niet zien. Gebruik de cameraknop in de adresbalk om dit op te lossen.", "cameraTimeoutError": "Er heeft een camera timeout opgetreden.", "cameraUnknownError": "Kan de camera om een onbekende reden niet gebruiken.", "cameraUnsupportedResolutionError": "Uw camera ondersteunt de vereiste videoresolutie niet.", @@ -231,25 +335,34 @@ "conferenceReloadMsg": "Er wordt geprobeerd om dit op te lossen. Over {{seconds}} sec. wordt opnieuw geprobeerd verbinding te maken…", "conferenceReloadTitle": "Er is helaas iets misgegaan.", "confirm": "Bevestigen", + "confirmBack": "Terug", "confirmNo": "Nee", "confirmYes": "Ja", - "connectError": "Oeps! Er is iets misgegaan en er kon geen verbinding met de vergadering worden gemaakt.", - "connectErrorWithMsg": "Oeps! Er is iets misgegaan en er kon geen verbinding met de vergadering worden gemaakt: {{msg}}", + "connectError": "Oeps! Er is iets misgegaan en er kon geen verbinding met de conferentie worden gemaakt.", + "connectErrorWithMsg": "Oeps! Er is iets misgegaan en er kon geen verbinding met de conferentie worden gemaakt: {{msg}}", "connecting": "Verbinding maken", - "contactSupport": "Contact opnemen met ondersteuning", + "contactSupport": "Contact met ondersteuning", "copied": "Gekopieerd", "copy": "Kopiëren", + "demoteParticipantDialog": "Weet u zeker dat u deze deelnemer wilt verplaatsen naar toeschouwer?", + "demoteParticipantTitle": "Verplaatsen naar toeschouwer", "dismiss": "Negeren", "displayNameRequired": "Hallo! Wat is uw naam?", "done": "Gereed", "e2eeDescription": "Eind-tot-Eind-Versleuteling is momenteel EXPERIMENTEEL. Houd er rekening mee dat inschakelen van eind-tot-eind-versleuteling de door de server geleverde services zal uitschakelen zoals: opnemen, livestreamen en deelname via telefoon. Houd er ook rekening mee dat de vergadering alleen zal werken voor personen die deelnemen vanaf browsers met ondersteuning voor insertable streams.", + "e2eeDisabledDueToMaxModeDescription": "Eind-tot-Eind-Versleuteling kan niet worden ingeschakeld vanwege het grote aantal deelnemers aan de conferentie.", "e2eeLabel": "Sleutel", "e2eeWarning": "WAARSCHUWING: Niet alle deelnemers in deze vergadering lijken ondersteuning te hebben voor eind-tot-eind-versleuteling. Als u het inschakelt zullen zij u niet kunnen zien of horen.", + "e2eeWillDisableDueToMaxModeDescription": "WAARSCHUWING: Eind-tot-Eind-Versleuteling wordt automatisch uitgeschakeld als meer deelnemers aan de conferentie deelnemen.", + "embedMeeting": "Vergadering insluiten", "enterDisplayName": "Voer hier uw naam in", "error": "Fout", + "errorRoomCreationRestriction": "U probeerde te snel deel te nemen, kom alsjeblieft even later terug.", "gracefulShutdown": "Onze service is momenteel niet beschikbaar vanwege onderhoud. Probeer het later opnieuw.", "grantModeratorDialog": "Weet u zeker dat u de moderatorrechten wilt verlenen aan deze deelnemer?", "grantModeratorTitle": "Moderatorrechten verlenen", + "hide": "Verbergen", + "hideShareAudioHelper": "Dit dialoogvenster niet meer tonen", "incorrectPassword": "Onjuiste gebruikersnaam of wachtwoord", "incorrectRoomLockPassword": "Onjuist wachtwoord", "internalError": "Oeps! Er is iets misgegaan. De volgende fout trad op: {{error}}", @@ -258,18 +371,23 @@ "kickParticipantButton": "Verwijderen", "kickParticipantDialog": "Weet u zeker dat u deze deelnemer wilt verwijderen?", "kickParticipantTitle": "Deze deelnemer verwijderen?", + "kickSystemTitle": "Oei! U bent uit de vergadering verwijderd", "kickTitle": "Oei! {{participantDisplayName}} heeft u uit de vergadering verwijderd", + "learnMore": "meer informatie", + "linkMeeting": "Vergadering koppelen", + "linkMeetingTitle": "Vergadering koppelen aan Verkoop", "liveStreaming": "Livestreamen", "liveStreamingDisabledBecauseOfActiveRecordingTooltip": "Niet mogelijk tijdens opnemen", - "liveStreamingDisabledTooltip": "Livestream starten uitgeschakeld.", - "lockMessage": "Het vergrendelen van de vergadering is mislukt.", - "lockRoom": "$t(lockRoomPasswordUppercase) voor vergadering toevoegen", - "lockTitle": "Vergrendelen mislukt", - "login": "Login", - "logoutQuestion": "Weet u zeker dat u zich wilt afmelden en de vergadering wilt stoppen?", + "localUserControls": "Lokale gebruikerscontrole", + "lockMessage": "Het vergrendelen van de conferentie is mislukt.", + "lockRoom": "$t(lockRoomPassword) voor vergadering toevoegen", + "lockTitle": "Vergrendelen is mislukt", + "login": "Inloggen", + "loginQuestion": "Weet u zeker dat u wilt inloggen en de conferentie verlaten?", + "logoutQuestion": "Weet u zeker dat u zich wilt afmelden en de conferentie wilt stoppen?", "logoutTitle": "Afmelden", - "maxUsersLimitReached": "Het maximale aantal deelnemers is bereikt. De vergadering is vol. Neem contact op met de eigenaar van de vergadering of probeer het later opnieuw!", - "maxUsersLimitReachedTitle": "Maximaal aantal deelnemers bereikt", + "maxUsersLimitReached": "Het maximale aantal deelnemers is bereikt. De conferentie is vol. Neem contact op met de eigenaar van de vergadering of probeer het later opnieuw!", + "maxUsersLimitReachedTitle": "Maximum aantal deelnemers bereikt", "micConstraintFailedError": "Uw microfoon voldoet niet aan alle vereiste beperkingen.", "micNotFoundError": "Microfoon niet gevonden.", "micNotSendingData": "Ga naar de instellingen van uw computer om het dempen van uw microfoon op te heffen en het geluidsniveau aan te passen", @@ -278,37 +396,57 @@ "micTimeoutError": "Kan de microfoon niet gebruiken vanwege een timeout fout.", "micUnknownError": "Kan de microfoon om een onbekende reden niet gebruiken.", "moderationAudioLabel": "Sta deelnemers toe om voor henzelf dempen op te heffen", + "moderationDesktopLabel": "Sta niet-moderators toe om hun scherm te delen", "moderationVideoLabel": "Sta deelnemers toe om hun camera aan te zetten", "muteEveryoneDialog": "Weet u zeker dat u iedereen wilt dempen? U kunt het dempen niet opheffen, maar zij kunnen dit wel ieder moment zelf doen.", - "muteEveryoneDialogModerationOn": "De deelnemers kunnen te allen tijde een verzoek om te spreken indienen.", + "muteEveryoneDialogModerationOn": "De deelnemers kunnen altijd een verzoek om te spreken indienen.", "muteEveryoneElseDialog": "Eenmaal gedempt kunt u het dempen niet opheffen, maar zij kunnen dit wel ieder moment zelf doen.", "muteEveryoneElseTitle": "Iedereen dempen behalve {{whom}}?", + "muteEveryoneElsesDesktopDialog": "Zodra het delen is gestopt, kunt u het niet opnieuw opstarten, maar ze kunnen dit op elk moment doen.", + "muteEveryoneElsesDesktopTitle": "Ieders schermdeling stoppen, behalve {{whom}}?", "muteEveryoneElsesVideoDialog": "Als u de camera's uitzet kunt u hem niet meer aanzetten, maar de andere deelnemers kunnen dit wel ieder moment zelf doen.", "muteEveryoneElsesVideoTitle": "De camera van iedereen behalve {{whom}} uitzetten?", "muteEveryoneSelf": "uzelf", "muteEveryoneStartMuted": "Iedereen start vanaf nu gedempt", "muteEveryoneTitle": "Iedereen dempen?", - "muteEveryonesVideoDialog": "Weet u zeker dat u de camera van iedereen wilt uitzetten? Als u de camera's uitzet kunt u deze niet meer aanzetten, maar de andere deelnemers kunnen dit wel ieder moment zelf doen.", - "muteEveryonesVideoDialogModerationOn": "De deelnemers kunnen te allen tijde een verzoek om hun camera aan te zetten indienen.", + "muteEveryonesDesktopDialog": "De deelnemers kunnen op elk moment hun scherm delen.", + "muteEveryonesDesktopDialogModerationOn": "De deelnemers kunnen op elk moment een verzoek sturen om hun scherm te delen.", + "muteEveryonesDesktopTitle": "Iedereen's scherm delen stoppen?", + "muteEveryonesVideoDialog": "De deelnemers kunnen hun video op elk moment inschakelen.", + "muteEveryonesVideoDialogModerationOn": "De deelnemers kunnen altijd een verzoek om hun camera aan te zetten indienen.", "muteEveryonesVideoDialogOk": "Uitzetten", - "muteEveryonesVideoTitle": "Camera van iedereen uitzetten?", + "muteEveryonesVideoTitle": "Iedereen's camera uitzetten?", "muteParticipantBody": "U kunt het dempen niet opheffen, maar zij kunnen dit wel ieder moment zelf doen.", "muteParticipantButton": "Dempen", - "muteParticipantDialog": "Weet u zeker dat u deze deelnemer wilt dempen? U kunt het dempen niet opheffen, maar deze deelnemer kan dit wel ieder moment zelf doen.", - "muteParticipantTitle": "Deze deelnemer dempen?", - "muteParticipantsVideoBody": "Het is niet mogelijk voor u om de camera weer aan te zetten, de deelnemer kan de camera wel weer aanzetten.", - "muteParticipantsVideoButton": "Camera uitzetten", - "muteParticipantsVideoTitle": "Camera van deze deelnemer uitzetten?", - "passwordLabel": "De vergadering is vergrendeld door een deelnemer. Voer het $t(lockRoomPassword) in om deel te nemen.", + "muteParticipantsDesktopBody": "U kunt hun schermdeling niet starten, maar zij kunnen dit op elk moment doen.", + "muteParticipantsDesktopBodyModerationOn": "U kunt hun schermdeling niet starten en zij ook niet.", + "muteParticipantsDesktopButton": "Schermdeling stoppen", + "muteParticipantsDesktopDialog": "Weet u zeker dat u de schermdeling van deze deelnemer wilt uitschakelen? U kunt het niet opnieuw opstarten, maar zij kunnen dit op elk moment doen.", + "muteParticipantsDesktopDialogModerationOn": "Weet u zeker dat u de schermdeling van deze deelnemer wilt uitschakelen? U kunt het scherm niet weer aanzetten en zij ook niet.", + "muteParticipantsDesktopTitle": "Schermdeling van deze deelnemer uitschakelen?", + "muteParticipantsVideoBody": "U kunt de camera niet weer inschakelen, maar zij kunnen hem op elk moment weer inschakelen.", + "muteParticipantsVideoBodyModerationOn": "U kunt de camera niet weer aanzetten en zij ook niet.", + "muteParticipantsVideoButton": "Video stoppen", + "muteParticipantsVideoDialog": "Weet u zeker dat u de camera van deze deelnemer wilt uitschakelen? U kunt de camera niet weer inschakelen, maar zij kunnen hem op elk moment weer inschakelen.", + "muteParticipantsVideoDialogModerationOn": "Weet u zeker dat u de camera van deze deelnemer wilt uitschakelen? U kunt de camera niet weer aanzetten en zij ook niet.", + "muteParticipantsVideoTitle": "Camera van deze deelnemer uitschakelen?", + "noDropboxToken": "Geen geldig Dropbox-token", + "password": "Wachtwoord", + "passwordLabel": "De vergadering is door een deelnemer vergrendeld. Voer het $t(lockRoomPassword) in om deel te nemen.", "passwordNotSupported": "Instellen van een $t(lockRoomPassword) voor de vergadering wordt niet ondersteund.", "passwordNotSupportedTitle": "$t(lockRoomPasswordUppercase) niet ondersteund", "passwordRequired": "$t(lockRoomPasswordUppercase) vereist", - "popupError": "Uw browser blokkeert pop-upvensters van deze site. Schakel pop-ups in vanuit de beveiligingsinstellingen van uw browser en probeer het opnieuw.", - "popupErrorTitle": "Pop-up geblokkeerd", + "permissionCameraRequiredError": "Voor deelname aan conferenties met video is camera-toestemming vereist. Geef het alstublieft in Instellingen", + "permissionErrorTitle": "Toestemming vereist", + "permissionMicRequiredError": "Voor deelname aan conferenties met audio is microfoon-toestemming vereist. Geef het alstublieft in Instellingen", "readMore": "meer", - "recording": "Opnemen", + "recentlyUsedObjects": "Uw recent gebruikte objecten", + "recording": "Opname", "recordingDisabledBecauseOfActiveLiveStreamingTooltip": "Niet mogelijk tijdens een livestream", - "recordingDisabledTooltip": "Opname starten uitgeschakeld.", + "recordingInProgressDescription": "Deze bijeenkomst wordt opgenomen en geanalyseerd door AI{{learnMore}}. Uw audio en video zijn gedempt. Als u ervoor kiest om de demping op te heffen, stemt u ermee in dat u wordt opgenomen.", + "recordingInProgressDescriptionFirstHalf": "Deze bijeenkomst wordt opgenomen en geanalyseerd door AI", + "recordingInProgressDescriptionSecondHalf": ". Uw audio en video zijn gedempt. Als u ervoor kiest om de demping op te heffen, stemt u ermee in dat u wordt opgenomen.", + "recordingInProgressTitle": "Opname loopt", "rejoinNow": "Nu opnieuw deelnemen", "remoteControlAllowedMessage": "{{user}} heeft uw verzoek om extern beheer geaccepteerd.", "remoteControlDeniedMessage": "{{user}} heeft uw verzoek om extern beheer geweigerd.", @@ -317,27 +455,56 @@ "remoteControlShareScreenWarning": "Let op: als u op 'Toestaan' drukt, wordt uw scherm gedeeld!", "remoteControlStopMessage": "De sessie van extern beheer is geëindigd!", "remoteControlTitle": "Extern beheer van bureaublad", - "removePassword": "$t(lockRoomPasswordUppercase) verwijderen", + "remoteUserControls": "Extern gebruikersbeheer van {{username}}", + "removePassword": "$t(lockRoomPassword) verwijderen", "removeSharedVideoMsg": "Weet u zeker dat u uw gedeelde video wilt verwijderen?", "removeSharedVideoTitle": "Gedeelde video verwijderen", + "renameBreakoutRoomLabel": "Kamernaam", + "renameBreakoutRoomTitle": "Aparte kamer hernoemen", "reservationError": "Fout in reserveringssysteem", "reservationErrorMsg": "Foutcode: {{code}}, bericht: {{msg}}", "retry": "Opnieuw proberen", - "screenSharingAudio": "Deel audio", + "screenSharingAudio": "Audio delen", "screenSharingFailed": "Oeps! Er is iets misgegaan, de schermdeling kon niet worden gestart!", "screenSharingFailedTitle": "Schermdeling mislukt!", "screenSharingPermissionDeniedError": "Oeps! Er is iets misgegaan met uw toegangsrechten voor schermdeling. Herlaad en probeer opnieuw.", + "searchInSalesforce": "Zoeken bij Verkoop", + "searchResults": "Zoekresultaten({{count}})", + "searchResultsDetailsError": "Er ging iets mis bij het ophalen van eigenaargegevens.", + "searchResultsError": "Er ging iets mis bij het ophalen van gegevens", + "searchResultsNotFound": "Geen zoekresultaten.", + "searchResultsTryAgain": "Probeer alternatieve zoektermen.", "sendPrivateMessage": "U hebt recentelijk een privébericht ontvangen. Bent u van plan daar privé op te reageren, of wilt u uw bericht naar de groep sturen?", - "sendPrivateMessageCancel": "Stuur naar de groep", + "sendPrivateMessageCancel": "Naar de groep sturen", "sendPrivateMessageOk": "Privé versturen", "sendPrivateMessageTitle": "Privé versturen?", "serviceUnavailable": "Service niet beschikbaar", "sessTerminated": "Gesprek beëindigd", - "sessionRestarted": "Gesprek herstart door de server", - "shareVideoLinkError": "Geef een juiste link op", - "shareVideoTitle": "Een video delen", + "sessTerminatedReason": "De vergadering is beëindigd", + "sessionRestarted": "Gesprek opnieuw gestart vanwege een verbindingsprobleem.", + "shareAudio": "Doorgaan", + "shareAudioAltText": "om de gewenste inhoud te delen, navigeert u naar \"Browser Tab\", selecteert u de inhoud, activeert u het vinkje \"audio delen\" en klikt u op de knop \"delen\"", + "shareAudioTitle": "Hoe audio te delen", + "shareAudioWarningD1": "u moet de schermdeling stoppen voordat u uw audio deelt.", + "shareAudioWarningD2": "u moet de schermdeling opnieuw opstarten en de optie \"audio delen\" aanvinken.", + "shareAudioWarningH1": "Als u alleen audio wilt delen:", + "shareAudioWarningTitle": "U moet de schermdeling stoppen voordat u uw audio deelt", + "shareMediaWarningGenericH2": "Als u uw scherm en audio wilt delen", + "shareScreenWarningD1": "u moet stoppen met het delen van audio voordat u uw scherm deelt.", + "shareScreenWarningD2": "u moet stoppen met het delen van audio, het delen van schermen starten en de optie \"audio delen\" aanvinken.", + "shareScreenWarningH1": "Als u alleen uw scherm wilt delen:", + "shareScreenWarningTitle": "U moet stoppen met het delen van audio voordat u uw scherm deelt", + "shareVideoConfirmPlay": "U staat op het punt een externe website te openen. Wilt u doorgaan?", + "shareVideoConfirmPlayTitle": "{{name}} heeft een video met u gedeeld.", + "shareVideoLinkError": "Oeps, deze video kan niet worden afgespeeld.", + "shareVideoLinkStopped": "De video van {{name}} is gestopt.", + "shareVideoTitle": "Video delen", "shareYourScreen": "Uw scherm delen", "shareYourScreenDisabled": "Schermdeling is uitgeschakeld.", + "sharedVideoDialogError": "Fout: Ongeldige of verboden URL", + "sharedVideoLinkPlaceholder": "YouTube-link of directe video-link", + "show": "Weergeven", + "start": "Start ", "startLiveStreaming": "Livestream starten", "startRecording": "Opname starten", "startRemoteControlErrorMessage": "Er is een fout opgetreden tijdens het starten van de sessie van extern beheer.", @@ -348,13 +515,44 @@ "streamKey": "Sleutel voor livestream", "thankYou": "Bedankt voor het gebruik van {{appName}}.", "token": "token", - "tokenAuthFailed": "Sorry, u bent niet toegestaan om deel te nemen aan dit gesprek.", + "tokenAuthFailed": "Sorry, u mag niet deelnemen aan dit gesprek.", + "tokenAuthFailedReason": { + "audInvalid": "Ongeldige `aud`-waarde. Het zou `jitsi` moeten zijn.", + "contextNotFound": "Het object `context` ontbreekt aan de lading.", + "expInvalid": "Ongeldige `exp`-warde.", + "featureInvalid": "Ongeldige functie: {{feature}}, waarschijnlijk nog niet geïmplementeerd.", + "featureValueInvalid": "Ongeldige waarde voor functie: {{feature}}.", + "featuresNotFound": "Het object `features` ontbreekt aan de lading.", + "headerNotFound": "De kop ontbreekt.", + "issInvalid": "Ongeldige `iss`-waarde. Het zou `chat` moeten zijn.", + "kidMismatch": "Key-ID (kid) komt niet overeen met sub.", + "kidNotFound": "Key-ID (kid) ontbreekt.", + "nbfFuture": "De waarde `nbf` is in de toekomst.", + "nbfInvalid": "Ongeldige `nbf`-waarde.", + "payloadNotFound": "De lading ontbreekt.", + "tokenExpired": "Token is verlopen." + }, "tokenAuthFailedTitle": "Authenticering mislukt", + "tokenAuthFailedWithReasons": "Sorry, u mag niet meedoen aan dit gesprek. Mogelijke redenen: {{reason}}", + "tokenAuthUnsupported": "Token-URL is niet ondersteund.", "transcribing": "Transcriberen", - "unlockRoom": "$t(lockRoomPasswordUppercase) voor vergadering verwijderen", - "user": "gebruiker", - "userPassword": "gebruikerswachtwoord", - "videoLink": "Video link", + "unauthenticatedAccessDisabled": "Deze oproep vereist authenticatie. Log in om verder te gaan.", + "unlockRoom": "$t(lockRoomPassword) voor vergadering verwijderen", + "user": "Gebruiker", + "userIdentifier": "Gebruikersidentificatie", + "userPassword": "Gebruikerswachtwoord", + "verifyParticipantConfirm": "Ze komen overeen", + "verifyParticipantDismiss": "Ze komen niet overeen", + "verifyParticipantQuestion": "EXPERIMENTEEL: Vraag deelnemer {{participantName}} of zij dezelfde inhoud in dezelfde volgorde zien.", + "verifyParticipantTitle": "Gebruikersverificatie", + "videoLink": "Video-link", + "viewUpgradeOptions": "Upgrade-opties weergeven", + "viewUpgradeOptionsContent": "Om onbeperkte toegang te krijgen tot premiumfuncties zoals opnemen, transcripties, RTMP-streaming en meer, moet u uw abonnement opwaarderen.", + "viewUpgradeOptionsTitle": "U heeft een premiumfunctie ontdekt!", + "whiteboardLimitContent": "Sorry, de limiet van gelijktijdige whiteboardgebruikers is bereikt.", + "whiteboardLimitReference": "Voor meer informatie kunt u terecht op", + "whiteboardLimitReferenceUrl": "onze website", + "whiteboardLimitTitle": "Whiteboardgebruik beperkt", "yourEntireScreen": "Uw gehele scherm" }, "documentSharing": { @@ -367,16 +565,45 @@ "title": "Deze vergadering embedden" }, "feedback": { + "accessibilityLabel": { + "yourChoice": "Uw keuze: {{rating}}" + }, "average": "Gemiddeld", "bad": "Slecht", "detailsLabel": "We horen er graag meer over.", "good": "Goed", "rateExperience": "Beoordeel uw vergaderervaring", + "star": "Ster", "veryBad": "Zeer Slecht", "veryGood": "Zeer Goed" }, - "helpView": { - "title": "Helpcentrum" + "fileSharing": { + "downloadFailedDescription": "Probeer het nog eens.", + "downloadFailedTitle": "Download is mislukt", + "downloadFile": "Downloaden", + "downloadStarted": "Bestandsdownload gestart", + "dragAndDrop": "Sleep bestanden hier of overal op het scherm", + "fileAlreadyUploaded": "Bestand is al geüpload naar deze bijeenkomst.", + "fileRemovedByOther": "Uw bestand '{{ fileName }}' is verwijderd", + "fileTooLargeDescription": "Zorg ervoor dat het bestand niet groter is dan {{ maxFileSize }}.", + "fileTooLargeTitle": "Het geselecteerde bestand is te groot", + "fileUploadProgress": "Voortgang bestandsupload", + "fileUploadedSuccessfully": "Bestand met succes geüpload", + "newFileNotification": "{{ participantName }} deelde '{{ fileName }}'", + "removeFile": "Verwijderen", + "removeFileSuccess": "Bestand met succes verwijderd", + "uploadFailedDescription": "Probeer het nog eens.", + "uploadFailedTitle": "Upload is mislukt", + "uploadFile": "Bestand delen" + }, + "filmstrip": { + "accessibilityLabel": { + "heading": "Video-minaturen" + } + }, + "giphy": { + "noResults": "Geen resultaten gevonden :(", + "search": "GIPHY zoeken" }, "incomingCall": { "answer": "Beantwoorden", @@ -390,7 +617,7 @@ "addPassword": "$t(lockRoomPasswordUppercase) toevoegen", "cancelPassword": "$t(lockRoomPasswordUppercase) annuleren", "conferenceURL": "Link:", - "copyNumber": "Kopieer nummer", + "copyNumber": "Nummer kopiëren", "country": "Land", "dialANumber": "Om deel te nemen aan uw vergadering, belt u een van deze nummers en voert u vervolgens de pincode in.", "dialInConferenceID": "Pincode:", @@ -402,19 +629,30 @@ "inviteLiveStream": "Om de livestream van deze vergadering te bekijken, klikt u op deze link: {{url}}", "invitePhone": "Om deel te nemen via uw telefoon, tik hierop: {{number}},,{{conferenceID}}#\n", "invitePhoneAlternatives": "Op zoek naar een ander inbelnummer?\nBekijk de inbelnummers voor de vergadering: {{url}}\n\n\nAls u ook inbelt via een conferentietelefoon, neem dan deel zonder audio: {{silentUrl}}", + "inviteSipEndpoint": "Om deel te nemen met behulp van het SIP-adres, voert u dit in: {{sipUri}}", + "inviteTextiOSInviteUrl": "Klik op de volgende link om deel te nemen: {{inviteUrl}}.", + "inviteTextiOSJoinSilent": "Als u inbelt via een kamertelefoon, gebruik dan deze link om deel te nemen zonder audiverbinding: {{silentUrl}}.", + "inviteTextiOSPersonal": "{{name}} nodigt u uit voor een vergadering.", + "inviteTextiOSPhone": "Om deel te nemen via een telefoon, gebruik dan dit nummer: {{number}},,{{conferenceID}}#. If you are looking for a different number, this is the full list: {{didUrl}}.", "inviteURLFirstPartGeneral": "U bent uitgenodigd om aan een vergadering deel te nemen.", "inviteURLFirstPartPersonal": "{{name}} nodigt u uit voor een vergadering.\n", "inviteURLSecondPart": "\nDeelnemen aan de vergadering:\n{{url}}\n", - "label": "Vergaderingsgegevens", + "label": "Inbelgegevens", "liveStreamURL": "Livestream:", "moreNumbers": "Meer nummers", "noNumbers": "Geen inbelnummers.", "noPassword": "Geen", "noRoom": "Er is geen ruimte opgegeven om naar in te bellen.", + "noWhiteboard": "Kon het whiteboard niet laden.", "numbers": "Inbelnummers", "password": "$t(lockRoomPasswordUppercase):", + "reachedLimit": "U hebt de beperkingen van uw abonnement bereikt.", + "sip": "SIP-adres", + "sipAudioOnly": "SIP-audio-only-adres", "title": "Delen", - "tooltip": "De link en inbelgegevens voor deze vergadering delen" + "tooltip": "De link en inbelgegevens voor deze vergadering delen", + "upgradeOptions": "Lees de opwaarderingsopties op", + "whiteboardError": "Fout bij het laden van het whiteboard. Probeer het later nog eens." }, "inlineDialogFailure": { "msg": "Er is een fout opgetreden.", @@ -425,30 +663,33 @@ "inviteDialog": { "alertText": "Niet alle deelnemers zijn uitgenodigd.", "header": "Uitnodigen", - "searchCallOnlyPlaceholder": "Voer telefoonnummer in", - "searchPeopleOnlyPlaceholder": "Zoek deelnemers", + "searchCallOnlyPlaceholder": "Telefoonnummer invoeren", + "searchPeopleOnlyPlaceholder": "Deelnemers zoeken", "searchPlaceholder": "Deelnemer of telefoonnummer", "send": "Verzenden" }, + "jitsiHome": "{{logo}} Logo, verwijst naar Homepage", "keyboardShortcuts": { "focusLocal": "Focus op uw video", "focusRemote": "Focus op de video van een andere persoon", "fullScreen": "Volledig scherm weergeven of afsluiten", + "giphyMenu": "GIPHY-menu schakelen", "keyboardShortcuts": "Sneltoetsen", "localRecording": "Besturingselementen voor lokale opnamen weergeven of verbergen", "mute": "Uw microfoon dempen of het dempen opheffen", "pushToTalk": "Druk om te spreken", "raiseHand": "Uw hand opsteken of laten zakken", - "showSpeakerStats": "Sprekerstatistieken weergeven", + "showSpeakerStats": "Deelnemersstatistieken weergeven", "toggleChat": "Chat openen of sluiten", "toggleFilmstrip": "Videominiaturen weergeven of verbergen", + "toggleParticipantsPane": "Deelnemersvenster weergeven of verbergen", "toggleScreensharing": "Wisselen tussen camera en schermdeling", "toggleShortcuts": "Sneltoetsen weergeven of verbergen", - "videoMute": "Uw camera aanzetten of uitzetten" + "videoMute": "Uw camera aan- of uitzetten" }, "largeVideo": { "screenIsShared": "U deelt uw scherm", - "showMeWhatImSharing": "Laat me zien wat ik deel" + "showMeWhatImSharing": "Laat mij zien wat ik deel" }, "liveStreaming": { "busy": "Er wordt gewerkt aan het vrijmaken van streamingmiddelen. Probeer het over enkele minuten opnieuw.", @@ -466,6 +707,7 @@ "failedToStart": "Livestream starten mislukt", "getStreamKeyManually": "Er konden geen livestreams opgehaald worden. Probeer uw livestream-sleutel van YouTube te krijgen.", "googlePrivacyPolicy": "Privacybeleid Google", + "inProgress": "Opname of livestreaming gaande", "invalidStreamKey": "Livestream-sleutel is mogelijk onjuist.", "limitNotificationDescriptionNative": "Uw stream zal beperkt worden tot {{limit}} min. Voor ongelimiteerd streamen, probeer {{app}}.", "limitNotificationDescriptionWeb": "Vanwege een grote vraag zal uw stream beperkt worden tot {{limit}} min. Voor ongelimiteerd streamen, probeer {{app}}.", @@ -474,7 +716,9 @@ "on": "Livestream", "onBy": "{{name}} heeft de livestream gestart.", "pending": "Livestream starten…", + "policyError": "U probeerde te snel een livestream te starten. Probeer het later nog eens!", "serviceName": "Livestreamservice", + "sessionAlreadyActive": "Deze sessie wordt al opgenomen of live gestreamd.", "signIn": "Aanmelden met Google", "signInCTA": "Meld u aan of voer uw livestream-sleutel van YouTube in.", "signOut": "Afmelden", @@ -486,15 +730,15 @@ "youtubeTerms": "Servicevoorwaarden YouTube" }, "lobby": { - "allow": "Toestaan", - "backToKnockModeButton": "Geen wachtwoord, vraag om deel te mogen nemen", - "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?", + "backToKnockModeButton": "Vraag om deel te mogen nemen", + "chat": "Chat", + "dialogTitle": "Wachtruimte-modus", + "disableDialogContent": "Wachtruimte-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 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.", + "enableDialogText": "Met de Wachtruimte-modus kunt u uw vergadering beveiligen, door deelnemers alleen toe te laten na een formele goedkeuring van een moderator.", "enterPasswordButton": "Voer wachtwoord voor vergadering in", "enterPasswordTitle": "Voer wachtwoord in om deel te nemen aan vergadering", "errorMissingPassword": "Voer alstublieft het wachtwoord van de vergadering in", @@ -509,16 +753,19 @@ "knockButton": "Vragen om deel te nemen", "knockTitle": "Iemand wil deelnemen aan de vergadering", "knockingParticipantList": "Lijst van aankloppende deelnemers", + "lobbyChatStartedNotification": "{{moderator}} startte een wachtruimtegesprek met {{attendee}}", + "lobbyChatStartedTitle": "{{moderator}} startte een wachtruimtegesprek met u.", + "lobbyClosed": "De wachtruimte is gesloten.", "nameField": "Voer uw naam in", "notificationLobbyAccessDenied": "{{targetParticipantName}} is afgewezen om deel te nemen door {{originParticipantName}}", "notificationLobbyAccessGranted": "{{targetParticipantName}} is toegestaan om deel te nemen door {{originParticipantName}}", - "notificationLobbyDisabled": "Lobby is uitgeschakeld door {{originParticipantName}}", - "notificationLobbyEnabled": "Lobby is ingeschakeld door {{originParticipantName}}", - "notificationTitle": "Lobby", - "passwordField": "Voer wachtwoord voor vergadering in", + "notificationLobbyDisabled": "Wachtruimte is uitgeschakeld door {{originParticipantName}}", + "notificationLobbyEnabled": "Wachtruimte is ingeschakeld door {{originParticipantName}}", + "notificationTitle": "Wachtruimte", "passwordJoinButton": "Deelnemen", - "title": "Lobby", - "toggleLabel": "Lobby inschakelen" + "title": "Wachtruimte", + "toggleLabel": "Wachtruimte inschakelen", + "waitForModerator": "De conferentie is nog niet begonnen omdat er nog geen moderators zijn gearriveerd. Als u moderator wilt worden, log dan in. Anders, wacht alsjeblieft." }, "localRecording": { "clientState": { @@ -544,9 +791,12 @@ "no": "Nee", "participant": "Deelnemer", "participantStats": "Deelnemerstatistieken", + "selectTabTitle": "🎥 Selecteer dit tabblad voor opname", "sessionToken": "Sessietoken", "start": "Opname starten", "stop": "Opname stoppen", + "stopping": "Opname stoppen", + "wait": "Wacht even terwijl we uw opname opslaan", "yes": "Ja" }, "lockRoomPassword": "wachtwoord", @@ -558,26 +808,53 @@ "me": "ik", "notify": { "OldElectronAPPTitle": "Beveiligingskwetsbaarheid!", - "allowAction": "Toestaan", + "allowAll": "Alles toestaan", + "allowAudio": "Audio toestaan", + "allowDesktop": "Schermdeling toestaan", + "allowVideo": "Video toestaan", "allowedUnmute": "U kunt het dempen van uw microfoon opheffen, uw camera aanzetten of uw scherm delen.", - "audioUnmuteBlockedTitle": "Dempen opheffen is geblokkeerd!", + "audioUnmuteBlockedDescription": "Demping opheffen van de microfoon is tijdelijk geblokkerd vanwege systeembeperkingen.", + "audioUnmuteBlockedTitle": "Demping opheffen is geblokkeerd!", "chatMessages": "Chatberichten", "connectedOneMember": "{{name}} neemt nu deel aan de vergadering", "connectedThreePlusMembers": "{{name}} en {{count}} anderen nemen nu deel aan de vergadering", "connectedTwoMembers": "{{first}} en {{second}} nemen nu deel aan de vergadering", + "connectionFailed": "Verbinding mislukt. Probeer het later nog eens!", + "dataChannelClosed": "De videokwaliteit kan verminderd zijn", + "dataChannelClosedDescription": "Het overbruggingskanaal is uitgevallen en de videokwaliteit kan dus beperkt zijn tot de laagste instelling.", + "dataChannelClosedDescriptionWithAudio": "Het overbruggingskanaal is uitgevallen en er kunnen dus verstoringen van audio en video optreden.", + "dataChannelClosedWithAudio": "De audio- en videokwaliteit kan verminderd zijn", + "desktopMutedRemotelyTitle": "Uw schermdeling is gestopt door {{participantDisplayName}}", + "disabledIframe": "Insluiten is alleen bedoeld voor demo-doeleinden, dus deze oproep wordt binnen {{timeout}} minuten verbroken.", + "disabledIframeSecondaryNative": "Het insluiten van {{domein}} is alleen bedoeld voor demo-doeleinden, dus deze oproep wordt binnen {{timeout}} minuten verbroken.", + "disabledIframeSecondaryWeb": "Het insluiten van {{domein}} is alleen bedoeld voor demo-doeleinden, dus deze oproep wordt binnen {{timeout}} minuten verbroken. Gebruik alstublieft Jitsi as a Service voor productie-insluiting!", "disconnected": "verbinding verbroken", "displayNotifications": "Toon meldingen voor", + "dontRemindMe": "Herinner me er niet aan", "focus": "Focus van vergadering", "focusFail": "{{component}} niet beschikbaar - probeer over {{ms}} sec. opnieuw", + "gifsMenu": "GIPHY", "groupTitle": "Meldingen", - "hostAskedUnmute": "De moderator wil graag dat u spreekt", + "hostAskedUnmute": "De moderator wil graag dat u deelneemt.", + "invalidTenant": "Ongeldige huurder", + "invalidTenantHyphenDescription": "De huurder die u gebruikt is ongeldig (start met of eindigt op '-').", + "invalidTenantLengthDescription": "De huurder die u gebruikt is te lang.", "invitedOneMember": "{{name}} is uitgenodigd", "invitedThreePlusMembers": "{{name}} en {{count}} anderen zijn uitgenodigd", "invitedTwoMembers": "{{first}} en {{second}} zijn uitgenodigd", + "joinMeeting": "Deelnemen", "kickParticipant": "{{kicked}} is verwijderd door {{kicker}}", "leftOneMember": "{{name}} heeft de vergadering verlaten", "leftThreePlusMembers": "{{name}} en vele anderen hebben de vergadering verlaten", "leftTwoMembers": "{{first}} en {{second}} hebben de vergadering verlaten", + "linkToSalesforce": "Koppelen aan Verkoop", + "linkToSalesforceDescription": "U kunt de samenvatting van de vergadering koppelen aan een Verkoop-object.", + "linkToSalesforceError": "Koppelen aan Verkoop is mislukt", + "linkToSalesforceKey": "Deze vergadering koppelen", + "linkToSalesforceProgress": "Vergadering koppelen aan Verkoop…", + "linkToSalesforceSuccess": "De vergadering is gekoppeld aan Verkoop", + "localRecordingStarted": "{{name}} heeft een lokale opname gestart.", + "localRecordingStopped": "{{name}} heeft een lokale opname gestopt.", "me": "Ik", "moderationInEffectCSDescription": "Steek uw hand op als u uw scherm wilt delen.", "moderationInEffectCSTitle": "Schermdelen is geblokkeerd door de moderator", @@ -587,47 +864,80 @@ "moderationInEffectVideoTitle": "Uw camera is geblokkeerd door de moderator", "moderationRequestFromModerator": "De host vraagt u om dempen op te heffen", "moderationRequestFromParticipant": "Wil graag spreken", - "moderator": "Moderatorrechten verleend!", + "moderationStartedTitle": "Moderatie gestart", + "moderationStoppedTitle": "Moderatie gestopt", + "moderationToggleDescription": "door {{participantDisplayName}}", + "moderator": "U bent nu moderator!", "muted": "U hebt het gesprek gedempt gestart.", "mutedRemotelyDescription": "U kunt het dempen altijd opheffen wanneer u klaar bent om te spreken. Demp opnieuw wanneer u klaar bent, om ruis buiten de vergadering te houden.", "mutedRemotelyTitle": "U bent gedempt door {{participantDisplayName}}!", "mutedTitle": "U bent gedempt!", - "newDeviceAction": "Gebruik", + "newDeviceAction": "Gebruiken", "newDeviceAudioTitle": "Nieuw audioapparaat gedetecteerd", "newDeviceCameraTitle": "Nieuwe camera gedetecteerd", + "nextToSpeak": "U bent de volgende om te spreken", + "noiseSuppressionDesktopAudioDescription": "Extra ruisonderdrukking kan niet worden ingeschakeld tijdens het delen van desktopaudio. Schakel dit uit en probeer het opnieuw.", + "noiseSuppressionFailedTitle": "Kan geen extra ruisonderdrukking starten", + "noiseSuppressionStereoDescription": "Extra ruisonderdrukking wordt momenteel niet ondersteund met stereo-audio.", "oldElectronClientDescription1": "Het lijkt erop dat u een oude versie van Jitsi Meet gebruikt, waarvan beveiligingskwetsbaarheden bekend zijn. Zorg ervoor dat u nu bijwerkt naar de ", "oldElectronClientDescription2": "nieuwste versie", "oldElectronClientDescription3": "!", + "openChat": "Chat openen", "participantWantsToJoin": "Wil deelnemen aan de vergadering", "participantsWantToJoin": "Willen deelnemen aan de vergadering", "passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) verwijderd door een andere deelnemer", "passwordSetRemotely": "$t(lockRoomPasswordUppercase) ingesteld door een ander deelnemer", "raiseHandAction": "Hand opsteken", - "raisedHand": "{{name}} zou graag willen spreken.", + "raisedHand": "Wilgraag deelnemen.", "raisedHands": "{{participantName}} en {{raisedHands}} meer mensen", "reactionSounds": "Geluiden uitschakelen", "reactionSoundsForAll": "Geluiden uitschakelen voor iedereen", + "screenShareNoAudio": "Audio delen is niet aangevinkt in het vensterselectiescherm.", + "screenShareNoAudioTitle": "Kon systeemaudio niet delen!", + "screenSharingAudioOnlyDescription": "Houd er rekening mee dat u door het delen van uw scherm de modus \"Beste prestaties\" beïnvloedt en dat u meer bandbreedte zult gebruiken.", + "screenSharingAudioOnlyTitle": "\"Beste prestaties\"-modus", + "selfViewTitle": "U kunt het zelfbeeld altijd uit de instellingen verwijderen", "somebody": "Iemand", "startSilentDescription": "Neem opnieuw aan de vergadering deel om audio in te schakelen", "startSilentTitle": "U neemt deel zonder audio-output!", "suboptimalBrowserWarning": "Helaas zal uw vergaderervaring hier waarschijnlijk niet zo goed zijn. Er wordt geprobeerd dit in de toekomst te verbeteren, maar tot die tijd kunt u proberen een van de volledig ondersteunde browsers te gebruiken.", "suboptimalExperienceTitle": "Browserwaarschuwing", + "suggestRecordingAction": "Starten", + "suggestRecordingDescription": "Wil je een opname starten?", + "suggestRecordingTitle": "Deze bijeenkomst opnemen", "unmute": "Dempen opheffen", - "videoMutedRemotelyDescription": "U kan hem ten alle tijden weer aanzetten.", + "unmuteScreen": "Schermdeling starten", + "unmuteVideo": "Videodemping opheffen", + "videoMutedRemotelyDescription": "U kan het altijd weer aanzetten.", "videoMutedRemotelyTitle": "Uw camera is uitgezet door {{participantDisplayName}}!", - "viewLobby": "Lobby bekijken", - "waitingParticipants": "{{waitingParticipants}} mensen" + "videoUnmuteBlockedDescription": "Camerademping opheffen en schermdeling is tijdelijk geblokkeerd vanwege systeemlimieten.", + "videoUnmuteBlockedTitle": "Camerademping opheffen en schermdeling is tijdelijk geblokkeerd!", + "viewLobby": "Wachtruimte weergeven", + "viewParticipants": "Deelnemers weergeven", + "viewVisitors": "Toeschouwers weergeven", + "waitingParticipants": "{{waitingParticipants}} mensen", + "waitingVisitors": "Toeschouwers in de wachtrij: {{waitingVisitors}}", + "waitingVisitorsTitle": "De vergadering is nog niet live!", + "whiteboardLimitDescription": "Sla uw voortgang op, want de gebruikerslimiet wordt binnenkort bereikt en het whiteboard wordt gesloten.", + "whiteboardLimitTitle": "Whiteboardgebruik" }, "participantsPane": { "actions": { "admit": "Toelaten", "admitAll": "Allen toelaten", "allow": "Sta deelnemers toe:", + "allowDesktop": "Schermdeling toestaan", "allowVideo": "Video toestaan", + "askDesktop": "Vragen om scherm te delen", "askUnmute": "Vragen om dempen op te heffen", "audioModeration": "Voor henzelf dempen op te heffen", "blockEveryoneMicCamera": "Blokkeer microfoon en camera van allen", + "breakoutRooms": "Aparte kamers", + "desktopModeration": "Schermdeling starten", + "goLive": "Live gaan", "invite": "Iemand uitnodigen", + "lowerAllHands": "Alle handen omlaag doen", + "lowerHand": "Hand omlaag doen", "moreModerationActions": "Meer moderatoropties", "moreModerationControls": "Meer moderatorinstellingen", "moreParticipantOptions": "Meer deelnemeropties", @@ -635,6 +945,8 @@ "muteAll": "Allen dempen", "muteEveryoneElse": "Alle anderen dempen", "reject": "Afwijzen", + "stopDesktop": "Schermdeling stoppen", + "stopEveryonesDesktop": "Schermdeling van iedereen stoppen", "stopEveryonesVideo": "Camera's van iedereen uitzetten", "stopVideo": "Camera uitzetten", "unblockEveryoneMicCamera": "Deblokkeer microfoon en camera van allen", @@ -642,31 +954,45 @@ }, "close": "Sluiten", "headings": { - "lobby": "Lobby ({{count}})", + "lobby": "Wachtruimte ({{count}})", "participantsList": "Deelnemers aan vergadering ({{count}})", - "waitingLobby": "In de lobby aan het wachten ({{count}})" + "viewerRequests": "Toeschouwersaanvragen {{count}}", + "visitorInQueue": " (wachten {{count}})", + "visitorRequests": " (aanvragen {{count}})", + "visitors": "Toeschouwers {{count}}", + "visitorsList": "Toeschouwers ({{count}})", + "waitingLobby": "Wachtenden in de wachtruimte ({{count}})" }, - "search": "Zoek deelnemers", + "search": "Deelnemers zoeken", + "searchDescription": "Begin met typen om deelnemers te filteren", "title": "Deelnemers" }, "passwordDigitsOnly": "Maximaal {{number}} cijfers", "passwordSetRemotely": "ingesteld door een andere deelnemer", + "pinParticipant": "{{participantName}} - Vastzetten", + "pinnedParticipant": "De deelnemer is vastgezet", "polls": { "answer": { - "skip": "Sla over", - "submit": "Verzend" + "edit": "Bewerken", + "send": "Verzenden", + "skip": "Overslaan", + "submit": "Indienen" }, "by": "Door {{ name }}", + "closeButton": "Peiling sluiten", "create": { - "addOption": "Voeg optie toe", + "accessibilityLabel": { + "send": "Peiling verzenden" + }, + "addOption": "Optie toevoegen", "answerPlaceholder": "Optie {{index}}", - "cancel": "Annuleer", - "create": "Creeër een peiling", + "cancel": "Annuleren", + "create": "Een peiling aanmaken", "pollOption": "Peiling optie {{index}}", "pollQuestion": "Peiling vraag", "questionPlaceholder": "Stel een vraag", - "removeOption": "Verwijder optie", - "send": "Verstuur" + "removeOption": "Optie verwijderen", + "send": "Opslaan" }, "errors": { "notUniqueOption": "Opties moeten uniek zijn" @@ -676,30 +1002,47 @@ "title": "Een nieuwe peiling is aangemaakt in deze vergadering" }, "results": { - "changeVote": "Verander stem", - "empty": "Er zijn nog geen peilingen in deze vergadering. Start hier een peiling!", - "hideDetailedResults": "Verberg details", - "showDetailedResults": "Toon details", - "vote": "Stem" + "changeVote": "Stem wijzigen", + "empty": "Er zijn nog geen peilingen in deze vergadering.", + "hideDetailedResults": "Details verbergen", + "showDetailedResults": "Details weergeven", + "vote": "Stemmen" } }, "poweredby": "mogelijk gemaakt door", "prejoin": { "audioAndVideoError": "Audio- en videofout:", - "audioDeviceProblem": "Er is een probleem met uw microfoon", + "audioDeviceProblem": "Er is een probleem met uw audio-apparaat", "audioOnlyError": "Audiofout:", "audioTrackError": "Kon audiotrack niet aanmaken.", - "callMe": "Bel me", - "callMeAtNumber": "Bel me op dit nummer:", + "callMe": "Bel mij", + "callMeAtNumber": "Bel mij op dit nummer:", "calling": "Bellen", "configuringDevices": "Apparaten instellen…", "connectedWithAudioQ": "Bent u verbonden met audio?", "connection": { + "failed": "Verbindingstest is mislukt!", "good": "Uw internetverbinding lijkt goed te zijn!", "nonOptimal": "Uw internetverbinding is niet optimaal", - "poor": "Uw internetverbinding is slecht" + "poor": "Uw internetverbinding is slecht", + "running": "Verbindingstest uitvoeren…" }, - "copyAndShare": "Kopieer & deel link naar vergadering", + "connectionDetails": { + "audioClipping": "We verwachten dat uw audio wordt overstuurd.", + "audioHighQuality": "Wij verwachten dat uw audio een uitstekende kwaliteit heeft.", + "audioLowNoVideo": "Wij verwachten dat uw audiokwaliteit laag is en dat er geen video is.", + "goodQuality": "Fantastisch! Uw mediakwaliteit wordt geweldig.", + "noMediaConnectivity": "We konden geen manier vinden om voor deze test een mediaverbinding tot stand te brengen. Dit wordt doorgaans veroorzaakt door een firewall of NAT.", + "noVideo": "We verwachten dat je video verschrikkelijk zal zijn.", + "testFailed": "De verbindingstest stuitte op onverwachte problemen, maar dit heeft mogelijk geen invloed op uw ervaring.", + "undetectable": "Als u nog steeds niet in de browser kunt bellen, raden wij u aan ervoor te zorgen dat uw luidsprekers, microfoon en camera goed zijn ingesteld, dat u uw browser rechten heeft verleend om uw microfoon en camera te gebruiken en dat uw browserversie up-to-date is. Als u nog steeds problemen ondervindt met bellen, kunt u contact opnemen met de ontwikkelaar van de webapplicatie.", + "veryPoorConnection": "We verwachten dat uw gesprekskwaliteit echt verschrikkelijk is.", + "videoFreezing": "We verwachten dat uw video bevriest, zwart wordt en gepixeld is.", + "videoHighQuality": "Wij verwachten dat uw video van goede kwaliteit is.", + "videoLowQuality": "We verwachten dat uw video een lage kwaliteit heeft qua framesnelheid en resolutie.", + "videoTearing": "We verwachten dat uw video gepixeld is of visuele artefacten heeft." + }, + "copyAndShare": "Link naar vergadering kopiëren & delen", "dialInMeeting": "Inbellen naar de vergadering", "dialInPin": "Inbellen naar de vergadering en pincode invoeren:", "dialing": "Inbellen", @@ -716,14 +1059,18 @@ "initiated": "Gesprek gestart", "joinAudioByPhone": "Deelnemen met telefoonaudio", "joinMeeting": "Deelnemen aan de vergadering", + "joinMeetingInLowBandwidthMode": "Deelnemen met lage bandbreedte", "joinWithoutAudio": "Deelnemen zonder audio", "keyboardShortcuts": "Sneltoetsen inschakelen", "linkCopied": "Link gekopieerd naar klembord", "lookGood": "Het klinkt alsof uw microfoon naar behoren werkt", "or": "of", "premeeting": "Voorbeeldscherm", + "proceedAnyway": "Toch doorgaan", + "recordingWarning": "Mogelijk nemen andere deelnemers dit gesprek op", "screenSharingError": "Fout bij schermdeling:", "startWithPhone": "Starten met telefoonaudio", + "unsafeRoomConsent": "Ik begrijp de risico's en ik wil deelnemen aan de vergadering", "videoOnlyError": "Videofout:", "videoTrackError": "Kon videotrack niet aanmaken.", "viewAllNumbers": "bekijk alle nummers" @@ -742,45 +1089,83 @@ "rejected": "Geweigerd", "ringing": "Gaat over…" }, - "privacyView": { - "title": "Privacy" - }, "profile": { - "setDisplayNameLabel": "Uw weergavenaam instellen", - "setEmailInput": "Voer e-mailadres in", - "setEmailLabel": "Uw Gravatar e-mail instellen", + "avatar": "avatar", + "setDisplayNameLabel": "Naam", + "setEmailInput": "E-mailadres invoeren", + "setEmailLabel": "Gravatar e-mailadres", "title": "Profiel" }, "raisedHand": "Zou graag willen spreken", + "raisedHandsLabel": "Aantal handen omhoog", + "record": { + "already": { + "linked": "De vergadering is al gekoppeld aan dit Verkoop-object." + }, + "type": { + "account": "Account", + "contact": "Contact", + "lead": "Potentiële klant", + "opportunity": "Kans", + "owner": "Eigenaar" + } + }, "recording": { "authDropboxText": "Uploaden naar Dropbox", "availableSpace": "Beschikbare ruimte: {{spaceLeft}} MB (circa {{duration}} minuten aan opname)", "beta": "BÈTA", "busy": "Er worden opnamemiddelen vrijgemaakt. Probeer het over enkele minuten opnieuw.", "busyTitle": "Alle opnemers zijn momenteel bezet", + "copyLink": "Link kopiëren", "error": "Opname is mislukt. Probeer het opnieuw.", + "errorFetchingLink": "Fout bij het ophalen van opname-link.", "expandedOff": "Opname is gestopt", "expandedOn": "De vergadering wordt momenteel opgenomen.", "expandedPending": "Opname wordt gestart…", "failedToStart": "Opname starten mislukt", "fileSharingdescription": "Opname delen met deelnemers aan vergadering", + "highlight": "Markeringen", + "highlightMoment": "Markeringsmoment", + "highlightMomentDisabled": "U kunt momenten markeren als de opname begint", + "highlightMomentSuccess": "Moment gemarkeerd", + "highlightMomentSucessDescription": "Uw gemarkeerde moment wordt toegevoegd aan de samenvatting van de vergadering.", + "inProgress": "Opname of live streaming in uitvoering", "limitNotificationDescriptionNative": "Vanwege een grote vraag wordt uw opname beperkt tot {{limit}} min. Voor ongelimiteerde opnamen, probeer <3>{{app}}.", "limitNotificationDescriptionWeb": "Vanwege een grote vraag wordt uw opname beperkt tot {{limit}} min. Voor ongelimiteerde opnamen, probeer {{app}}.", - "live": "LIVE", + "linkGenerated": "Wij hebben een link naar uw opname gegenereerd.", + "localRecordingNoNotificationWarning": "De opname wordt niet aan andere deelnemers bekendgemaakt. U moet hen laten weten dat de vergadering is opgenomen.", + "localRecordingNoVideo": "Video wordt niet opgenomen", + "localRecordingStartWarning": "Zorg ervoor dat u de opname stopt voordat u de vergadering verlaat, zodat u deze kunt opslaan.", + "localRecordingStartWarningTitle": "Stop de opname om deze op te slaan", + "localRecordingVideoStop": "Als u uw video stopt, stopt u ook de lokale opname. Weet u zeker dat u door wilt gaan?", + "localRecordingVideoWarning": "Om uw video op te nemen moet u hem aan hebben staan bij het starten van de opname", + "localRecordingWarning": "Zorg ervoor dat u het huidige tabblad selecteert om de juiste video en audio te gebruiken.", "loggedIn": "Aangemeld als {{userName}}", + "noMicPermission": "Microfoontrack kon niet worden gemaakt. Geef toestemming om de microfoon te gebruiken.", + "noStreams": "Geen audio - of videostream gedetecteerd.", "off": "Opname gestopt", "offBy": "{{name}} heeft de opname gestopt", "on": "Opnemen", "onBy": "{{name}} heeft de opname gestart", + "onlyRecordSelf": "Neem alleen mijn audio - en videostreams op", "pending": "Voorbereiden op opnemen van de vergadering…", - "rec": "OPN", + "policyError": "U probeerde te snel een opname te starten. Probeer het later nog eens!", + "recordAudioAndVideo": "Audio en video opnemen", + "recordTranscription": "Transcriptie opnemen", + "saveLocalRecording": "Opnamebestand lokaal opslaan (Bèta)", "serviceDescription": "Uw opname wordt opgeslagen door de opnameservice", + "serviceDescriptionCloud": "Cloud-opname", + "serviceDescriptionCloudInfo": "Opgenomen vergaderingen worden automatisch 24 uur na hun opnametijd gewist.", "serviceName": "Opnameservice", + "sessionAlreadyActive": "Deze sessie wordt al opgenomen of live gestreamd.", + "showAdvancedOptions": "Geavanceerde opties", "signIn": "Aanmelden", "signOut": "Afmelden", + "surfaceError": "Selecteer het huidige tabblad.", "title": "Opnemen", "unavailable": "Oeps! {{serviceName}} is momenteel niet beschikbaar. Er wordt aan een oplossing gewerkt. Probeer het later opnieuw.", - "unavailableTitle": "Opname niet beschikbaar" + "unavailableTitle": "Opname niet beschikbaar", + "uploadToCloud": "Uploaden naar de cloud" }, "screenshareDisplayName": "Scherm van {{name}}", "sectionList": { @@ -789,10 +1174,18 @@ "security": { "about": "U kunt een $t(lockRoomPassword) toevoegen aan uw vergadering. Deelnemers moeten het $t(lockRoomPassword) opgeven voordat zij aan de vergadering mogen deelnemen.", "aboutReadOnly": "Moderators kunnen een $t(lockRoomPassword) toevoegen aan de vergadering. Deelnemers moeten het $t(lockRoomPassword) opgeven voordat zij aan de vergadering mogen deelnemen.", - "insecureRoomNameWarning": "De naam van de ruimte is onveilig. Ongewenste deelnemers kunnen deelnemen aan uw vergadering. Overweeg uw vergadering te beveiligen via de beveiligingsknop.", - "title": "Beveiligingsopties" + "insecureRoomNameWarningNative": "De kamernaam is onveilig. Ongewenste deelnemers kunnen deelnemen aan uw vergadering. {{recommendAction}} Lees meer over het beveiligen van uw vergadering", + "insecureRoomNameWarningWeb": "De kamernaam is onveilig. Ongewenste deelnemers kunnen deelnemen aan uw vergadering. {{recommendAction}} Lees hier meer over het beveiligen van uw vergadering.", + "title": "Beveiligingsopties", + "unsafeRoomActions": { + "meeting": "Overweeg om uw vergadering te beveiligen met behulp van de beveiligingsknop.", + "prejoin": "Overweeg om een meer unieke vergadernaam te gebruiken.", + "welcome": "Overweeg om een meer unieke vergadernaam te gebruiken, of kies een van de suggesties." + } }, "settings": { + "audio": "Audio", + "buttonLabel": "Instellingen", "calendar": { "about": "De agenda-integratie van {{appName}} wordt gebruikt voor een veilige toegang tot uw agenda, zodat het aankomende afspraken kan uitlezen.", "disconnect": "Verbinding verbreken", @@ -800,30 +1193,44 @@ "signedIn": "Agenda-afspraken voor {{email}} worden uitgelezen. Klik op de knop 'Verbinding verbreken' hieronder om de toegang tot agenda-afspraken te stoppen.", "title": "Agenda" }, + "chatWithPermissions": "Chat uitschakelen voor niet-moderators", "desktopShareFramerate": "Framesnelheid schermdeling", "desktopShareHighFpsWarning": "Een hogere framesnelheid voor schermdeling kan invloed hebben op de bandbreedte. U moet het schermdelen opnieuw beginnen om de nieuwe instellingen toe te passen.", "desktopShareWarning": "U moet het schermdelen opnieuw beginnen om de nieuwe instellingen toe te passen.", "devices": "Apparaten", "followMe": "Iedereen volgt mij", + "followMeRecorder": "Opname volgt mij", "framesPerSecond": "beelden per seconde", "incomingMessage": "Binnenkomend bericht", "language": "Taal", "loggedIn": "Aangemeld als {{name}}", + "maxStageParticipants": "Maximaal aantal deelnemers dat kan worden vastgezet op het hoofdpodium", "microphones": "Microfoons", "moderator": "Moderator", + "moderatorOptions": "Moderator-opties", "more": "Meer", "name": "Naam", "noDevice": "Geen", + "notifications": "Meldingen", + "participantJoined": "Deelnemer heeft zich aangesloten", + "participantKnocking": "Deelnemer heeft de wachtruimte betreden", + "participantLeft": "Deelnemer is vertrokken", + "playSounds": "Speel geluid af bij", + "reactions": "Vergaderreacties", + "sameAsSystem": "Zelfde als systeem ({{label}})", "selectAudioOutput": "Audio-uitvoer", "selectCamera": "Camera", "selectMic": "Microfoon", "selfView": "Eigen beeld", - "sounds": "Geluiden", - "speakers": "Speakers", + "shortcuts": "Sneltoetsen", + "showSubtitlesOnStage": "Ondertitels op het podium tonen", + "speakers": "Sprekers", "startAudioMuted": "Iedereen start gedempt", "startReactionsMuted": "Reactiegeluiden voor iedereen dempen", "startVideoMuted": "Iedereen start verborgen", - "title": "Instellingen" + "talkWhileMuted": "Spreken terwijl gedempt", + "title": "Instellingen", + "video": "Video" }, "settingsView": { "advanced": "Geavanceerd", @@ -831,6 +1238,7 @@ "alertOk": "OK", "alertTitle": "Waarschuwing", "alertURLText": "De ingevoerde server-URL is ongeldig", + "apply": "Toepassen", "buildInfoSection": "Versiegegevens", "conferenceSection": "Vergadering", "disableCallIntegration": "Schakel inbellen via telefoon uit", @@ -838,13 +1246,23 @@ "disableCrashReportingWarning": "Weet u zeker dat u crashrapportage wilt uitschakelen? De instelling wordt toegepast nadat u de app opnieuw hebt opgestart.", "disableP2P": "Schakel Peer-to-Peer-modus uit", "displayName": "Weergavenaam", - "email": "E‑mail", + "displayNamePlaceholderText": "Bijv: Jan Jansen", + "email": "E-mail", + "emailPlaceholderText": "email@example.com", + "gavatarMessage": "Als uw e-mail is gekoppeld aan een Gravatar-account, gebruiken we deze om als profielfoto weer te geven.", + "goTo": "Ga naar", "header": "Instellingen", + "help": "Hulp", + "links": "Links", + "privacy": "Privacy", "profileSection": "Profiel", + "sdkVersion": "SDK-versie", "serverURL": "Server-URL", "showAdvanced": "Geavanceerde instellingen weergeven", + "startCarModeInLowBandwidthMode": "Start de Auto-modus met lage bandbreedte", "startWithAudioMuted": "Met audio gedempt starten", "startWithVideoMuted": "Met video gedempt starten", + "terms": "Voorwaarden", "version": "Versie" }, "share": { @@ -853,14 +1271,24 @@ }, "speaker": "Spreker", "speakerStats": { - "hours": "{{count}}u", + "angry": "Boos", + "disgusted": "Walgend", + "displayEmotions": "Emoties weergeven", + "fearful": "Angstig", + "happy": "Blij", + "hours": "{{count}}h", + "labelTooltip": "Aantal deelnemers: {{count}}", "minutes": "{{count}}m", "name": "Naam", + "neutral": "Neutraal", + "sad": "Triest", "search": "Zoeken", - "searchHint": "Zoek deelnemers", + "searchDescription": "Begin met typen om deelnemers te filteren", + "searchHint": "Deelnemers zoeken", "seconds": "{{count}}s", "speakerStats": "Sprekerstatistieken", - "speakerTime": "Sprekertijd" + "speakerTime": "Sprekertijd", + "surprised": "Verrast" }, "startupoverlay": { "genericTitle": "De vergadering heeft toegang tot uw microfoon en camera nodig.", @@ -872,70 +1300,135 @@ "text": "Druk op de knop Opnieuw deelnemen om opnieuw verbinding te maken.", "title": "Uw videogesprek is onderbroken omdat deze computer op de slaapstand overging." }, + "termsView": { + "title": "Voorwaarden" + }, + "toggleTopPanelLabel": "Bovenpaneel schakelen", "toolbar": { "Settings": "Instellingen", "accessibilityLabel": { "Settings": "Instellingen in- of uitschakelen", "audioOnly": "Alleen audio in- of uitschakelen", "audioRoute": "Het afspeelapparaat selecteren", - "breakoutRoom": "Neem deel aan/verlaat aparte vergaderruimte", + "boo": "Boe", + "breakoutRooms": "Aparte vergaderruimtes", "callQuality": "Videokwaliteit beheren", + "carmode": "Auto-modus", "cc": "Ondertiteling in- of uitschakelen", "chat": "Chatvenster in- of uitschakelen", + "clap": "Klappen", + "closeChat": "Chat sluiten", + "closeCustomPanel": "Sluiten", + "closeMoreActions": "Menu Meer acties sluiten", + "closeParticipantsPane": "Deelnemrsvenster sluiten", + "closedCaptions": "Ondertiteling", + "collapse": "Inklappen", "document": "Gedeeld document in- of uitschakelen", + "documentClose": "Gedeeld document sluiten", + "documentOpen": "Gedeeld document openne", "download": "Download onze apps", "embedMeeting": "Vergadering embedden", "endConference": "Vergadering voor iedereen beëindigen", + "enterFullScreen": "Volledig scherm weergeven", + "enterTileView": "Tegelweergave activeren", + "exitFullScreen": "Volledig scherm sluiten", + "exitTileView": "Tegelweergave sluiten", + "expand": "Uitbreiden", "feedback": "Feedback achterlaten", "fullScreen": "Volledig scherm in- of uitschakelen", + "giphy": "GIPHY-menu in- of uitschakelen", "grantModerator": "Moderatorrechten verlenen", "hangup": "Het gesprek verlaten", + "heading": "Werkbalk", "help": "Hulp", + "hideWhiteboard": "Whiteboard verbergen", "invite": "Personen uitnodigen", "kick": "Deelnemer verwijderen", + "laugh": "Lachen", "leaveConference": "Vergadering verlaten", - "lobbyButton": "Lobby-modus in- of uitschakelen", + "like": "Duim omhoog", + "linkToSalesforce": "Koppelen met Verkoop", + "lobbyButton": "Wachtruimte in- of uitschakelen", "localRecording": "Besturingselementen voor lokale opname in- of uitschakelen", "lockRoom": "Wachtwoord voor vergadering in- of uitschakelen", + "love": "Hart", + "lowerHand": "Hand omlaag doen", "moreActions": "Meer acties menu in- of uitschakelen", "moreActionsMenu": "Meer acties menu", "moreOptions": "Meer opties weergeven", "mute": "Audio dempen in- of uitschakelen", "muteEveryone": "Iedereen dempen", "muteEveryoneElse": "Alle anderen dempen", + "muteEveryoneElsesVideoStream": "Video van alle anderen stoppen", + "muteEveryonesVideoStream": "Video van iedereen stoppen", + "muteGUMPending": "Verbinding maken met uw microfoon", "noiseSuppression": "Ruisonderdrukking", + "openChat": "Chat openen", "participants": "Deelnemers", "pip": "Picture-in-Picture-modus in- of uitschakelen", "privateMessage": "Verstuur privébericht", "profile": "Uw profiel bewerken", "raiseHand": "Hand opsteken in- of uitschakelen", + "react": "Reacties op berichten", + "reactions": "Reacties", + "reactionsMenu": "Reacties-menu", "recording": "Opnemen in- of uitschakelen", "remoteMute": "Deelnemer dempen", "remoteVideoMute": "Camera van deelnemer uitschakelen", "security": "Beveiligingsopties", "selectBackground": "Achtergrond selecteren", + "selfView": "Zelfbeeld in- of uitschakelen", "shareRoom": "Iemand uitnodigen", "shareYourScreen": "Schermdeling in- of uitschakelen", "shareaudio": "Audio delen", "sharedvideo": "Video delen in- of uitschakelen", "shortcuts": "Sneltoetsen in- of uitschakelen", "show": "Op podium weergeven", + "showWhiteboard": "Whiteboard weergeven", + "silence": "Stilte", "speakerStats": "Sprekerstatistieken in- of uitschakelen", + "stopScreenSharing": "Schermdeling stoppen", + "stopSharedVideo": "Video stoppen", + "surprised": "Verrast", "tileView": "Tegelweergave in- of uitschakelen", "toggleCamera": "Camera wisselen", "toggleFilmstrip": "Filmstrip in- of uitschakelen", - "videomute": "Video dempen in- of uitschakelen" + "unmute": "Microfoondemping opheffen", + "videoblur": "Videovervaging in- of uitschakelen", + "videomute": "Camera stoppen", + "videomuteGUMPending": "Verbinding maken met uw camera", + "videounmute": "Camera starten" }, "addPeople": "Personen aan uw gesprek toevoegen", + "advancedAudioSettings": { + "aec": { + "label": "Akoestische echo-onderdrukking" + }, + "agc": { + "label": "Automatische versterking" + }, + "ns": { + "label": "Ruisonderdrukking" + }, + "stereo": { + "label": "Stereo" + } + }, "audioOnlyOff": "Lage bandbreedte-modus uitschakelen", "audioOnlyOn": "Lage bandbreedte-modus inschakelen", "audioRoute": "Het afspeelapparaat selecteren", "audioSettings": "Audio-instellingen", "authenticate": "Authenticeren", + "boo": "Boe", "callQuality": "Videokwaliteit beheren", "chat": "Chat openen / sluiten", + "clap": "Klappen", "closeChat": "Chat sluiten", + "closeCustomPanel": "Sluiten", + "closeParticipantsPane": "Deelnemersvenster sluiten", "closeReactionsMenu": "Reactiemenu sluiten", + "closedCaptions": "Ondertiteling", + "copilot": "Copilot", "disableNoiseSuppression": "Ruisonderdrukking uitschakelen", "disableReactionSounds": "U kunt reactiegeluiden uitschakelen voor deze vergadering", "documentClose": "Gedeeld document sluiten", @@ -943,28 +1436,37 @@ "download": "Download onze apps", "e2ee": "Eind-tot-eind-versleuteling", "embedMeeting": "Vergadering embedden", + "enableNoiseSuppression": "Extra ruisonderdrukking inschakelen", "endConference": "Vergadering voor iedereen beëindigen", "enterFullScreen": "Volledig scherm weergeven", "enterTileView": "Tegelweergave openen", "exitFullScreen": "Volledig scherm sluiten", "exitTileView": "Tegelweergave sluiten", "feedback": "Feedback achterlaten", - "hangup": "Verlaten", + "fileSharing": "Bestanden delen", + "giphy": "GIPHY-menu in- of uitschakelen", + "hangup": "Vergadering verlaten", "help": "Hulp", + "hideWhiteboard": "Whiteboard verbergen", "invite": "Personen uitnodigen", "joinBreakoutRoom": "Deelnemen aan aparte vergaderruimte", + "laugh": "Lachen", "leaveBreakoutRoom": "Aparte vergaderruimte verlaten", "leaveConference": "Vergadering verlaten", - "lobbyButtonDisable": "Schakel lobby-modus uit", - "lobbyButtonEnable": "Schakel lobby-modus in", + "like": "Duim omhoog", + "linkToSalesforce": "Koppelen aan Verkoop", + "lobbyButtonDisable": "Wachtruimte uitschakelen", + "lobbyButtonEnable": "Wachtruimte inschakelen", "login": "Aanmelden", "logout": "Afmelden", + "love": "Hart", "lowerYourHand": "Uw hand laten zakken", "moreActions": "Meer acties", "moreOptions": "Meer opties", "mute": "Dempen / dempen opheffen", "muteEveryone": "Iedereen dempen", "muteEveryonesVideo": "Camera's van iedereen uitzetten", + "muteGUMPending": "Verbinding maken met uw microfoon", "noAudioSignalDesc": "Als u niet met opzet hebt gedempt vanuit systeeminstellingen of hardware, overweeg dan van apparaat te wisselen.", "noAudioSignalDescSuggestion": "Als u niet met opzet hebt gedempt vanuit systeeminstellingen of hardware, overweeg dan over te schakelen naar het gesuggereerde apparaat.", "noAudioSignalDialInDesc": "U kunt ook inbellen met:", @@ -977,40 +1479,63 @@ "openReactionsMenu": "Reactiemenu openen", "participants": "Deelnemers", "pip": "Picture-in-Picture-modus activeren", + "polls": "Peilingen", "privateMessage": "Verstuur privébericht", "profile": "Uw profiel bewerken", "raiseHand": "Uw hand opsteken / laten zakken", "raiseYourHand": "Uw hand opsteken", + "reactionBoo": "Reactie Boe versturen", + "reactionClap": "Reactie Klappen versturen", + "reactionHeart": "Reactie Hart versturen", + "reactionLaugh": "Reactie Lachen versturen", + "reactionLike": "Reactie Duim omhoog versturen", + "reactionLove": "Reactie Liefde versturen", + "reactionSilence": "Reactie Stilte versturen", + "reactionSurprised": "Reactie Verrast verstruren", + "reactions": "Reacties", "security": "Beveiligingsopties", "selectBackground": "Achtergrond selecteren", "shareRoom": "Iemand uitnodigen", "shareaudio": "Audio delen", "sharedvideo": "Een video delen", "shortcuts": "Sneltoetsen weergeven", + "showWhiteboard": "Whiteboard weergeven", + "silence": "Stilte", "speakerStats": "Sprekerstatistieken", "startScreenSharing": "Schermdeling starten", "startSubtitles": "Ondertiteling starten", + "stopAudioSharing": "Audio delen stoppen", "stopScreenSharing": "Schermdeling stoppen", "stopSharedVideo": "Video stoppen", "stopSubtitles": "Ondertiteling stoppen", + "surprised": "Verrast", "talkWhileMutedPopup": "Probeert u te spreken? U bent gedempt.", "tileViewToggle": "Tegelweergave in- of uitschakelen", "toggleCamera": "Camera wisselen", + "unmute": "Microfoondemping opheffen", "videoSettings": "Instellingen van camera", - "videomute": "Camera aanzetten / uitzetten" + "videomute": "Camera aanzetten / uitzetten", + "videomuteGUMPending": "Verbinding maken met uw camera", + "videounmute": "Camera starten" }, "transcribing": { "ccButtonTooltip": "Ondertiteling starten / stoppen", - "error": "Transcriberen is mislukt. Probeer het opnieuw.", - "expandedLabel": "Transcriberen is momenteel ingeschakeld", - "failedToStart": "Transcriberen starten mislukt", - "labelToolTip": "De vergadering wordt getranscribeerd", - "off": "Transcriberen gestopt", - "pending": "Voorbereiden op transcriberen van de vergadering…", - "start": "Weergave van ondertiteling starten", - "stop": "Weergave van ondertiteling stoppen", - "tr": "TR" + "expandedLabel": "Transcribering is momenteel aan de gang", + "failed": "Transcriberen is mislukt", + "labelTooltip": "Deze bijeenkomst wordt getranscribeerd.", + "labelTooltipExtra": "Daarnaast zal er later een transcript beschikbaar zijn.", + "openClosedCaptions": "Ondertiteling openen", + "original": "Origineel", + "sourceLanguageDesc": "Momenteel is de vergadertaal ingesteld op {{sourceLanguage}}.
U kunt het vanaf hier ", + "sourceLanguageHere": "veranderen", + "start": "Ondertiteling starten", + "stop": "Ondertiteling stoppen", + "subtitles": "Ondertiteling", + "subtitlesOff": "Uit", + "tr": "TR", + "translateTo": "Vertalen naar" }, + "unpinParticipant": "{{participantName}} - Losmaken", "userMedia": { "grantPermissions": "Verleen toestemming om uw camera en microfoon te gebruiken." }, @@ -1027,7 +1552,7 @@ "videoStatus": { "adjustFor": "Aanpassen voor:", "audioOnly": "AUD", - "audioOnlyExpanded": "U bent in lage bandbreedte-modus. In deze modus ontvangt u alleen audio en schermdeling.", + "audioOnlyExpanded": "U bent in beperkte bandbreedte-modus. In deze modus ontvangt u alleen audio en schermdeling.", "bestPerformance": "Beste prestatie", "callQuality": "Videokwaliteit", "hd": "HD", @@ -1035,48 +1560,100 @@ "highDefinition": "Hoge resolutie", "highestQuality": "Hoogste kwaliteit", "labelTooiltipNoVideo": "Geen video", - "labelTooltipAudioOnly": "Lage bandbreedte-modus ingeschakeld", + "labelTooltipAudioOnly": "Beperkte bandbreedte-modus ingeschakeld", "ld": "LD", "ldTooltip": "U bekijkt video in lage resolutie", "lowDefinition": "Lage resolutie", "performanceSettings": "Prestatie instellingen", + "recording": "Deze bijeenkomst wordt opgenomen.", "sd": "SD", "sdTooltip": "U bekijkt video in standaard resolutie", - "standardDefinition": "Standaard resolutie" + "standardDefinition": "Standaard resolutie", + "streaming": "Streaming in uitvoering" }, "videothumbnail": { "connectionInfo": "Verbindingsinformatie", + "demote": "Verplaatsen naar toeschouwer", "domute": "Dempen", + "domuteDesktop": "Schermdeling stoppen", + "domuteDesktopOfOthers": "Stop met het delen van schermen voor alle anderen", "domuteOthers": "Alle anderen dempen", "domuteVideo": "Camera uitschakelen", "domuteVideoOfOthers": "Camera van alle anderen uitschakelen", "flip": "Omdraaien", "grantModerator": "Moderatorrechten verlenen", - "hideSelfView": "Verberg eigen beeld", + "hideSelfView": "Zelfbeeld verbergen", "kick": "Verwijderen", + "mirrorVideo": "Mijn video spiegelen", "moderator": "Moderator", "mute": "Deelnemer is gedempt", "muted": "Gedempt", + "pinToStage": "Vastzetten aan het podium", "remoteControl": "Extern beheer starten / stoppen", + "screenSharing": "Deelnemer deelt hun scherm", "show": "Op podium weergeven", + "showSelfView": "Zelfbeeld weergeven", + "unpinFromStage": "Losmaken", + "verify": "Deelnemer verifiëren", "videoMuted": "Camera uitgeschakeld", "videomute": "Deelnemer heeft de camera gestopt" }, "virtualBackground": { + "accessibilityLabel": { + "currentBackground": "Huidige achtergron: {{background}}", + "selectBackground": "Achtergrond selecteren" + }, "addBackground": "Achtergrond toevoegen", "apply": "Toepassen", + "backgroundEffectError": "Toepassen van achtergrondeffect niet gelukt.", "blur": "Vervagen", + "deleteImage": "Afbeelding verwijderen", + "desktopShare": "Bureaublad delen", + "desktopShareError": "Kon bureaublad delen niet aanmaken", + "image1": "Strand", + "image2": "Witte neutrale muur", + "image3": "Witte lege kamer", + "image4": "Zwarte vloerlamp", + "image5": "Berg", + "image6": "Bos ", + "image7": "Zonsopkomst", "none": "Geen", "pleaseWait": "Even geduld a.u.b…", "removeBackground": "Verwijder achtergrond", "slightBlur": "Licht vervagen", - "title": "Achtergronden" + "title": "Virtuele achtergronden", + "uploadedImage": "Geüploade afbeelding {{index}}", + "webAssemblyWarning": "WebAssembly is niet ondersteund", + "webAssemblyWarningDescription": "WebAssembly uitgeschakeld of niet ondersteund door deze browser" }, + "visitors": { + "chatIndicator": "(toeschouwer)", + "joinMeeting": { + "description": "U bent momenteel een toeschouwer bij deze conferentie.", + "raiseHand": "Raise your hand", + "title": "Joining meeting", + "wishToSpeak": "If you wish to speak, please raise your hand below and wait for the moderator's approval." + }, + "labelTooltip": "Number of viewers: {{count}}", + "notification": { + "demoteDescription": "Sent here by {{actor}}, raise your hand to participate", + "noMainParticipantsDescription": "A participant needs to start the meeting. Please try again in a bit.", + "noMainParticipantsTitle": "This meeting hasn't started yet.", + "noVisitorLobby": "You cannot join while there is a lobby enabled for the meeting.", + "notAllowedPromotion": "A participant needs to allow your request first.", + "requestToJoin": "Hand Raised", + "requestToJoinDescription": "Your request was sent to the moderators. Hang tight!", + "title": "You are a viewer in the meeting" + }, + "waitingMessage": "You'll join the meeting as soon as it is live!" + }, + "volumeSlider": "Volume slider", "welcomepage": { "accessibilityLabel": { "join": "Tik om deel te nemen", "roomname": "Voer naam van ruimte in" }, + "addMeetingName": "Add Meeting name", "appDescription": "U kunt nu videochatten met het gehele team. Nodig uit wie u maar wilt. {{app}} is een volledig versleutelde, 100% open-source oplossing voor videovergaderingen, die u wanneer u maar wilt gratis kunt gebruiken — zonder dat u een account nodig hebt.", "audioVideoSwitch": { "audio": "Spraak", @@ -1094,18 +1671,39 @@ "info": "Informatie", "jitsiOnMobile": "Jitsi op de mobiele telefoon - download onze apps en start overal een vergadering", "join": "AANMAKEN / DEELNEMEN", + "logo": { + "calendar": "Agenda logo", + "desktopPreviewThumbnail": "Desktop preview thumbnail", + "googleLogo": "Google logo", + "logoDeepLinking": "Jitsi meet logo", + "microsoftLogo": "Microsoft logo", + "policyLogo": "Policy logo" + }, + "meetingsAccessibilityLabel": "Meetings", + "mobileDownLoadLinkAndroid": "Download mobile app for Android", + "mobileDownLoadLinkFDroid": "Download mobile app for F-Droid", + "mobileDownLoadLinkIos": "Download mobile app for iOS", "moderatedMessage": "Of boek een vergadering URL van tevoren waar u de enige moderator bent.", "privacy": "Privacy", "recentList": "Recent", "recentListDelete": "Verwijderen", "recentListEmpty": "Uw recent lijst is momenteel leeg. Wanneer u chat met uw team, vindt u hier al uw recente vergaderingen.", + "recentMeetings": "Uw recente bijeenkomsten", "reducedUIText": "Welkom in {{app}}!", "roomNameAllowedChars": "Naam van vergadering mag geen van deze tekens bevatten: ?, &, :, ', \", %, #.", "roomname": "Voer naam van ruimte in", "roomnameHint": "Voer de naam of URL in van de ruimte die u wilt betreden. U kunt een naam verzinnen, maar laat deze wel weten aan de andere deelnemers, zodat zij dezelfde naam invoeren.", "sendFeedback": "Feedback versturen", + "settings": "Instellingen", "startMeeting": "Vergadering starten", "terms": "Voorwaarden", - "title": "Veilige, volledig uitgeruste en geheel gratis videovergaderingen" + "title": "Veilige, volledig uitgeruste en geheel gratis videovergaderingen", + "upcomingMeetings": "Uw komende vergaderingen" + }, + "whiteboard": { + "accessibilityLabel": { + "heading": "Whiteboard" + }, + "screenTitle": "Whiteboard" } } diff --git a/lang/main.json b/lang/main.json index 683af4d8bdce..db2e40dde604 100644 --- a/lang/main.json +++ b/lang/main.json @@ -234,6 +234,9 @@ "video_ssrc": "Video SSRC:", "yes": "yes" }, + "customPanel": { + "close": "Close" + }, "dateUtils": { "earlier": "Earlier", "today": "Today", @@ -598,6 +601,7 @@ "newFileNotification": "{{ participantName }} shared '{{ fileName }}'", "removeFile": "Remove", "removeFileSuccess": "File removed successfully", + "uploadDisabled": "Not allowed to upload files. Ask a moderator for permission rights for that operation.", "uploadFailedDescription": "Please try again.", "uploadFailedTitle": "Upload failed", "uploadFile": "Share file" @@ -1505,6 +1509,7 @@ "chat": "Open / Close chat", "clap": "Clap", "closeChat": "Close chat", + "closeCustomPanel": "Close", "closeMoreActions": "Close more actions menu", "closeParticipantsPane": "Close participants pane", "closedCaptions": "Closed captions", @@ -1610,9 +1615,11 @@ "chat": "Open / Close chat", "clap": "Clap", "closeChat": "Close chat", + "closeCustomPanel": "Close", "closeParticipantsPane": "Close participants pane", "closeReactionsMenu": "Close reactions menu", "closedCaptions": "Closed captions", + "copilot": "Copilot", "disableNoiseSuppression": "Disable extra noise suppression", "disableReactionSounds": "You can disable reaction sounds for this meeting", "documentClose": "Close shared document", diff --git a/modules/API/API.js b/modules/API/API.js index 6e904141910f..7c87172a96e1 100755 --- a/modules/API/API.js +++ b/modules/API/API.js @@ -240,13 +240,27 @@ function initCommands() { APP.store.dispatch(muteAllParticipants(exclude, muteMediaType)); }, 'mute-remote-participant': (participantId, mediaType) => { - if (!isLocalParticipantModerator(APP.store.getState())) { - logger.error('Missing moderator rights to mute remote participant'); + const state = APP.store.getState(); + const muteMediaType = mediaType ? mediaType : MEDIA_TYPE.AUDIO; + const localParticipant = getLocalParticipant(state); + + // Check if targeting the local participant + if (participantId === localParticipant?.id) { + + if (muteMediaType === MEDIA_TYPE.AUDIO) { + APP.conference.toggleAudioMuted(false); + } else if (muteMediaType === MEDIA_TYPE.VIDEO) { + APP.conference.toggleVideoMuted(false, true); + } return; } - const muteMediaType = mediaType ? mediaType : MEDIA_TYPE.AUDIO; + if (!isLocalParticipantModerator(state)) { + logger.error('Missing moderator rights to mute remote participant'); + + return; + } APP.store.dispatch(muteRemote(participantId, muteMediaType)); }, @@ -790,7 +804,7 @@ function initCommands() { } if (transcription) { - APP.store.dispatch(setRequestingSubtitles(true, false, null)); + APP.store.dispatch(setRequestingSubtitles(true, false, null, true)); } }, @@ -812,7 +826,7 @@ function initCommands() { } if (transcription) { - APP.store.dispatch(setRequestingSubtitles(false, false, null)); + APP.store.dispatch(setRequestingSubtitles(false, false, null, true)); } if (mode === 'local') { @@ -1417,17 +1431,15 @@ class API { * * @param {string} participantId - The ID of the participant. * @param {boolean} isMuted - True if muted, false if unmuted. - * @param {string} mediaType - Media type that was muted ('audio', 'video', or 'desktop'). - * @param {boolean} isSelfMuted - True if participant muted themselves, false if muted by moderator. + * @param {string} mediaType - Media type that was muted ('audio' or 'video'). * @returns {void} */ - notifyParticipantMuted(participantId, isMuted, mediaType, isSelfMuted = true) { + notifyParticipantMuted(participantId, isMuted, mediaType) { this._sendEvent({ name: 'participant-muted', id: participantId, isMuted, - mediaType, - isSelfMuted + mediaType }); } @@ -2241,6 +2253,32 @@ class API { }); } + /** + * Notify the external application that a file has been uploaded. + * + * @param {Object} fileMetadata - The file metadata. + * @returns {void} + */ + notifyFileUploaded(fileMetadata) { + this._sendEvent({ + name: 'file-uploaded', + file: fileMetadata + }); + } + + /** + * Notify the external application that a file has been deleted. + * + * @param {string} fileId - The ID of the deleted file. + * @returns {void} + */ + notifyFileDeleted(fileId) { + this._sendEvent({ + name: 'file-deleted', + fileId + }); + } + /** * Notify the external application that the audio or video is being shared by a participant. * diff --git a/modules/API/external/external_api.js b/modules/API/external/external_api.js index b139558b78bd..04ca95ead351 100644 --- a/modules/API/external/external_api.js +++ b/modules/API/external/external_api.js @@ -133,6 +133,8 @@ const events = { 'face-landmark-detected': 'faceLandmarkDetected', 'feedback-submitted': 'feedbackSubmitted', 'feedback-prompt-displayed': 'feedbackPromptDisplayed', + 'file-deleted': 'fileDeleted', + 'file-uploaded': 'fileUploaded', 'filmstrip-display-changed': 'filmstripDisplayChanged', 'incoming-message': 'incomingMessage', 'knocking-participant': 'knockingParticipant', diff --git a/package.json b/package.json index ce7bf80a03ef..17cd3badbc7c 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "js-md5": "0.6.1", "js-sha512": "0.8.0", "jwt-decode": "2.2.0", - "lib-jitsi-meet": "https://github.com/internxt/lib-jitsi-meet/releases/download/v.0.0.20-debug/lib-jitsi-meet-0.0.20-debug.tgz", + "lib-jitsi-meet": "https://github.com/internxt/lib-jitsi-meet/releases/download/v.0.0.21/lib-jitsi-meet-0.0.21.tgz", "lodash-es": "4.17.23", "moment": "2.29.4", "moment-duration-format": "2.2.2", @@ -234,7 +234,7 @@ "unorm": "1.6.0", "vitest": "^4.0.15", "webdriverio": "^9.23.0", - "webpack": "5.95.0", + "webpack": "5.105.1", "webpack-bundle-analyzer": "4.4.2", "webpack-cli": "5.1.4", "webpack-dev-server": "5.2.1" diff --git a/react/features/app/actions.native.ts b/react/features/app/actions.native.ts index 23ef235c14dc..1dc5c5f6360e 100644 --- a/react/features/app/actions.native.ts +++ b/react/features/app/actions.native.ts @@ -1,4 +1,4 @@ -import { setRoom } from '../base/conference/actions'; +import { setRoom } from '../base/conference/actions.native'; import { getConferenceState } from '../base/conference/functions'; import { configWillLoad, @@ -29,7 +29,7 @@ import { } from '../mobile/navigation/rootNavigationContainerRef'; import { screen } from '../mobile/navigation/routes'; import { clearNotifications } from '../notifications/actions'; -import { isUnsafeRoomWarningEnabled } from '../prejoin/functions'; +import { isUnsafeRoomWarningEnabled } from '../prejoin/functions.native'; import { maybeRedirectToTokenAuthUrl } from './actions.any'; import { addTrackStateToURL, getDefaultURL } from './functions.native'; diff --git a/react/features/app/middlewares.web.ts b/react/features/app/middlewares.web.ts index 1b96dabc899a..57beae50b446 100644 --- a/react/features/app/middlewares.web.ts +++ b/react/features/app/middlewares.web.ts @@ -27,5 +27,6 @@ import '../talk-while-muted/middleware'; import '../toolbox/middleware'; import '../web-hid/middleware'; import '../whiteboard/middleware.web'; +import '../custom-panel/middleware.web'; import "./middlewares.any"; diff --git a/react/features/app/reducers.web.ts b/react/features/app/reducers.web.ts index 238a196793e5..4fc2ccd47716 100644 --- a/react/features/app/reducers.web.ts +++ b/react/features/app/reducers.web.ts @@ -1,5 +1,6 @@ import '../base/devices/reducer'; import '../base/premeeting/reducer'; +import '../custom-panel/reducer'; import '../base/tooltip/reducer'; import '../e2ee/reducer'; import '../face-landmarks/reducer'; diff --git a/react/features/app/types.ts b/react/features/app/types.ts index 5ee8cbd65fd7..7b63e537c96f 100644 --- a/react/features/app/types.ts +++ b/react/features/app/types.ts @@ -33,6 +33,7 @@ import { IUserInteractionState } from '../base/user-interaction/reducer'; import { IBreakoutRoomsState } from '../breakout-rooms/reducer'; import { ICalendarSyncState } from '../calendar-sync/reducer'; import { IChatState } from '../chat/reducer'; +import { ICustomPanelState } from '../custom-panel/reducer'; import { IDeepLinkingState } from '../deep-linking/reducer'; import { IDropboxState } from '../dropbox/reducer'; import { IDynamicBrandingState } from '../dynamic-branding/reducer'; @@ -127,6 +128,7 @@ export interface IReduxState { 'features/calendar-sync': ICalendarSyncState; 'features/call-integration': ICallIntegrationState; 'features/chat': IChatState; + 'features/custom-panel': ICustomPanelState; 'features/deep-linking': IDeepLinkingState; 'features/dropbox': IDropboxState; 'features/dynamic-branding': IDynamicBrandingState; diff --git a/react/features/av-moderation/middleware.ts b/react/features/av-moderation/middleware.ts index 7d88e7549cfc..d6761517b52d 100644 --- a/react/features/av-moderation/middleware.ts +++ b/react/features/av-moderation/middleware.ts @@ -81,7 +81,6 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => { break; } case LOCAL_PARTICIPANT_MODERATION_NOTIFICATION: { - let descriptionKey; let titleKey; let uid = ''; const localParticipant = getLocalParticipant(getState); @@ -111,8 +110,6 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => { !raisedHand && dispatch(raiseHand(true)); dispatch(hideNotification(uid)); }) ], - descriptionKey, - sticky: true, titleKey, uid }, NOTIFICATION_TIMEOUT_TYPE.MEDIUM)); @@ -271,7 +268,6 @@ StateListenerRegistry.register( dispatch(showNotification({ titleKey: 'notify.hostAskedUnmute', - sticky: true, customActionNameKey, customActionHandler, uid: ASKED_TO_UNMUTE_NOTIFICATION_ID diff --git a/react/features/base/conference/functions.ts b/react/features/base/conference/functions.ts index 2fe66f14ffcb..31c674786219 100644 --- a/react/features/base/conference/functions.ts +++ b/react/features/base/conference/functions.ts @@ -93,10 +93,17 @@ export function commonUserJoinedHandling( if (!user.isHidden()) { const isReplacing = user?.isReplacing(); const isPromoted = conference?.getMetadataHandler().getMetadata()?.visitors?.promoted?.[id]; + const userIdentity = user.getIdentity()?.user; + + // Map identity from JWT context to userContext for external API + const userContext = userIdentity ? { + id: userIdentity.id, + name: userIdentity.name + } : undefined; // the identity and avatar come from jwt and never change in the presence dispatch(participantJoined({ - avatarURL: user.getIdentity()?.user?.avatar, + avatarURL: userIdentity?.avatar, botType: user.getBotType(), conference, id, @@ -105,7 +112,8 @@ export function commonUserJoinedHandling( role: user.getRole(), isPromoted, isReplacing, - sources: user.getSources() + sources: user.getSources(), + userContext })); } } diff --git a/react/features/base/conference/reducer.ts b/react/features/base/conference/reducer.ts index 44e62fa9a26d..129f74a1c17d 100644 --- a/react/features/base/conference/reducer.ts +++ b/react/features/base/conference/reducer.ts @@ -132,6 +132,7 @@ export interface IJitsiConference { myUserId: Function; off: Function; on: Function; + once: Function; options: any; removeTrack: Function; replaceTrack: Function; diff --git a/react/features/base/config/configType.ts b/react/features/base/config/configType.ts index eed32a999dd6..cb4757a4cbe0 100644 --- a/react/features/base/config/configType.ts +++ b/react/features/base/config/configType.ts @@ -559,6 +559,7 @@ export interface IConfig { skipConsentInMeeting?: boolean; suggestRecording?: boolean; }; + reducedUIEnabled?: boolean; reducedUImainToolbarButtons?: Array; remoteVideoMenu?: { disableDemote?: boolean; diff --git a/react/features/base/config/configWhitelist.ts b/react/features/base/config/configWhitelist.ts index 5d0520c500b9..dd69a74d0a89 100644 --- a/react/features/base/config/configWhitelist.ts +++ b/react/features/base/config/configWhitelist.ts @@ -215,6 +215,7 @@ export default [ 'recordings.showPrejoinWarning', 'recordings.showRecordingLink', 'recordings.suggestRecording', + 'reducedUIEnabled', 'reducedUImainToolbarButtons', 'replaceParticipant', 'resolution', diff --git a/react/features/base/config/functions.any.ts b/react/features/base/config/functions.any.ts index dae1ad4e2a15..9fed40943b1b 100644 --- a/react/features/base/config/functions.any.ts +++ b/react/features/base/config/functions.any.ts @@ -387,7 +387,8 @@ export function setConfigFromURLParams( // When not in an iframe, start without media if the pre-join page is not enabled. if (!isEmbedded() - && 'config.prejoinConfig.enabled' in params && config.prejoinConfig?.enabled === false) { + && ('config.prejoinConfig' in params || 'config.prejoinConfig.enabled' in params) + && config.prejoinConfig?.enabled === false) { logger.warn('Using prejoinConfig.enabled config URL overwrite implies starting without media.'); config.disableInitialGUM = true; } diff --git a/react/features/base/connection/actions.native.ts b/react/features/base/connection/actions.native.ts index 4fc948d94602..65a2fcad63f0 100644 --- a/react/features/base/connection/actions.native.ts +++ b/react/features/base/connection/actions.native.ts @@ -4,6 +4,7 @@ import { getCustomerDetails } from '../../jaas/actions.any'; import { getJaasJWT, isVpaasMeeting } from '../../jaas/functions'; import { navigateRoot } from '../../mobile/navigation/rootNavigationContainerRef'; import { screen } from '../../mobile/navigation/routes'; +import { conferenceWillLeave } from '../conference/actions.native'; import { setJWT } from '../jwt/actions'; import { JitsiConnectionErrors } from '../lib-jitsi-meet'; @@ -58,5 +59,8 @@ export function connect(id?: string, password?: string) { * @returns {Function} */ export function hangup(_requestFeedback = false) { - return (dispatch: IStore['dispatch']) => dispatch(appNavigate(undefined)); + return (dispatch: IStore['dispatch']) => { + dispatch(conferenceWillLeave()); + dispatch(appNavigate(undefined)); + }; } diff --git a/react/features/base/environment/utils.ts b/react/features/base/environment/utils.any.ts similarity index 99% rename from react/features/base/environment/utils.ts rename to react/features/base/environment/utils.any.ts index 63141805710c..f02fd2a28ae2 100644 --- a/react/features/base/environment/utils.ts +++ b/react/features/base/environment/utils.any.ts @@ -29,4 +29,3 @@ export function isIpadMobileBrowser() { // @ts-ignore return isIosMobileBrowser() && Platform.isPad; } - diff --git a/react/features/base/environment/utils.native.ts b/react/features/base/environment/utils.native.ts new file mode 100644 index 000000000000..269bed94afb5 --- /dev/null +++ b/react/features/base/environment/utils.native.ts @@ -0,0 +1 @@ +export * from './utils.any'; diff --git a/react/features/base/environment/utils.web.ts b/react/features/base/environment/utils.web.ts new file mode 100644 index 000000000000..f076193c047f --- /dev/null +++ b/react/features/base/environment/utils.web.ts @@ -0,0 +1,37 @@ +import { MIN_FILMSTRIP_RESIZE_WIDTH } from '../../filmstrip/constants'; + +/** + * Detects if the current device has touch capability. + * This includes smartphones, tablets, and laptops with touch screens. + * + * @returns {boolean} True if the device supports touch events. + */ +export function isTouchDevice(): boolean { + // Check maxTouchPoints (most reliable for modern browsers) + if ('maxTouchPoints' in navigator) { + return navigator.maxTouchPoints > 0; + } + + return false; +} + +/** + * Determines if resize functionality should be enabled based on device capabilities + * and screen size. On touch devices, resize is only enabled for larger screens. + * On non-touch devices (desktop), resize is always enabled. + * + * @returns {boolean} True if resize functionality should be available to the user. + */ +export function shouldEnableResize(): boolean { + const hasTouch = isTouchDevice(); + + // On non-touch devices (desktop), always enable resize + if (!hasTouch) { + return true; + } + + // On touch devices, only enable if screen is large enough. + return window?.innerWidth >= MIN_FILMSTRIP_RESIZE_WIDTH; +} + +export * from './utils.any'; diff --git a/react/features/base/icons/svg/AI.svg b/react/features/base/icons/svg/AI.svg new file mode 100644 index 000000000000..e2e7a649346f --- /dev/null +++ b/react/features/base/icons/svg/AI.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/react/features/base/icons/svg/constants.ts b/react/features/base/icons/svg/constants.ts index a16cccebacdf..852db4ce42a6 100644 --- a/react/features/base/icons/svg/constants.ts +++ b/react/features/base/icons/svg/constants.ts @@ -1,3 +1,4 @@ +import { default as IconAI } from './AI.svg'; import { default as IconRecordAccount } from './account-record.svg'; import { default as IconAddUser } from './add-user.svg'; import { default as IconArrowBack } from './arrow-back.svg'; @@ -114,6 +115,7 @@ import { default as IconYahoo } from './yahoo.svg'; */ export const DEFAULT_ICON: Record = { IconAddUser, + IconAI, IconArrowBack, IconArrowDown, IconArrowDownLarge, diff --git a/react/features/base/icons/svg/index.ts b/react/features/base/icons/svg/index.ts index 98a673b0290f..4b90ed774ff9 100644 --- a/react/features/base/icons/svg/index.ts +++ b/react/features/base/icons/svg/index.ts @@ -4,6 +4,7 @@ import { DEFAULT_ICON } from './constants'; const { IconAddUser, + IconAI, IconArrowBack, IconArrowDown, IconArrowDownLarge, @@ -125,6 +126,7 @@ const { export { IconAddUser, + IconAI, IconArrowBack, IconArrowDown, IconArrowDownLarge, diff --git a/react/features/base/label/components/web/Label.tsx b/react/features/base/label/components/web/Label.tsx index 9488a021a1d2..8e1e5e64f9bc 100644 --- a/react/features/base/label/components/web/Label.tsx +++ b/react/features/base/label/components/web/Label.tsx @@ -56,9 +56,9 @@ const useStyles = makeStyles()(theme => { label: { ...theme.typography.labelRegular, alignItems: 'center', - background: theme.palette.ui04, + background: theme.palette.labelBackground, borderRadius: '4px', - color: theme.palette.text01, + color: theme.palette.labelText, display: 'flex', margin: '0 2px', padding: '6px', @@ -72,11 +72,11 @@ const useStyles = makeStyles()(theme => { cursor: 'pointer' }, [COLORS.white]: { - background: theme.palette.ui09, - color: theme.palette.text04, + background: theme.palette.labelWhiteBackground, + color: theme.palette.labelWhiteText, '& svg': { - fill: theme.palette.icon04 + fill: theme.palette.labelWhiteIcon } }, [COLORS.green]: { diff --git a/react/features/base/media/subscriber.ts b/react/features/base/media/subscriber.ts index 513cd7e1470e..842a516260e5 100644 --- a/react/features/base/media/subscriber.ts +++ b/react/features/base/media/subscriber.ts @@ -1,4 +1,5 @@ import { IReduxState, IStore } from '../../app/types'; +import { getLocalParticipant } from '../participants/functions'; import StateListenerRegistry from '../redux/StateListenerRegistry'; /** @@ -13,6 +14,13 @@ StateListenerRegistry.register( if (muted !== previousMuted) { APP.API.notifyAudioMutedStatusChanged(muted); + + // Also fire the participantMuted event for consistency + const localParticipant = getLocalParticipant(store.getState()); + + if (localParticipant) { + APP.API.notifyParticipantMuted(localParticipant.id, muted, 'audio'); + } } } ); diff --git a/react/features/base/modal/components/JitsiScreen.tsx b/react/features/base/modal/components/JitsiScreen.tsx index c83b9cffc60e..312c32265588 100644 --- a/react/features/base/modal/components/JitsiScreen.tsx +++ b/react/features/base/modal/components/JitsiScreen.tsx @@ -17,7 +17,7 @@ interface IProps { /** * The children component(s) of the Modal, to be rendered. */ - children: React.ReactNode; + children?: React.ReactNode; /** * Additional style to be appended to the KeyboardAvoidingView content container. @@ -63,7 +63,7 @@ const JitsiScreen = ({ footerComponent, hasBottomTextInput = false, hasExtraHeaderHeight = false, - safeAreaInsets = [ 'left', 'right' ], + safeAreaInsets = [ 'bottom', 'left', 'right' ], style }: IProps) => { const renderContent = () => ( @@ -78,8 +78,8 @@ const JitsiScreen = ({ edges = { safeAreaInsets } style = { styles.safeArea }> { children } + { footerComponent?.() } - { footerComponent?.() } ); diff --git a/react/features/base/participants/functions.ts b/react/features/base/participants/functions.ts index 104f07add9f0..d937eeba1522 100644 --- a/react/features/base/participants/functions.ts +++ b/react/features/base/participants/functions.ts @@ -60,66 +60,6 @@ const AVATAR_CHECKER_FUNCTIONS = [ return null; } ]; -/* eslint-enable arrow-body-style */ - -/** - * Returns the list of active speakers that should be moved to the top of the sorted list of participants so that the - * dominant speaker is visible always on the vertical filmstrip in stage layout. - * - * @param {Function | Object} stateful - The (whole) redux state, or redux's {@code getState} function to be used to - * retrieve the state. - * @returns {Array} - */ -export function getActiveSpeakersToBeDisplayed(stateful: IStateful) { - const state = toState(stateful); - const { - dominantSpeaker, - fakeParticipants, - sortedRemoteVirtualScreenshareParticipants, - speakersList - } = state['features/base/participants']; - const { visibleRemoteParticipants } = state['features/filmstrip']; - let activeSpeakers = new Map(speakersList); - - // Do not re-sort the active speakers if dominant speaker is currently visible. - if (dominantSpeaker && visibleRemoteParticipants.has(dominantSpeaker)) { - return activeSpeakers; - } - let availableSlotsForActiveSpeakers = visibleRemoteParticipants.size; - - if (activeSpeakers.has(dominantSpeaker ?? '')) { - activeSpeakers.delete(dominantSpeaker ?? ''); - } - - // Add dominant speaker to the beginning of the list (not including self) since the active speaker list is always - // alphabetically sorted. - if (dominantSpeaker && dominantSpeaker !== getLocalParticipant(state)?.id) { - const updatedSpeakers = Array.from(activeSpeakers); - - updatedSpeakers.splice(0, 0, [ dominantSpeaker, getParticipantById(state, dominantSpeaker)?.name ?? '' ]); - activeSpeakers = new Map(updatedSpeakers); - } - - // Remove screenshares from the count. - if (sortedRemoteVirtualScreenshareParticipants) { - availableSlotsForActiveSpeakers -= sortedRemoteVirtualScreenshareParticipants.size * 2; - for (const screenshare of Array.from(sortedRemoteVirtualScreenshareParticipants.keys())) { - const ownerId = getVirtualScreenshareParticipantOwnerId(screenshare as string); - - activeSpeakers.delete(ownerId); - } - } - - // Remove fake participants from the count. - if (fakeParticipants) { - availableSlotsForActiveSpeakers -= fakeParticipants.size; - } - const truncatedSpeakersList = Array.from(activeSpeakers).slice(0, availableSlotsForActiveSpeakers); - - truncatedSpeakersList.sort((a: any, b: any) => a[1].localeCompare(b[1])); - - return new Map(truncatedSpeakersList); -} /** * Resolves the first loadable avatar URL for a participant. diff --git a/react/features/base/participants/middleware.ts b/react/features/base/participants/middleware.ts index ab66928a99bc..697bc74bad9d 100644 --- a/react/features/base/participants/middleware.ts +++ b/react/features/base/participants/middleware.ts @@ -606,13 +606,21 @@ function _e2eeUpdated({ getState, dispatch }: IStore, conference: IJitsiConferen function _localParticipantJoined({ getState, dispatch }: IStore, next: Function, action: AnyAction) { const result = next(action); - const settings = getState()['features/base/settings']; + const state = getState(); + const settings = state['features/base/settings']; + const jwtUser = state['features/base/jwt']?.user; + + const userContext = jwtUser ? { + id: jwtUser.id, + name: jwtUser.name + } : undefined; dispatch(localParticipantJoined({ avatarURL: settings.avatarURL, email: settings.email, name: settings.displayName, - id: '' + id: '', + userContext })); return result; diff --git a/react/features/base/participants/reducer.ts b/react/features/base/participants/reducer.ts index e5de274a5450..51c6f43b8d08 100644 --- a/react/features/base/participants/reducer.ts +++ b/react/features/base/participants/reducer.ts @@ -67,6 +67,7 @@ const PARTICIPANT_PROPS_TO_OMIT_WHEN_UPDATE = [ ]; const DEFAULT_STATE = { + activeSpeakers: new Set(), dominantSpeaker: undefined, fakeParticipants: new Map(), local: undefined, @@ -85,6 +86,7 @@ const DEFAULT_STATE = { }; export interface IParticipantsState { + activeSpeakers: Set; dominantSpeaker?: string; fakeParticipants: Map; local?: ILocalParticipant; @@ -628,6 +630,7 @@ function _participantJoined({ participant }: { participant: IParticipant }) { presence, role, sources, + userContext } = participant; let { conference, id } = participant; @@ -660,6 +663,7 @@ function _participantJoined({ participant }: { participant: IParticipant }) { presence, role: role || PARTICIPANT_ROLE.NONE, sources, + userContext }; } diff --git a/react/features/base/participants/types.ts b/react/features/base/participants/types.ts index 04a0b8d3c296..4f7ad4886216 100644 --- a/react/features/base/participants/types.ts +++ b/react/features/base/participants/types.ts @@ -41,6 +41,12 @@ export interface IParticipant { role?: string; sources?: Map>; supportsRemoteControl?: boolean; + userContext?: IUserContext; +} + +export interface IUserContext { + id?: string; + name?: string; } export interface ILocalParticipant extends IParticipant { diff --git a/react/features/base/premeeting/components/web/ActionButton.tsx b/react/features/base/premeeting/components/web/ActionButton.tsx index 8e1d61893e80..4acc44f6bd4e 100644 --- a/react/features/base/premeeting/components/web/ActionButton.tsx +++ b/react/features/base/premeeting/components/web/ActionButton.tsx @@ -84,7 +84,7 @@ const useStyles = makeStyles()(theme => { ...theme.typography.bodyLongBold, borderRadius: theme.shape.borderRadius, boxSizing: 'border-box', - color: theme.palette.text01, + color: theme.palette.actionButtonText, cursor: 'pointer', display: 'inline-block', marginBottom: '16px', @@ -95,20 +95,20 @@ const useStyles = makeStyles()(theme => { border: 0, '&.primary': { - background: theme.palette.action01, - color: theme.palette.text01, + background: theme.palette.prejoinActionButtonPrimary, + color: theme.palette.prejoinActionButtonPrimaryText, '&:hover': { - backgroundColor: theme.palette.action01Hover + backgroundColor: theme.palette.prejoinActionButtonPrimaryHover } }, '&.secondary': { - background: theme.palette.action02, - color: theme.palette.text04, + background: theme.palette.prejoinActionButtonSecondary, + color: theme.palette.prejoinActionButtonSecondaryText, '&:hover': { - backgroundColor: theme.palette.action02Hover + backgroundColor: theme.palette.prejoinActionButtonSecondaryHover } }, @@ -120,7 +120,7 @@ const useStyles = makeStyles()(theme => { }, '&.disabled': { - background: theme.palette.disabled01, + background: theme.palette.prejoinActionButtonDisabled, border: '1px solid #5E6D7A', color: '#AFB6BC', cursor: 'initial', diff --git a/react/features/base/premeeting/components/web/PreMeetingScreen.tsx b/react/features/base/premeeting/components/web/PreMeetingScreen.tsx index 314eac659064..c59471ce5af6 100644 --- a/react/features/base/premeeting/components/web/PreMeetingScreen.tsx +++ b/react/features/base/premeeting/components/web/PreMeetingScreen.tsx @@ -109,7 +109,7 @@ const useStyles = makeStyles()(theme => { position: 'absolute', inset: '0 0 0 0', display: 'flex', - backgroundColor: theme.palette.ui01, + backgroundColor: theme.palette.preMeetingBackground, zIndex: 252, '@media (max-width: 720px)': { @@ -163,7 +163,7 @@ const useStyles = makeStyles()(theme => { }, title: { ...theme.typography.heading4, - color: `${theme.palette.text01}!important`, + color: theme.palette.prejoinTitleText, marginBottom: theme.spacing(3), textAlign: 'center', @@ -179,7 +179,7 @@ const useStyles = makeStyles()(theme => { roomName: { ...theme.typography.heading5, - color: theme.palette.text01, + color: theme.palette.prejoinRoomNameText, display: 'inline-block', overflow: 'hidden', textOverflow: 'ellipsis', diff --git a/react/features/base/premeeting/components/web/RecordingWarning.tsx b/react/features/base/premeeting/components/web/RecordingWarning.tsx index 0323208f37a8..d0aaf108fba4 100644 --- a/react/features/base/premeeting/components/web/RecordingWarning.tsx +++ b/react/features/base/premeeting/components/web/RecordingWarning.tsx @@ -6,7 +6,7 @@ const useStyles = makeStyles()(theme => { return { warning: { bottom: 0, - color: theme.palette.text03, + color: theme.palette.prejoinRecordingWarningText, display: 'flex', justifyContent: 'center', ...theme.typography.bodyShortRegular, diff --git a/react/features/base/premeeting/components/web/UnsafeRoomWarning.tsx b/react/features/base/premeeting/components/web/UnsafeRoomWarning.tsx index 4bc69f47d760..b83e4885b03b 100644 --- a/react/features/base/premeeting/components/web/UnsafeRoomWarning.tsx +++ b/react/features/base/premeeting/components/web/UnsafeRoomWarning.tsx @@ -11,8 +11,8 @@ import { setUnsafeRoomConsent } from '../../actions.web'; const useStyles = makeStyles()(theme => { return { warning: { - backgroundColor: theme.palette.warning01, - color: theme.palette.text04, + backgroundColor: theme.palette.prejoinWarningBackground, + color: theme.palette.prejoinWarningText, ...theme.typography.bodyShortRegular, padding: theme.spacing(3), borderRadius: theme.shape.borderRadius, diff --git a/react/features/base/react/components/web/InlineDialogFailure.tsx b/react/features/base/react/components/web/InlineDialogFailure.tsx index c85d39550709..f1e093709fef 100644 --- a/react/features/base/react/components/web/InlineDialogFailure.tsx +++ b/react/features/base/react/components/web/InlineDialogFailure.tsx @@ -9,11 +9,11 @@ import { getSupportUrl } from '../../functions'; const useStyles = makeStyles()(theme => { return { dialog: { - backgroundColor: theme.palette.ui01, - border: `1px solid ${theme.palette.ui04}`, + backgroundColor: theme.palette.dialogBackground, + border: `1px solid ${theme.palette.inlineDialogBorder}`, borderRadius: `${Number(theme.shape.borderRadius)}px`, boxShadow: '0px 1px 2px rgba(41, 41, 41, 0.25)', - color: theme.palette.text01, + color: theme.palette.dialogText, ...theme.typography.bodyShortRegular, padding: `${theme.spacing(3)} 10`, '& .retry-button': { diff --git a/react/features/base/responsive-ui/actions.ts b/react/features/base/responsive-ui/actions.ts index 7b94afe41ff1..80fe96cd2eb2 100644 --- a/react/features/base/responsive-ui/actions.ts +++ b/react/features/base/responsive-ui/actions.ts @@ -2,6 +2,7 @@ import { batch } from 'react-redux'; import { IStore } from '../../app/types'; import { CHAT_SIZE } from '../../chat/constants'; +import { getCustomPanelWidth } from '../../custom-panel/functions'; import { getParticipantsPaneWidth } from '../../participants-pane/functions'; import { @@ -43,6 +44,7 @@ export function clientResized(clientWidth: number, clientHeight: number) { if (navigator.product !== 'ReactNative') { const state = getState(); + const { reducedUIEnabled = true } = state['features/base/config']; const { isOpen: isChatOpen, width } = state['features/chat']; if (isChatOpen) { @@ -50,8 +52,9 @@ export function clientResized(clientWidth: number, clientHeight: number) { } availableWidth -= getParticipantsPaneWidth(state); + availableWidth -= getCustomPanelWidth(state); - dispatch(setReducedUI(availableWidth, clientHeight)); + reducedUIEnabled && dispatch(setReducedUI(availableWidth, clientHeight)); } batch(() => { @@ -112,7 +115,7 @@ export function setReducedUI(width: number, height: number) { const threshold = navigator.product === 'ReactNative' ? REDUCED_UI_THRESHOLD : WEB_REDUCED_UI_THRESHOLD; - const reducedUI = Math.min(width, height) < threshold; + const reducedUI = Math.max(width, height) < threshold; if (reducedUI !== getState()['features/base/responsive-ui'].reducedUI) { return dispatch({ diff --git a/react/features/base/tooltip/components/Tooltip.tsx b/react/features/base/tooltip/components/Tooltip.tsx index 482b13775444..8941cb12ce5d 100644 --- a/react/features/base/tooltip/components/Tooltip.tsx +++ b/react/features/base/tooltip/components/Tooltip.tsx @@ -22,11 +22,11 @@ interface IProps { const useStyles = makeStyles()(theme => { return { container: { - backgroundColor: theme.palette.uiBackground, + backgroundColor: theme.palette.tooltipBackground, borderRadius: '3px', padding: theme.spacing(2), ...theme.typography.labelRegular, - color: theme.palette.text01, + color: theme.palette.tooltipText, position: 'relative', '&.mounting-animation': { diff --git a/react/features/base/tracks/middleware.web.ts b/react/features/base/tracks/middleware.web.ts index 1df33ecd2539..1c347c936925 100644 --- a/react/features/base/tracks/middleware.web.ts +++ b/react/features/base/tracks/middleware.web.ts @@ -51,7 +51,7 @@ import './subscriber.web'; MiddlewareRegistry.register(store => next => action => { switch (action.type) { case TRACK_ADDED: { - const { local } = action.track; + const { local, jitsiTrack } = action.track; // The devices list needs to be refreshed when no initial video permissions // were granted and a local video track is added by umuting the video. @@ -65,6 +65,16 @@ MiddlewareRegistry.register(store => next => action => { if (participantId) { logTracksForParticipant(store.getState()['features/base/tracks'], participantId, 'Track added'); + + // Fire participantMuted event for initial state of remote tracks + if (typeof action.track?.muted !== 'undefined' && jitsiTrack) { + const isVideoTrack = jitsiTrack.getType() !== MEDIA_TYPE.AUDIO; + const mediaType = isVideoTrack + ? (jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP ? 'desktop' : 'video') + : 'audio'; + + APP.API.notifyParticipantMuted(participantId, action.track.muted, mediaType); + } } return result; @@ -119,6 +129,16 @@ MiddlewareRegistry.register(store => next => action => { // TODO Remove the following calls to APP.UI once components interested // in track mute changes are moved into React and/or redux. + const { jitsiTrack } = action.track; + const participantID = jitsiTrack.getParticipantId(); + const isVideoTrack = jitsiTrack.type !== MEDIA_TYPE.AUDIO; + const local = jitsiTrack.isLocal(); + + // Get old muted state BEFORE updating + const tracks = store.getState()['features/base/tracks']; + const oldTrack = tracks.find((t: ITrack) => t.jitsiTrack === jitsiTrack); + const oldMutedState = oldTrack?.muted; + const result = next(action); const state = store.getState(); @@ -126,11 +146,6 @@ MiddlewareRegistry.register(store => next => action => { return result; } - const { jitsiTrack } = action.track; - const participantID = jitsiTrack.getParticipantId(); - const isVideoTrack = jitsiTrack.type !== MEDIA_TYPE.AUDIO; - const local = jitsiTrack.isLocal(); - if (isVideoTrack) { if (local && !(jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP)) { APP.conference.setVideoMuteStatus(); @@ -144,12 +159,14 @@ MiddlewareRegistry.register(store => next => action => { if (typeof action.track?.muted !== 'undefined' && participantID && !local) { logTracksForParticipant(store.getState()['features/base/tracks'], participantID, 'Track updated'); - // Notify external API when remote participant mutes/unmutes themselves - const mediaType = isVideoTrack - ? (jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP ? 'desktop' : 'video') - : 'audio'; + // Fire participantMuted event only if muted state actually changed + if (oldMutedState !== action.track.muted) { + const mediaType = isVideoTrack + ? (jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP ? 'desktop' : 'video') + : 'audio'; - APP.API.notifyParticipantMuted(participantID, action.track.muted, mediaType, true); + APP.API.notifyParticipantMuted(participantID, action.track.muted, mediaType); + } } return result; diff --git a/react/features/base/tracks/subscriber.web.ts b/react/features/base/tracks/subscriber.web.ts index 1fba331c3e2b..fea8c51ce431 100644 --- a/react/features/base/tracks/subscriber.web.ts +++ b/react/features/base/tracks/subscriber.web.ts @@ -4,7 +4,7 @@ import { isEqual, sortBy } from 'lodash-es'; import VideoLayout from '../../../../modules/UI/videolayout/VideoLayout'; import { getAutoPinSetting } from '../../video-layout/functions.any'; import { MEDIA_TYPE } from '../media/constants'; -import { getScreenshareParticipantIds } from '../participants/functions'; +import { getLocalParticipant, getScreenshareParticipantIds } from '../participants/functions'; import StateListenerRegistry from '../redux/StateListenerRegistry'; import { isLocalTrackMuted } from './functions'; @@ -47,6 +47,13 @@ StateListenerRegistry.register( /* listener */ (muted, store, previousMuted) => { if (muted !== previousMuted) { APP.API.notifyVideoMutedStatusChanged(muted); + + // Also fire the participantMuted event for consistency + const localParticipant = getLocalParticipant(store.getState()); + + if (localParticipant) { + APP.API.notifyParticipantMuted(localParticipant.id, muted, 'video'); + } } } ); diff --git a/react/features/base/ui/Tokens.ts b/react/features/base/ui/Tokens.ts index e3b3e6472b64..70dffda02b73 100644 --- a/react/features/base/ui/Tokens.ts +++ b/react/features/base/ui/Tokens.ts @@ -1,3 +1,4 @@ +/* eslint-disable @stylistic/no-multi-spaces */ // Mapping between the token used and the color export const colorMap = { // ----- Surfaces ----- @@ -7,7 +8,7 @@ export const colorMap = { // - JitsiMeetView.java uiBackground: 'surface01', - // Container backgrounds + // Container backgrounds (legacy tokens) ui01: 'surface02', ui02: 'surface03', ui03: 'ui02', @@ -47,6 +48,426 @@ export const colorMap = { // Focus focus01: 'focus01', + // ----- Semantic Tokens (component-based, backwards compatible) ----- + + // Dialog/Modal Components + dialogBackground: 'surface02', // Main dialog background (same as ui01) + dialogOverlay: 'surface03', // Overlay/backdrop (same as ui02) + dialogBorder: 'ui02', // Dialog borders + dialogText: 'textColor01', // Primary dialog text (same as text01) + dialogSecondaryText: 'textColor02', // Secondary dialog text (same as text02) + + // Large Video + largeVideoBackground: 'surface03', // Main video area background (same as ui02) + largeVideoPlaceholder: 'surface03', // Placeholder when no video (same as ui02) + + // Filmstrip + filmstripBackground: 'surface03', // Filmstrip container background (same as ui02) + filmstripBackgroundHover: 'uiBackground', // Filmstrip background on hover/focus + filmstripDragHandle: 'icon02', // Filmstrip resize drag handle color + filmstripDragHandleHover: 'icon01', // Filmstrip resize drag handle hover color + thumbnailBackground: 'surface03', // Individual thumbnail background (same as ui02) + thumbnailBorder: 'ui03', // Thumbnail borders (same as ui03) + thumbnailHover: 'hover05', // Thumbnail hover state (same as action03Hover) + thumbnailTintBackground: 'uiBackground', // Thumbnail tint overlay background + thumbnailRaisedHandIcon: 'uiBackground', // Thumbnail raised hand indicator icon + thumbnailVideoBackground: 'uiBackground', // Thumbnail video/placeholder background + + // Chat + chatBackground: 'surface02', // Chat panel background (same as ui01) + chatBackdrop: 'ui04', // Chat screen background (same as ui10) + chatEmptyText: 'ui03', // Empty component text + chatInputBackground: 'surface03', // Chat input field background (same as ui02) + chatInputBorder: 'surface03', // Chat input border (same as ui02) + chatLink: 'action01', // Chat link color (same as link01) + chatLobbyMessageBubble: 'support06', // Lobby message bubble background + chatLobbyMessageNotice: 'surface01', // Lobby message notice text + chatLobbyRecipientContainer: 'support06', // Lobby recipient container background + chatMessageLocal: 'surface05', // Local participant message bubble (same as ui04) + chatMessagePrivate: 'support05', // Private/DM message bubble + chatMessageRemote: 'surface03', // Remote participant message bubble (same as ui02) + chatMessageText: 'textColor01', // Chat message text + chatPrivateNotice: 'textColor02', // Private message notice text + chatRecipientCancelIcon: 'icon01', // Recipient cancel icon color + chatRecipientContainer: 'support05', // Recipient container background + chatRecipientText: 'textColor01', // Recipient text color + chatReplyIcon: 'icon01', // Reply icon color + chatSenderName: 'textColor02', // Sender display name color + chatTimestamp: 'ui03', // Chat timestamp text + + // Toolbox/Toolbar + toolboxBackground: 'surface02', // Main toolbox background + drawerBackground: 'surface02', // Drawer/side panel background + toolboxIconHover: 'surface05', // Toolbox icon hover background + toolboxIconActive: 'ui02', // Toolbox icon active/pressed background + toolboxIconToggled: 'ui02', // Toolbox icon toggled background + toolbarButton: 'action01', // Toolbar button color + toolbarButtonHover: 'hover01', // Toolbar button hover (same as action01Hover) + toolbarButtonActive: 'active01', // Toolbar button active/pressed state + toolbarIcon: 'icon01', // Toolbar icon color + toolbarIconHover: 'icon01', // Toolbar icon hover state + toolbarIconActive: 'action01', // Toolbar icon active/toggled state + + // Overflow Menu (More Actions) + overflowMenuBackground: 'surface02', // Overflow menu background + overflowMenuBorder: 'surface05', // Overflow menu border + overflowMenuItemText: 'text01', // Overflow menu item text + overflowMenuItemIcon: 'text01', // Overflow menu item icon + overflowMenuItemHover: 'surface03', // Overflow menu item hover background + overflowMenuItemDisabled: 'text03', // Overflow menu item disabled text/icon + overflowMenuSeparator: 'ui03', // Overflow menu group separator + + // Participants Pane + participantsPaneBackground: 'surface02', // Participants list background + participantItemBackground: 'surface03', // Individual participant item background + participantItemHover: 'hover05', // Participant item hover + participantItemBorder: 'ui02', // Participant item border + participantCounterBadge: 'ui02', // Participant counter badge background + participantCounterText: 'text01', // Participant counter text + participantModeratorLabel: 'text03', // Moderator label text + participantSectionText: 'text02', // Section header/subtitle text + participantActionButton: 'action02', // Action button background + participantLinkText: 'link01', // Link text color + participantWarningText: 'warning02', // Warning text color + participantRaisedHandBadge: 'warning02', // Raised hand indicator background + participantRaisedHandIcon: 'icon04', // Raised hand icon color + + // Lobby + lobbyBackground: 'surface02', // Lobby screen background (same as ui01) + lobbyPreviewBackground: 'surface03', // Video preview background (same as ui02) + + // Speaker Stats + speakerStatsBackground: 'surface02', // Speaker stats panel background + speakerStatsRowBackground: 'ui02', // Individual stat row background + speakerStatsRowAlternate: 'ui03', // Alternate row background + speakerStatsBorder: 'surface03', // Speaker stats borders + speakerStatsHeaderBackground: 'ui09', // Header background + speakerStatsSearchBackground: 'field01', // Search input background + speakerStatsSearchBorder: 'ui05', // Search input border + speakerStatsSearchText: 'text01', // Search input text + speakerStatsSearchPlaceholder: 'text03', // Search placeholder + speakerStatsSearchIcon: 'icon03', // Search icon color + speakerStatsLabelText: 'text03', // Label text color + speakerStatsSuccessBar: 'success02', // Success/progress bar + speakerStatsAvatarLeft: 'surface05', // Avatar background for participants who left + + // Pre-meeting/Prejoin + preMeetingBackground: 'surface02', // Pre-meeting screen container background + preMeetingPreview: 'ui01', // Video preview in pre-meeting + prejoinDialogBackground: 'uiBackground', // Prejoin dialog background + prejoinDialogDelimiter: 'ui03', // Prejoin dialog delimiter line + prejoinDialogDelimiterText: 'text01', // Prejoin dialog delimiter text + prejoinTitleText: 'text01', // Prejoin title text color + prejoinRoomNameText: 'text01', // Prejoin room name text color + prejoinWarningBackground: 'warning01', // Warning banner background + prejoinWarningText: 'text04', // Warning banner text + prejoinRecordingWarningText: 'text03', // Recording warning text + prejoinActionButtonPrimary: 'action01', // Primary action button + prejoinActionButtonPrimaryHover: 'action01Hover', // Primary button hover + prejoinActionButtonPrimaryText: 'text01', // Primary button text + prejoinActionButtonSecondary: 'action02', // Secondary action button + prejoinActionButtonSecondaryHover: 'action02Hover', // Secondary button hover + prejoinActionButtonSecondaryText: 'text04', // Secondary button text + prejoinActionButtonDanger: 'actionDanger', // Danger button (leave) + prejoinActionButtonDisabled: 'disabled01', // Disabled button + prejoinCountryPickerBackground: 'ui01', // Country picker background + prejoinCountryPickerBorder: 'ui03', // Country picker border + prejoinCountryPickerText: 'text01', // Country picker text + prejoinCountryRowBackground: 'action03', // Country row background + prejoinCountryRowHover: 'action03Hover', // Country row hover + prejoinDeviceStatusOk: 'success01', // Device status OK background + prejoinDeviceStatusWarning: 'warning01', // Device status warning background + prejoinDeviceStatusText: 'uiBackground', // Device status text + + // Notifications + notificationBackground: 'ui04', // Notification background + notificationNormalIcon: 'action01', // Normal notification icon + notificationError: 'iconError', // Error notification icon + notificationSuccess: 'success01', // Success notification icon + notificationWarning: 'warning01', // Warning notification icon + notificationText: 'text04', // Notification text + notificationActionText: 'action01', // Notification action text + notificationErrorText: 'textError', // Error notification text + notificationActionFocus: 'action01', // Notification action focus outline + notificationCloseIcon: 'icon04', // Notification close icon + + // Forms/Inputs + inputBackground: 'field01', // Input field background + inputBorder: 'surface03', // Input field border (same as ui02) + inputText: 'textColor01', // Input field text (same as text01) + inputPlaceholder: 'textColor02', // Input placeholder text (same as text02) + + // Breakout Rooms + breakoutRoomBackground: 'ui01', // Breakout rooms panel background + breakoutRoomItemBackground: 'surface03', // Individual breakout room background + breakoutRoomArrowBackground: 'ui02', // Breakout room arrow container background + + // Settings + settingsBackground: 'ui01', // Settings dialog background + settingsSectionBackground: 'ui02', // Settings section background + settingsTabText: 'text01', // Settings tab text + settingsShortcutKey: 'surface05', // Keyboard shortcut key background + settingsVideoPreviewBorder: 'action01Hover', // Video preview border (selected) + settingsErrorIcon: 'iconError', // Error icon color + + // Visitors + visitorsCountBadge: 'warning02', // Visitors count badge background + visitorsCountText: 'uiBackground', // Visitors count badge text + visitorsCountIcon: 'icon04', // Visitors count icon + visitorsQueueBackground: 'ui01', // Visitors queue panel background + visitorsQueueText: 'text01', // Visitors queue text + visitorsArrowBackground: 'ui02', // Visitors arrow container background + + // Welcome Page + welcomeBackground: 'surface01', // Welcome page background (same as uiBackground) + welcomeCard: 'ui01', // Welcome page tab bar background + welcomeTabActive: 'icon01', // Welcome page active tab icon + welcomeTabInactive: 'icon03', // Welcome page inactive tab icon + + // ----- Form Components ----- + + // Input + inputLabel: 'text01', // Input field label text + inputFieldBackground: 'ui02', // Input field background color + inputFieldBorder: 'ui02', // Input field border color + inputFieldText: 'text01', // Input field text color + inputFieldPlaceholder: 'text02', // Input field placeholder text + inputFieldDisabled: 'text03', // Input field disabled text + inputFieldError: 'textError', // Input field error state + inputFieldFocus: 'focus01', // Input field focus outline + inputClearButton: 'transparent', // Input clear button background + inputBottomLabel: 'text02', // Input bottom label text + inputBottomLabelError: 'textError', // Input bottom label error text + + // Select + selectLabel: 'text01', // Select label text + selectBackground: 'ui02', // Select background color + selectText: 'text01', // Select text color + selectDisabled: 'text03', // Select disabled text + selectError: 'textError', // Select error state + selectFocus: 'focus01', // Select focus outline + selectIcon: 'icon01', // Select dropdown icon (enabled) + selectIconDisabled: 'icon03', // Select dropdown icon (disabled) + selectBottomLabel: 'text02', // Select bottom label text + selectBottomLabelError: 'textError', // Select bottom label error text + + // MultiSelect + multiSelectBackground: 'ui01', // MultiSelect dropdown background + multiSelectBorder: 'ui04', // MultiSelect dropdown border + multiSelectItemText: 'text01', // MultiSelect item text + multiSelectItemHover: 'ui02', // MultiSelect item hover background + multiSelectItemDisabled: 'text03', // MultiSelect disabled item text + + // Checkbox + checkboxLabel: 'text01', // Checkbox label text + checkboxBorder: 'icon03', // Checkbox border color + checkboxChecked: 'action01', // Checkbox checked background + checkboxDisabledBackground: 'ui02', // Checkbox disabled background + checkboxDisabledBorder: 'surface05', // Checkbox disabled border + checkboxDisabledChecked: 'ui02', // Checkbox disabled checked background + checkboxIcon: 'icon01', // Checkbox check icon (enabled) + checkboxIconDisabled: 'icon03', // Checkbox check icon (disabled) + + // Switch + switchBackground: 'ui01', // Switch background (unchecked) + switchBackgroundOn: 'action01', // Switch background (checked) + switchToggle: 'ui04', // Switch toggle circle + switchToggleDisabled: 'ui03', // Switch toggle circle (disabled) + switchFocus: 'focus01', // Switch focus outline + + // Tabs + tabText: 'text02', // Tab text (unselected) + tabTextHover: 'text01', // Tab text (hover) + tabTextSelected: 'text01', // Tab text (selected) + tabTextDisabled: 'text03', // Tab text (disabled) + tabBorder: 'ui05', // Tab bottom border (unselected) + tabBorderHover: 'ui10', // Tab bottom border (hover) + tabBorderSelected: 'action01', // Tab bottom border (selected) + tabBorderDisabled: 'ui05', // Tab bottom border (disabled) + tabFocus: 'focus01', // Tab focus outline + tabBadgeBackground: 'warning01', // Tab count badge background + tabBadgeText: 'text04', // Tab count badge text + + // ListItem + listItemText: 'text01', // List item text color + listItemBackground: 'ui01', // List item default background + listItemHover: 'surface03', // List item hover background + listItemHighlighted: 'surface03', // List item highlighted/active background + listItemBoxShadow: 'ui02', // List item actions box shadow color + + // ClickableIcon + clickableIconBackground: 'transparent', // Clickable icon background + clickableIconHover: 'ui02', // Clickable icon hover background + clickableIconActive: 'ui03', // Clickable icon active/pressed background + clickableIconFocus: 'focus01', // Clickable icon focus outline + + // Label + labelBackground: 'ui04', // Label default background + labelText: 'text01', // Label text color + labelWhiteBackground: 'ui08', // Label white variant background + labelWhiteText: 'text04', // Label white variant text + labelWhiteIcon: 'surface01', // Label white variant icon + + // Tooltip + tooltipBackground: 'uiBackground', // Tooltip background color + tooltipText: 'text01', // Tooltip text color + + // Polls + pollsBackground: 'surface03', // Poll container background + pollsTitle: 'text01', // Poll title text + pollsSubtitle: 'text02', // Poll subtitle/secondary text + pollsQuestion: 'text01', // Poll question text + pollsAnswer: 'text01', // Poll answer text + pollsBarBackground: 'ui03', // Poll results bar background + pollsBarPercentage: 'text01', // Poll results percentage text + pollsVotersBackground: 'ui03', // Poll voters list background + pollsVotersText: 'text01', // Poll voters list text + pollsSeparator: 'ui03', // Poll section separator + pollsSendLabel: 'text01', // Poll send button label + pollsSendDisabled: 'text03', // Poll send button disabled label + pollsPaneBackground: 'ui01', // Poll pane container background + pollsPaneBorder: 'ui05', // Poll pane border + pollsCreateBackground: 'uiBackground', // Poll create dialog background + pollsCreateBorder: 'ui06', // Poll create dialog border + + // Video Quality / Slider + sliderKnob: 'text01', // Slider knob/thumb color + sliderTrack: 'text03', // Slider track color + sliderFocus: 'ui06', // Slider focus outline + videoQualityText: 'text01', // Video quality dialog text + videoQualityBackground: 'surface02', // Video quality dialog background + + // Connection Indicator + connectionIndicatorLost: 'ui05', // Connection indicator lost status + connectionIndicatorOther: 'action01', // Connection indicator other status + + // Device Selection + deviceSelectorBackground: 'ui01', // Device selector background + deviceSelectorText: 'text01', // Device selector text + deviceSelectorBorder: 'ui03', // Device selector border + deviceSelectorTextBackground: 'uiBackground', // Device selector text-only background + deviceSelectorVideoPreview: 'uiBackground', // Device selector video preview background + + // Invite / Dial-in + dialInBackground: 'ui01', // Dial-in summary background + dialInText: 'text01', // Dial-in summary text + dialInSecondaryText: 'text02', // Dial-in summary secondary text + + // Reactions + reactionsMenuBackground: 'ui01', // Reactions menu background + reactionsMenuBorder: 'ui02', // Reactions menu border + + // Recording / Live Stream + recordingBackground: 'ui01', // Recording panel background + recordingText: 'text01', // Recording panel text + recordingHighlightButton: 'ui04', // Recording highlight button background + recordingHighlightButtonDisabled: 'text02', // Recording highlight button disabled background + recordingHighlightButtonIcon: 'ui02', // Recording highlight button icon color + recordingHighlightButtonIconDisabled: 'text03', // Recording highlight button disabled icon color + recordingNotificationText: 'surface01', // Recording notification text color + recordingNotificationAction: 'action01', // Recording notification action color + + // Virtual Background + virtualBackgroundBackground: 'ui01', // Virtual background picker background + virtualBackgroundText: 'text01', // Virtual background picker text + virtualBackgroundBorder: 'ui03', // Virtual background item border + virtualBackgroundPreview: 'uiBackground', // Virtual background preview container + + // Conference / Meeting + conferenceTimerText: 'text01', // Conference timer text + conferenceSubjectText: 'text01', // Conference subject text + conferenceNoticeBackground: 'uiBackground', // Conference notice background + conferenceNoticeText: 'text01', // Conference notice text + conferenceRaisedHandLabelText: 'uiBackground', // Raised hands count label text + conferenceRaisedHandLabelIcon: 'surface01', // Raised hands count label icon + + // Subtitle Messages + subtitleMessageBackground: 'ui02', // Subtitle message background + subtitleMessageText: 'text01', // Subtitle message text + subtitleMessageSender: 'text02', // Subtitle message sender name + subtitleMessageTime: 'text03', // Subtitle message timestamp + + // Language Selector + languageSelectorBackground: 'ui01', // Language selector background + languageSelectorText: 'text01', // Language selector text + languageSelectorHover: 'ui02', // Language selector item hover + + // Video Menu + videoMenuBackground: 'ui01', // Video menu background + videoMenuBorder: 'ui02', // Video menu border + videoMenuText: 'text01', // Video menu text + videoMenuSliderBackground: 'ui03', // Video menu slider background + + // File Sharing + fileSharingBackground: 'ui01', // File sharing panel background + fileSharingText: 'text01', // File sharing text + fileSharingEmptyText: 'text02', // File sharing empty state text + fileSharingEmptyIcon: 'icon03', // File sharing empty state icon + fileSharingItemBackground: 'surface03', // File sharing item background + fileSharingItemBorder: 'ui02', // File sharing item hover/border + + // Gifs + gifsBackground: 'ui01', // GIFs panel background + gifsText: 'text01', // GIFs panel text + + // Whiteboard + whiteboardBackground: 'ui03', // Whiteboard background + whiteboardText: 'text01', // Whiteboard panel text + + // Salesforce + salesforceSearchBackground: 'field01', // Salesforce search input background + salesforceSearchBorder: 'ui05', // Salesforce search input border + salesforceSearchText: 'dialogText', // Salesforce search input text + salesforceSearchPlaceholder: 'text03', // Salesforce search placeholder + salesforceSearchIcon: 'text03', // Salesforce search icon + + // Security Dialog + securityDialogBackground: 'ui01', // Security dialog background + securityDialogText: 'text01', // Security dialog text + securityDialogSecondaryText: 'text02', // Security dialog secondary text + securityDialogBorder: 'ui07', // Security dialog border color + + // Deep Linking + deepLinkingBackground: 'ui01', // Deep linking page content pane background + deepLinkingBorder: 'ui03', // Deep linking page content pane border + deepLinkingText: 'text01', // Deep linking page text + deepLinkingSeparator: 'ui03', // Deep linking separator line + deepLinkingLabelText: 'text02', // Deep linking label text + deepLinkingLink: 'link01', // Deep linking link color + + // Base React Components + baseReactBackground: 'ui01', // Base react component background + baseReactText: 'text01', // Base react component text + baseReactBorder: 'ui03', // Base react component border + + // Inline Dialog + inlineDialogBackground: 'ui01', // Inline dialog background + inlineDialogText: 'text01', // Inline dialog text + inlineDialogBorder: 'ui02', // Inline dialog border + + // Pre-meeting / Action Button + actionButtonBackground: 'ui01', // Action button background (different from main buttons) + actionButtonText: 'text01', // Action button text + actionButtonBorder: 'ui03', // Action button border + + // Audio Route Picker + audioRoutePickerBackground: 'ui01', // Audio route picker background + audioRoutePickerText: 'text01', // Audio route picker text + audioRoutePickerBorder: 'ui03', // Audio route picker border + + // Etherpad + etherpadBackground: 'ui01', // Etherpad panel background + etherpadText: 'text01', // Etherpad panel text + + // Display Name + displayNameBackground: 'ui01', // Display name background + displayNameText: 'text01', // Display name text + + // Car Mode + carModeBackground: 'ui01', // Car mode background + carModeText: 'text01', // Car mode text + carModeBorder: 'ui03', // Car mode border + // ----- Links ----- link01: 'action01', diff --git a/react/features/base/ui/components/web/BaseDialog.tsx b/react/features/base/ui/components/web/BaseDialog.tsx index b0c43599fe80..8e52dec53ca8 100644 --- a/react/features/base/ui/components/web/BaseDialog.tsx +++ b/react/features/base/ui/components/web/BaseDialog.tsx @@ -14,7 +14,7 @@ const useStyles = makeStyles()(theme => { width: '100%', height: '100%', position: 'fixed', - color: theme.palette.text01, + color: theme.palette.dialogText, ...theme.typography.bodyLongRegular, top: 0, left: 0, @@ -49,13 +49,13 @@ const useStyles = makeStyles()(theme => { height: '100%', top: 0, left: 0, - backgroundColor: theme.palette.ui02, + backgroundColor: theme.palette.dialogOverlay, opacity: 0.75 }, modal: { - backgroundColor: theme.palette.ui01, - border: `1px solid ${theme.palette.ui03}`, + backgroundColor: theme.palette.dialogBackground, + border: `1px solid ${theme.palette.dialogBorder}`, boxShadow: '0px 4px 25px 4px rgba(20, 20, 20, 0.6)', borderRadius: `${theme.shape.borderRadius}px`, display: 'flex', diff --git a/react/features/base/ui/components/web/Checkbox.tsx b/react/features/base/ui/components/web/Checkbox.tsx index 53c657364f82..9dc69d758af4 100644 --- a/react/features/base/ui/components/web/Checkbox.tsx +++ b/react/features/base/ui/components/web/Checkbox.tsx @@ -47,7 +47,7 @@ const useStyles = makeStyles()(theme => { return { formControl: { ...theme.typography.bodyLongRegular, - color: theme.palette.text01, + color: theme.palette.checkboxLabel, display: 'inline-flex', alignItems: 'center', @@ -76,10 +76,10 @@ const useStyles = makeStyles()(theme => { backgroundColor: 'transparent', margin: '3px', font: 'inherit', - color: theme.palette.icon03, + color: theme.palette.checkboxBorder, width: '18px', height: '18px', - border: `2px solid ${theme.palette.icon03}`, + border: `2px solid ${theme.palette.checkboxBorder}`, borderRadius: '3px', display: 'grid', @@ -90,7 +90,7 @@ const useStyles = makeStyles()(theme => { width: '18px', height: '18px', opacity: 0, - backgroundColor: theme.palette.action01, + backgroundColor: theme.palette.checkboxChecked, display: 'flex', alignItems: 'center', justifyContent: 'center', @@ -104,11 +104,11 @@ const useStyles = makeStyles()(theme => { }, '&:disabled': { - backgroundColor: theme.palette.ui03, - borderColor: theme.palette.ui04, + backgroundColor: theme.palette.checkboxDisabledBackground, + borderColor: theme.palette.checkboxDisabledBorder, '&::before': { - backgroundColor: theme.palette.ui04 + backgroundColor: theme.palette.checkboxDisabledChecked } }, @@ -173,7 +173,7 @@ const Checkbox = ({ diff --git a/react/features/base/ui/components/web/ClickableIcon.tsx b/react/features/base/ui/components/web/ClickableIcon.tsx index 8cb19e98399f..92d1b5f818ca 100644 --- a/react/features/base/ui/components/web/ClickableIcon.tsx +++ b/react/features/base/ui/components/web/ClickableIcon.tsx @@ -16,22 +16,22 @@ const useStyles = makeStyles()(theme => { return { button: { padding: '2px', - backgroundColor: theme.palette.action03, + backgroundColor: theme.palette.clickableIconBackground, border: 0, outline: 0, borderRadius: `${theme.shape.borderRadius}px`, '&:hover': { - backgroundColor: theme.palette.ui02 + backgroundColor: theme.palette.clickableIconHover }, '&.focus-visible': { outline: 0, - boxShadow: `0px 0px 0px 2px ${theme.palette.focus01}` + boxShadow: `0px 0px 0px 2px ${theme.palette.clickableIconFocus}` }, '&:active': { - backgroundColor: theme.palette.ui03 + backgroundColor: theme.palette.clickableIconActive }, '&.is-mobile': { diff --git a/react/features/base/ui/components/web/ContextMenu.tsx b/react/features/base/ui/components/web/ContextMenu.tsx index 074cd9be188e..16aee67a776b 100644 --- a/react/features/base/ui/components/web/ContextMenu.tsx +++ b/react/features/base/ui/components/web/ContextMenu.tsx @@ -141,7 +141,7 @@ const useStyles = makeStyles()((theme) => { //border: `1px solid ${theme.palette.ui04}`, borderRadius: `${Number(theme.shape.borderRadius)}px`, boxShadow: "0px 16px 32px rgba(0, 0, 0, 0.25)", - color: theme.palette.text01, + color: theme.palette.overflowMenuItemText, ...withPixelLineHeight(theme.typography.bodyShortRegular), marginTop: "48px", position: "absolute", diff --git a/react/features/base/ui/components/web/ContextMenuItem.tsx b/react/features/base/ui/components/web/ContextMenuItem.tsx index 3c415ebb45ea..9eaa5c5b494b 100644 --- a/react/features/base/ui/components/web/ContextMenuItem.tsx +++ b/react/features/base/ui/components/web/ContextMenuItem.tsx @@ -121,7 +121,7 @@ const useStyles = makeStyles()((theme) => { }, "&:hover": { - //backgroundColor: theme.palette.ui02, + // backgroundColor: theme.palette.overflowMenuItemHover, borderRadius: theme.shape.borderRadius, backgroundColor: "rgba(255, 255, 255, 0.15)", }, @@ -157,19 +157,19 @@ const useStyles = makeStyles()((theme) => { contextMenuItemIconDisabled: { marginRight: "8px", "& svg": { - fill: `${theme.palette.text03} !important`, + fill: `${theme.palette.overflowMenuItemDisabled} !important`, }, }, contextMenuItemLabelDisabled: { - color: theme.palette.text03, + color: theme.palette.overflowMenuItemDisabled, "&:hover": { background: "none", }, "& svg": { - fill: theme.palette.text03, + fill: theme.palette.overflowMenuItemDisabled, }, }, @@ -179,7 +179,7 @@ const useStyles = makeStyles()((theme) => { contextMenuItemIcon: { "& svg": { - fill: theme.palette.icon01, + fill: theme.palette.overflowMenuItemIcon, }, }, @@ -192,16 +192,16 @@ const useStyles = makeStyles()((theme) => { text: { ...withPixelLineHeight(theme.typography.bodyShortRegular), - // color: theme.palette.text01, + // color: theme.palette.overflowMenuItemText, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", - color: theme.palette.text01, + color: theme.palette.overflowMenuItemText, width: "100%", }, textSelected: { ...withPixelLineHeight(theme.typography.bodyShortRegular), - // color: theme.palette.text01, + // color: theme.palette.overflowMenuItemText, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", diff --git a/react/features/base/ui/components/web/ContextMenuItemGroup.tsx b/react/features/base/ui/components/web/ContextMenuItemGroup.tsx index 35bd375398e9..80fcebe6b48e 100644 --- a/react/features/base/ui/components/web/ContextMenuItemGroup.tsx +++ b/react/features/base/ui/components/web/ContextMenuItemGroup.tsx @@ -30,7 +30,7 @@ const useStyles = makeStyles()((theme) => { }, "& + &:not(:empty)": { - borderTop: `1px solid ${theme.palette.ui03}`, + borderTop: `1px solid ${theme.palette.overflowMenuSeparator}`, }, "&:first-of-type": { diff --git a/react/features/base/ui/components/web/Dialog.tsx b/react/features/base/ui/components/web/Dialog.tsx index 34bed701270b..b07c20fc4e2e 100644 --- a/react/features/base/ui/components/web/Dialog.tsx +++ b/react/features/base/ui/components/web/Dialog.tsx @@ -24,7 +24,7 @@ const useStyles = makeStyles()(theme => { }, title: { - color: theme.palette.text01, + color: theme.palette.dialogText, ...theme.typography.heading5, margin: 0, padding: 0 diff --git a/react/features/base/ui/components/web/DialogWithTabs.tsx b/react/features/base/ui/components/web/DialogWithTabs.tsx index 86313b77793d..7a6e7223f846 100644 --- a/react/features/base/ui/components/web/DialogWithTabs.tsx +++ b/react/features/base/ui/components/web/DialogWithTabs.tsx @@ -43,7 +43,7 @@ const useStyles = makeStyles()(theme => { flexDirection: 'column', minWidth: '211px', maxWidth: '100%', - borderRight: `1px solid ${theme.palette.ui03}`, + borderRight: `1px solid ${theme.palette.dialogBorder}`, [`@media (max-width: ${MOBILE_BREAKPOINT}px)`]: { width: '100%', @@ -71,7 +71,7 @@ const useStyles = makeStyles()(theme => { title: { ...theme.typography.heading5, - color: `${theme.palette.text01} !important`, + color: `${theme.palette.dialogText} !important`, margin: 0, padding: 0 }, @@ -307,7 +307,7 @@ const DialogWithTabs = ({ } return null; - }, [ selectedTabIndex, tabStates ]); + }, [ selectedTabIndex, tabStates, tabs ]); const closeIcon = useMemo(() => ( { }, label: { - color: theme.palette.text01, + color: theme.palette.inputLabel, ...theme.typography.bodyShortRegular, marginBottom: theme.spacing(2), @@ -64,9 +64,9 @@ const useStyles = makeStyles()(theme => { }, input: { - backgroundColor: theme.palette.ui03, - background: theme.palette.ui03, - color: theme.palette.text01, + backgroundColor: theme.palette.inputFieldBackground, + background: theme.palette.inputFieldBackground, + color: theme.palette.inputFieldText, ...theme.typography.bodyShortRegular, padding: '10px 16px', borderRadius: theme.shape.borderRadius, @@ -76,16 +76,16 @@ const useStyles = makeStyles()(theme => { width: '100%', '&::placeholder': { - color: theme.palette.text02 + color: theme.palette.inputFieldPlaceholder }, '&:focus': { outline: 0, - boxShadow: `0px 0px 0px 2px ${theme.palette.focus01}` + boxShadow: `0px 0px 0px 2px ${theme.palette.inputFieldFocus}` }, '&:disabled': { - color: theme.palette.text03 + color: theme.palette.inputFieldDisabled }, '&.is-mobile': { @@ -99,7 +99,7 @@ const useStyles = makeStyles()(theme => { }, '&.error': { - boxShadow: `0px 0px 0px 2px ${theme.palette.textError}` + boxShadow: `0px 0px 0px 2px ${theme.palette.inputFieldError}` }, '&.clearable-input': { paddingRight: '46px' @@ -131,7 +131,7 @@ const useStyles = makeStyles()(theme => { right: '16px', top: '10px', cursor: 'pointer', - backgroundColor: theme.palette.action03, + backgroundColor: theme.palette.inputClearButton, border: 0, padding: 0 }, @@ -139,14 +139,14 @@ const useStyles = makeStyles()(theme => { bottomLabel: { marginTop: theme.spacing(2), ...theme.typography.labelRegular, - color: theme.palette.text02, + color: theme.palette.inputBottomLabel, '&.is-mobile': { ...theme.typography.bodyShortRegular }, '&.error': { - color: theme.palette.textError + color: theme.palette.inputBottomLabelError } } }; diff --git a/react/features/base/ui/components/web/ListItem.tsx b/react/features/base/ui/components/web/ListItem.tsx index 950637198953..65f636ad8d52 100644 --- a/react/features/base/ui/components/web/ListItem.tsx +++ b/react/features/base/ui/components/web/ListItem.tsx @@ -83,7 +83,7 @@ const useStyles = makeStyles()(theme => { return { container: { alignItems: 'center', - color: theme.palette.text01, + color: theme.palette.listItemText, display: 'flex', ...theme.typography.bodyShortBold, margin: `0 -${participantsPaneTheme.panePadding}px`, @@ -93,7 +93,7 @@ const useStyles = makeStyles()(theme => { minHeight: '40px', '&:hover, &:focus-within': { - backgroundColor: theme.palette.ui02, + backgroundColor: theme.palette.listItemHover, '& .indicators': { display: 'none' @@ -103,8 +103,8 @@ const useStyles = makeStyles()(theme => { display: 'flex', position: 'relative', top: 'auto', - boxShadow: `-15px 0px 10px -5px ${theme.palette.ui02}`, - backgroundColor: theme.palette.ui02 + boxShadow: `-15px 0px 10px -5px ${theme.palette.listItemBoxShadow}`, + backgroundColor: theme.palette.listItemHover } }, @@ -115,14 +115,14 @@ const useStyles = makeStyles()(theme => { }, highlighted: { - backgroundColor: theme.palette.ui02, + backgroundColor: theme.palette.listItemHighlighted, '& .actions': { display: 'flex', position: 'relative', top: 'auto', - boxShadow: `-15px 0px 10px -5px ${theme.palette.ui02}`, - backgroundColor: theme.palette.ui02 + boxShadow: `-15px 0px 10px -5px ${theme.palette.listItemBoxShadow}`, + backgroundColor: theme.palette.listItemHighlighted } }, @@ -170,20 +170,20 @@ const useStyles = makeStyles()(theme => { actionsContainer: { position: 'absolute', top: '-1000px', - boxShadow: `-15px 0px 10px -5px ${theme.palette.ui02}`, - backgroundColor: theme.palette.ui02 + boxShadow: `-15px 0px 10px -5px ${theme.palette.listItemBoxShadow}`, + backgroundColor: theme.palette.listItemHover }, actionsPermanent: { display: 'flex', - boxShadow: `-15px 0px 10px -5px ${theme.palette.ui01}`, - backgroundColor: theme.palette.ui01 + boxShadow: `-15px 0px 10px -5px ${theme.palette.listItemBackground}`, + backgroundColor: theme.palette.listItemBackground }, actionsVisible: { display: 'flex', - boxShadow: `-15px 0px 10px -5px ${theme.palette.ui02}`, - backgroundColor: theme.palette.ui02 + boxShadow: `-15px 0px 10px -5px ${theme.palette.listItemBoxShadow}`, + backgroundColor: theme.palette.listItemHighlighted } }; }); diff --git a/react/features/base/ui/components/web/MultiSelect.tsx b/react/features/base/ui/components/web/MultiSelect.tsx index 667e1eb54f61..823b848e5706 100644 --- a/react/features/base/ui/components/web/MultiSelect.tsx +++ b/react/features/base/ui/components/web/MultiSelect.tsx @@ -38,8 +38,8 @@ const useStyles = makeStyles()(theme => { }, marginTop: theme.spacing(2), width: '100%', - backgroundColor: theme.palette.ui01, - border: `1px solid ${theme.palette.ui04}`, + backgroundColor: theme.palette.multiSelectBackground, + border: `1px solid ${theme.palette.multiSelectBorder}`, borderRadius: `${Number(theme.shape.borderRadius)}px`, ...theme.typography.bodyShortRegular, zIndex: 2, @@ -57,7 +57,7 @@ const useStyles = makeStyles()(theme => { inlineSize: 'calc(100% - 38px)', overflowWrap: 'break-word', marginLeft: theme.spacing(2), - color: theme.palette.text01, + color: theme.palette.multiSelectItemText, '&.with-remove': { // 60px because of the icon before the content and the remove button inlineSize: 'calc(100% - 60px)', @@ -76,15 +76,15 @@ const useStyles = makeStyles()(theme => { cursor: 'pointer', padding: `10px ${theme.spacing(3)}`, '&:hover': { - backgroundColor: theme.palette.ui02 + backgroundColor: theme.palette.multiSelectItemHover } }, '&.disabled': { cursor: 'not-allowed', '&:hover': { - backgroundColor: theme.palette.ui01 + backgroundColor: theme.palette.multiSelectBackground }, - color: theme.palette.text03 + color: theme.palette.multiSelectItemDisabled } }, errorMessage: { diff --git a/react/features/base/ui/components/web/Select.tsx b/react/features/base/ui/components/web/Select.tsx index 7a2f1c48645d..4688f992d798 100644 --- a/react/features/base/ui/components/web/Select.tsx +++ b/react/features/base/ui/components/web/Select.tsx @@ -70,7 +70,7 @@ const useStyles = makeStyles()(theme => { }, label: { - color: theme.palette.text01, + color: theme.palette.selectLabel, ...theme.typography.bodyShortRegular, marginBottom: theme.spacing(2), @@ -84,11 +84,11 @@ const useStyles = makeStyles()(theme => { }, select: { - backgroundColor: theme.palette.ui03, + backgroundColor: theme.palette.selectBackground, borderRadius: `${theme.shape.borderRadius}px`, width: '100%', ...theme.typography.bodyShortRegular, - color: theme.palette.text01, + color: theme.palette.selectText, padding: '10px 16px', paddingRight: '42px', border: 0, @@ -99,11 +99,11 @@ const useStyles = makeStyles()(theme => { '&:focus': { outline: 0, - boxShadow: `0px 0px 0px 2px ${theme.palette.focus01}` + boxShadow: `0px 0px 0px 2px ${theme.palette.selectFocus}` }, '&:disabled': { - color: theme.palette.text03 + color: theme.palette.selectDisabled }, '&.is-mobile': { @@ -113,7 +113,7 @@ const useStyles = makeStyles()(theme => { }, '&.error': { - boxShadow: `0px 0px 0px 2px ${theme.palette.textError}` + boxShadow: `0px 0px 0px 2px ${theme.palette.selectError}` } }, @@ -132,14 +132,14 @@ const useStyles = makeStyles()(theme => { bottomLabel: { marginTop: theme.spacing(2), ...theme.typography.labelRegular, - color: theme.palette.text02, + color: theme.palette.selectBottomLabel, '&.is-mobile': { ...theme.typography.bodyShortRegular }, '&.error': { - color: theme.palette.textError + color: theme.palette.selectBottomLabelError } } }; @@ -180,7 +180,7 @@ const Select = ({ diff --git a/react/features/base/ui/components/web/Switch.tsx b/react/features/base/ui/components/web/Switch.tsx index 0a369e25b54b..dd33c2839ade 100644 --- a/react/features/base/ui/components/web/Switch.tsx +++ b/react/features/base/ui/components/web/Switch.tsx @@ -18,7 +18,7 @@ const useStyles = makeStyles()(theme => { return { container: { position: 'relative', - backgroundColor: theme.palette.ui05, + backgroundColor: theme.palette.switchBackground, borderRadius: '12px', width: '40px', height: '24px', @@ -29,11 +29,11 @@ const useStyles = makeStyles()(theme => { display: 'inline-block', '&.disabled': { - backgroundColor: theme.palette.ui05, + backgroundColor: theme.palette.switchBackground, cursor: 'default', '& .toggle': { - backgroundColor: theme.palette.ui03 + backgroundColor: theme.palette.switchToggleDisabled } }, @@ -45,7 +45,7 @@ const useStyles = makeStyles()(theme => { }, containerOn: { - backgroundColor: theme.palette.action01 + backgroundColor: theme.palette.switchBackgroundOn }, toggle: { @@ -55,7 +55,7 @@ const useStyles = makeStyles()(theme => { zIndex: 5, top: '4px', left: '4px', - backgroundColor: theme.palette.ui10, + backgroundColor: theme.palette.switchToggle, borderRadius: '100%', transition: '.3s', @@ -87,7 +87,7 @@ const useStyles = makeStyles()(theme => { '&.focus-visible + .toggle-checkbox-ring': { outline: 0, - boxShadow: `0px 0px 0px 2px ${theme.palette.focus01}` + boxShadow: `0px 0px 0px 2px ${theme.palette.switchFocus}` } }, diff --git a/react/features/base/ui/components/web/Tabs.tsx b/react/features/base/ui/components/web/Tabs.tsx index de2a4cd2a011..e5a9ea24045a 100644 --- a/react/features/base/ui/components/web/Tabs.tsx +++ b/react/features/base/ui/components/web/Tabs.tsx @@ -29,13 +29,13 @@ const useStyles = makeStyles()(theme => { tab: { ...theme.typography.bodyShortBold, - color: theme.palette.text02, + color: theme.palette.tabText, flex: 1, padding: '14px', background: 'none', border: 0, appearance: 'none', - borderBottom: `2px solid ${theme.palette.ui05}`, + borderBottom: `2px solid ${theme.palette.tabBorder}`, transition: 'color, border-color 0.2s', display: 'flex', alignItems: 'center', @@ -43,25 +43,25 @@ const useStyles = makeStyles()(theme => { borderRadius: 0, '&:hover': { - color: theme.palette.text01, - borderColor: theme.palette.ui10 + color: theme.palette.tabTextHover, + borderColor: theme.palette.tabBorderHover }, '&.focus-visible': { outline: 0, - boxShadow: `0px 0px 0px 2px ${theme.palette.focus01}`, + boxShadow: `0px 0px 0px 2px ${theme.palette.tabFocus}`, border: 0, - color: theme.palette.text01 + color: theme.palette.tabTextSelected }, '&.selected': { - color: theme.palette.text01, - borderColor: theme.palette.action01 + color: theme.palette.tabTextSelected, + borderColor: theme.palette.tabBorderSelected }, '&:disabled': { - color: theme.palette.text03, - borderColor: theme.palette.ui05 + color: theme.palette.tabTextDisabled, + borderColor: theme.palette.tabBorderDisabled }, '&.is-mobile': { @@ -72,9 +72,9 @@ const useStyles = makeStyles()(theme => { badge: { ...theme.typography.labelBold, alignItems: 'center', - backgroundColor: theme.palette.warning01, + backgroundColor: theme.palette.tabBadgeBackground, borderRadius: theme.spacing(2), - color: theme.palette.text04, + color: theme.palette.tabBadgeText, display: 'inline-flex', height: theme.spacing(3), justifyContent: 'center', diff --git a/react/features/base/ui/constants.web.ts b/react/features/base/ui/constants.web.ts index 070ea5028d77..519ae4e05db6 100644 --- a/react/features/base/ui/constants.web.ts +++ b/react/features/base/ui/constants.web.ts @@ -11,6 +11,23 @@ export * from './constants.any'; */ export const commonStyles = (theme: Theme) => { return { + ':root': { + // Inject semantic tokens as CSS custom properties for use in SCSS + '--toolbox-background-color': theme.palette.toolboxBackground, + '--drawer-background-color': theme.palette.drawerBackground, + '--toolbar-button-color': theme.palette.toolbarButton, + '--toolbar-button-hover-color': theme.palette.toolbarButtonHover, + '--toolbar-button-active-color': theme.palette.toolbarButtonActive, + '--toolbar-icon-color': theme.palette.toolbarIcon, + '--toolbar-icon-hover-color': theme.palette.toolbarIconHover, + '--toolbar-icon-active-color': theme.palette.toolbarIconActive, + '--overflow-menu-background-color': theme.palette.overflowMenuBackground, + '--overflow-menu-item-text-color': theme.palette.overflowMenuItemText, + '--overflow-menu-item-icon-color': theme.palette.overflowMenuItemIcon, + '--overflow-menu-item-hover-color': theme.palette.overflowMenuItemHover, + '--overflow-menu-item-disabled-color': theme.palette.overflowMenuItemDisabled + }, + '.empty-list': { listStyleType: 'none', margin: 0, @@ -39,7 +56,7 @@ export const commonStyles = (theme: Theme) => { '.overflow-menu-item': { alignItems: 'center', - color: theme.palette.text01, + color: theme.palette.overflowMenuItemText, cursor: 'pointer', display: 'flex', fontSize: '0.875rem', @@ -59,20 +76,20 @@ export const commonStyles = (theme: Theme) => { '&.disabled': { cursor: 'initial', - color: theme.palette.text03, + color: theme.palette.overflowMenuItemDisabled, '&:hover': { background: 'none' }, '& svg': { - fill: theme.palette.text03 + fill: theme.palette.overflowMenuItemDisabled } }, '@media (hover: hover) and (pointer: fine)': { '&:hover': { - background: theme.palette.action02Hover + background: theme.palette.overflowMenuItemHover }, '&.unclickable:hover': { background: 'inherit' @@ -100,14 +117,14 @@ export const commonStyles = (theme: Theme) => { }, '& svg': { - fill: theme.palette.text01, + fill: theme.palette.overflowMenuItemIcon, height: 20, width: 20 } }, '.prejoin-dialog': { - backgroundColor: theme.palette.uiBackground, + backgroundColor: theme.palette.prejoinDialogBackground, boxShadow: '0px 2px 20px rgba(0, 0, 0, 0.5)', borderRadius: theme.shape.borderRadius, color: '#fff', @@ -173,7 +190,7 @@ export const commonStyles = (theme: Theme) => { }, '.prejoin-dialog-delimiter': { - background: theme.palette.ui03, + background: theme.palette.prejoinDialogDelimiter, border: '0', height: '1px', margin: '0', @@ -194,8 +211,8 @@ export const commonStyles = (theme: Theme) => { }, '.prejoin-dialog-delimiter-txt': { - background: theme.palette.uiBackground, - color: theme.palette.text01, + background: theme.palette.prejoinDialogBackground, + color: theme.palette.prejoinDialogDelimiterText, fontSize: '0.75rem', textTransform: 'uppercase' as const, padding: `0 ${theme.spacing(2)}` @@ -219,11 +236,11 @@ export const commonStyles = (theme: Theme) => { '@media (hover: hover) and (pointer: fine)': { '&:hover': { - backgroundColor: theme.palette.ui04 + backgroundColor: theme.palette.toolboxIconHover }, '&:active': { - backgroundColor: theme.palette.ui03 + backgroundColor: theme.palette.toolboxIconActive } }, [theme.breakpoints.down(320)]: { @@ -232,7 +249,7 @@ export const commonStyles = (theme: Theme) => { }, '&.toggled': { - backgroundColor: theme.palette.ui03 + backgroundColor: theme.palette.toolboxIconToggled }, '&.disabled': { @@ -240,13 +257,13 @@ export const commonStyles = (theme: Theme) => { backgroundColor: `${theme.palette.disabled01} !important`, '& svg': { - fill: `${theme.palette.text03} !important` + fill: `${theme.palette.icon03} !important` } } }, '.toolbox-button': { - color: theme.palette.text01, + color: theme.palette.toolbarIcon, cursor: 'pointer', display: 'inline-block', lineHeight: '3rem', @@ -254,7 +271,7 @@ export const commonStyles = (theme: Theme) => { }, '.toolbox-content-items': { - background: theme.palette.ui01, + background: theme.palette.toolboxBackground, borderRadius: 6, margin: '0 auto', padding: 6, diff --git a/react/features/base/ui/types.d.ts b/react/features/base/ui/types.d.ts new file mode 100644 index 000000000000..cd4eb6560d4d --- /dev/null +++ b/react/features/base/ui/types.d.ts @@ -0,0 +1,11 @@ +import '@mui/material/styles'; + +import { IPalette, ITypography } from './types'; + +declare module '@mui/material/styles' { + interface Palette extends IPalette {} + interface PaletteOptions extends Partial {} + + interface Typography extends ITypography {} + interface TypographyOptions extends Partial {} +} diff --git a/react/features/base/ui/types.ts b/react/features/base/ui/types.ts index 0cd52e03c0bb..4c94f0418051 100644 --- a/react/features/base/ui/types.ts +++ b/react/features/base/ui/types.ts @@ -5,6 +5,7 @@ interface ITypographyType { lineHeight: string; } +/* eslint-disable typescript-sort-keys/interface */ export interface IPalette { action01: string; action01Active: string; @@ -58,6 +59,324 @@ export interface IPalette { uiBackground: string; warning01: string; warning02: string; + + // Semantic tokens (component-based, more descriptive names) + breakoutRoomArrowBackground: string; + breakoutRoomBackground: string; + breakoutRoomItemBackground: string; + chatBackground: string; + chatBackdrop: string; + chatEmptyText: string; + chatInputBackground: string; + chatInputBorder: string; + chatLink: string; + chatLobbyMessageBubble: string; + chatLobbyMessageNotice: string; + chatLobbyRecipientContainer: string; + chatMessageLocal: string; + chatMessagePrivate: string; + chatMessageRemote: string; + chatMessageText: string; + chatPrivateNotice: string; + chatRecipientCancelIcon: string; + chatRecipientContainer: string; + chatRecipientText: string; + chatReplyIcon: string; + chatSenderName: string; + chatTimestamp: string; + dialogBackground: string; + dialogBorder: string; + dialogOverlay: string; + dialogSecondaryText: string; + dialogText: string; + drawerBackground: string; + filmstripBackground: string; + filmstripBackgroundHover: string; + filmstripDragHandle: string; + filmstripDragHandleHover: string; + inputBackground: string; + inputBorder: string; + inputPlaceholder: string; + inputText: string; + largeVideoBackground: string; + largeVideoPlaceholder: string; + lobbyBackground: string; + lobbyPreviewBackground: string; + notificationActionFocus: string; + notificationActionText: string; + notificationBackground: string; + notificationCloseIcon: string; + notificationError: string; + notificationErrorText: string; + notificationNormalIcon: string; + notificationSuccess: string; + notificationText: string; + notificationWarning: string; + overflowMenuBackground: string; + overflowMenuBorder: string; + overflowMenuItemDisabled: string; + overflowMenuItemHover: string; + overflowMenuItemIcon: string; + overflowMenuItemText: string; + overflowMenuSeparator: string; + participantActionButton: string; + participantCounterBadge: string; + participantCounterText: string; + participantItemBackground: string; + participantItemBorder: string; + participantItemHover: string; + participantLinkText: string; + participantModeratorLabel: string; + participantRaisedHandBadge: string; + participantRaisedHandIcon: string; + participantSectionText: string; + participantsPaneBackground: string; + participantWarningText: string; + preMeetingBackground: string; + preMeetingPreview: string; + prejoinActionButtonDanger: string; + prejoinActionButtonDisabled: string; + prejoinActionButtonPrimary: string; + prejoinActionButtonPrimaryHover: string; + prejoinActionButtonPrimaryText: string; + prejoinActionButtonSecondary: string; + prejoinActionButtonSecondaryHover: string; + prejoinActionButtonSecondaryText: string; + prejoinCountryPickerBackground: string; + prejoinCountryPickerBorder: string; + prejoinCountryPickerText: string; + prejoinCountryRowBackground: string; + prejoinCountryRowHover: string; + prejoinDeviceStatusOk: string; + prejoinDeviceStatusText: string; + prejoinDeviceStatusWarning: string; + prejoinDialogBackground: string; + prejoinDialogDelimiter: string; + prejoinDialogDelimiterText: string; + prejoinRecordingWarningText: string; + prejoinRoomNameText: string; + prejoinTitleText: string; + prejoinWarningBackground: string; + prejoinWarningText: string; + settingsBackground: string; + settingsErrorIcon: string; + settingsSectionBackground: string; + settingsShortcutKey: string; + settingsTabText: string; + settingsVideoPreviewBorder: string; + speakerStatsAvatarLeft: string; + speakerStatsBackground: string; + speakerStatsBorder: string; + speakerStatsHeaderBackground: string; + speakerStatsLabelText: string; + speakerStatsRowAlternate: string; + speakerStatsRowBackground: string; + speakerStatsSearchBackground: string; + speakerStatsSearchBorder: string; + speakerStatsSearchIcon: string; + speakerStatsSearchPlaceholder: string; + speakerStatsSearchText: string; + speakerStatsSuccessBar: string; + thumbnailBackground: string; + thumbnailBorder: string; + thumbnailHover: string; + thumbnailRaisedHandIcon: string; + thumbnailTintBackground: string; + thumbnailVideoBackground: string; + toolbarButton: string; + toolbarButtonActive: string; + toolbarButtonHover: string; + toolbarIcon: string; + toolbarIconActive: string; + toolbarIconHover: string; + toolboxBackground: string; + toolboxIconActive: string; + toolboxIconHover: string; + toolboxIconToggled: string; + visitorsArrowBackground: string; + visitorsCountBadge: string; + visitorsCountIcon: string; + visitorsCountText: string; + visitorsQueueBackground: string; + visitorsQueueText: string; + welcomeBackground: string; + welcomeCard: string; + welcomeTabActive: string; + welcomeTabInactive: string; + + // Form components + actionButtonBackground: string; + actionButtonBorder: string; + actionButtonText: string; + audioRoutePickerBackground: string; + audioRoutePickerBorder: string; + audioRoutePickerText: string; + baseReactBackground: string; + baseReactBorder: string; + baseReactText: string; + carModeBackground: string; + carModeBorder: string; + carModeText: string; + checkboxBorder: string; + checkboxChecked: string; + checkboxDisabledBackground: string; + checkboxDisabledBorder: string; + checkboxDisabledChecked: string; + checkboxIcon: string; + checkboxIconDisabled: string; + checkboxLabel: string; + clickableIconActive: string; + clickableIconBackground: string; + clickableIconFocus: string; + clickableIconHover: string; + conferenceNoticeBackground: string; + conferenceNoticeText: string; + conferenceRaisedHandLabelIcon: string; + conferenceRaisedHandLabelText: string; + conferenceSubjectText: string; + conferenceTimerText: string; + connectionIndicatorLost: string; + connectionIndicatorOther: string; + deepLinkingBackground: string; + deepLinkingBorder: string; + deepLinkingLabelText: string; + deepLinkingLink: string; + deepLinkingSeparator: string; + deepLinkingText: string; + deviceSelectorBackground: string; + deviceSelectorBorder: string; + deviceSelectorText: string; + deviceSelectorTextBackground: string; + deviceSelectorVideoPreview: string; + dialInBackground: string; + dialInSecondaryText: string; + dialInText: string; + displayNameBackground: string; + displayNameText: string; + etherpadBackground: string; + etherpadText: string; + fileSharingBackground: string; + fileSharingEmptyIcon: string; + fileSharingEmptyText: string; + fileSharingItemBackground: string; + fileSharingItemBorder: string; + fileSharingText: string; + gifsBackground: string; + gifsText: string; + inlineDialogBackground: string; + inlineDialogBorder: string; + inlineDialogText: string; + inputBottomLabel: string; + inputBottomLabelError: string; + inputClearButton: string; + inputFieldBackground: string; + inputFieldBorder: string; + inputFieldDisabled: string; + inputFieldError: string; + inputFieldFocus: string; + inputFieldPlaceholder: string; + inputFieldText: string; + inputLabel: string; + labelBackground: string; + labelText: string; + labelWhiteBackground: string; + labelWhiteIcon: string; + labelWhiteText: string; + languageSelectorBackground: string; + languageSelectorHover: string; + languageSelectorText: string; + listItemBackground: string; + listItemBoxShadow: string; + listItemHighlighted: string; + listItemHover: string; + listItemText: string; + multiSelectBackground: string; + multiSelectBorder: string; + multiSelectItemDisabled: string; + multiSelectItemHover: string; + multiSelectItemText: string; + pollsAnswer: string; + pollsBackground: string; + pollsBarBackground: string; + pollsBarPercentage: string; + pollsCreateBackground: string; + pollsCreateBorder: string; + pollsPaneBackground: string; + pollsPaneBorder: string; + pollsQuestion: string; + pollsSendDisabled: string; + pollsSendLabel: string; + pollsSeparator: string; + pollsSubtitle: string; + pollsTitle: string; + pollsVotersBackground: string; + pollsVotersText: string; + reactionsMenuBackground: string; + reactionsMenuBorder: string; + recordingBackground: string; + recordingHighlightButton: string; + recordingHighlightButtonDisabled: string; + recordingHighlightButtonIcon: string; + recordingHighlightButtonIconDisabled: string; + recordingNotificationAction: string; + recordingNotificationText: string; + recordingText: string; + securityDialogBackground: string; + securityDialogBorder: string; + securityDialogSecondaryText: string; + securityDialogText: string; + selectBackground: string; + selectBottomLabel: string; + selectBottomLabelError: string; + selectDisabled: string; + selectError: string; + selectFocus: string; + selectIcon: string; + selectIconDisabled: string; + selectLabel: string; + selectText: string; + sliderFocus: string; + sliderKnob: string; + sliderTrack: string; + subtitleMessageBackground: string; + subtitleMessageSender: string; + subtitleMessageText: string; + subtitleMessageTime: string; + switchBackground: string; + switchBackgroundOn: string; + switchFocus: string; + switchToggle: string; + switchToggleDisabled: string; + tabBadgeBackground: string; + tabBadgeText: string; + tabBorder: string; + tabBorderDisabled: string; + tabBorderHover: string; + tabBorderSelected: string; + tabFocus: string; + tabText: string; + tabTextDisabled: string; + tabTextHover: string; + tabTextSelected: string; + tooltipBackground: string; + tooltipText: string; + videoMenuBackground: string; + videoMenuBorder: string; + videoMenuSliderBackground: string; + videoMenuText: string; + videoQualityBackground: string; + videoQualityText: string; + virtualBackgroundBackground: string; + virtualBackgroundBorder: string; + virtualBackgroundPreview: string; + virtualBackgroundText: string; + whiteboardBackground: string; + whiteboardText: string; + salesforceSearchBackground: string; + salesforceSearchBorder: string; + salesforceSearchIcon: string; + salesforceSearchPlaceholder: string; + salesforceSearchText: string; } export interface ITypography { diff --git a/react/features/base/ui/utils.ts b/react/features/base/ui/utils.ts index 25b07b1a3a30..0e8e46957750 100644 --- a/react/features/base/ui/utils.ts +++ b/react/features/base/ui/utils.ts @@ -11,13 +11,49 @@ import * as tokens from './tokens.json'; */ export function createColorTokens(colorMap: Object): any { const allTokens = merge({}, tokens, jitsiTokens); + const result: any = {}; - return Object.entries(colorMap) - .reduce((result, [ token, value ]: [any, string]) => { - const color = allTokens[value as keyof typeof allTokens] || value; + // First pass: resolve tokens that reference allTokens directly + Object.entries(colorMap).forEach(([ token, value ]: [any, string]) => { + const color = allTokens[value as keyof typeof allTokens] || value; - return Object.assign(result, { [token]: color }); - }, {}); + result[token] = color; + }); + + // Second pass: resolve semantic tokens that reference other colorMap entries + // Recursively resolve until we get actual color values + const resolveToken = (value: string, depth = 0): string => { + // Prevent infinite loops + if (depth > 10) { + return value; + } + + // If it's already a color (starts with # or rgb/rgba), return it + if (value.startsWith('#') || value.startsWith('rgb')) { + return value; + } + + // Look up in the result map first (for colorMap token references) + if (result[value]) { + return resolveToken(result[value], depth + 1); + } + + // Then look up in allTokens + const resolved = allTokens[value as keyof typeof allTokens]; + + if (resolved && resolved !== value && typeof resolved === 'string') { + return resolveToken(resolved, depth + 1); + } + + return value; + }; + + // Third pass: recursively resolve all values + Object.entries(result).forEach(([ token, value ]) => { + result[token] = resolveToken(String(value)); + }); + + return result; } /** diff --git a/react/features/breakout-rooms/components/native/styles.ts b/react/features/breakout-rooms/components/native/styles.ts index 250c5147f435..a984fbfb05f5 100644 --- a/react/features/breakout-rooms/components/native/styles.ts +++ b/react/features/breakout-rooms/components/native/styles.ts @@ -8,7 +8,7 @@ export default { button: { marginBottom: BaseTheme.spacing[4], - marginHorizontal: BaseTheme.spacing[2] + marginHorizontal: BaseTheme.spacing[3] }, collapsibleList: { diff --git a/react/features/breakout-rooms/functions.ts b/react/features/breakout-rooms/functions.ts index 108e811307fb..70d32b59fcde 100644 --- a/react/features/breakout-rooms/functions.ts +++ b/react/features/breakout-rooms/functions.ts @@ -44,6 +44,16 @@ export const getMainRoom = (stateful: IStateful) => { * @returns {IRoomsInfo} The rooms info. */ export const getRoomsInfo = (stateful: IStateful) => { + const state = toState(stateful); + const localParticipant = getLocalParticipant(stateful); + const jwtUser = state['features/base/jwt']?.user; + const localUserContext = jwtUser ? { + id: jwtUser.id, + name: jwtUser.name + } : { + id: localParticipant?.jwtId, + name: localParticipant?.name + }; const breakoutRooms = getBreakoutRooms(stateful); const conference = getCurrentConference(stateful); @@ -57,7 +67,6 @@ export const getRoomsInfo = (stateful: IStateful) => { const conferenceParticipants = conference?.getParticipants() .filter((participant: IJitsiParticipant) => !participant.isHidden()); - const localParticipant = getLocalParticipant(stateful); let localParticipantInfo; if (localParticipant) { @@ -65,7 +74,8 @@ export const getRoomsInfo = (stateful: IStateful) => { role: localParticipant.role, displayName: localParticipant.name, avatarUrl: localParticipant.loadableAvatarUrl, - id: localParticipant.id + id: localParticipant.id, + userContext: localUserContext }; } @@ -86,7 +96,8 @@ export const getRoomsInfo = (stateful: IStateful) => { role: participantItem.getRole(), displayName: participantItem.getDisplayName(), avatarUrl: storeParticipant?.loadableAvatarUrl, - id: participantItem.getId() + id: participantItem.getId(), + userContext: storeParticipant?.userContext } as IRoomInfoParticipant; }) ] : [ localParticipantInfo ] @@ -110,13 +121,18 @@ export const getRoomsInfo = (stateful: IStateful) => { const storeParticipant = getParticipantById(stateful, ids.length > 1 ? ids[1] : participantItem.jid); + // Check if this is the local participant + const isLocal = storeParticipant?.id === localParticipant?.id; + const userContext = isLocal ? localUserContext : (storeParticipant?.userContext || participantItem.userContext); + return { jid: participantItem?.jid, role: participantItem?.role, displayName: participantItem?.displayName, avatarUrl: storeParticipant?.loadableAvatarUrl, id: storeParticipant ? storeParticipant.id - : participantLongId + : participantLongId, + userContext } as IRoomInfoParticipant; }) : [] } as IRoomInfo; diff --git a/react/features/breakout-rooms/middleware.ts b/react/features/breakout-rooms/middleware.ts index 2e27666a8228..b4d5827a04ce 100644 --- a/react/features/breakout-rooms/middleware.ts +++ b/react/features/breakout-rooms/middleware.ts @@ -44,19 +44,59 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => { switch (type) { case UPDATE_BREAKOUT_ROOMS: { - // edit name if it was overwritten + // Enrich participants with userContext from Redux store if (!action.updatedNames) { - const { overwrittenNameList } = getState()['features/base/participants']; + const state = getState(); + const { overwrittenNameList, local: localParticipant } = state['features/base/participants']; + const jwtUser = state['features/base/jwt']?.user; + const localUserContext = jwtUser ? { + id: jwtUser.id, + name: jwtUser.name + } : { + id: localParticipant?.jwtId, + name: localParticipant?.name + }; + + // Get existing userContext cache + const existingCache = state['features/breakout-rooms'].userContextCache || {}; + const newCache = { ...existingCache }; + + const newRooms: IRooms = {}; + + Object.entries(action.rooms as IRooms).forEach(([ key, r ]) => { + let participants = r?.participants || {}; + + // Add userContext to each participant + const enhancedParticipants: typeof participants = {}; + + for (const [ participantJid, participantData ] of Object.entries(participants)) { + const ids = participantJid.split('/'); + const participantId = ids.length > 1 ? ids[1] : participantData.jid; + const storeParticipant = getParticipantById(state, participantId); + const isLocal = storeParticipant?.id === localParticipant?.id; + + // Try to get userContext from: local, store, cache, or incoming data + const userContext = isLocal + ? localUserContext + : (storeParticipant?.userContext || newCache[participantId] || participantData.userContext); + + // Update cache if we have userContext + if (userContext && participantId) { + newCache[participantId] = userContext; + } - if (Object.keys(overwrittenNameList).length > 0) { - const newRooms: IRooms = {}; + enhancedParticipants[participantJid] = { + ...participantData, + userContext + }; + } - Object.entries(action.rooms as IRooms).forEach(([ key, r ]) => { - let participants = r?.participants || {}; - let jid; + participants = enhancedParticipants; + // Apply overwritten display names + if (Object.keys(overwrittenNameList).length > 0) { for (const id of Object.keys(overwrittenNameList)) { - jid = Object.keys(participants).find(p => p.slice(p.indexOf('/') + 1) === id); + const jid = Object.keys(participants).find(p => p.slice(p.indexOf('/') + 1) === id); if (jid) { participants = { @@ -68,15 +108,16 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => { }; } } + } - newRooms[key] = { - ...r, - participants - }; - }); + newRooms[key] = { + ...r, + participants + }; + }); - action.rooms = newRooms; - } + action.rooms = newRooms; + action.userContextCache = newCache; } // edit the chat history to match names for participants in breakout rooms diff --git a/react/features/breakout-rooms/reducer.ts b/react/features/breakout-rooms/reducer.ts index 5412c28b7985..964064b6aca1 100644 --- a/react/features/breakout-rooms/reducer.ts +++ b/react/features/breakout-rooms/reducer.ts @@ -10,12 +10,19 @@ import { IRooms } from './types'; const DEFAULT_STATE = { rooms: {}, - roomCounter: 0 + roomCounter: 0, + userContextCache: {} }; export interface IBreakoutRoomsState { roomCounter: number; rooms: IRooms; + userContextCache: { + [participantId: string]: { + id?: string; + name?: string; + }; + }; } /** @@ -29,12 +36,13 @@ ReducerRegistry.register(FEATURE_KEY, (state = DEFAULT_STAT roomCounter: action.roomCounter }; case UPDATE_BREAKOUT_ROOMS: { - const { roomCounter, rooms } = action; + const { roomCounter, rooms, userContextCache } = action; return { ...state, roomCounter, - rooms + rooms, + userContextCache: userContextCache || state.userContextCache }; } case _RESET_BREAKOUT_ROOMS: { diff --git a/react/features/breakout-rooms/types.ts b/react/features/breakout-rooms/types.ts index bb4a47f6e4ce..b7fde49ac826 100644 --- a/react/features/breakout-rooms/types.ts +++ b/react/features/breakout-rooms/types.ts @@ -8,6 +8,10 @@ export interface IRoom { displayName: string; jid: string; role: string; + userContext?: { + id?: string; + name?: string; + }; }; }; } @@ -33,4 +37,8 @@ export interface IRoomInfoParticipant { id: string; jid: string; role: string; + userContext?: { + id?: string; + name?: string; + }; } diff --git a/react/features/chat/actions.native.ts b/react/features/chat/actions.native.ts index 3a57d63662a6..83816e1a45fa 100644 --- a/react/features/chat/actions.native.ts +++ b/react/features/chat/actions.native.ts @@ -22,7 +22,7 @@ export function openChat(participant?: IParticipant | undefined | Object, disabl if (disablePolls) { navigate(screen.conference.chat); } else { - navigate(screen.conference.chatandpolls.main); + navigate(screen.conference.chatTabs.main); } dispatch(setFocusedTab(ChatTabs.CHAT)); diff --git a/react/features/chat/components/AbstractClosedCaptions.tsx b/react/features/chat/components/AbstractClosedCaptions.tsx new file mode 100644 index 000000000000..a02ebb51cce7 --- /dev/null +++ b/react/features/chat/components/AbstractClosedCaptions.tsx @@ -0,0 +1,111 @@ +import React, { ComponentType, useCallback, useEffect, useMemo, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; + +import { IReduxState } from '../../app/types'; +import { openDialog } from '../../base/dialog/actions'; +import { IMessageGroup, groupMessagesBySender } from '../../base/util/messageGrouping'; +// @ts-ignore +import { StartRecordingDialog } from '../../recording/components/Recording'; +import { setRequestingSubtitles } from '../../subtitles/actions.any'; +import { canStartSubtitles } from '../../subtitles/functions.any'; +import { ISubtitle } from '../../subtitles/types'; +import { isTranscribing } from '../../transcribing/functions'; + +export type AbstractProps = { + canStartSubtitles: boolean; + filteredSubtitles: ISubtitle[]; + groupedSubtitles: IMessageGroup[]; + isButtonPressed: boolean; + isTranscribing: boolean; + startClosedCaptions: () => void; +}; + +const AbstractClosedCaptions = (Component: ComponentType) => () => { + const dispatch = useDispatch(); + const subtitles = useSelector((state: IReduxState) => state['features/subtitles'].subtitlesHistory); + const language = useSelector((state: IReduxState) => state['features/subtitles']._language); + const selectedLanguage = language?.replace('translation-languages:', ''); + const _isTranscribing = useSelector(isTranscribing); + const _canStartSubtitles = useSelector(canStartSubtitles); + const [ isButtonPressed, setButtonPressed ] = useState(false); + const subtitlesError = useSelector((state: IReduxState) => state['features/subtitles']._hasError); + const isAsyncTranscriptionEnabled = useSelector((state: IReduxState) => + state['features/base/conference'].conference?.getMetadataHandler()?.getMetadata()?.asyncTranscription); + + const filteredSubtitles = useMemo(() => { + // First, create a map of transcription messages by message ID + const transcriptionMessages = new Map( + subtitles + .filter(s => s.isTranscription) + .map(s => [ s.id, s ]) + ); + + if (!selectedLanguage) { + // When no language is selected, show all original transcriptions + return Array.from(transcriptionMessages.values()); + } + + // Then, create a map of translation messages by message ID + const translationMessages = new Map( + subtitles + .filter(s => !s.isTranscription && s.language === selectedLanguage) + .map(s => [ s.id, s ]) + ); + + // When a language is selected, for each transcription message: + // 1. Use its translation if available + // 2. Fall back to the original transcription if no translation exists + return Array.from(transcriptionMessages.values()) + .filter((m: ISubtitle) => !m.interim) + .map(m => translationMessages.get(m.id) ?? m); + }, [ subtitles, selectedLanguage ]); + + const groupedSubtitles = useMemo(() => + groupMessagesBySender(filteredSubtitles), [ filteredSubtitles ]); + + const startClosedCaptions = useCallback(() => { + if (isAsyncTranscriptionEnabled) { + dispatch(openDialog('StartRecordingDialog', StartRecordingDialog, { + recordAudioAndVideo: false + })); + } else { + if (isButtonPressed) { + return; + } + dispatch(setRequestingSubtitles(true, false, null)); + setButtonPressed(true); + } + + }, [ isAsyncTranscriptionEnabled, dispatch, isButtonPressed, openDialog, setButtonPressed ]); + + useEffect(() => { + if (subtitlesError && isButtonPressed && !isAsyncTranscriptionEnabled) { + setButtonPressed(false); + } + }, [ subtitlesError, isButtonPressed, isAsyncTranscriptionEnabled ]); + + useEffect(() => { + if (!_isTranscribing && isButtonPressed && !isAsyncTranscriptionEnabled) { + setButtonPressed(false); + } + }, [ _isTranscribing, isButtonPressed, isAsyncTranscriptionEnabled ]); + + useEffect(() => { + if (isButtonPressed && !isAsyncTranscriptionEnabled) { + setButtonPressed(false); + } + }, [ isButtonPressed, isAsyncTranscriptionEnabled ]); + + return ( + + ); +}; + +export default AbstractClosedCaptions; + diff --git a/react/features/chat/components/native/Chat.tsx b/react/features/chat/components/native/Chat.tsx index 03a654be98df..37389ed2a4e6 100644 --- a/react/features/chat/components/native/Chat.tsx +++ b/react/features/chat/components/native/Chat.tsx @@ -1,39 +1,29 @@ /* eslint-disable react/no-multi-comp */ import { Route, useIsFocused } from '@react-navigation/native'; import React, { Component, useEffect } from 'react'; -import { connect } from 'react-redux'; +import { connect, useSelector } from 'react-redux'; import { IReduxState } from '../../../app/types'; import { translate } from '../../../base/i18n/functions'; import JitsiScreen from '../../../base/modal/components/JitsiScreen'; +import { StyleType } from '../../../base/styles/functions.native'; import { TabBarLabelCounter } from '../../../mobile/navigation/components/TabBarLabelCounter'; -import { getUnreadPollCount } from '../../../polls/functions'; +import { pollsStyles } from '../../../polls/components/native/styles'; import { closeChat, sendMessage } from '../../actions.native'; -import { getUnreadFilesCount } from '../../functions'; +import { ChatTabs } from '../../constants'; import { IChatProps as AbstractProps } from '../../types'; import ChatInputBar from './ChatInputBar'; import MessageContainer from './MessageContainer'; import MessageRecipient from './MessageRecipient'; -import styles from './styles'; interface IProps extends AbstractProps { - /** - * The number of unread file messages. - */ - _unreadFilesCount: number; - /** * The number of unread messages. */ _unreadMessagesCount: number; - /** - * The number of unread polls. - */ - _unreadPollsCount: number; - /** * Default prop for navigating between screen components(React Navigation). */ @@ -62,6 +52,7 @@ class Chat extends Component { // Bind event handlers so they are only bound once per instance. this._onSendMessage = this._onSendMessage.bind(this); + this._renderFooter = this._renderFooter.bind(this); } /** @@ -76,14 +67,10 @@ class Chat extends Component { return ( - - } + footerComponent = { this._renderFooter } hasBottomTextInput = { true } hasExtraHeaderHeight = { true } - style = { styles.chatContainer }> + style = { pollsStyles.pollPaneContainer as StyleType }> {/* @ts-ignore */} @@ -91,6 +78,16 @@ class Chat extends Component { ); } + /** + * Renders the footer component. + * + * @private + * @returns {React$Element<*>} + */ + _renderFooter() { + return ; + } + /** * Sends a text message. * @@ -113,9 +110,7 @@ class Chat extends Component { * @private * @returns {{ * _messages: Array, - * _unreadMessagesCount: number, - * _unreadPollsCount: number, - * _unreadFilesCount: number + * _unreadMessagesCount: number * }} */ function _mapStateToProps(state: IReduxState, _ownProps: any) { @@ -123,34 +118,34 @@ function _mapStateToProps(state: IReduxState, _ownProps: any) { return { _messages: messages, - _unreadMessagesCount: unreadMessagesCount, - _unreadPollsCount: getUnreadPollCount(state), - _unreadFilesCount: getUnreadFilesCount(state) + _unreadMessagesCount: unreadMessagesCount }; } export default translate(connect(_mapStateToProps)((props: IProps) => { - const { _unreadMessagesCount, _unreadPollsCount, _unreadFilesCount, dispatch, navigation, t } = props; - const totalUnread = _unreadMessagesCount + _unreadPollsCount + _unreadFilesCount; - const unreadMessagesNr = totalUnread > 0; + const { _unreadMessagesCount, dispatch, navigation, t } = props; + + const isChatTabFocused = useSelector((state: IReduxState) => state['features/chat'].focusedTab === ChatTabs.CHAT); const isFocused = useIsFocused(); + const activeUnreadMessagesNr = !isChatTabFocused && _unreadMessagesCount > 0; + useEffect(() => { navigation?.setOptions({ tabBarLabel: () => ( + unreadCount = { _unreadMessagesCount } /> ) }); return () => { isFocused && dispatch(closeChat()); }; - }, [ isFocused, _unreadMessagesCount, _unreadPollsCount, _unreadFilesCount ]); + }, [ isFocused, _unreadMessagesCount ]); return ( diff --git a/react/features/chat/components/native/ChatButton.ts b/react/features/chat/components/native/ChatButton.ts index dcf50e4e1228..5a2146abcb49 100644 --- a/react/features/chat/components/native/ChatButton.ts +++ b/react/features/chat/components/native/ChatButton.ts @@ -43,7 +43,7 @@ class ChatButton extends AbstractButton { override _handleClick() { this.props._isPollsDisabled ? navigate(screen.conference.chat) - : navigate(screen.conference.chatandpolls.main); + : navigate(screen.conference.chatTabs.main); } /** diff --git a/react/features/chat/components/native/ChatInputBar.tsx b/react/features/chat/components/native/ChatInputBar.tsx index 6ab8dac9f89b..08e0d22bdfaa 100644 --- a/react/features/chat/components/native/ChatInputBar.tsx +++ b/react/features/chat/components/native/ChatInputBar.tsx @@ -7,7 +7,6 @@ import { connect } from 'react-redux'; import { IReduxState } from '../../../app/types'; import { translate } from '../../../base/i18n/functions'; import { IconSend } from '../../../base/icons/svg'; -import { ASPECT_RATIO_WIDE } from '../../../base/responsive-ui/constants'; import IconButton from '../../../base/ui/components/native/IconButton'; import Input from '../../../base/ui/components/native/Input'; import { BUTTON_TYPES } from '../../../base/ui/constants.native'; @@ -85,14 +84,6 @@ class ChatInputBar extends Component { * @inheritdoc */ override render() { - let inputBarStyles; - - if (this.props.aspectRatio === ASPECT_RATIO_WIDE) { - inputBarStyles = styles.inputBarWide; - } else { - inputBarStyles = styles.inputBarNarrow; - } - if (this.props._isSendGroupChatDisabled && !this.props._privateMessageRecipientId) { return ( { { id = { this.props.t('chat.sendButton') } onPress = { this._onSubmit } src = { IconSend } + style = { styles.sendButton } type = { BUTTON_TYPES.PRIMARY } /> ); diff --git a/react/features/chat/components/native/ClosedCaptions.tsx b/react/features/chat/components/native/ClosedCaptions.tsx new file mode 100644 index 000000000000..4d5095a93354 --- /dev/null +++ b/react/features/chat/components/native/ClosedCaptions.tsx @@ -0,0 +1,130 @@ +import { useNavigation } from '@react-navigation/native'; +import React, { useCallback, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { TouchableHighlight, View, ViewStyle } from 'react-native'; +import { Text } from 'react-native-paper'; +import { useSelector } from 'react-redux'; + +import { IReduxState } from '../../../app/types'; +import Icon from '../../../base/icons/components/Icon'; +import { IconArrowRight, IconSubtitles } from '../../../base/icons/svg'; +import JitsiScreen from '../../../base/modal/components/JitsiScreen'; +import { StyleType } from '../../../base/styles/functions.any'; +import BaseTheme from '../../../base/ui/components/BaseTheme.native'; +import Button from '../../../base/ui/components/native/Button'; +import { BUTTON_TYPES } from '../../../base/ui/constants.native'; +import { TabBarLabelCounter } from '../../../mobile/navigation/components/TabBarLabelCounter'; +import { navigate } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef'; +import { screen } from '../../../mobile/navigation/routes'; +import { ChatTabs } from '../../constants'; +import AbstractClosedCaptions, { AbstractProps } from '../AbstractClosedCaptions'; + +import { SubtitlesMessagesContainer } from './SubtitlesMessagesContainer'; +import { closedCaptionsStyles } from './styles'; + +/** + * Component that displays the closed captions interface. + * + * @returns {JSX.Element} - The ClosedCaptions component. + */ +const ClosedCaptions = ({ + canStartSubtitles, + filteredSubtitles, + groupedSubtitles, + isButtonPressed, + isTranscribing, + startClosedCaptions +}: AbstractProps): JSX.Element => { + const navigation = useNavigation(); + const { t } = useTranslation(); + const isCCTabFocused = useSelector((state: IReduxState) => state['features/chat'].focusedTab === ChatTabs.CLOSED_CAPTIONS); + const selectedLanguage = useSelector((state: IReduxState) => state['features/subtitles']._language); + const navigateToLanguageSelect = useCallback(() => { + navigate(screen.conference.subtitles); + }, [ navigation, screen ]); + const isAsyncTranscriptionEnabled = useSelector((state: IReduxState) => + state['features/base/conference'].conference?.getMetadataHandler()?.getMetadata()?.asyncTranscription); + + useEffect(() => { + navigation?.setOptions({ + tabBarLabel: () => ( + + ) + }); + }, [ isCCTabFocused, navigation, t ]); + + const getContentContainerStyle = () => { + if (isTranscribing) { + return closedCaptionsStyles.transcribingContainer as StyleType; + } + + return closedCaptionsStyles.emptyContentContainer as StyleType; + }; + + const renderContent = () => { + if (!isTranscribing) { + if (canStartSubtitles) { + return ( + + @@ -335,7 +335,7 @@ const FileItem = ({ onClick = { handleRemove } type = 'button'> diff --git a/react/features/file-sharing/components/web/FileSharing.tsx b/react/features/file-sharing/components/web/FileSharing.tsx index 0684016e672b..34d530b92474 100644 --- a/react/features/file-sharing/components/web/FileSharing.tsx +++ b/react/features/file-sharing/components/web/FileSharing.tsx @@ -6,6 +6,7 @@ import { makeStyles } from 'tss-react/mui'; import { IReduxState } from '../../../app/types'; import Icon from '../../../base/icons/components/Icon'; import { IconCloudUpload } from '../../../base/icons/svg'; +import Tooltip from '../../../base/tooltip/components/Tooltip'; import BaseTheme from '../../../base/ui/components/BaseTheme.web'; import Button from '../../../base/ui/components/web/Button'; import { BUTTON_TYPES } from '../../../base/ui/constants.web'; @@ -32,8 +33,8 @@ const useStyles = makeStyles()(theme => { }, dropZone: { - backgroundColor: theme.palette.ui02, - border: `2px dashed ${theme.palette.ui03}`, + backgroundColor: theme.palette.fileSharingItemBackground, + border: `2px dashed ${theme.palette.fileSharingItemBorder}`, borderRadius: theme.shape.borderRadius, bottom: 0, left: 0, @@ -44,7 +45,7 @@ const useStyles = makeStyles()(theme => { zIndex: 0, '&.dragging': { - backgroundColor: theme.palette.ui03, + backgroundColor: theme.palette.fileSharingItemBorder, borderColor: theme.palette.action01, opacity: 0.8, zIndex: 2 @@ -85,7 +86,7 @@ const useStyles = makeStyles()(theme => { noFilesText: { ...theme.typography.bodyLongBold, - color: theme.palette.text02, + color: theme.palette.fileSharingEmptyText, padding: '0 24px', textAlign: 'center' }, @@ -189,7 +190,7 @@ const FileSharing = () => { tabIndex = { 0 }> @@ -226,16 +227,20 @@ const FileSharing = () => { ) } { - isUploadEnabled && ( +