Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot]
0d32907178 chore(ci): bump actions/setup-node from 6.1.0 to 6.2.0
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 6.1.0 to 6.2.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](395ad32622...6044e13b5d)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: 6.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-19 17:55:45 +00:00
303 changed files with 2696 additions and 9864 deletions

View File

@@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f #v6.1.0
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 #v6.2.0
with:
node-version-file: '.nvmrc'
cache: 'npm'
@@ -43,7 +43,7 @@ jobs:
os: [macos-latest, ubuntu-latest]
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f #v6.1.0
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 #v6.2.0
with:
node-version-file: '.nvmrc'
cache: 'npm'
@@ -60,7 +60,7 @@ jobs:
runs-on: macos-15
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f #v6.1.0
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 #v6.2.0
with:
node-version-file: '.nvmrc'
cache: 'npm'
@@ -75,7 +75,7 @@ jobs:
runs-on: macos-15
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f #v6.1.0
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 #v6.2.0
with:
node-version-file: '.nvmrc'
cache: 'npm'
@@ -117,7 +117,7 @@ jobs:
rm -rf /host/usr/share/swift
df -h /
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f #v6.1.0
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 #v6.2.0
with:
node-version-file: '.nvmrc'
cache: 'npm'
@@ -138,7 +138,7 @@ jobs:
runs-on: macos-15
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f #v6.1.0
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 #v6.2.0
with:
node-version-file: '.nvmrc'
cache: 'npm'
@@ -188,7 +188,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f #v6.1.0
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 #v6.2.0
with:
node-version-file: '.nvmrc'
cache: 'npm'

View File

@@ -96,53 +96,23 @@ public class JitsiMeetActivity extends AppCompatActivity
public static void addTopBottomInsets(@NonNull Window w, @NonNull View v) {
// Enable edge-to-edge mode
androidx.core.view.WindowCompat.setDecorFitsSystemWindows(w, false);
// Make system bars transparent so content is visible underneath
w.setStatusBarColor(android.graphics.Color.TRANSPARENT);
w.setNavigationBarColor(android.graphics.Color.TRANSPARENT);
View decorView = w.getDecorView();
// Get display metrics for calculating density-independent caps
final android.util.DisplayMetrics metrics = v.getContext().getResources().getDisplayMetrics();
final int screenHeight = metrics.heightPixels;
final float density = metrics.density;
// Listen for window inset changes
// when system bars visibility is toggled or when the device rotates
ViewCompat.setOnApplyWindowInsetsListener(decorView, (view, windowInsets) -> {
// Get the actual inset values reported by the system
int statusBarInset = windowInsets.getInsets(WindowInsetsCompat.Type.statusBars()).top;
int navBarInset = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom;
// Calculate maximum allowed inset values to prevent device-specific bugs
final int maxTopInset = Math.min((int)(60 * density), (int)(screenHeight * 0.10));
final int maxBottomInset = Math.min((int)(120 * density), (int)(screenHeight * 0.10));
int topInset = Math.min(statusBarInset, maxTopInset);
int bottomInset = Math.min(navBarInset, maxBottomInset);
// Apply calculated insets
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) v.getLayoutParams();
// Update margins only if they've changed
if (params.topMargin != topInset || params.bottomMargin != bottomInset) {
params.topMargin = topInset;
params.bottomMargin = bottomInset;
decorView.post(() -> {
WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(decorView);
if (insets != null) {
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) v.getLayoutParams();
params.topMargin = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top;
params.bottomMargin = insets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom;
v.setLayoutParams(params);
decorView.setOnApplyWindowInsetsListener((view, windowInsets) -> {
view.setBackgroundColor(JitsiMeetView.BACKGROUND_COLOR);
return windowInsets;
});
}
view.setBackgroundColor(JitsiMeetView.BACKGROUND_COLOR);
// Return CONSUMED to prevent double-application of margins
return WindowInsetsCompat.CONSUMED;
});
// Manually trigger the inset listener to apply margins immediately
ViewCompat.requestApplyInsets(decorView);
}
// Overrides
@@ -166,11 +136,10 @@ public class JitsiMeetActivity extends AppCompatActivity
setContentView(R.layout.activity_jitsi_meet);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM
&& getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
addTopBottomInsets(getWindow(), findViewById(android.R.id.content));
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM
&& getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
addTopBottomInsets(getWindow(), findViewById(android.R.id.content));
}
this.jitsiView = findViewById(R.id.jitsiView);

View File

@@ -927,9 +927,6 @@ 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.
@@ -1600,8 +1597,6 @@ var config = {
// An option to get for user info (name, picture, email) in the token outside the user context.
// Can be used with Firebase tokens.
// tokenGetUserInfoOutOfContext: false,
// An option to pass the token in the iframe API directly instead of using the redirect flow.
// tokenAuthInline: false,
// You can put an array of values to target different entity types in the invite dialog.
// Valid values are "phone", "room", "sip", "user", "videosipgw" and "email"

View File

@@ -45,7 +45,7 @@ body {
.jitsi-icon {
&-default svg {
fill: var(--icon-svg-fill, white);
fill: white;
}
}

View File

@@ -1,5 +1,5 @@
.always-on-top-toolbox {
background-color: var(--toolbox-background-color, $newToolbarBackgroundColor);
background-color: $newToolbarBackgroundColor;
border-radius: 3px;
display: flex;
z-index: $toolbarZ;

View File

@@ -2,8 +2,8 @@
.reactions-menu {
width: 330px;
background: var(--reactions-menu-background, #242528);
box-shadow: 0px 3px 16px var(--reactions-menu-box-shadow-1, rgba(0, 0, 0, 0.6)), 0px 0px 4px 1px var(--reactions-menu-box-shadow-2, rgba(0, 0, 0, 0.25));
background: #242528;
box-shadow: 0px 3px 16px rgba(0, 0, 0, 0.6), 0px 0px 4px 1px rgba(0, 0, 0, 0.25);
border-radius: 6px;
padding: 16px;
@@ -14,7 +14,7 @@
top: 3px;
& .toolbox-icon.toggled {
background-color: var(--reactions-menu-button-toggled, #000000);
background-color: #000000;
}
}
}

View File

@@ -2,7 +2,7 @@
* Round badge.
*/
.badge-round {
background-color: var(--toolbar-badge-background, #165ECC);
background-color: #165ECC;
border-radius: 50%;
box-sizing: border-box;
color: #FFFFFF;
@@ -93,7 +93,7 @@
.toolbox-content-wrapper::after {
content: '';
background: var(--toolbox-background-color, $newToolbarBackgroundColor);
background: $newToolbarBackgroundColor;
padding-bottom: env(safe-area-inset-bottom, 0);
}

View File

@@ -60,7 +60,7 @@
}
#notification-participant-list {
background-color: var(--toolbox-background-color, $newToolbarBackgroundColor);
background-color: $newToolbarBackgroundColor;
border: 1px solid rgba(255, 255, 255, .4);
border-radius: 8px;
left: 0;

View File

@@ -106,7 +106,7 @@
}
#preview {
background: var(--prejoin-preview-background, #040404);
background: #040404;
display: flex;
align-items: center;
justify-content: center;

2
debian/control vendored
View File

@@ -34,7 +34,7 @@ Description: Configuration for web serving of Jitsi Meet
Package: jitsi-meet-prosody
Architecture: all
Depends: openssl, prosody (>= 0.12.0) | prosody-trunk | prosody-0.12 | prosody-13.0, ca-certificates-java, lua-sec, lua-basexx, lua-luaossl, lua-cjson, lua-inspect
Depends: openssl, prosody (>= 0.12.0) | prosody-trunk | prosody-0.12 | prosody-13.0, lua-sec, lua-basexx, lua-luaossl, lua-cjson, lua-inspect
Replaces: jitsi-meet-tokens
Description: Prosody configuration for Jitsi Meet
Jitsi Meet is a WebRTC JavaScript application that uses Jitsi

View File

@@ -284,17 +284,13 @@ case "$1" in
# and drop the wait and the prosody restart
sleep 1
invoke-rc.d prosody restart || true
fi
;;
triggered)
for trigger in $2; do
if [ "$trigger" = "update-ca-certificates-java" ]; then
echo "Java certificates updated, restarting Jitsi components..."
# In case we had updated the certificates and restarted prosody, let's restart and the bridge and jicofo if possible
if [ -d /run/systemd/system ] && [ "$CERT_ADDED_TO_TRUST" = "true" ]; then
systemctl restart jitsi-videobridge2.service >/dev/null || true
systemctl restart jicofo.service >/dev/null || true
fi
done
exit 0
fi
;;
abort-upgrade|abort-remove|abort-deconfigure)

View File

@@ -1 +0,0 @@
interest-noawait update-ca-certificates-java

View File

@@ -1407,7 +1407,7 @@ PODS:
- Yoga
- react-native-performance (5.1.2):
- React-Core
- react-native-safe-area-context (5.6.1):
- react-native-safe-area-context (5.5.2):
- 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: 2243039f43d10cb1ea30ec5ac57fc6d1448413f4
react-native-safe-area-context: 0f7bf11598f9a61b7ceac8dc3f59ef98697e99e1
react-native-slider: 1205801a8d29b28cacc14eef08cb120015cdafcb
react-native-video: eb861d67a71dfef1bbf6086a811af5f338b13781
react-native-webrtc: e8f0ce746353adc2744a2b933645e1aeb41eaa74

View File

@@ -227,9 +227,6 @@
"video_ssrc": "Video-SSRC:",
"yes": "Ja"
},
"customPanel": {
"close": "Schließen"
},
"dateUtils": {
"earlier": "Früher",
"today": "Heute",
@@ -592,7 +589,6 @@
"newFileNotification": "{{ participantName }} hat Datei '{{ fileName }}' hochgeladen",
"removeFile": "Entfernen",
"removeFileSuccess": "Datei erfolgreich entfernt",
"uploadDisabled": "Keine Berechtigung, Dateien hochzuladen. Bitte bei der Moderation anfragen.",
"uploadFailedDescription": "Bitte versuchen Sie es erneut.",
"uploadFailedTitle": "Dateiupload fehlgeschlagen",
"uploadFile": "Datei hochladen"
@@ -1319,7 +1315,6 @@
"chat": "Chatfenster öffnen / schließen",
"clap": "Klatschen",
"closeChat": "Chat schließen",
"closeCustomPanel": "Schließen",
"closeMoreActions": "„Weitere Einstellungen“ schließen",
"closeParticipantsPane": "Anwesendenliste schließen",
"closedCaptions": "Untertitel",
@@ -1425,11 +1420,9 @@
"chat": "Chat öffnen / schließen",
"clap": "Klatschen",
"closeChat": "Chat schließen",
"closeCustomPanel": "Schließen",
"closeParticipantsPane": "Anwesenheitsliste schließen",
"closeReactionsMenu": "Interaktionsmenü schließen",
"closedCaptions": "Untertitel",
"copilot": "Copilot",
"disableNoiseSuppression": "Rauschunterdrückung deaktivieren",
"disableReactionSounds": "Sie können die Interaktionstöne für diese Konferenz deaktivieren",
"documentClose": "Geteiltes Dokument schließen",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -114,9 +114,6 @@
"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": "jai téléversé un fichier",
"fileDeleted": "Un fichier a été supprimé",
"guestsChatIndicator": "(invité)",
"lobbyChatMessageTo": "Message de salle d'attente à {{recipient}}",
"message": "Message",
@@ -126,16 +123,8 @@
"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",
@@ -227,9 +216,6 @@
"video_ssrc": "Video SSRC :",
"yes": "oui"
},
"customPanel": {
"close": "Fermer"
},
"dateUtils": {
"earlier": "Plus tôt",
"today": "Aujourd'hui",
@@ -536,7 +522,6 @@
"tokenAuthFailedWithReasons": "Désolé, vous nêtes pas autorisé à rejoindre lappel. 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",
@@ -584,12 +569,10 @@
"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.",
@@ -981,9 +964,6 @@
"by": "Par {{ name }}",
"closeButton": "Fermer le sondage",
"create": {
"accessibilityLabel": {
"send": "Envoyer le sondage"
},
"addOption": "Ajouter une option",
"answerPlaceholder": "Option {{index}}",
"cancel": "Annuler",
@@ -992,7 +972,8 @@
"pollQuestion": "Question du sondage",
"questionPlaceholder": "Poser une question",
"removeOption": "Supprimer l'option",
"save": "Enregistrer"
"save": "Enregistrer",
"send": "Envoyer"
},
"errors": {
"notUniqueOption": "Les options doivent être uniques"
@@ -1318,7 +1299,6 @@
"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",
@@ -1424,11 +1404,9 @@
"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é",
@@ -1443,7 +1421,6 @@
"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",
@@ -1479,7 +1456,6 @@
"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",

View File

@@ -1,14 +1,10 @@
{
"addPeople": {
"accessibilityLabel": {
"meetingLink": "मीटिंग लिंक: {{url}}"
},
"add": "आमंत्रित करें",
"addContacts": "संपर्क सूची से आमंत्रित करे",
"contacts": "संपर्क",
"copyInvite": "मीटिंग के आमंत्रण कि प्रतिलिपि बनाये",
"copyLink": "मीटिंग कि लिंक कि प्रतिलिपि बनाये",
"copyStream": "सीधे प्रसारण कि लिंक कि प्रतिलिपि बनाये",
"copyStream": "सीधे प्रसारण कि लिंक कि प्रतिलिपि बनाये",
"countryNotSupported": "अभी हम इस गतव्य के लिये सक्षम न‍ही है ।",
"countryReminder": "यू.एस. के बाहर से काल कर रहे है तो कृपया सुनिश्चित करे कि अपने देश के कोड़ से प्रारंभ कर रहे है !",
"defaultEmail": "अपना ई-मेल पता लिखें",
@@ -39,7 +35,6 @@
},
"audioDevices": {
"bluetooth": "ब्लूटूथ",
"car": "कार ऑडियो",
"headphones": "हेडफ़ोन",
"none": "कोई ऑडियो डिवाइस उपलब्ध नहीं",
"phone": "फ़ोन",
@@ -48,47 +43,9 @@
"audioOnly": {
"audioOnly": "लो बैंडविड्थ"
},
"bandwidthSettings": {
"assumedBandwidthBps": "उदाहरण: 10 Mbps के लिए 10000000",
"assumedBandwidthBpsWarning": "अधिक मान नेटवर्क समस्याएँ पैदा कर सकते हैं।",
"customValue": "कस्टम मान",
"customValueEffect": "वास्तविक bps मान सेट करने के लिए",
"leaveEmpty": "खाली छोड़ें",
"leaveEmptyEffect": "अनुमान लगाने की अनुमति देने के लिए",
"possibleValues": "संभावित मान",
"setAssumedBandwidthBps": "अनुमानित बैंडविड्थ (bps)",
"title": "बैंडविड्थ सेटिंग्स",
"zeroEffect": "वीडियो अक्षम करने के लिए"
},
"breakoutRooms": {
"actions": {
"add": "ब्रेकआउट रूम जोड़ें",
"autoAssign": "ब्रेकआउट रूम में स्वतः असाइन करें",
"close": "बंद करें",
"join": "जॉइन करें",
"leaveBreakoutRoom": "ब्रेकआउट रूम छोड़ें",
"more": "और",
"remove": "हटाएँ",
"rename": "नाम बदलें",
"renameBreakoutRoom": "ब्रेकआउट रूम का नाम बदलें",
"sendToBreakoutRoom": "प्रतिभागी को भेजें:"
},
"breakoutList": "ब्रेकआउट सूची",
"buttonLabel": "ब्रेकआउट रूम",
"defaultName": "ब्रेकआउट रूम #{{index}}",
"hideParticipantList": "प्रतिभागी सूची छिपाएँ",
"mainRoom": "मुख्य रूम",
"notifications": {
"joined": "\"{{name}}\" ब्रेकआउट रूम में शामिल हो रहे हैं",
"joinedMainRoom": "मुख्य रूम में शामिल हो रहे हैं",
"joinedTitle": "ब्रेकआउट रूम"
},
"showParticipantList": "प्रतिभागी सूची दिखाएँ",
"title": "ब्रेकआउट रूम"
},
"calendarSync": {
"addMeetingURL": "एक मीटिंग लिंक जोड़ें",
"confirmAddLink": "क्या आप इस इवेंट में एक जित्सी लिंक जोड़ना चाहते हैं?",
"confirmAddLink": "क्या आप इस इवेंट में एक Jitsi लिंक जोड़ना चाहते हैं?",
"error": {
"appConfiguration": "कैलेंडर एकीकरण ठीक से कॉन्फ़िगर नहीं किया गया है।",
"generic": "एक त्रुटि हुई है। कृपया अपनी कैलेंडर सेटिंग जांचें या कैलेंडर को रीफ़्रेश करने का प्रयास करें।",
@@ -104,72 +61,28 @@
"refresh": "कैलेंडर रीफ़्रेश करें",
"today": "आज"
},
"carmode": {
"actions": {
"selectSoundDevice": "साउंड डिवाइस चुनें"
},
"labels": {
"buttonLabel": "कार मोड",
"title": "कार मोड",
"videoStopped": "आपका वीडियो बंद है"
}
},
"chat": {
"disabled": "चैट संदेश भेजना अक्षम है।",
"enter": "चैट रूम में प्रवेश करें",
"error": "त्रुटि: आपका संदेश नहीं भेजा गया । कारण: {{error}}",
"everyone": "सभी",
"fieldPlaceHolder": "अपना संदेश यहां लिखें",
"fileAccessibleTitle": "{{user}} ने एक फ़ाइल अपलोड की",
"fileAccessibleTitleMe": "मैंने एक फ़ाइल अपलोड की",
"fileDeleted": "एक फ़ाइल हटा दी गई",
"guestsChatIndicator": "(अतिथि)",
"messageTo": "{{recipient}} के लिए निजी संदेश",
"messagebox": "एक संदेश टाइप करें",
"newMessages": "नए संदेश",
"nickname": {
"featureChat": "चैट",
"featureClosedCaptions": "बंद कैप्शन",
"featureFileSharing": "फ़ाइल साझा करना",
"featurePolls": "पोल",
"popover": "एक उपनाम चुनें",
"title": "चैट का उपयोग करने के लिए एक उपनाम दर्ज करें",
"titleWith1Features": "{{feature1}} का उपयोग करने के लिए एक उपनाम दर्ज करें",
"titleWith2Features": "{{feature1}} और {{feature2}} का उपयोग करने के लिए एक उपनाम दर्ज करें",
"titleWith3Features": "{{feature1}}, {{feature2}} और {{feature3}} का उपयोग करने के लिए एक उपनाम दर्ज करें",
"titleWith4Features": "{{feature1}}, {{feature2}}, {{feature3}} और {{feature4}} का उपयोग करने के लिए एक उपनाम दर्ज करें",
"titleWithCC": "चैट और बंद कैप्शन का उपयोग करने के लिए एक उपनाम दर्ज करें",
"titleWithPolls": "चैट और पोल का उपयोग करने के लिए एक उपनाम दर्ज करें",
"titleWithPollsAndCC": "चैट, पोल और बंद कैप्शन का उपयोग करने के लिए एक उपनाम दर्ज करें",
"titleWithPollsAndCCAndFileSharing": "चैट, पोल, बंद कैप्शन और फ़ाइलों का उपयोग करने के लिए एक उपनाम दर्ज करें"
"titleWithPolls": "चैट का उपयोग करने के लिए एक उपनाम दर्ज करें"
},
"noMessagesMessage": "अभी तक मीटिंग में कोई संदेश नहीं आया है। वार्तालाप प्रारंभ करें!",
"privateNotice": "{{recipient}} के लिए निजी संदेश",
"sendButton": "भेजें",
"smileysPanel": "इमोजी पैनल",
"systemDisplayName": "सिस्टम",
"tabs": {
"chat": "चैट",
"closedCaptions": "सीसी",
"fileSharing": "फ़ाइलें",
"polls": "पोल"
},
"title": "चैट",
"titleWithCC": "सीसी",
"titleWithFeatures": "चैट और",
"titleWithFileSharing": "फ़ाइलें",
"titleWithPolls": "पोल",
"titleWithPolls": "चैट",
"you": "आप"
},
"chromeExtensionBanner": {
"buttonText": "क्रोम एक्सटेंशन इंस्टॉल करें",
"buttonTextEdge": "Edge एक्सटेंशन इंस्टॉल करें",
"close": "बंद करें",
"dontShowAgain": "मुझे यह फिर से न दिखाएं"
},
"closedCaptionsTab": {
"emptyState": "मॉडरेटर द्वारा शुरू किए जाने पर बंद कैप्शन की सामग्री उपलब्ध होगी",
"startClosedCaptionsButton": "बंद कैप्शन शुरू करें"
"dontShowAgain": "मुझे यह फिर से न दिखाएं",
"installExtensionText": ",गूगल कैलेंडर और ऑफिस 365 एकीकरण के लिए एक्सटेंशन इंस्टॉल करें"
},
"connectingOverlay": {
"joiningRoom": "आपको आपकी मीटिंग से कनेक्ट किया जा रहा है…"
@@ -197,7 +110,6 @@
"bridgeCount": "सर्वर गणना: ",
"codecs": "कोडेक (ए/वी): ",
"connectedTo": "से जुड़ा हुआ है :",
"e2eeVerified": "E2EE सत्यापित:",
"framerate": "फ्रेम दर:",
"less": "कम दिखाएं",
"localaddress": "स्थानीय पता:",
@@ -224,8 +136,7 @@
"status": "सम्पर्क:",
"transport": "ट्रांसपोर्ट :",
"transport_plural": "ट्रांसपोर्ट्स:",
"video_ssrc": "वीडियो एस.आर.सी.सी.:",
"yes": "हाँ"
"video_ssrc": "वीडियो एस.आर.सी.सी.:"
},
"dateUtils": {
"earlier": "पिछला कल",
@@ -235,25 +146,14 @@
"deepLinking": {
"appNotInstalled": "आपको अपने फ़ोन पर इस मीटिंग में शामिल होने के लिए {{app}} मोबाइल ऐप की आवश्यकता है। ",
"description": "हमने आपकी मीटिंग {{app}} डेस्कटॉप ऐप में लॉन्च करने की कोशिश की। कुछ नहीं हुआ? फिर से कोशिश करें या {{app}} वेब ऐप में लॉन्च करें।",
"descriptionNew": "कुछ नहीं हुआ? हमने आपकी मीटिंग को {{app}} डेस्कटॉप ऐप में खोलने की कोशिश की। <br /><br /> आप दोबारा प्रयास कर सकते हैं या इसे वेब पर खोल सकते हैं।",
"descriptionWithoutWeb": "हमने आपकी मीटिंग {{app}} डेस्कटॉप ऐप में लॉन्च करने की कोशिश की। कुछ नहीं हुआ?",
"downloadApp": "एप्लिकेशन डाउनलोड करें",
"downloadMobileApp": "App Store से डाउनलोड करें",
"ifDoNotHaveApp": "यदि आपके पास अभी तक ऐप नहीं है:",
"ifHaveApp": "यदि आपके पास पहले से ही ऐप है:",
"joinInApp": "ऐप का उपयोग करके इस मीटिंग में शामिल हों",
"joinInAppNew": "ऐप में जुड़ें",
"joinInBrowser": "ब्राउज़र में जुड़ें",
"launchMeetingLabel": "आप इस मीटिंग में कैसे जुड़ना चाहते हैं?",
"launchWebButton": "वेब में लॉन्च करे",
"noDesktopApp": "क्या आपके पास ऐप नहीं है?",
"noMobileApp": "क्या आपके पास ऐप नहीं है?",
"or": "या",
"termsAndConditions": "जारी रखने पर आप हमारी <a href='{{termsAndConditionsLink}}' rel='noopener noreferrer' target='_blank'>नियम और शर्तों</a> से सहमत होते हैं।",
"title": "{{app}} में आपकी मीटिंग शुरू की जा रही हैं…",
"titleNew": "आपकी मीटिंग शुरू की जा रही है…",
"tryAgainButton": "डेस्कटॉप में फिर से प्रयास करें",
"unsupportedBrowser": "ऐसा लगता है कि आप ऐसे ब्राउज़र का उपयोग कर रहे हैं जिसे हम सपोर्ट नहीं करते।"
"tryAgainButton": "डेस्कटॉप में फिर से प्रयास करें"
},
"defaultLink": "उदाहरण {{url}}",
"defaultNickname": "उदा. सतीष कुमार",
@@ -264,12 +164,6 @@
"microphonePermission": "माइक्रोफ़ोन अनुमति प्राप्त करने में त्रुटि"
},
"deviceSelection": {
"hid": {
"callControl": "कॉल नियंत्रण",
"connectedDevices": "कनेक्टेड डिवाइस:",
"deleteDevice": "डिवाइस हटाएँ",
"pairDevice": "डिवाइस जोड़ें"
},
"noPermission": "अनुमति नहीं दी गई",
"previewUnavailable": "पूर्वदर्शन अनुपलब्ध",
"selectADevice": "डिवाइस का चयन करें",
@@ -284,23 +178,16 @@
"IamHost": "मैं मेजबान हूँ",
"Ok": "ठीक है",
"Remove": "निकालें",
"Share": "साझा करें",
"Share": "Share",
"Submit": "सबमिट करें",
"Understand": "मैं समझता/समझती हूँ, अभी मुझे म्यूट रखें",
"UnderstandAndUnmute": "मैं समझता/समझती हूँ, कृपया मुझे अनम्यूट करें",
"WaitForHostMsg": "सम्मेलन अभी तक शुरू नहीं हुआ है। यदि आप मेजबान हैं तो कृपया प्रमाणित करें। अन्यथा, कृपया मेजबान के आने की प्रतीक्षा करें।",
"WaitingForHostButton": "मॉडरेटर की प्रतीक्षा करें",
"WaitingForHostTitle": "होस्ट की प्रतीक्षा कर रहा है…",
"Yes": "हाँ",
"accessibilityLabel": {
"liveStreaming": "सीधा प्रसारण"
},
"add": "जोड़ें",
"addMeetingNote": "इस मीटिंग के बारे में एक नोट जोड़ें",
"addOptionalNote": "एक नोट जोड़ें (वैकल्पिक):",
"allow": "अनुमति दें",
"allowToggleCameraDialog": "क्या आप {{initiatorName}} को आपके कैमरे का फ़ेसिंग मोड बदलने की अनुमति देते हैं?",
"allowToggleCameraTitle": "कैमरा बदलने की अनुमति दें?",
"alreadySharedVideoMsg": "एक अन्य प्रतिभागी पहले से ही वीडियो साझा कर रहा है। यह सम्मेलन एक समय में केवल एक साझा की अनुमति देता है।",
"alreadySharedVideoTitle": "एक समय में केवल एक साझा वीडियो की अनुमति है",
"applicationWindow": "एप्लिकेशन विंडो",
@@ -325,7 +212,7 @@
"connectErrorWithMsg": "उफ़! कुछ गड़बड़ हो गई और हम सम्मेलन से नहीं जुड़ सके: {{msg}}",
"connecting": "संपर्क जोड़ा जा रहा है ",
"contactSupport": "सहयोग के लिए संपर्क करें",
"copied": "प्रतिलिपि बनाई गयी",
"copied": "प्रतिलिपि बनाई गयी ",
"copy": "प्रतिलिपि बनाये",
"dismiss": "खारिज करें",
"displayNameRequired": "नमस्ते! आपका नाम क्या है?",
@@ -392,7 +279,7 @@
"readMore": "अधिक",
"recording": "रिकॉर्डिंग",
"recordingDisabledBecauseOfActiveLiveStreamingTooltip": "संभव नहीं है जब एक लाइव स्ट्रीम सक्रिय है",
"recordingDisabledTooltip": "रिकॉर्डिंग शुरू करना अक्षम करें|",
"recordingDisabledTooltip": "रिकॉर्डिंग शुरू करना अक्षम करें.",
"rejoinNow": "पुनः जुड़े",
"remoteControlAllowedMessage": "{{user}} ने आपका रिमोट कंट्रोल अनुरोध स्वीकार कर लिया!",
"remoteControlDeniedMessage": "{{user}} ने आपका रिमोट कंट्रोल अनुरोध अस्वीकार कर दिया!",
@@ -408,7 +295,7 @@
"reservationErrorMsg": "Error code: {{code}}, message: {{msg}}",
"retry": "पुनः प्रयास करें",
"screenSharingAudio": "ऑडियो साझा करें",
"screenSharingFailed": "स्क्रीन शेयरिंग शुरू नहीं हो पाई।",
"screenSharingFailed": "उफ़! कुछ गड़बड़ हो गई, हम स्क्रीन शेयरिंग शुरू करने में सक्षम नहीं थे!",
"screenSharingFailedTitle": "स्क्रीन साझा करना विफल हुआ!",
"screenSharingPermissionDeniedError": "उफ़! आपकी स्क्रीन शेयरिंग अनुमतियों में कुछ गड़बड़ हो गई है। कृपया पुनः लोड करें और पुनः प्रयास करें।",
"sendPrivateMessage": "आपने हाल ही में एक निजी संदेश प्राप्त किया है। क्या आप उसका निजी रूप से जवाब देने का इरादा रखते हैं? या आप अपना संदेश समूह को भेजना चाहते हैं?",
@@ -460,34 +347,6 @@
"veryBad": "बहुत बुरा",
"veryGood": "बहुत अच्छा"
},
"fileSharing": {
"downloadFailedDescription": "कृपया फिर से प्रयास करें।",
"downloadFailedTitle": "डाउनलोड विफल",
"downloadFile": "डाउनलोड करें",
"downloadStarted": "फ़ाइल डाउनलोड शुरू हो गया",
"dragAndDrop": "फ़ाइलों को यहाँ या स्क्रीन पर कहीं भी खींचें और छोड़ें",
"fileAlreadyUploaded": "फ़ाइल पहले ही इस मीटिंग में अपलोड हो चुकी है।",
"fileRemovedByOther": "आपकी फ़ाइल '{{ fileName }}' हटा दी गई",
"fileTooLargeDescription": "कृपया सुनिश्चित करें कि फ़ाइल {{ maxFileSize }} से अधिक नहीं है।",
"fileTooLargeTitle": "चयनित फ़ाइल बहुत बड़ी है",
"fileUploadProgress": "फ़ाइल अपलोड प्रगति",
"fileUploadedSuccessfully": "फ़ाइल सफलतापूर्वक अपलोड हो गई",
"newFileNotification": "{{ participantName }} ने '{{ fileName }}' साझा की",
"removeFile": "हटाएँ",
"removeFileSuccess": "फ़ाइल सफलतापूर्वक हटा दी गई",
"uploadFailedDescription": "कृपया फिर से प्रयास करें।",
"uploadFailedTitle": "अपलोड विफल",
"uploadFile": "फ़ाइल साझा करें"
},
"filmstrip": {
"accessibilityLabel": {
"heading": "वीडियो थंबनेल"
}
},
"giphy": {
"noResults": "कोई परिणाम नहीं मिला :(",
"search": "GIPHY खोजें"
},
"helpView": {
"title": "सहायता केंद्र"
},
@@ -503,7 +362,6 @@
"addPassword": "$t(lockRoomPassword)जोड़ें",
"cancelPassword": "$t(lockRoomPassword)रद्द करें",
"conferenceURL": "लिंक:",
"copyNumber": "नंबर कॉपी करें",
"country": "देश",
"dialANumber": "अपनी मीटिंग में शामिल होने के लिए, इनमें से किसी एक नंबर को डायल करें और फिर पिन डालें।",
"dialInConferenceID": "पिन:",
@@ -526,16 +384,11 @@
"noRoom": "डायल-इन करने के लिए कोई कक्ष निर्दिष्ट नहीं किया गया।",
"numbers": "डायल-इन नंबर",
"password": "$t(lockRoomPasswordUppercase):",
"reachedLimit": "आप अपने प्लान की सीमा तक पहुँच चुके हैं।",
"sip": "SIP पता",
"sipAudioOnly": "केवल ऑडियो SIP पता",
"title": "साझा करें",
"tooltip": "इस मीटिंग के लिए लिंक और डायल-इन जानकारी साझा करें",
"upgradeOptions": "कृपया अपग्रेड विकल्पों की जाँच करें",
"whiteboardError": "व्हाइटबोर्ड लोड करने में त्रुटि। कृपया बाद में पुनः प्रयास करें।"
"tooltip": "इस मीटिंग के लिए लिंक और डायल-इन जानकारी साझा करें"
},
"inlineDialogFailure": {
"msg": "कुछ समस्या हुई।",
"msg": "We stumbled a bit.",
"retry": "पुनः प्रयास करें",
"support": "सहायता",
"supportMsg": "ऐसा बार बार हो रहा हो, तो सम्पर्क करे "
@@ -564,10 +417,6 @@
"toggleShortcuts": "कीबोर्ड शॉर्टकट दिखाएं या छिपाएं",
"videoMute": "अपना कैमरा प्रारंभ या बंद करें"
},
"largeVideo": {
"screenIsShared": "आप अपनी स्क्रीन साझा कर रहे हैं",
"showMeWhatImSharing": "मुझे दिखाएँ कि मैं क्या साझा कर रहा हूँ"
},
"liveStreaming": {
"busy": "हम स्ट्रीमिंग संसाधनों को मुक्त करने पर काम कर रहे हैं। कृपया कुछ मिनटों में पुनः प्रयास करें।",
"busyTitle": "सभी स्ट्रीमर वर्तमान में व्यस्त हैं",
@@ -612,7 +461,7 @@
"emailField": "अपना ईमेल पता दर्ज करें",
"enableDialogPasswordField": "पासवर्ड सेट करें (वैकल्पिक)",
"enableDialogSubmit": "सक्षम करें",
"enableDialogText": "लॉबी मोड से आप अपनी मीटिंग को सुरक्षित रख सकते हैं। केवल मॉडरेटर की अनुमति मिलने के बाद ही लोग इसमें शामिल हो पाएंगे।",
"enableDialogText": "Lobby mode lets you protect your meeting by only allowing people to enter after a formal approval by a moderator.",
"enterPasswordButton": "मीटिंग पासवर्ड दर्ज करें",
"enterPasswordTitle": "मीटिंग में शामिल होने के लिए पासवर्ड दर्ज करें",
"invalidPassword": "अमान्य पासवर्ड",
@@ -626,8 +475,8 @@
"knockTitle": "कोई व्यक्ति बैठक में शामिल होना चाहता है",
"knockingParticipantList": "प्रतिभागी सूची दस्तक",
"nameField": "अपना नाम दर्ज करें",
"notificationLobbyAccessDenied": "{{targetParticipantName}} को {{originParticipantName}} ने मीटिंग में शामिल होने की अनुमति नहीं दी।",
"notificationLobbyAccessGranted": "{{targetParticipantName}} को {{originParticipantName}} ने मीटिंग में शामिल होने की अनुमति दी।",
"notificationLobbyAccessDenied": "{{targetParticipantName}} has been rejected to join by {{originParticipantName}}",
"notificationLobbyAccessGranted": "{{targetParticipantName}} has been allowed to join by {{originParticipantName}}",
"notificationLobbyDisabled": "लॉबी को {{originParticipantName}}द्वारा अक्षम कर दिया गया",
"notificationLobbyEnabled": "लॉबी को {{originParticipantName}}द्वारा सक्षम किया गया",
"notificationTitle": "लॉबी",
@@ -660,12 +509,9 @@
"no": "नहीं",
"participant": "प्रतिभागी",
"participantStats": "प्रतिभागी आँकड़े",
"selectTabTitle": "🎥 रिकॉर्डिंग के लिए कृपया इस टैब को चुनें",
"sessionToken": "सत्र टोकन",
"start": "रिकॉर्डिंग प्रारंभ करें",
"stop": "रिकॉर्डिंग बंद करें",
"stopping": "रिकॉर्डिंग बंद की जा रही है",
"wait": "कृपया प्रतीक्षा करें, आपकी रिकॉर्डिंग सेव की जा रही है",
"yes": "हाँ"
},
"lockRoomPassword": "पासवर्ड",
@@ -676,30 +522,11 @@
},
"me": "मैं",
"notify": {
"OldElectronAPPTitle": "सुरक्षा में खामी",
"allowAll": "सभी की अनुमति दें",
"allowAudio": "ऑडियो की अनुमति दें",
"allowDesktop": "स्क्रीन शेयरिंग की अनुमति दें",
"allowVideo": "वीडियो की अनुमति दें",
"allowedUnmute": "आप माइक्रोफोन अनम्यूट कर सकते हैं, कैमरा चालू कर सकते हैं या स्क्रीन साझा कर सकते हैं।",
"audioUnmuteBlockedDescription": "सिस्टम सीमाओं के कारण माइक्रोफोन अनम्यूट अस्थायी रूप से ब्लॉक कर दिया गया है।",
"audioUnmuteBlockedTitle": "माइक्रोफोन अनम्यूट ब्लॉक!",
"chatMessages": "चैट संदेश",
"OldElectronAPPTitle": "Security vulnerability!",
"connectedOneMember": "{{name}} मीटिंग में शामिल हुए",
"connectedThreePlusMembers": "{{name}} और {{count}} अन्य लोग मीटिंग में शामिल हुए",
"connectedTwoMembers": "{{first}} और {{second}} मीटिंग में शामिल हुआ",
"connectionFailed": "कनेक्शन विफल। कृपया बाद में पुनः प्रयास करें!",
"dataChannelClosed": "वीडियो की गुणवत्ता प्रभावित हो सकती है",
"dataChannelClosedDescription": "ब्रिज चैनल बंद है, इसलिए वीडियो की गुणवत्ता न्यूनतम स्तर तक सीमित हो सकती है।",
"dataChannelClosedDescriptionWithAudio": "ब्रिज चैनल बंद है, इसलिए ऑडियो और वीडियो में व्यवधान हो सकता है।",
"dataChannelClosedWithAudio": "ऑडियो और वीडियो की गुणवत्ता प्रभावित हो सकती है",
"desktopMutedRemotelyTitle": "आपकी स्क्रीन शेयरिंग {{participantDisplayName}} द्वारा रोकी गई है",
"disabledIframe": "एंबेडिंग केवल डेमो उद्देश्यों के लिए है, इसलिए यह कॉल {{timeout}} मिनट में डिस्कनेक्ट हो जाएगी।",
"disabledIframeSecondaryNative": "एंबेडिंग {{domain}} केवल डेमो उद्देश्यों के लिए है, इसलिए यह कॉल {{timeout}} मिनट में डिस्कनेक्ट हो जाएगी।",
"disabledIframeSecondaryWeb": "एंबेडिंग {{domain}} केवल डेमो उद्देश्यों के लिए है, इसलिए यह कॉल {{timeout}} मिनट में डिस्कनेक्ट हो जाएगी। उत्पादन एंबेडिंग के लिए कृपया <a href='{{jaasDomain}}' rel='noopener noreferrer' target='_blank'>Jitsi as a Service</a> का उपयोग करें!",
"disconnected": "डिस्कनेक्ट",
"displayNotifications": "सूचनाएँ दिखाएँ",
"dontRemindMe": "मुझे याद न दिलाएँ",
"focus": "Conference focus",
"focusFail": "{{component}} उपलब्ध नहीं - {{ms}} सेकंड में पुनः प्रयास करें",
"grantedTo": "Moderator rights granted to {{to}}!",
@@ -707,29 +534,7 @@
"invitedThreePlusMembers": "{{name}} और {{count}} अन्य लोगों को आमंत्रित किया गया",
"invitedTwoMembers": "{{first}} और {{second}} को आमंत्रित किया गया",
"kickParticipant": "{{kicked}} को {{kicker}} द्वारा किक किया गया",
"leftOneMember": "{{name}} ने मीटिंग छोड़ दी",
"leftThreePlusMembers": "{{name}} और कई अन्य ने मीटिंग छोड़ दी",
"leftTwoMembers": "{{first}} और {{second}} ने मीटिंग छोड़ दी",
"linkToSalesforce": "Salesforce से लिंक करें",
"linkToSalesforceDescription": "आप मीटिंग सारांश को Salesforce ऑब्जेक्ट से लिंक कर सकते हैं।",
"linkToSalesforceError": "मीटिंग को Salesforce से लिंक करने में विफल",
"linkToSalesforceKey": "इस मीटिंग को लिंक करें",
"linkToSalesforceProgress": "मीटिंग को Salesforce से लिंक किया जा रहा है…",
"linkToSalesforceSuccess": "मीटिंग सफलतापूर्वक Salesforce से लिंक हो गई",
"localRecordingStarted": "{{name}} ने लोकल रिकॉर्डिंग शुरू की।",
"localRecordingStopped": "{{name}} ने लोकल रिकॉर्डिंग बंद की।",
"me": "मैं",
"moderationInEffectCSDescription": "यदि आप अपनी स्क्रीन साझा करना चाहते हैं तो कृपया हाथ उठाएँ।",
"moderationInEffectCSTitle": "स्क्रीन शेयरिंग मॉडरेटर द्वारा ब्लॉक की गई है",
"moderationInEffectDescription": "यदि आप बोलना चाहते हैं तो कृपया हाथ उठाएँ।",
"moderationInEffectTitle": "आपका माइक्रोफोन मॉडरेटर द्वारा म्यूट किया गया है",
"moderationInEffectVideoDescription": "यदि आप अपना कैमरा चालू करना चाहते हैं तो कृपया हाथ उठाएँ।",
"moderationInEffectVideoTitle": "आपका कैमरा मॉडरेटर द्वारा ब्लॉक किया गया है",
"moderationRequestFromModerator": "होस्ट चाहता है कि आप अनम्यूट करें",
"moderationRequestFromParticipant": "बोलना चाहता है",
"moderationStartedTitle": "मॉडरेशन शुरू हो गई",
"moderationStoppedTitle": "मॉडरेशन बंद हो गई",
"moderationToggleDescription": "{{participantDisplayName}} द्वारा",
"moderator": "मॉडरेटर के अधिकार दिए गए!",
"muted": "आपने वार्तालाप को म्यूट करके शुरू किया है।",
"mutedRemotelyDescription": "जब आप बोलने के लिए तैयार हों, तो आप हमेशा अनम्यूट कर सकते हैं। बैठक में शोर कम रखने के लिए बोलने के बाद म्यूट कर दें।",
@@ -738,85 +543,24 @@
"newDeviceAction": "उपयोग करें",
"newDeviceAudioTitle": "नए ऑडियो डिवाइस का पता चला",
"newDeviceCameraTitle": "नए कैमरे का पता चला",
"nextToSpeak": "आप अगली बारी में बोलने वाले हैं",
"noiseSuppressionDesktopAudioDescription": "डेस्कटॉप ऑडियो साझा करते समय अतिरिक्त नॉइज़ सप्रेशन सक्षम नहीं किया जा सकता, कृपया इसे अक्षम करें और पुनः प्रयास करें।",
"noiseSuppressionFailedTitle": "अतिरिक्त नॉइज़ सप्रेशन शुरू करने में विफल",
"noiseSuppressionStereoDescription": "स्टीरियो ऑडियो के साथ अतिरिक्त नॉइज़ सप्रेशन वर्तमान में समर्थित नहीं है।",
"oldElectronClientDescription1": "आप जित्सी मीट क्लाइंट के एक पुराने संस्करण का उपयोग करते हुए दिखाई देते हैं, जिसमे सुरक्षा कमजोरियां ज्ञात हैं।",
"oldElectronClientDescription2": "नवीनतम बिल्ड",
"oldElectronClientDescription3": " अब!",
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) किसी अन्य प्रतिभागी द्वारा हटा दिया गया",
"passwordSetRemotely": "$t(lockRoomPasswordUppercase) दूसरे प्रतिभागी द्वारा निर्धारित",
"raisedHand": "{{name}} बोलना चाहेंगे।",
"raisedHands": "{{participantName}} और {{raisedHands}} अन्य लोग",
"reactionSounds": "ध्वनि बंद करें",
"reactionSoundsForAll": "सभी के लिए ध्वनि बंद करें",
"screenShareNoAudio": "विंडो चयन स्क्रीन में 'ऑडियो साझा करें' बॉक्स चयनित नहीं था।",
"screenShareNoAudioTitle": "सिस्टम ऑडियो साझा नहीं किया जा सका!",
"screenSharingAudioOnlyDescription": "कृपया ध्यान दें कि अपनी स्क्रीन साझा करने से \"सर्वोत्तम प्रदर्शन\" मोड प्रभावित होगा और आप अधिक बैंडविड्थ का उपयोग करेंगे।",
"screenSharingAudioOnlyTitle": "\"सर्वोत्तम प्रदर्शन\" मोड",
"selfViewTitle": "आप सेटिंग्स से हमेशा सेल्फ-व्यू को अन-हाइड कर",
"somebody": "Somebody",
"startSilentDescription": "ऑडियो सक्षम करने के लिए मीटिंग को फिर से करें",
"startSilentTitle": "आप बिना ऑडियो आउटपुट के साथ शामिल हुए!",
"suboptimalBrowserWarning": "हमें डर है कि आपकी मीटिंग अनुभव यहाँ बहुत अच्छा नहीं होने वाला है। हम इसे सुधारने के तरीके ढूंढ़ रहे हैं, लेकिन उस समय तक कृपया <a href='{{recommendedBrowserPageLink}}' target='_blank'>पूरी तरह से समर्थित ब्राउज़र</a> में से एक का प्रयास करें",
"suboptimalExperienceTitle": "ब्राउज़र चेतावनी",
"suggestRecordingAction": "शुरू करें",
"suggestRecordingDescription": "क्या आप रिकॉर्डिंग शुरू करना चाहेंगे?",
"suggestRecordingTitle": "इस मीटिंग को रिकॉर्ड करें",
"unmute": "अनम्यूट",
"videoMutedRemotelyDescription": "आप इसे हमेशा फिर से चालू कर सकते हैं।",
"videoMutedRemotelyTitle": "आपका कैमरा {{participantDisplayName}}द्वारा अक्षम कर दिया गया है!",
"videoUnmuteBlockedDescription": "सिस्टम सीमाओं के कारण कैमरा अनम्यूट और डेस्कटॉप शेयरिंग अस्थायी रूप से ब्लॉक कर दी गई है।",
"videoUnmuteBlockedTitle": "कैमरा अनम्यूट और डेस्कटॉप शेयरिंग ब्लॉक!",
"viewParticipants": "प्रतिभागियों को देखें",
"viewVisitors": "दर्शकों को देखें",
"waitingParticipants": "{{waitingParticipants}} लोग",
"waitingVisitors": "कतार में प्रतीक्षारत दर्शक: {{waitingVisitors}}",
"waitingVisitorsTitle": "मीटिंग अभी लाइव नहीं है!",
"whiteboardLimitDescription": "कृपया अपनी प्रगति सहेजें, क्योंकि उपयोगकर्ता सीमा जल्द ही पहुँच जाएगी और व्हाइटबोर्ड बंद हो जाएगा।",
"whiteboardLimitTitle": "व्हाइटबोर्ड उपयोग"
"videoMutedRemotelyTitle": "आपका कैमरा {{participantDisplayName}}द्वारा अक्षम कर दिया गया है!"
},
"participantsPane": {
"actions": {
"admit": "स्वीकृत करें",
"admitAll": "सभी को स्वीकृत करें",
"allow": "नॉन-मॉडरेटर को अनुमति दें:",
"allowDesktop": "स्क्रीन शेयरिंग की अनुमति दें",
"allowVideo": "वीडियो की अनुमति दें",
"askDesktop": "स्क्रीन साझा करने के लिए पूछें",
"askUnmute": "अनम्यूट करने के लिए पूछें",
"audioModeration": "स्वयं अनम्यूट करें",
"blockEveryoneMicCamera": "सभी के माइक्रोफोन और कैमरा ब्लॉक करें",
"breakoutRooms": "ब्रेकआउट रूम",
"desktopModeration": "स्क्रीन शेयरिंग शुरू करें",
"goLive": "लाइव जाएँ",
"invite": "किसी को आमंत्रित करें",
"lowerAllHands": "सभी हाथ नीचे करें",
"lowerHand": "हाथ नीचे करें",
"moreModerationActions": "अधिक मॉडरेशन विकल्प",
"moreModerationControls": "अधिक मॉडरेशन नियंत्रण",
"moreParticipantOptions": "अधिक प्रतिभागी विकल्प",
"mute": "म्यूट करें",
"muteAll": "सभी को म्यूट करें",
"muteEveryoneElse": "बाकी सभी को म्यूट करें",
"reject": "अस्वीकार",
"stopDesktop": "स्क्रीन शेयरिंग बंद करें",
"stopEveryonesDesktop": "सभी की स्क्रीन शेयरिंग बंद करें",
"stopEveryonesVideo": "सभी का वीडियो बंद करें",
"stopVideo": "वीडियो बंद करें",
"unblockEveryoneMicCamera": "सभी के माइक्रोफोन और कैमरा अनब्लॉक करें",
"videoModeration": "उनका वीडियो शुरू करें"
},
"headings": {
"lobby": "लॉबी ({{count}})",
"participantsList": "मीटिंग प्रतिभागी ({{count}})",
"viewerRequests": "दर्शकों के अनुरोध {{count}}",
"visitorInQueue": " (प्रतीक्षा में {{count}})",
"visitorRequests": " (अनुरोध {{count}})",
"visitors": "दर्शक {{count}}",
"visitorsList": "दर्शक ({{count}})",
"waitingLobby": "लॉबी में प्रतीक्षा कर रहे हैं ({{count}})"
"reject": "स्वीकार"
}
},
"passwordDigitsOnly": "अधिकतम {{number}} अंक",
@@ -873,7 +617,6 @@
"joinAudioByPhone": "फोन ऑडियो के साथ जुड़ें",
"joinMeeting": "मीटिंग में शामिल हों",
"joinWithoutAudio": "ऑडियो के बिना जुड़ें",
"keyboardShortcuts": "कीबोर्ड शॉर्टकट सक्षम करें",
"linkCopied": "लिंक क्लिपबोर्ड पर कॉपी किया गया",
"lookGood": "ऐसा लगता है कि आपका माइक्रोफ़ोन ठीक से काम कर रहा है",
"or": "या",
@@ -899,10 +642,9 @@
"ringing": "Ringing…"
},
"profile": {
"avatar": "अवतार",
"setDisplayNameLabel": "अपना नाम सेट करें",
"setEmailInput": "ई-मेल दर्ज करें",
"setEmailLabel": "Gravatar ईमेल",
"setEmailLabel": "Set अपना ग्रेवार्ट ईमेल सेट करें",
"title": "प्रोफ़ाइल"
},
"raisedHand": "बोलना चाहेंगे",
@@ -918,12 +660,6 @@
"expandedPending": "रिकॉर्डिंग शुरू की जा रही है…",
"failedToStart": "रिकॉर्डिंग शुरू करने में विफलता हुई।",
"fileSharingdescription": "रिकॉर्डिंग को बैठक प्रतिभागियों के साथ साझा करें",
"highlight": "हाइलाइट",
"highlightMoment": "महत्वपूर्ण क्षण",
"highlightMomentDisabled": "रिकॉर्डिंग शुरू होने पर आप क्षणों को हाइलाइट कर सकते हैं",
"highlightMomentSuccess": "क्षण हाइलाइट किया गया",
"highlightMomentSucessDescription": "आपका हाइलाइट किया गया क्षण मीटिंग सारांश में जोड़ा जाएगा।",
"inProgress": "रिकॉर्डिंग या लाइव स्ट्रीमिंग प्रगति में है",
"limitNotificationDescriptionNative": "उच्च मांग के कारण आपकी रिकॉर्डिंग {{limit}} मिनट तक सीमित रहेगी। असीमित रिकॉर्डिंग के लिए <3>{{app}}</3> आज़माएँ।",
"limitNotificationDescriptionWeb": "उच्च मांग के कारण आपकी रिकॉर्डिंग {{limit}} मिनट तक सीमित रहेगी। असीमित रिकॉर्डिंग के लिए <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a> आज़माएँ।",
"live": "लाइव",
@@ -941,26 +677,18 @@
"signOut": "साइन आउट करें",
"title": "रिकॉर्डिंग",
"unavailable": "ओह! {{serviceName}} वर्तमान में अनुपलब्ध है। हम इस समस्या को हल करने पर काम कर रहे हैं। कृपया बाद में पुनः प्रयास करें।",
"unavailableTitle": "रिकॉर्डिंग उपलब्ध नहीं है",
"uploadToCloud": "क्लाउड पर अपलोड करें"
"unavailableTitle": "रिकॉर्डिंग उपलब्ध नहीं है"
},
"sectionList": {
"pullToRefresh": "रीफ़्रेश करने के लिए नीचे खींचें"
},
"security": {
"about": "आप अपनी मीटिंग में $t(lockRoomPassword) जोड़ सकते हैं। सहभागियों को मीटिंग में शामिल होने से पहले $t(lockRoomPassword) प्रदान करना होगा।",
"aboutReadOnly": "मॉडरेटर मीटिंग में $t(lockRoomPassword) जोड़ सकते हैं। प्रतिभागियों को मीटिंग में शामिल होने से पहले यह $t(lockRoomPassword) प्रदान करना होगा।",
"aboutReadOnly": "Moderator participants can add a $t(lockRoomPassword) to the meeting. Participants will need to provide the $t(lockRoomPassword) before they are allowed to join the meeting.",
"insecureRoomNameWarning": "कमरे का नाम असुरक्षित है। अनचाहे सहभागियों की कॉन्फ्रेंस में शामिल हो सकते हैं। सुरक्षा बटन का उपयोग करके अपनी मीटिंग को सुरक्षित बनाने का विचार करें। ",
"title": "सुरक्षा विकल्प",
"unsafeRoomActions": {
"meeting": "सुरक्षा बटन का उपयोग करके अपनी मीटिंग को सुरक्षित करने पर विचार करें।",
"prejoin": "कृपया अधिक विशिष्ट मीटिंग नाम का उपयोग करने पर विचार करें।",
"welcome": "कृपया अधिक विशिष्ट मीटिंग नाम का उपयोग करें या दिए गए सुझावों में से किसी एक को चुनें।"
}
"securityOptions": "Security options"
},
"settings": {
"audio": "ऑडियो",
"buttonLabel": "सेटिंग्स",
"calendar": {
"about": "{{appName}} कैलेंडर एकीकरण आपके कैलेंडर तक सुरक्षित रूप से पहुंचने के लिए उपयोग किया जाता है ताकि यह आगामी कार्यक्रम पढ़ सके।",
"disconnect": "डिस्कनेक्ट करें",
@@ -970,35 +698,23 @@
},
"devices": "डिवाइस",
"followMe": "हर कोई मेरा अनुसरण करेगा",
"incomingMessage": "आने वाला संदेश",
"language": "भाषा",
"loggedIn": "{{name}} के रूप में लॉग इन किया",
"maxStageParticipants": "मुख्य स्टेज पर पिन किए जा सकने वाले प्रतिभागियों की अधिकतम संख्या",
"microphones": "माइक्रोफोन",
"moderator": "होस्ट",
"more": "अधिक",
"moderator": "Moderator",
"more": "More",
"name": "नाम",
"noDevice": "कोई नहीं",
"notifications": "सूचना",
"participantJoined": "प्रतिभागी जुड़े",
"participantKnocking": "प्रतिभागी लॉबी में आए",
"participantLeft": "प्रतिभागी बाहर गए",
"playSounds": "ध्वनि चलाएँ",
"reactions": "मीटिंग प्रतिक्रियाएँ",
"sameAsSystem": "सिस्टम के समान ({{label}})",
"selectAudioOutput": "ऑडियो आउटपुट",
"selectCamera": "कैमरा",
"selectMic": "माइक्रोफोन",
"shortcuts": "शॉर्टकट्स",
"speakers": "वक्ता",
"speakers": "Speakers",
"startAudioMuted": "सभी लोग म्यूट से शुरू करेंगे",
"startVideoMuted": "सभी लोग छिपे हुए शुरू करेंगे",
"talkWhileMuted": "म्यूट रहते हुए बोलें",
"title": "सेटिंग",
"video": "वीडियो"
"title": "सेटिंग"
},
"settingsView": {
"advanced": "उन्नत",
"advanced": "Advanced",
"alertCancel": "रद्द करें",
"alertOk": "ओके",
"alertTitle": "चेतावनी",
@@ -1114,14 +830,14 @@
"exitTileView": "टाइल दृश्य से बाहर निकलें",
"feedback": "प्रतिक्रिया छोड़ें",
"hangup": "छोड़ें",
"help": "सहायता",
"help": "Help",
"invite": "लोगों को आमंत्रित करें",
"lobbyButtonDisable": "लॉबी मोड को अक्षम करें",
"lobbyButtonEnable": "लॉबी मोड सक्षम करें",
"login": "लॉग इन",
"logout": "लॉगआउट",
"lowerYourHand": "अपना हाथ नीचे करें",
"moreActions": "अधिक कार्रवाइयाँ",
"moreActions": "More actions",
"moreOptions": "अधिक विकल्प",
"mute": "म्यूट / अनम्यूट",
"muteEveryone": "सभी को म्यूट करें",
@@ -1142,21 +858,19 @@
"security": "सुरक्षा विकल्प",
"selectBackground": "पृष्ठभूमि का चयन करें",
"shareRoom": "किसी को आमंत्रित करें",
"shareaudio": "डियो साझा करें",
"sharedvideo": "एक वीडियो साझा करें",
"sharedvideo": "एक YouTube वीडियो साझा करें",
"shortcuts": "शॉर्टकट देखें",
"speakerStats": "स्पीकर आँकड़े",
"startScreenSharing": "स्क्रीन साझाकरण प्रारंभ करें",
"startSubtitles": "सबटाइटल शुरू करें",
"startSubtitles": "Start subtitles",
"stopScreenSharing": "स्क्रीन शेयरिंग बंद करो",
"stopSharedVideo": "वीडियो बंद करें",
"stopSharedVideo": "YouTube वीडियो बंद करें",
"stopSubtitles": "उपशीर्षक बंद करें",
"talkWhileMutedPopup": "बोलने की कोशिश कर रहा है? आप मौन हैं",
"tileViewToggle": "टॉगल टाइल दृश्य",
"toggleCamera": "कैमरा टॉगल करें",
"videoSettings": "वीडियो सेटिंग्स",
"videomute": "स्टॉप कैमरा",
"videounmute": "स्टार्ट कैमरा"
"videomute": "स्टार्ट / स्टॉप कैमरा"
},
"transcribing": {
"ccButtonTooltip": "सबटाइटल शुरू / बंद करें",
@@ -1205,11 +919,10 @@
"domuteOthers": "सभी को म्यूट करें",
"domuteVideo": "कैमरा अक्षम करें",
"domuteVideoOfOthers": "अन्य सभी के लिए कैमरा बंद करें",
"flip": "उलटना",
"grantModerator": "संचालक बनाएं",
"hideSelfView": "स्वयं का दृश्य छिपाएँ",
"flip": "Flip",
"grantModerator": "Grant Moderator",
"kick": "निकालें",
"moderator": "संचालक",
"moderator": "Moderator",
"mute": "प्रतिभागी मौन है",
"muted": "म्यूटेड",
"remoteControl": "स्टार्ट / स्टॉप रिमोट कंट्रोल",
@@ -1245,20 +958,8 @@
"headerSubtitle": "सुरक्षित और उच्च गुणवत्ता बैठकें",
"headerTitle": "जित्सी मीट",
"info": "डायल-इन जानकारी",
"jitsiOnMobile": "मोबाइल पर जित्सी हमारे एप्लिकेशन डाउनलोड करें और कहीं से भी एक बैठक शुरू करें",
"jitsiOnMobile": "मोबाइल पर Jitsi हमारे एप्लिकेशन डाउनलोड करें और कहीं से भी एक बैठक शुरू करें",
"join": "बनाये / जुड़े ",
"logo": {
"calendar": "कैलेंडर लोगो",
"desktopPreviewThumbnail": "डेस्कटॉप प्रीव्यू थंबनेल",
"googleLogo": "गूगल लोगो",
"logoDeepLinking": "जित्सी मीट लोगो",
"microsoftLogo": "माइक्रोसॉफ्ट लोगो",
"policyLogo": "नीति लोगो"
},
"meetingsAccessibilityLabel": "मीटिंग्स",
"mobileDownLoadLinkAndroid": "एंड्रॉइड के लिए मोबाइल ऐप डाउनलोड करें",
"mobileDownLoadLinkFDroid": "F-Droid के लिए मोबाइल ऐप डाउनलोड करें",
"mobileDownLoadLinkIos": "iOS के लिए मोबाइल ऐप डाउनलोड करें",
"moderatedMessage": "Or <a href=\"{{url}}\" rel=\"noopener noreferrer\" target=\"_blank\">book a meeting URL</a> in advance where you are the only moderator.",
"privacy": "गोपनीयता",
"recentList": "हाल का",
@@ -1271,13 +972,6 @@
"sendFeedback": "फ़ीडबैक भेजें",
"startMeeting": "मीटिंग प्रारंभ करें",
"terms": "शर्तें",
"title": "सुरक्षित, पूरी तरह से चित्रित, और पूरी तरह से मुक्त वीडियो कॉन्फ्रेंसिंग",
"upcomingMeetings": "आपकी आगामी मीटिंग्स"
},
"whiteboard": {
"accessibilityLabel": {
"heading": "व्हाइटबोर्ड"
},
"screenTitle": "व्हाइटबोर्ड"
"title": "सुरक्षित, पूरी तरह से चित्रित, और पूरी तरह से मुक्त वीडियो कॉन्फ्रेंसिंग"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -227,9 +227,6 @@
"video_ssrc": "Video SSRC:",
"yes": "yes"
},
"customPanel": {
"close": "Close"
},
"dateUtils": {
"earlier": "Earlier",
"today": "Today",
@@ -383,8 +380,6 @@
"lockRoom": "Add meeting $t(lockRoomPassword)",
"lockTitle": "Lock failed",
"login": "Login",
"loginFailed": "Login failed.",
"loginOnResume": "Your authentication session has expired. You need to login again to continue the meeting.",
"loginQuestion": "Are you sure you want to login and leave the conference?",
"logoutQuestion": "Are you sure you want to logout and leave the conference?",
"logoutTitle": "Logout",
@@ -594,7 +589,6 @@
"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"
@@ -1321,7 +1315,6 @@
"chat": "Open / Close chat",
"clap": "Clap",
"closeChat": "Close chat",
"closeCustomPanel": "Close",
"closeMoreActions": "Close more actions menu",
"closeParticipantsPane": "Close participants pane",
"closedCaptions": "Closed captions",
@@ -1427,11 +1420,9 @@
"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",

View File

@@ -240,28 +240,14 @@ function initCommands() {
APP.store.dispatch(muteAllParticipants(exclude, muteMediaType));
},
'mute-remote-participant': (participantId, mediaType) => {
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;
}
if (!isLocalParticipantModerator(state)) {
if (!isLocalParticipantModerator(APP.store.getState())) {
logger.error('Missing moderator rights to mute remote participant');
return;
}
const muteMediaType = mediaType ? mediaType : MEDIA_TYPE.AUDIO;
APP.store.dispatch(muteRemote(participantId, muteMediaType));
},
'toggle-lobby': isLobbyEnabled => {
@@ -804,7 +790,7 @@ function initCommands() {
}
if (transcription) {
APP.store.dispatch(setRequestingSubtitles(true, false, null, true));
APP.store.dispatch(setRequestingSubtitles(true, false, null));
}
},
@@ -826,7 +812,7 @@ function initCommands() {
}
if (transcription) {
APP.store.dispatch(setRequestingSubtitles(false, false, null, true));
APP.store.dispatch(setRequestingSubtitles(false, false, null));
}
if (mode === 'local') {
@@ -1431,15 +1417,17 @@ 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' or 'video').
* @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.
* @returns {void}
*/
notifyParticipantMuted(participantId, isMuted, mediaType) {
notifyParticipantMuted(participantId, isMuted, mediaType, isSelfMuted = true) {
this._sendEvent({
name: 'participant-muted',
id: participantId,
isMuted,
mediaType
mediaType,
isSelfMuted
});
}
@@ -2253,32 +2241,6 @@ 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.
*

View File

@@ -133,8 +133,6 @@ 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',
@@ -775,7 +773,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
* @returns {void}
*
* @deprecated
* NOTE: This method is not removed for backward compatibility purposes.
* NOTE: This method is not removed for backward comatability purposes.
*/
addEventListener(event, listener) {
this.on(event, listener);
@@ -862,7 +860,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
* @returns {void}
*
* @deprecated
* NOTE: This method is not removed for backward compatibility purposes.
* NOTE: This method is not removed for backward comatability purposes.
*/
addEventListeners(listeners) {
for (const event in listeners) { // eslint-disable-line guard-for-in
@@ -1413,7 +1411,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
* @returns {void}
*
* @deprecated
* NOTE: This method is not removed for backward compatibility purposes.
* NOTE: This method is not removed for backward comatability purposes.
*/
removeEventListener(event) {
this.removeAllListeners(event);
@@ -1426,7 +1424,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
* @returns {void}
*
* @deprecated
* NOTE: This method is not removed for backward compatibility purposes.
* NOTE: This method is not removed for backward comatability purposes.
*/
removeEventListeners(eventList) {
eventList.forEach(event => this.removeEventListener(event));

2149
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -72,7 +72,7 @@
"js-md5": "0.6.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2131.0.0+6912eed8/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2118.0.0+67fd2c84/lib-jitsi-meet.tgz",
"lodash-es": "4.17.21",
"null-loader": "4.0.1",
"optional-require": "1.0.3",
@@ -97,7 +97,7 @@
"react-native-pager-view": "6.8.1",
"react-native-paper": "5.10.3",
"react-native-performance": "5.1.2",
"react-native-safe-area-context": "5.6.1",
"react-native-safe-area-context": "5.5.2",
"react-native-screens": "4.11.1",
"react-native-sound": "https://github.com/jitsi/react-native-sound.git#ea13c97b5c2a4ff5e0d9bacbd9ff5e4457fe2c3c",
"react-native-splash-view": "0.0.18",
@@ -164,12 +164,12 @@
"@types/w3c-image-capture": "1.0.6",
"@types/w3c-web-hid": "1.0.3",
"@types/zxcvbn": "4.4.1",
"@wdio/allure-reporter": "9.23.2",
"@wdio/cli": "9.23.2",
"@wdio/globals": "9.23.0",
"@wdio/junit-reporter": "9.23.2",
"@wdio/local-runner": "9.23.2",
"@wdio/mocha-framework": "9.23.2",
"@wdio/allure-reporter": "9.22.0",
"@wdio/cli": "9.22.0",
"@wdio/globals": "9.17.0",
"@wdio/junit-reporter": "9.21.0",
"@wdio/local-runner": "9.22.0",
"@wdio/mocha-framework": "9.22.0",
"babel-loader": "9.1.0",
"babel-plugin-optional-require": "0.3.1",
"circular-dependency-plugin": "5.2.0",
@@ -194,7 +194,7 @@
"typescript": "5.7.2",
"unorm": "1.6.0",
"webdriverio": "9.22.0",
"webpack": "5.105.0",
"webpack": "5.95.0",
"webpack-bundle-analyzer": "4.4.2",
"webpack-cli": "5.1.4",
"webpack-dev-server": "5.1.0"

View File

@@ -113,13 +113,12 @@ export function maybeRedirectToTokenAuthUrl(
const audioMuted = isLocalTrackMuted(state['features/base/tracks'], MEDIA_TYPE.AUDIO);
const videoMuted = isLocalTrackMuted(state['features/base/tracks'], MEDIA_TYPE.VIDEO);
if (!isTokenAuthEnabled(state)) {
if (!isTokenAuthEnabled(config)) {
return false;
}
// if tokenAuthUrl check jwt if is about to expire go through the url to get new token
const jwt = state['features/base/jwt'].jwt;
const refreshToken = state['features/base/jwt'].refreshToken;
const expirationDate = getJwtExpirationDate(jwt);
// if there is jwt and its expiration time is less than 3 minutes away
@@ -138,8 +137,7 @@ export function maybeRedirectToTokenAuthUrl(
videoMuted
},
room,
tenant,
refreshToken
tenant
)
.then((tokenAuthServiceUrl: string | undefined) => {
if (!tokenAuthServiceUrl) {

View File

@@ -1,4 +1,4 @@
import { setRoom } from '../base/conference/actions.native';
import { setRoom } from '../base/conference/actions';
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.native';
import { isUnsafeRoomWarningEnabled } from '../prejoin/functions';
import { maybeRedirectToTokenAuthUrl } from './actions.any';
import { addTrackStateToURL, getDefaultURL } from './functions.native';

View File

@@ -11,9 +11,8 @@ const route = {
* store.
*
* @param {any} _stateful - Used on web.
* @param {any} _dispatch - Used on web.
* @returns {Promise<Object>}
*/
export function _getRouteToRender(_stateful?: any): Promise<object> {
export function _getRouteToRender(_stateful?: any) {
return Promise.resolve(route);
}

View File

@@ -1,10 +1,13 @@
// @ts-expect-error
import { generateRoomWithoutSeparator } from '@jitsi/js-utils/random';
import { getTokenAuthUrl } from '../authentication/functions.web';
import { IStateful } from '../base/app/types';
import { isRoomValid } from '../base/conference/functions';
import { isSupportedBrowser } from '../base/environment/environment';
import { browser } from '../base/lib-jitsi-meet';
import { toState } from '../base/redux/functions';
import { parseURIString } from '../base/util/uri';
import Conference from '../conference/components/web/Conference';
import { getDeepLinkingPage } from '../deep-linking/functions';
import UnsupportedDesktopBrowser from '../unsupported-browser/components/UnsupportedDesktopBrowser';
@@ -20,10 +23,9 @@ import { IReduxState } from './types';
*
* @param {(Function|Object)} stateful - THe redux store, state, or
* {@code getState} function.
* @param {Dispatch} dispatch - The Redux dispatch function.
* @returns {Promise<Object>}
*/
export function _getRouteToRender(stateful: IStateful): Promise<object> {
export function _getRouteToRender(stateful: IStateful) {
const state = toState(stateful);
return _getWebConferenceRoute(state) || _getWebWelcomePageRoute(state);
@@ -34,10 +36,9 @@ export function _getRouteToRender(stateful: IStateful): Promise<object> {
* a valid conference is being joined.
*
* @param {Object} state - The redux state.
* @param {Dispatch} dispatch - The Redux dispatch function.
* @returns {Promise|undefined}
*/
function _getWebConferenceRoute(state: IReduxState): Promise<any> | undefined {
function _getWebConferenceRoute(state: IReduxState) {
const room = state['features/base/conference'].room;
if (!isRoomValid(room)) {
@@ -45,6 +46,36 @@ function _getWebConferenceRoute(state: IReduxState): Promise<any> | undefined {
}
const route = _getEmptyRoute();
const config = state['features/base/config'];
// if we have auto redirect enabled, and we have previously logged in successfully
// let's redirect to the auth url to get the token and login again
if (!browser.isElectron() && config.tokenAuthUrl && config.tokenAuthUrlAutoRedirect
&& state['features/authentication'].tokenAuthUrlSuccessful
&& !state['features/base/jwt'].jwt && room) {
const { locationURL = { href: '' } as URL } = state['features/base/connection'];
const { tenant } = parseURIString(locationURL.href) || {};
const { startAudioOnly } = config;
return getTokenAuthUrl(
config,
locationURL,
{
audioMuted: false,
audioOnlyEnabled: startAudioOnly,
skipPrejoin: false,
videoMuted: false
},
room,
tenant
)
.then((url: string | undefined) => {
route.href = url;
return route;
})
.catch(() => Promise.resolve(route));
}
// Update the location if it doesn't match. This happens when a room is
// joined from the welcome page. The reason for doing this instead of using

View File

@@ -3,7 +3,7 @@ import '../authentication/middleware';
import '../av-moderation/middleware';
import '../base/conference/middleware';
import '../base/i18n/middleware';
import '../base/jwt/middleware.any';
import '../base/jwt/middleware';
import '../base/known-domains/middleware';
import '../base/lastn/middleware';
import '../base/lib-jitsi-meet/middleware';

View File

@@ -1,5 +1,4 @@
import '../base/app/middleware';
import '../base/jwt/middleware.web';
import '../base/config/middleware';
import '../base/connection/middleware';
import '../base/devices/middleware';
@@ -27,6 +26,5 @@ import '../face-landmarks/middleware';
import '../gifs/middleware';
import '../whiteboard/middleware.web';
import '../file-sharing/middleware.web';
import '../custom-panel/middleware.web';
import './middlewares.any';

View File

@@ -1,6 +1,5 @@
import '../base/devices/reducer';
import '../base/premeeting/reducer';
import '../custom-panel/reducer';
import '../base/tooltip/reducer';
import '../e2ee/reducer';
import '../face-landmarks/reducer';

View File

@@ -31,7 +31,6 @@ 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';
@@ -122,7 +121,6 @@ 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;

View File

@@ -88,13 +88,3 @@ export function openTokenAuthUrl(tokenAuthServiceUrl: string) {
Linking.openURL(tokenAuthServiceUrl);
};
}
/**
* Not used.
*
* @param {string} tokenAuthServiceUrl - Authentication service URL.
* @returns {Promise<any>} Resolves.
*/
export function loginWithPopup(tokenAuthServiceUrl: string): Promise<any> {
return Promise.resolve(tokenAuthServiceUrl);
}

View File

@@ -1,14 +1,10 @@
import { maybeRedirectToWelcomePage } from '../app/actions.web';
import { IStore } from '../app/types';
import { openDialog } from '../base/dialog/actions';
import { setJWT } from '../base/jwt/actions';
import { browser } from '../base/lib-jitsi-meet';
import { showErrorNotification } from '../notifications/actions';
import { CANCEL_LOGIN } from './actionTypes';
import LoginQuestionDialog from './components/web/LoginQuestionDialog';
import { isTokenAuthInline } from './functions.any';
import logger from './logger';
export * from './actions.any';
@@ -50,147 +46,6 @@ export function redirectToDefaultLocation() {
return (dispatch: IStore['dispatch']) => dispatch(maybeRedirectToWelcomePage());
}
/**
* Generates a cryptographic nonce.
*
* @returns {string} The generated nonce.
*/
function generateNonce(): string {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
}
/**
* Performs login with a popup window.
*
* @param {string} tokenAuthServiceUrl - Authentication service URL.
* @returns {Promise<any>} A promise that resolves with the authentication
* result or rejects with an error.
*/
export function loginWithPopup(tokenAuthServiceUrl: string): Promise<any> {
return new Promise<any>((resolve, reject) => {
// Open popup
const width = 500;
const height = 600;
const left = window.screen.width / 2 - width / 2;
const top = window.screen.height / 2 - height / 2;
let nonceParam = '';
try {
const nonce = generateNonce();
sessionStorage.setItem('oauth_nonce', nonce);
nonceParam = `&nonce=${nonce}`;
} catch (e) {
if (e instanceof DOMException && e.name === 'SecurityError') {
logger.warn(
'sessionStorage access denied (cross-origin or restricted context) enable it to improve security',
e);
} else {
logger.error('Unable to save nonce in session storage', e);
}
}
const popup = window.open(
`${tokenAuthServiceUrl}${nonceParam}`,
`Auth-${Date.now()}`,
`width=${width},height=${height},left=${left},top=${top}`
);
if (!popup) {
reject(new Error('Popup blocked'));
return;
}
let closedPollInterval: ReturnType<typeof setInterval> | undefined = undefined;
const cleanup = (handler: (event: MessageEvent) => void) => {
window.removeEventListener('message', handler);
clearInterval(closedPollInterval);
popup.close();
try {
sessionStorage.removeItem('oauth_nonce');
} catch (e) {
// ignore
}
try {
sessionStorage.removeItem('code_verifier');
} catch (e) {
// ignore
}
};
const handler = (event: MessageEvent) => {
// Verify origin
if (event.origin !== window.location.origin) {
return;
}
if (event.data.type === 'oauth-success') {
cleanup(handler);
resolve({
accessToken: event.data.accessToken,
idToken: event.data.idToken,
refreshToken: event.data.refreshToken
});
} else if (event.data.type === 'oauth-error') {
cleanup(handler);
reject(new Error(event.data.error));
}
};
// Listen for messages from the popup
window.addEventListener('message', handler);
// Detect manual popup close before authentication completes
closedPollInterval = setInterval(() => {
if (popup.closed) {
cleanup(handler);
reject(new Error('Login cancelled'));
}
}, 500);
});
}
/**
* Performs silent logout by loading the token authentication logout service URL in an
* invisible iframe.
*
* @param {string} tokenAuthLogoutServiceUrl - Logout service URL.
* @returns {Promise<any>} A promise that resolves when logout is complete.
*/
export function silentLogout(tokenAuthLogoutServiceUrl: string): any {
return new Promise<void>(resolve => {
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = tokenAuthLogoutServiceUrl;
document.body.appendChild(iframe);
// Listen for logout completion
const handler = (event: any) => {
if (event.origin !== window.location.origin) return;
if (event.data.type === 'logout-success') {
window.removeEventListener('message', handler);
document.body.removeChild(iframe);
resolve();
}
};
window.addEventListener('message', handler);
});
}
/**
* Opens token auth URL page.
*
@@ -208,42 +63,6 @@ export function openTokenAuthUrl(tokenAuthServiceUrl: string): any {
}
};
if (!browser.isElectron() && isTokenAuthInline(getState()['features/base/config'])) {
loginWithPopup(tokenAuthServiceUrl)
.then((result: { accessToken: string; idToken: string; refreshToken?: string; }) => {
// @ts-ignore
const token: string = result.accessToken;
const idToken: string = result.idToken;
const refreshToken: string | undefined = result.refreshToken;
// @ts-ignore
dispatch(setJWT(token, idToken, refreshToken));
logger.info('Reconnecting to conference with new token.');
const { connection } = getState()['features/base/connection'];
connection?.refreshToken(token).then(
() => {
const { membersOnly } = getState()['features/base/conference'];
membersOnly?.join();
})
.catch((err: any) => {
dispatch(setJWT());
logger.error(err);
});
})
.catch(err => {
dispatch(showErrorNotification({
titleKey: 'dialog.loginFailed'
}));
logger.error(err);
});
return;
}
// Show warning for leaving conference only when in a conference.
if (!browser.isElectron() && getState()['features/base/conference'].conference) {
dispatch(openDialog('LoginQuestionDialog', LoginQuestionDialog, {

View File

@@ -1,30 +1,15 @@
import { IReduxState } from '../app/types';
import { IConfig } from '../base/config/configType';
import { parseURLParams } from '../base/util/parseURLParams';
import { getBackendSafeRoomName } from '../base/util/uri';
import { isVpaasMeeting } from '../jaas/functions';
/**
* Checks if the token for authentication URL is available and the meeting is not jaas.
*
* @param {IReduxState} state - The state of the app.
* @returns {boolean}
*/
export const isTokenAuthEnabled = (state: IReduxState): boolean => {
const config = state['features/base/config'];
return typeof config.tokenAuthUrl === 'string' && config.tokenAuthUrl.length > 0
&& !isVpaasMeeting(state);
};
/**
* Checks if the token authentication should be done inline.
* Checks if the token for authentication is available.
*
* @param {Object} config - Configuration state object from store.
* @returns {boolean}
*/
export const isTokenAuthInline = (config: IConfig): boolean =>
config.tokenAuthInline === true;
export const isTokenAuthEnabled = (config: IConfig): boolean =>
typeof config.tokenAuthUrl === 'string' && config.tokenAuthUrl.length > 0;
/**
* Returns the state that we can add as a parameter to the tokenAuthUrl.
@@ -38,7 +23,6 @@ export const isTokenAuthInline = (config: IConfig): boolean =>
* }.
* @param {string?} roomName - The room name.
* @param {string?} tenant - The tenant name if any.
* @param {string?} refreshToken - The refresh token if available.
*
* @returns {Object} The state object.
*/
@@ -51,10 +35,8 @@ export const _getTokenAuthState = (
videoMuted: boolean | undefined;
},
roomName: string | undefined,
tenant: string | undefined,
refreshToken?: string): object => {
tenant: string | undefined): object => {
const state = {
refreshToken,
room: roomName,
roomSafe: getBackendSafeRoomName(roomName),
tenant

View File

@@ -23,7 +23,6 @@ export * from './functions.any';
* }.
* @param {string?} roomName - The room name.
* @param {string?} tenant - The tenant name if any.
* @param {string?} refreshToken - The refreshToken if any.
*
* @returns {Promise<string|undefined>} - The URL pointing to JWT login service or
* <tt>undefined</tt> if the pattern stored in config is not a string and the URL can not be
@@ -40,9 +39,7 @@ export const getTokenAuthUrl = (
},
roomName: string | undefined,
// eslint-disable-next-line max-params
tenant: string | undefined,
// eslint-disable-next-line max-params
refreshToken?: string | undefined): Promise<string | undefined> => {
tenant: string | undefined): Promise<string | undefined> => {
const {
audioMuted = false,
@@ -67,8 +64,7 @@ export const getTokenAuthUrl = (
videoMuted
},
roomName,
tenant,
refreshToken
tenant
);
// Append ios=true or android=true to the token URL.

View File

@@ -4,7 +4,6 @@ import { IConfig } from '../base/config/configType';
import { browser } from '../base/lib-jitsi-meet';
import { _getTokenAuthState } from './functions.any';
import logger from './logger';
export * from './functions.any';
@@ -42,7 +41,6 @@ function _cryptoRandom() {
* }.
* @param {string?} roomName - The room name.
* @param {string?} tenant - The tenant name if any.
* @param {string?} refreshToken - The refresh token if available.
*
* @returns {Promise<string|undefined>} - The URL pointing to JWT login service or
* <tt>undefined</tt> if the pattern stored in config is not a string and the URL can not be
@@ -58,10 +56,9 @@ export const getTokenAuthUrl = (
videoMuted: boolean | undefined;
},
roomName: string | undefined,
// eslint-disable max-params
tenant: string | undefined,
refreshToken?: string): Promise<string | undefined> => {
// eslint-enable max-params
// eslint-disable-next-line max-params
tenant: string | undefined): Promise<string | undefined> => {
const {
audioMuted = false,
audioOnlyEnabled = false,
@@ -85,8 +82,7 @@ export const getTokenAuthUrl = (
videoMuted
},
roomName,
tenant,
refreshToken
tenant
);
if (browser.isElectron()) {
@@ -107,17 +103,7 @@ export const getTokenAuthUrl = (
codeVerifier += POSSIBLE_CHARS.charAt(Math.floor(_cryptoRandom() * POSSIBLE_CHARS.length));
}
try {
window.sessionStorage.setItem('code_verifier', codeVerifier);
} catch (e) {
if (e instanceof DOMException && e.name === 'SecurityError') {
logger.warn(
'sessionStorage access denied (cross-origin or restricted context) enable it to improve security',
e);
} else {
logger.error('Unable to save code verifier in session storage', e);
}
}
window.sessionStorage.setItem('code_verifier', codeVerifier);
return window.crypto.subtle.digest('SHA-256', new TextEncoder().encode(codeVerifier))
.then(digest => {

View File

@@ -1,4 +1,5 @@
import { IStore } from '../app/types';
import { APP_WILL_NAVIGATE } from '../base/app/actionTypes';
import {
CONFERENCE_FAILED,
CONFERENCE_JOINED,
@@ -16,7 +17,6 @@ import { MEDIA_TYPE } from '../base/media/constants';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
import { isLocalTrackMuted } from '../base/tracks/functions.any';
import { parseURIString } from '../base/util/uri';
import { PREJOIN_JOINING_IN_PROGRESS } from '../prejoin/actionTypes';
import { openLogoutDialog } from '../settings/actions';
import {
@@ -130,7 +130,7 @@ MiddlewareRegistry.register(store => next => action => {
const state = getState();
const config = state['features/base/config'];
if (isTokenAuthEnabled(state)
if (isTokenAuthEnabled(config)
&& config.tokenAuthUrlAutoRedirect
&& state['features/base/jwt'].jwt) {
// auto redirect is turned on and we have successfully logged in
@@ -187,11 +187,7 @@ MiddlewareRegistry.register(store => next => action => {
break;
}
case PREJOIN_JOINING_IN_PROGRESS: {
if (!action.value) {
break;
}
case APP_WILL_NAVIGATE: {
const { dispatch, getState } = store;
const state = getState();
const config = state['features/base/config'];
@@ -292,7 +288,6 @@ function _handleLogin({ dispatch, getState }: IStore) {
const { enabled: audioOnlyEnabled } = state['features/base/audio-only'];
const audioMuted = isLocalTrackMuted(state['features/base/tracks'], MEDIA_TYPE.AUDIO);
const videoMuted = isLocalTrackMuted(state['features/base/tracks'], MEDIA_TYPE.VIDEO);
const refreshToken = state['features/base/jwt'].refreshToken;
if (!room) {
logger.warn('Cannot handle login, room is undefined!');
@@ -300,7 +295,7 @@ function _handleLogin({ dispatch, getState }: IStore) {
return;
}
if (!isTokenAuthEnabled(state)) {
if (!isTokenAuthEnabled(config)) {
dispatch(openLoginDialog());
return;
@@ -316,8 +311,7 @@ function _handleLogin({ dispatch, getState }: IStore) {
videoMuted
},
room,
tenant,
refreshToken
tenant
)
.then((tokenAuthServiceUrl: string | undefined) => {
if (!tokenAuthServiceUrl) {

View File

@@ -81,6 +81,7 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
break;
}
case LOCAL_PARTICIPANT_MODERATION_NOTIFICATION: {
let descriptionKey;
let titleKey;
let uid = '';
const localParticipant = getLocalParticipant(getState);
@@ -110,6 +111,8 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
!raisedHand && dispatch(raiseHand(true));
dispatch(hideNotification(uid));
}) ],
descriptionKey,
sticky: true,
titleKey,
uid
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
@@ -268,6 +271,7 @@ StateListenerRegistry.register(
dispatch(showNotification({
titleKey: 'notify.hostAskedUnmute',
sticky: true,
customActionNameKey,
customActionHandler,
uid: ASKED_TO_UNMUTE_NOTIFICATION_ID

View File

@@ -354,7 +354,7 @@ export function e2eRttChanged(participant: Object, rtt: number) {
* authLogin: string
* }}
*/
export function authStatusChanged(authEnabled: boolean, authLogin?: string) {
export function authStatusChanged(authEnabled: boolean, authLogin: string) {
return {
type: AUTH_STATUS_CHANGED,
authEnabled,

View File

@@ -93,17 +93,10 @@ 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: userIdentity?.avatar,
avatarURL: user.getIdentity()?.user?.avatar,
botType: user.getBotType(),
conference,
id,
@@ -112,8 +105,7 @@ export function commonUserJoinedHandling(
role: user.getRole(),
isPromoted,
isReplacing,
sources: user.getSources(),
userContext
sources: user.getSources()
}));
}
}

View File

@@ -14,7 +14,6 @@ import { sendAnalytics } from '../../analytics/functions';
import { reloadNow } from '../../app/actions';
import { IStore } from '../../app/types';
import { login } from '../../authentication/actions.any';
import { isTokenAuthEnabled } from '../../authentication/functions.any';
import { removeLobbyChatParticipant } from '../../chat/actions.any';
import { openDisplayNamePrompt } from '../../display-name/actions';
import { isVpaasMeeting } from '../../jaas/functions';
@@ -267,11 +266,8 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
descriptionKey = 'dialog.errorRoomCreationRestriction';
} else if (type === JitsiConferenceErrors.AUTH_ERROR_TYPES.ROOM_UNAUTHENTICATED_ACCESS_DISABLED) {
titleKey = 'dialog.unauthenticatedAccessDisabled';
if (isTokenAuthEnabled(getState())) {
customActionNameKey = [ 'toolbar.login' ];
customActionHandler = [ () => dispatch(login()) ]; // show login button if not jaas
}
customActionNameKey = [ 'toolbar.login' ];
customActionHandler = [ () => dispatch(login()) ];
}
dispatch(showErrorNotification({
@@ -406,8 +402,7 @@ async function _connectionEstablished({ dispatch, getState }: IStore, next: Func
email = getLocalParticipant(getState())?.email;
}
// it may happen to be already set
dispatch(authStatusChanged(true, email || getState()['features/base/conference'].authLogin || ''));
dispatch(authStatusChanged(true, email || ''));
}
// FIXME: Workaround for the web version. Currently, the creation of the

View File

@@ -559,7 +559,6 @@ export interface IConfig {
skipConsentInMeeting?: boolean;
suggestRecording?: boolean;
};
reducedUIEnabled?: boolean;
reducedUImainToolbarButtons?: Array<string>;
remoteVideoMenu?: {
disableDemote?: boolean;
@@ -616,7 +615,6 @@ export interface IConfig {
disabled?: boolean;
numberOfVisibleTiles?: number;
};
tokenAuthInline?: boolean;
tokenAuthUrl?: string;
tokenAuthUrlAutoRedirect?: string;
tokenGetUserInfoOutOfContext?: boolean;

View File

@@ -215,7 +215,6 @@ export default [
'recordings.showPrejoinWarning',
'recordings.showRecordingLink',
'recordings.suggestRecording',
'reducedUIEnabled',
'reducedUImainToolbarButtons',
'replaceParticipant',
'resolution',

View File

@@ -387,8 +387,7 @@ export function setConfigFromURLParams(
// When not in an iframe, start without media if the pre-join page is not enabled.
if (!isEmbedded()
&& ('config.prejoinConfig' in params || 'config.prejoinConfig.enabled' in params)
&& config.prejoinConfig?.enabled === false) {
&& 'config.prejoinConfig.enabled' in params && config.prejoinConfig?.enabled === false) {
logger.warn('Using prejoinConfig.enabled config URL overwrite implies starting without media.');
config.disableInitialGUM = true;
}

View File

@@ -51,16 +51,6 @@ export const CONNECTION_PROPERTIES_UPDATED = 'CONNECTION_PROPERTIES_UPDATED';
*/
export const CONNECTION_WILL_CONNECT = 'CONNECTION_WILL_CONNECT';
/**
* The type of (redux) action which signals that the token for a connection is expired.
*
* {
* type: CONNECTION_TOKEN_EXPIRED,
* connection: JitsiConnection
* }
*/
export const CONNECTION_TOKEN_EXPIRED = 'CONNECTION_TOKEN_EXPIRED';
/**
* The type of (redux) action which sets the location URL of the application,
* connection, conference, etc.

View File

@@ -17,7 +17,6 @@ import {
CONNECTION_ESTABLISHED,
CONNECTION_FAILED,
CONNECTION_PROPERTIES_UPDATED,
CONNECTION_TOKEN_EXPIRED,
CONNECTION_WILL_CONNECT,
SET_LOCATION_URL,
SET_PREFER_VISITOR
@@ -240,9 +239,6 @@ export function _connectInternal(id?: string, password?: string) {
connection.addEventListener(
JitsiConnectionEvents.PROPERTIES_UPDATED,
_onPropertiesUpdate);
connection.addEventListener(
JitsiConnectionEvents.CONNECTION_TOKEN_EXPIRED,
_onTokenExpired);
/**
* Unsubscribe the connection instance from
@@ -327,16 +323,6 @@ export function _connectInternal(id?: string, password?: string) {
dispatch(redirect(vnode, focusJid, username));
}
/**
* Connection will resume.
*
* @private
* @returns {void}
*/
function _onTokenExpired(): void {
dispatch(_connectionTokenExpired(connection));
}
/**
* Connection properties were updated.
*
@@ -378,23 +364,6 @@ function _connectionWillConnect(connection: Object) {
};
}
/**
* Create an action for when a connection token is expired.
*
* @param {JitsiConnection} connection - The {@code JitsiConnection} token is expired.
* @private
* @returns {{
* type: CONNECTION_TOKEN_EXPIRED,
* connection: JitsiConnection
* }}
*/
function _connectionTokenExpired(connection: Object) {
return {
type: CONNECTION_TOKEN_EXPIRED,
connection
};
}
/**
* Create an action for when connection properties are updated.
*

View File

@@ -4,7 +4,6 @@ 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 { conferenceLeft } from '../conference/actions.native';
import { setJWT } from '../jwt/actions';
import { JitsiConnectionErrors } from '../lib-jitsi-meet';
@@ -59,8 +58,5 @@ export function connect(id?: string, password?: string) {
* @returns {Function}
*/
export function hangup(_requestFeedback = false) {
return (dispatch: IStore['dispatch']) => {
dispatch(appNavigate(undefined));
dispatch(conferenceLeft());
};
return (dispatch: IStore['dispatch']) => dispatch(appNavigate(undefined));
}

View File

@@ -23,7 +23,6 @@ export interface IConnectionState {
getJid: () => string;
getLogs: () => Object;
initJitsiConference: Function;
refreshToken: Function;
removeFeature: Function;
};
error?: ConnectionFailedError;

View File

@@ -1 +0,0 @@
export * from './utils.any';

View File

@@ -29,3 +29,4 @@ export function isIpadMobileBrowser() {
// @ts-ignore
return isIosMobileBrowser() && Platform.isPad;
}

View File

@@ -1,37 +0,0 @@
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';

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-sparkles-icon lucide-sparkles"><path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"/><path d="M20 2v4"/><path d="M22 4h-4"/><circle cx="4" cy="20" r="2"/></svg>

Before

Width:  |  Height:  |  Size: 582 B

View File

@@ -1,4 +1,3 @@
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';
@@ -113,7 +112,6 @@ import { default as IconYahoo } from './yahoo.svg';
*/
export const DEFAULT_ICON: Record<string, any> = {
IconAddUser,
IconAI,
IconArrowBack,
IconArrowDown,
IconArrowDownLarge,

View File

@@ -4,7 +4,6 @@ import { DEFAULT_ICON } from './constants';
const {
IconAddUser,
IconAI,
IconArrowBack,
IconArrowDown,
IconArrowDownLarge,
@@ -124,7 +123,6 @@ const {
export {
IconAddUser,
IconAI,
IconArrowBack,
IconArrowDown,
IconArrowDownLarge,

View File

@@ -20,21 +20,15 @@ export function setDelayedLoadOfAvatarUrl(avatarUrl?: string) {
* Stores a specific JSON Web Token (JWT) into the redux store.
*
* @param {string} [jwt] - The JSON Web Token (JWT) to store.
* @param {string} idToken - The ID Token to store.
* @param {string} refreshToken - The Refresh Token to store.
* @returns {{
* type: SET_JWT,
* jwt: (string|undefined),
* idToken: (string|undefined),
* refreshToken: (string|undefined)
* jwt: (string|undefined)
* }}
*/
export function setJWT(jwt?: string, idToken?: string, refreshToken?: string) {
export function setJWT(jwt?: string) {
return {
type: SET_JWT,
jwt,
idToken,
refreshToken
jwt
};
}

View File

@@ -4,7 +4,6 @@ import { AnyAction } from 'redux';
import { IStore } from '../../app/types';
import { isVpaasMeeting } from '../../jaas/functions';
import { authStatusChanged } from '../conference/actions.any';
import { getCurrentConference } from '../conference/functions';
import { SET_CONFIG } from '../config/actionTypes';
import { CONNECTION_ESTABLISHED, SET_LOCATION_URL } from '../connection/actionTypes';
@@ -40,8 +39,6 @@ StateListenerRegistry.register(
* @returns {Function}
*/
MiddlewareRegistry.register(store => next => action => {
const state = store.getState();
switch (action.type) {
case SET_CONFIG:
case SET_LOCATION_URL:
@@ -49,6 +46,7 @@ MiddlewareRegistry.register(store => next => action => {
// have decided to store in the feature jwt
return _setConfigOrLocationURL(store, next, action);
case CONNECTION_ESTABLISHED: {
const state = store.getState();
const delayedLoadOfAvatarUrl = state['features/base/jwt'].delayedLoadOfAvatarUrl;
if (delayedLoadOfAvatarUrl) {
@@ -58,7 +56,6 @@ MiddlewareRegistry.register(store => next => action => {
store.dispatch(setDelayedLoadOfAvatarUrl());
store.dispatch(setKnownAvatarUrl(delayedLoadOfAvatarUrl));
}
break;
}
case SET_JWT:
return _setJWT(store, next, action);
@@ -152,7 +149,7 @@ function _setConfigOrLocationURL({ dispatch, getState }: IStore, next: Function,
*/
function _setJWT(store: IStore, next: Function, action: AnyAction) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { idToken, jwt, refreshToken, type, ...actionPayload } = action;
const { jwt, type, ...actionPayload } = action;
if (!Object.keys(actionPayload).length) {
const state = store.getState();
@@ -213,32 +210,24 @@ function _setJWT(store: IStore, next: Function, action: AnyAction) {
if (context.user && context.user.role === 'visitor') {
action.preferVisitor = true;
}
} else if (jwtPayload.name || jwtPayload.picture || jwtPayload.email) {
if (tokenGetUserInfoOutOfContext) {
// there are some tokens (firebase) having picture and name on the main level.
_overwriteLocalParticipant(store, {
avatarURL: jwtPayload.picture,
name: jwtPayload.name,
email: jwtPayload.email
});
}
store.dispatch(authStatusChanged(true, jwtPayload.email));
} else if (tokenGetUserInfoOutOfContext
&& (jwtPayload.name || jwtPayload.picture || jwtPayload.email)) {
// there are some tokens (firebase) having picture and name on the main level.
_overwriteLocalParticipant(store, {
avatarURL: jwtPayload.picture,
name: jwtPayload.name,
email: jwtPayload.email
});
}
}
} else {
if (typeof APP === 'undefined') {
// The logic of restoring JWT overrides make sense only on mobile.
// On Web it should eventually be restored from storage, but there's
// no such use case yet.
} else if (typeof APP === 'undefined') {
// The logic of restoring JWT overrides make sense only on mobile.
// On Web it should eventually be restored from storage, but there's
// no such use case yet.
const { user } = state['features/base/jwt'];
const { user } = state['features/base/jwt'];
user && _undoOverwriteLocalParticipant(store, user);
}
// clears authLogin
store.dispatch(authStatusChanged(true));
user && _undoOverwriteLocalParticipant(store, user);
}
}

View File

@@ -1,100 +0,0 @@
import { IStore } from '../../app/types';
import { loginWithPopup } from '../../authentication/actions';
import LoginQuestionDialog from '../../authentication/components/web/LoginQuestionDialog';
import { getTokenAuthUrl, isTokenAuthEnabled, isTokenAuthInline } from '../../authentication/functions';
import { hideNotification, showNotification } from '../../notifications/actions';
import { NOTIFICATION_TIMEOUT_TYPE, NOTIFICATION_TYPE } from '../../notifications/constants';
import { CONNECTION_TOKEN_EXPIRED } from '../connection/actionTypes';
import { openDialog } from '../dialog/actions';
import { browser } from '../lib-jitsi-meet';
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
import { parseURIString } from '../util/uri';
import { setJWT } from './actions';
import logger from './logger';
const PROMPT_LOGIN_NOTIFICATION_ID = 'PROMPT_LOGIN_NOTIFICATION_ID';
/**
* Middleware to handle token expiration on web - prompts the user to re-authenticate.
*
* @param {Store} store - The redux store.
* @private
* @returns {Function}
*/
MiddlewareRegistry.register((store: IStore) => next => action => {
if (action.type === CONNECTION_TOKEN_EXPIRED) {
const state = store.getState();
const jwt = state['features/base/jwt'].jwt;
const refreshToken = state['features/base/jwt'].refreshToken;
if (typeof APP !== 'undefined' && jwt && isTokenAuthEnabled(state)) {
const { connection, locationURL = { href: '' } as URL } = state['features/base/connection'];
const { tenant } = parseURIString(locationURL.href) || {};
const room = state['features/base/conference'].room;
const dispatch = store.dispatch;
getTokenAuthUrl(
state['features/base/config'],
locationURL,
{
audioMuted: false,
audioOnlyEnabled: false,
skipPrejoin: true,
videoMuted: false
},
room,
tenant,
refreshToken
)
.then((url: string | undefined) => {
if (url) {
dispatch(showNotification({
descriptionKey: 'dialog.loginOnResume',
titleKey: 'dialog.login',
uid: PROMPT_LOGIN_NOTIFICATION_ID,
customActionNameKey: [ 'dialog.login' ],
customActionHandler: [ () => {
store.dispatch(hideNotification(PROMPT_LOGIN_NOTIFICATION_ID));
if (isTokenAuthInline(state['features/base/config'])) {
loginWithPopup(url)
.then((result: { accessToken: string; idToken: string; refreshToken?: string; }) => {
const token: string = result.accessToken;
const idToken: string = result.idToken;
const newRefreshToken: string | undefined = result.refreshToken;
dispatch(setJWT(token, idToken, newRefreshToken || refreshToken));
connection?.refreshToken(token)
.catch((err: any) => {
dispatch(setJWT());
logger.error(err);
});
}).catch(logger.error);
} else {
dispatch(openDialog('LoginQuestionDialog', LoginQuestionDialog, {
handler: () => {
// Give time for the dialog to close.
setTimeout(() => {
if (browser.isElectron()) {
window.open(url, '_blank');
} else {
window.location.href = url;
}
}, 500);
}
}));
}
} ],
appearance: NOTIFICATION_TYPE.ERROR
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
}
})
.catch(logger.error);
}
}
return next(action);
});

View File

@@ -11,10 +11,8 @@ export interface IJwtState {
};
delayedLoadOfAvatarUrl?: string;
group?: string;
idToken?: string;
jwt?: string;
knownAvatarUrl?: string;
refreshToken?: string;
server?: string;
tenant?: string;
user?: {

View File

@@ -56,9 +56,9 @@ const useStyles = makeStyles()(theme => {
label: {
...theme.typography.labelRegular,
alignItems: 'center',
background: theme.palette.labelBackground,
background: theme.palette.ui04,
borderRadius: '4px',
color: theme.palette.labelText,
color: theme.palette.text01,
display: 'flex',
margin: '0 2px',
padding: '6px',
@@ -72,11 +72,11 @@ const useStyles = makeStyles()(theme => {
cursor: 'pointer'
},
[COLORS.white]: {
background: theme.palette.labelWhiteBackground,
color: theme.palette.labelWhiteText,
background: theme.palette.ui09,
color: theme.palette.text04,
'& svg': {
fill: theme.palette.labelWhiteIcon
fill: theme.palette.icon04
}
},
[COLORS.green]: {

View File

@@ -1,5 +1,4 @@
import { IReduxState, IStore } from '../../app/types';
import { getLocalParticipant } from '../participants/functions';
import StateListenerRegistry from '../redux/StateListenerRegistry';
/**
@@ -14,13 +13,6 @@ 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');
}
}
}
);

View File

@@ -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 = [ 'bottom', 'left', 'right' ],
safeAreaInsets = [ 'left', 'right' ],
style
}: IProps) => {
const renderContent = () => (
@@ -78,8 +78,8 @@ const JitsiScreen = ({
edges = { safeAreaInsets }
style = { styles.safeArea }>
{ children }
{ footerComponent?.() }
</SafeAreaView>
{ footerComponent?.() }
</JitsiKeyboardAvoidingView>
);

View File

@@ -60,6 +60,66 @@ 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<string>}
*/
export function getActiveSpeakersToBeDisplayed(stateful: IStateful) {
const state = toState(stateful);
const {
dominantSpeaker,
fakeParticipants,
sortedRemoteVirtualScreenshareParticipants,
speakersList
} = state['features/base/participants'];
const { visibleRemoteParticipants } = state['features/filmstrip'];
let activeSpeakers = new Map(speakersList);
// Do not re-sort the active speakers if dominant speaker is currently visible.
if (dominantSpeaker && visibleRemoteParticipants.has(dominantSpeaker)) {
return activeSpeakers;
}
let availableSlotsForActiveSpeakers = visibleRemoteParticipants.size;
if (activeSpeakers.has(dominantSpeaker ?? '')) {
activeSpeakers.delete(dominantSpeaker ?? '');
}
// Add dominant speaker to the beginning of the list (not including self) since the active speaker list is always
// alphabetically sorted.
if (dominantSpeaker && dominantSpeaker !== getLocalParticipant(state)?.id) {
const updatedSpeakers = Array.from(activeSpeakers);
updatedSpeakers.splice(0, 0, [ dominantSpeaker, getParticipantById(state, dominantSpeaker)?.name ?? '' ]);
activeSpeakers = new Map(updatedSpeakers);
}
// Remove screenshares from the count.
if (sortedRemoteVirtualScreenshareParticipants) {
availableSlotsForActiveSpeakers -= sortedRemoteVirtualScreenshareParticipants.size * 2;
for (const screenshare of Array.from(sortedRemoteVirtualScreenshareParticipants.keys())) {
const ownerId = getVirtualScreenshareParticipantOwnerId(screenshare as string);
activeSpeakers.delete(ownerId);
}
}
// Remove fake participants from the count.
if (fakeParticipants) {
availableSlotsForActiveSpeakers -= fakeParticipants.size;
}
const truncatedSpeakersList = Array.from(activeSpeakers).slice(0, availableSlotsForActiveSpeakers);
truncatedSpeakersList.sort((a: any, b: any) => a[1].localeCompare(b[1]));
return new Map(truncatedSpeakersList);
}
/**
* Resolves the first loadable avatar URL for a participant.

View File

@@ -606,21 +606,13 @@ function _e2eeUpdated({ getState, dispatch }: IStore, conference: IJitsiConferen
function _localParticipantJoined({ getState, dispatch }: IStore, next: Function, action: AnyAction) {
const result = next(action);
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;
const settings = getState()['features/base/settings'];
dispatch(localParticipantJoined({
avatarURL: settings.avatarURL,
email: settings.email,
name: settings.displayName,
id: '',
userContext
id: ''
}));
return result;

View File

@@ -68,7 +68,6 @@ const PARTICIPANT_PROPS_TO_OMIT_WHEN_UPDATE = [
];
const DEFAULT_STATE = {
activeSpeakers: new Set<string>(),
dominantSpeaker: undefined,
fakeParticipants: new Map(),
local: undefined,
@@ -83,10 +82,10 @@ const DEFAULT_STATE = {
remoteVideoSources: new Set<string>(),
sortedRemoteVirtualScreenshareParticipants: new Map(),
sortedRemoteParticipants: new Map(),
speakersList: new Map()
};
export interface IParticipantsState {
activeSpeakers: Set<string>;
dominantSpeaker?: string;
fakeParticipants: Map<string, IParticipant>;
local?: ILocalParticipant;
@@ -101,6 +100,7 @@ export interface IParticipantsState {
remoteVideoSources: Set<string>;
sortedRemoteParticipants: Map<string, string>;
sortedRemoteVirtualScreenshareParticipants: Map<string, string>;
speakersList: Map<string, string>;
}
/**
@@ -157,8 +157,22 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
const { participant } = action;
const { id, previousSpeakers = [] } = participant;
const { dominantSpeaker, local } = state;
const activeSpeakers = new Set(previousSpeakers
.filter((speakerId: string) => state.remote.has(speakerId) && (speakerId !== local?.id)));
const newSpeakers = [ id, ...previousSpeakers ];
const sortedSpeakersList: Array<Array<string>> = [];
for (const speaker of newSpeakers) {
if (speaker !== local?.id) {
const remoteParticipant = state.remote.get(speaker);
remoteParticipant
&& sortedSpeakersList.push(
[ speaker, _getDisplayName(state, remoteParticipant?.name) ]
);
}
}
// Keep the remote speaker list sorted alphabetically.
sortedSpeakersList.sort((a, b) => a[1].localeCompare(b[1]));
// Only one dominant speaker is allowed.
if (dominantSpeaker) {
@@ -169,7 +183,7 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
return {
...state,
dominantSpeaker: id, // @ts-ignore
activeSpeakers
speakersList: new Map(sortedSpeakersList)
};
}
@@ -424,7 +438,7 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
}
// Remove the participant from the list of speakers.
state.activeSpeakers.delete(id);
state.speakersList.has(id) && state.speakersList.delete(id);
if (pinnedParticipant === id) {
state.pinnedParticipant = undefined;
@@ -613,8 +627,7 @@ function _participantJoined({ participant }: { participant: IParticipant; }) {
pinned,
presence,
role,
sources,
userContext
sources
} = participant;
let { conference, id } = participant;
@@ -646,8 +659,7 @@ function _participantJoined({ participant }: { participant: IParticipant; }) {
pinned: pinned || false,
presence,
role: role || PARTICIPANT_ROLE.NONE,
sources,
userContext
sources
};
}

View File

@@ -41,12 +41,6 @@ export interface IParticipant {
role?: string;
sources?: Map<string, Map<string, ISourceInfo>>;
supportsRemoteControl?: boolean;
userContext?: IUserContext;
}
export interface IUserContext {
id?: string;
name?: string;
}
export interface ILocalParticipant extends IParticipant {

View File

@@ -84,7 +84,7 @@ const useStyles = makeStyles()(theme => {
...theme.typography.bodyLongBold,
borderRadius: theme.shape.borderRadius,
boxSizing: 'border-box',
color: theme.palette.actionButtonText,
color: theme.palette.text01,
cursor: 'pointer',
display: 'inline-block',
marginBottom: '16px',
@@ -95,20 +95,20 @@ const useStyles = makeStyles()(theme => {
border: 0,
'&.primary': {
background: theme.palette.prejoinActionButtonPrimary,
color: theme.palette.prejoinActionButtonPrimaryText,
background: theme.palette.action01,
color: theme.palette.text01,
'&:hover': {
backgroundColor: theme.palette.prejoinActionButtonPrimaryHover
backgroundColor: theme.palette.action01Hover
}
},
'&.secondary': {
background: theme.palette.prejoinActionButtonSecondary,
color: theme.palette.prejoinActionButtonSecondaryText,
background: theme.palette.action02,
color: theme.palette.text04,
'&:hover': {
backgroundColor: theme.palette.prejoinActionButtonSecondaryHover
backgroundColor: theme.palette.action02Hover
}
},
@@ -120,7 +120,7 @@ const useStyles = makeStyles()(theme => {
},
'&.disabled': {
background: theme.palette.prejoinActionButtonDisabled,
background: theme.palette.disabled01,
border: '1px solid #5E6D7A',
color: '#AFB6BC',
cursor: 'initial',

View File

@@ -109,7 +109,7 @@ const useStyles = makeStyles()(theme => {
position: 'absolute',
inset: '0 0 0 0',
display: 'flex',
backgroundColor: theme.palette.preMeetingBackground,
backgroundColor: theme.palette.ui01,
zIndex: 252,
'@media (max-width: 720px)': {
@@ -163,7 +163,7 @@ const useStyles = makeStyles()(theme => {
},
title: {
...theme.typography.heading4,
color: theme.palette.prejoinTitleText,
color: `${theme.palette.text01}!important`,
marginBottom: theme.spacing(3),
textAlign: 'center',
@@ -179,7 +179,7 @@ const useStyles = makeStyles()(theme => {
roomName: {
...theme.typography.heading5,
color: theme.palette.prejoinRoomNameText,
color: theme.palette.text01,
display: 'inline-block',
overflow: 'hidden',
textOverflow: 'ellipsis',

View File

@@ -6,7 +6,7 @@ const useStyles = makeStyles()(theme => {
return {
warning: {
bottom: 0,
color: theme.palette.prejoinRecordingWarningText,
color: theme.palette.text03,
display: 'flex',
justifyContent: 'center',
...theme.typography.bodyShortRegular,

View File

@@ -11,8 +11,8 @@ import { setUnsafeRoomConsent } from '../../actions.web';
const useStyles = makeStyles()(theme => {
return {
warning: {
backgroundColor: theme.palette.prejoinWarningBackground,
color: theme.palette.prejoinWarningText,
backgroundColor: theme.palette.warning01,
color: theme.palette.text04,
...theme.typography.bodyShortRegular,
padding: theme.spacing(3),
borderRadius: theme.shape.borderRadius,

View File

@@ -9,11 +9,11 @@ import { getSupportUrl } from '../../functions';
const useStyles = makeStyles()(theme => {
return {
dialog: {
backgroundColor: theme.palette.dialogBackground,
border: `1px solid ${theme.palette.inlineDialogBorder}`,
backgroundColor: theme.palette.ui01,
border: `1px solid ${theme.palette.ui04}`,
borderRadius: `${Number(theme.shape.borderRadius)}px`,
boxShadow: '0px 1px 2px rgba(41, 41, 41, 0.25)',
color: theme.palette.dialogText,
color: theme.palette.text01,
...theme.typography.bodyShortRegular,
padding: `${theme.spacing(3)} 10`,
'& .retry-button': {

View File

@@ -2,7 +2,6 @@ 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 {
@@ -44,7 +43,6 @@ 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) {
@@ -52,9 +50,8 @@ export function clientResized(clientWidth: number, clientHeight: number) {
}
availableWidth -= getParticipantsPaneWidth(state);
availableWidth -= getCustomPanelWidth(state);
reducedUIEnabled && dispatch(setReducedUI(availableWidth, clientHeight));
dispatch(setReducedUI(availableWidth, clientHeight));
}
batch(() => {
@@ -115,7 +112,7 @@ export function setReducedUI(width: number, height: number) {
const threshold = navigator.product === 'ReactNative'
? REDUCED_UI_THRESHOLD
: WEB_REDUCED_UI_THRESHOLD;
const reducedUI = Math.max(width, height) < threshold;
const reducedUI = Math.min(width, height) < threshold;
if (reducedUI !== getState()['features/base/responsive-ui'].reducedUI) {
return dispatch({

View File

@@ -22,11 +22,11 @@ interface IProps {
const useStyles = makeStyles()(theme => {
return {
container: {
backgroundColor: theme.palette.tooltipBackground,
backgroundColor: theme.palette.uiBackground,
borderRadius: '3px',
padding: theme.spacing(2),
...theme.typography.labelRegular,
color: theme.palette.tooltipText,
color: theme.palette.text01,
position: 'relative',
'&.mounting-animation': {

View File

@@ -51,7 +51,7 @@ import './subscriber.web';
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case TRACK_ADDED: {
const { local, jitsiTrack } = action.track;
const { local } = 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,16 +65,6 @@ 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;
@@ -129,16 +119,6 @@ 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();
@@ -146,6 +126,11 @@ 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();
@@ -159,14 +144,12 @@ MiddlewareRegistry.register(store => next => action => {
if (typeof action.track?.muted !== 'undefined' && participantID && !local) {
logTracksForParticipant(store.getState()['features/base/tracks'], participantID, 'Track updated');
// 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';
// Notify external API when remote participant mutes/unmutes themselves
const mediaType = isVideoTrack
? (jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP ? 'desktop' : 'video')
: 'audio';
APP.API.notifyParticipantMuted(participantID, action.track.muted, mediaType);
}
APP.API.notifyParticipantMuted(participantID, action.track.muted, mediaType, true);
}
return result;

View File

@@ -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 { getLocalParticipant, getScreenshareParticipantIds } from '../participants/functions';
import { getScreenshareParticipantIds } from '../participants/functions';
import StateListenerRegistry from '../redux/StateListenerRegistry';
import { isLocalTrackMuted } from './functions';
@@ -47,13 +47,6 @@ 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');
}
}
}
);

View File

@@ -1,4 +1,3 @@
/* eslint-disable @stylistic/no-multi-spaces */
// Mapping between the token used and the color
export const colorMap = {
// ----- Surfaces -----
@@ -8,7 +7,7 @@ export const colorMap = {
// - JitsiMeetView.java
uiBackground: 'surface01',
// Container backgrounds (legacy tokens)
// Container backgrounds
ui01: 'surface02',
ui02: 'surface03',
ui03: 'ui02',
@@ -48,430 +47,6 @@ 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
prejoinPreviewBackground: 'uiBackground', // Prejoin video preview background (#040404)
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: 'surface02', // Welcome page tab bar background
welcomeTabActive: 'icon01', // Welcome page active tab icon
welcomeTabInactive: 'ui03', // 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
reactionsMenuButtonToggled: 'surface01', // Reactions menu button toggled state background
reactionsMenuBoxShadow1: 'ui09', // Reactions menu box shadow primary
reactionsMenuBoxShadow2: 'ui08', // Reactions menu box shadow secondary
// 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: 'uiBackground', // Deep linking page content pane background (#1e1e1e)
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',
@@ -509,9 +84,6 @@ export const colorMap = {
// High-contrast
icon04: 'surface01',
// SVG fill color
iconSvgFill: 'icon01',
// Error
iconError: 'action03',

View File

@@ -14,7 +14,7 @@ const useStyles = makeStyles()(theme => {
width: '100%',
height: '100%',
position: 'fixed',
color: theme.palette.dialogText,
color: theme.palette.text01,
...theme.typography.bodyLongRegular,
top: 0,
left: 0,
@@ -49,13 +49,13 @@ const useStyles = makeStyles()(theme => {
height: '100%',
top: 0,
left: 0,
backgroundColor: theme.palette.dialogOverlay,
backgroundColor: theme.palette.ui02,
opacity: 0.75
},
modal: {
backgroundColor: theme.palette.dialogBackground,
border: `1px solid ${theme.palette.dialogBorder}`,
backgroundColor: theme.palette.ui01,
border: `1px solid ${theme.palette.ui03}`,
boxShadow: '0px 4px 25px 4px rgba(20, 20, 20, 0.6)',
borderRadius: `${theme.shape.borderRadius}px`,
display: 'flex',

View File

@@ -47,7 +47,7 @@ const useStyles = makeStyles()(theme => {
return {
formControl: {
...theme.typography.bodyLongRegular,
color: theme.palette.checkboxLabel,
color: theme.palette.text01,
display: 'inline-flex',
alignItems: 'center',
@@ -76,10 +76,10 @@ const useStyles = makeStyles()(theme => {
backgroundColor: 'transparent',
margin: '3px',
font: 'inherit',
color: theme.palette.checkboxBorder,
color: theme.palette.icon03,
width: '18px',
height: '18px',
border: `2px solid ${theme.palette.checkboxBorder}`,
border: `2px solid ${theme.palette.icon03}`,
borderRadius: '3px',
display: 'grid',
@@ -90,7 +90,7 @@ const useStyles = makeStyles()(theme => {
width: '18px',
height: '18px',
opacity: 0,
backgroundColor: theme.palette.checkboxChecked,
backgroundColor: theme.palette.action01,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
@@ -104,11 +104,11 @@ const useStyles = makeStyles()(theme => {
},
'&:disabled': {
backgroundColor: theme.palette.checkboxDisabledBackground,
borderColor: theme.palette.checkboxDisabledBorder,
backgroundColor: theme.palette.ui03,
borderColor: theme.palette.ui04,
'&::before': {
backgroundColor: theme.palette.checkboxDisabledChecked
backgroundColor: theme.palette.ui04
}
},
@@ -173,7 +173,7 @@ const Checkbox = ({
<Icon
aria-hidden = { true }
className = 'checkmark'
color = { disabled ? theme.palette.checkboxIconDisabled : theme.palette.checkboxIcon }
color = { disabled ? theme.palette.icon03 : theme.palette.icon01 }
size = { 18 }
src = { IconCheck } />
</div>

View File

@@ -16,22 +16,22 @@ const useStyles = makeStyles()(theme => {
return {
button: {
padding: '2px',
backgroundColor: theme.palette.clickableIconBackground,
backgroundColor: theme.palette.action03,
border: 0,
outline: 0,
borderRadius: `${theme.shape.borderRadius}px`,
'&:hover': {
backgroundColor: theme.palette.clickableIconHover
backgroundColor: theme.palette.ui02
},
'&.focus-visible': {
outline: 0,
boxShadow: `0px 0px 0px 2px ${theme.palette.clickableIconFocus}`
boxShadow: `0px 0px 0px 2px ${theme.palette.focus01}`
},
'&:active': {
backgroundColor: theme.palette.clickableIconActive
backgroundColor: theme.palette.ui03
},
'&.is-mobile': {

View File

@@ -133,11 +133,11 @@ const MAX_HEIGHT = 400;
const useStyles = makeStyles()(theme => {
return {
contextMenu: {
backgroundColor: theme.palette.overflowMenuBackground,
border: `1px solid ${theme.palette.overflowMenuBorder}`,
backgroundColor: theme.palette.ui01,
border: `1px solid ${theme.palette.ui04}`,
borderRadius: `${Number(theme.shape.borderRadius)}px`,
boxShadow: '0px 1px 2px rgba(41, 41, 41, 0.25)',
color: theme.palette.overflowMenuItemText,
color: theme.palette.text01,
...theme.typography.bodyShortRegular,
marginTop: '48px',
position: 'absolute',

View File

@@ -122,11 +122,11 @@ const useStyles = makeStyles()(theme => {
},
'&:hover': {
backgroundColor: theme.palette.overflowMenuItemHover
backgroundColor: theme.palette.ui02
},
'&:active': {
backgroundColor: theme.palette.overflowMenuItemHover
backgroundColor: theme.palette.ui03
},
'&.focus-visible': {
@@ -137,7 +137,7 @@ const useStyles = makeStyles()(theme => {
selected: {
borderLeft: `3px solid ${theme.palette.action01Hover}`,
paddingLeft: '13px',
backgroundColor: theme.palette.overflowMenuItemHover
backgroundColor: theme.palette.ui02
},
contextMenuItemDisabled: {
@@ -146,19 +146,19 @@ const useStyles = makeStyles()(theme => {
contextMenuItemIconDisabled: {
'& svg': {
fill: `${theme.palette.overflowMenuItemDisabled} !important`
fill: `${theme.palette.text03} !important`
}
},
contextMenuItemLabelDisabled: {
color: theme.palette.overflowMenuItemDisabled,
color: theme.palette.text03,
'&:hover': {
background: 'none'
},
'& svg': {
fill: theme.palette.overflowMenuItemDisabled
fill: theme.palette.text03
}
},
@@ -168,13 +168,13 @@ const useStyles = makeStyles()(theme => {
contextMenuItemIcon: {
'& svg': {
fill: theme.palette.overflowMenuItemIcon
fill: theme.palette.icon01
}
},
text: {
...theme.typography.bodyShortRegular,
color: theme.palette.overflowMenuItemText
color: theme.palette.text01
},
drawerText: {

View File

@@ -30,7 +30,7 @@ const useStyles = makeStyles()(theme => {
},
'& + &:not(:empty)': {
borderTop: `1px solid ${theme.palette.overflowMenuSeparator}`
borderTop: `1px solid ${theme.palette.ui03}`
},
'&:first-of-type': {

View File

@@ -24,7 +24,7 @@ const useStyles = makeStyles()(theme => {
},
title: {
color: theme.palette.dialogText,
color: theme.palette.text01,
...theme.typography.heading5,
margin: 0,
padding: 0

View File

@@ -42,7 +42,7 @@ const useStyles = makeStyles()(theme => {
flexDirection: 'column',
minWidth: '211px',
maxWidth: '100%',
borderRight: `1px solid ${theme.palette.dialogBorder}`,
borderRight: `1px solid ${theme.palette.ui03}`,
[`@media (max-width: ${MOBILE_BREAKPOINT}px)`]: {
width: '100%',
@@ -70,7 +70,7 @@ const useStyles = makeStyles()(theme => {
title: {
...theme.typography.heading5,
color: `${theme.palette.dialogText} !important`,
color: `${theme.palette.text01} !important`,
margin: 0,
padding: 0
},
@@ -301,7 +301,7 @@ const DialogWithTabs = ({
}
return null;
}, [ selectedTabIndex, tabStates, tabs ]);
}, [ selectedTabIndex, tabStates ]);
const closeIcon = useMemo(() => (
<ClickableIcon

View File

@@ -49,7 +49,7 @@ const useStyles = makeStyles()(theme => {
},
label: {
color: theme.palette.inputLabel,
color: theme.palette.text01,
...theme.typography.bodyShortRegular,
marginBottom: theme.spacing(2),
@@ -64,9 +64,9 @@ const useStyles = makeStyles()(theme => {
},
input: {
backgroundColor: theme.palette.inputFieldBackground,
background: theme.palette.inputFieldBackground,
color: theme.palette.inputFieldText,
backgroundColor: theme.palette.ui03,
background: theme.palette.ui03,
color: theme.palette.text01,
...theme.typography.bodyShortRegular,
padding: '10px 16px',
borderRadius: theme.shape.borderRadius,
@@ -76,20 +76,16 @@ const useStyles = makeStyles()(theme => {
width: '100%',
'&::placeholder': {
color: theme.palette.inputFieldPlaceholder
color: theme.palette.text02
},
'&:focus': {
outline: 0,
boxShadow: `0px 0px 0px 2px ${theme.palette.inputFieldFocus}`,
'&::placeholder': {
opacity: 0
}
boxShadow: `0px 0px 0px 2px ${theme.palette.focus01}`
},
'&:disabled': {
color: theme.palette.inputFieldDisabled
color: theme.palette.text03
},
'&.is-mobile': {
@@ -103,7 +99,7 @@ const useStyles = makeStyles()(theme => {
},
'&.error': {
boxShadow: `0px 0px 0px 2px ${theme.palette.inputFieldError}`
boxShadow: `0px 0px 0px 2px ${theme.palette.textError}`
},
'&.clearable-input': {
paddingRight: '46px'
@@ -135,7 +131,7 @@ const useStyles = makeStyles()(theme => {
right: '16px',
top: '10px',
cursor: 'pointer',
backgroundColor: theme.palette.inputClearButton,
backgroundColor: theme.palette.action03,
border: 0,
padding: 0
},
@@ -143,14 +139,14 @@ const useStyles = makeStyles()(theme => {
bottomLabel: {
marginTop: theme.spacing(2),
...theme.typography.labelRegular,
color: theme.palette.inputBottomLabel,
color: theme.palette.text02,
'&.is-mobile': {
...theme.typography.bodyShortRegular
},
'&.error': {
color: theme.palette.inputBottomLabelError
color: theme.palette.textError
}
}
};

View File

@@ -83,7 +83,7 @@ const useStyles = makeStyles()(theme => {
return {
container: {
alignItems: 'center',
color: theme.palette.listItemText,
color: theme.palette.text01,
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.listItemHover,
backgroundColor: theme.palette.ui02,
'& .indicators': {
display: 'none'
@@ -103,8 +103,8 @@ const useStyles = makeStyles()(theme => {
display: 'flex',
position: 'relative',
top: 'auto',
boxShadow: `-15px 0px 10px -5px ${theme.palette.listItemBoxShadow}`,
backgroundColor: theme.palette.listItemHover
boxShadow: `-15px 0px 10px -5px ${theme.palette.ui02}`,
backgroundColor: theme.palette.ui02
}
},
@@ -115,14 +115,14 @@ const useStyles = makeStyles()(theme => {
},
highlighted: {
backgroundColor: theme.palette.listItemHighlighted,
backgroundColor: theme.palette.ui02,
'& .actions': {
display: 'flex',
position: 'relative',
top: 'auto',
boxShadow: `-15px 0px 10px -5px ${theme.palette.listItemBoxShadow}`,
backgroundColor: theme.palette.listItemHighlighted
boxShadow: `-15px 0px 10px -5px ${theme.palette.ui02}`,
backgroundColor: theme.palette.ui02
}
},
@@ -170,20 +170,20 @@ const useStyles = makeStyles()(theme => {
actionsContainer: {
position: 'absolute',
top: '-1000px',
boxShadow: `-15px 0px 10px -5px ${theme.palette.listItemBoxShadow}`,
backgroundColor: theme.palette.listItemHover
boxShadow: `-15px 0px 10px -5px ${theme.palette.ui02}`,
backgroundColor: theme.palette.ui02
},
actionsPermanent: {
display: 'flex',
boxShadow: `-15px 0px 10px -5px ${theme.palette.listItemBackground}`,
backgroundColor: theme.palette.listItemBackground
boxShadow: `-15px 0px 10px -5px ${theme.palette.ui01}`,
backgroundColor: theme.palette.ui01
},
actionsVisible: {
display: 'flex',
boxShadow: `-15px 0px 10px -5px ${theme.palette.listItemBoxShadow}`,
backgroundColor: theme.palette.listItemHighlighted
boxShadow: `-15px 0px 10px -5px ${theme.palette.ui02}`,
backgroundColor: theme.palette.ui02
}
};
});

View File

@@ -38,8 +38,8 @@ const useStyles = makeStyles()(theme => {
},
marginTop: theme.spacing(2),
width: '100%',
backgroundColor: theme.palette.multiSelectBackground,
border: `1px solid ${theme.palette.multiSelectBorder}`,
backgroundColor: theme.palette.ui01,
border: `1px solid ${theme.palette.ui04}`,
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.multiSelectItemText,
color: theme.palette.text01,
'&.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.multiSelectItemHover
backgroundColor: theme.palette.ui02
}
},
'&.disabled': {
cursor: 'not-allowed',
'&:hover': {
backgroundColor: theme.palette.multiSelectBackground
backgroundColor: theme.palette.ui01
},
color: theme.palette.multiSelectItemDisabled
color: theme.palette.text03
}
},
errorMessage: {

View File

@@ -70,7 +70,7 @@ const useStyles = makeStyles()(theme => {
},
label: {
color: theme.palette.selectLabel,
color: theme.palette.text01,
...theme.typography.bodyShortRegular,
marginBottom: theme.spacing(2),
@@ -84,11 +84,11 @@ const useStyles = makeStyles()(theme => {
},
select: {
backgroundColor: theme.palette.selectBackground,
backgroundColor: theme.palette.ui03,
borderRadius: `${theme.shape.borderRadius}px`,
width: '100%',
...theme.typography.bodyShortRegular,
color: theme.palette.selectText,
color: theme.palette.text01,
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.selectFocus}`
boxShadow: `0px 0px 0px 2px ${theme.palette.focus01}`
},
'&:disabled': {
color: theme.palette.selectDisabled
color: theme.palette.text03
},
'&.is-mobile': {
@@ -113,7 +113,7 @@ const useStyles = makeStyles()(theme => {
},
'&.error': {
boxShadow: `0px 0px 0px 2px ${theme.palette.selectError}`
boxShadow: `0px 0px 0px 2px ${theme.palette.textError}`
}
},
@@ -132,14 +132,14 @@ const useStyles = makeStyles()(theme => {
bottomLabel: {
marginTop: theme.spacing(2),
...theme.typography.labelRegular,
color: theme.palette.selectBottomLabel,
color: theme.palette.text02,
'&.is-mobile': {
...theme.typography.bodyShortRegular
},
'&.error': {
color: theme.palette.selectBottomLabelError
color: theme.palette.textError
}
}
};
@@ -180,7 +180,7 @@ const Select = ({
</select>
<Icon
className = { cx(classes.icon, isMobile && 'is-mobile') }
color = { disabled ? theme.palette.selectIconDisabled : theme.palette.selectIcon }
color = { disabled ? theme.palette.icon03 : theme.palette.icon01 }
size = { 22 }
src = { IconArrowDown } />
</div>

View File

@@ -18,7 +18,7 @@ const useStyles = makeStyles()(theme => {
return {
container: {
position: 'relative',
backgroundColor: theme.palette.switchBackground,
backgroundColor: theme.palette.ui05,
borderRadius: '12px',
width: '40px',
height: '24px',
@@ -29,11 +29,11 @@ const useStyles = makeStyles()(theme => {
display: 'inline-block',
'&.disabled': {
backgroundColor: theme.palette.switchBackground,
backgroundColor: theme.palette.ui05,
cursor: 'default',
'& .toggle': {
backgroundColor: theme.palette.switchToggleDisabled
backgroundColor: theme.palette.ui03
}
},
@@ -45,7 +45,7 @@ const useStyles = makeStyles()(theme => {
},
containerOn: {
backgroundColor: theme.palette.switchBackgroundOn
backgroundColor: theme.palette.action01
},
toggle: {
@@ -55,7 +55,7 @@ const useStyles = makeStyles()(theme => {
zIndex: 5,
top: '4px',
left: '4px',
backgroundColor: theme.palette.switchToggle,
backgroundColor: theme.palette.ui10,
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.switchFocus}`
boxShadow: `0px 0px 0px 2px ${theme.palette.focus01}`
}
},

View File

@@ -29,13 +29,13 @@ const useStyles = makeStyles()(theme => {
tab: {
...theme.typography.bodyShortBold,
color: theme.palette.tabText,
color: theme.palette.text02,
flex: 1,
padding: '14px',
background: 'none',
border: 0,
appearance: 'none',
borderBottom: `2px solid ${theme.palette.tabBorder}`,
borderBottom: `2px solid ${theme.palette.ui05}`,
transition: 'color, border-color 0.2s',
display: 'flex',
alignItems: 'center',
@@ -43,25 +43,25 @@ const useStyles = makeStyles()(theme => {
borderRadius: 0,
'&:hover': {
color: theme.palette.tabTextHover,
borderColor: theme.palette.tabBorderHover
color: theme.palette.text01,
borderColor: theme.palette.ui10
},
'&.focus-visible': {
outline: 0,
boxShadow: `0px 0px 0px 2px ${theme.palette.tabFocus}`,
boxShadow: `0px 0px 0px 2px ${theme.palette.focus01}`,
border: 0,
color: theme.palette.tabTextSelected
color: theme.palette.text01
},
'&.selected': {
color: theme.palette.tabTextSelected,
borderColor: theme.palette.tabBorderSelected
color: theme.palette.text01,
borderColor: theme.palette.action01
},
'&:disabled': {
color: theme.palette.tabTextDisabled,
borderColor: theme.palette.tabBorderDisabled
color: theme.palette.text03,
borderColor: theme.palette.ui05
},
'&.is-mobile': {
@@ -72,9 +72,9 @@ const useStyles = makeStyles()(theme => {
badge: {
...theme.typography.labelBold,
alignItems: 'center',
backgroundColor: theme.palette.tabBadgeBackground,
backgroundColor: theme.palette.warning01,
borderRadius: theme.spacing(2),
color: theme.palette.tabBadgeText,
color: theme.palette.text04,
display: 'inline-flex',
height: theme.spacing(3),
justifyContent: 'center',

View File

@@ -11,29 +11,6 @@ export * from './constants.any';
*/
export const commonStyles = (theme: Theme) => {
return {
':root': {
// Inject semantic tokens as CSS custom properties for use in SCSS
'--drawer-background-color': theme.palette.drawerBackground,
'--icon-svg-fill': theme.palette.iconSvgFill,
'--overflow-menu-background-color': theme.palette.overflowMenuBackground,
'--overflow-menu-item-disabled-color': theme.palette.overflowMenuItemDisabled,
'--overflow-menu-item-hover-color': theme.palette.overflowMenuItemHover,
'--overflow-menu-item-icon-color': theme.palette.overflowMenuItemIcon,
'--overflow-menu-item-text-color': theme.palette.overflowMenuItemText,
'--prejoin-preview-background': theme.palette.prejoinPreviewBackground,
'--reactions-menu-background': theme.palette.reactionsMenuBackground,
'--reactions-menu-box-shadow-1': theme.palette.reactionsMenuBoxShadow1,
'--reactions-menu-box-shadow-2': theme.palette.reactionsMenuBoxShadow2,
'--reactions-menu-button-toggled': theme.palette.reactionsMenuButtonToggled,
'--toolbar-button-active-color': theme.palette.toolbarButtonActive,
'--toolbar-button-color': theme.palette.toolbarButton,
'--toolbar-button-hover-color': theme.palette.toolbarButtonHover,
'--toolbar-icon-active-color': theme.palette.toolbarIconActive,
'--toolbar-icon-color': theme.palette.toolbarIcon,
'--toolbar-icon-hover-color': theme.palette.toolbarIconHover,
'--toolbox-background-color': theme.palette.toolboxBackground
},
'.empty-list': {
listStyleType: 'none',
margin: 0,
@@ -62,7 +39,7 @@ export const commonStyles = (theme: Theme) => {
'.overflow-menu-item': {
alignItems: 'center',
color: theme.palette.overflowMenuItemText,
color: theme.palette.text01,
cursor: 'pointer',
display: 'flex',
fontSize: '0.875rem',
@@ -82,20 +59,20 @@ export const commonStyles = (theme: Theme) => {
'&.disabled': {
cursor: 'initial',
color: theme.palette.overflowMenuItemDisabled,
color: theme.palette.text03,
'&:hover': {
background: 'none'
},
'& svg': {
fill: theme.palette.overflowMenuItemDisabled
fill: theme.palette.text03
}
},
'@media (hover: hover) and (pointer: fine)': {
'&:hover': {
background: theme.palette.overflowMenuItemHover
background: theme.palette.action02Hover
},
'&.unclickable:hover': {
background: 'inherit'
@@ -123,14 +100,14 @@ export const commonStyles = (theme: Theme) => {
},
'& svg': {
fill: theme.palette.overflowMenuItemIcon,
fill: theme.palette.text01,
height: 20,
width: 20
}
},
'.prejoin-dialog': {
backgroundColor: theme.palette.prejoinDialogBackground,
backgroundColor: theme.palette.uiBackground,
boxShadow: '0px 2px 20px rgba(0, 0, 0, 0.5)',
borderRadius: theme.shape.borderRadius,
color: '#fff',
@@ -196,7 +173,7 @@ export const commonStyles = (theme: Theme) => {
},
'.prejoin-dialog-delimiter': {
background: theme.palette.prejoinDialogDelimiter,
background: theme.palette.ui03,
border: '0',
height: '1px',
margin: '0',
@@ -217,8 +194,8 @@ export const commonStyles = (theme: Theme) => {
},
'.prejoin-dialog-delimiter-txt': {
background: theme.palette.prejoinDialogBackground,
color: theme.palette.prejoinDialogDelimiterText,
background: theme.palette.uiBackground,
color: theme.palette.text01,
fontSize: '0.75rem',
textTransform: 'uppercase' as const,
padding: `0 ${theme.spacing(2)}`
@@ -242,11 +219,11 @@ export const commonStyles = (theme: Theme) => {
'@media (hover: hover) and (pointer: fine)': {
'&:hover': {
backgroundColor: theme.palette.toolboxIconHover
backgroundColor: theme.palette.ui04
},
'&:active': {
backgroundColor: theme.palette.toolboxIconActive
backgroundColor: theme.palette.ui03
}
},
[theme.breakpoints.down(320)]: {
@@ -255,7 +232,7 @@ export const commonStyles = (theme: Theme) => {
},
'&.toggled': {
backgroundColor: theme.palette.toolboxIconToggled
backgroundColor: theme.palette.ui03
},
'&.disabled': {
@@ -263,13 +240,13 @@ export const commonStyles = (theme: Theme) => {
backgroundColor: `${theme.palette.disabled01} !important`,
'& svg': {
fill: `${theme.palette.icon03} !important`
fill: `${theme.palette.text03} !important`
}
}
},
'.toolbox-button': {
color: theme.palette.toolbarIcon,
color: theme.palette.text01,
cursor: 'pointer',
display: 'inline-block',
lineHeight: '3rem',
@@ -277,7 +254,7 @@ export const commonStyles = (theme: Theme) => {
},
'.toolbox-content-items': {
background: theme.palette.toolboxBackground,
background: theme.palette.ui01,
borderRadius: 6,
margin: '0 auto',
padding: 6,

View File

@@ -1,11 +0,0 @@
import '@mui/material/styles';
import { IPalette, ITypography } from './types';
declare module '@mui/material/styles' {
interface Palette extends IPalette {}
interface PaletteOptions extends Partial<IPalette> {}
interface Typography extends ITypography {}
interface TypographyOptions extends Partial<ITypography> {}
}

View File

@@ -5,7 +5,6 @@ interface ITypographyType {
lineHeight: string;
}
/* eslint-disable typescript-sort-keys/interface */
export interface IPalette {
action01: string;
action01Active: string;
@@ -26,7 +25,6 @@ export interface IPalette {
icon02: string;
icon03: string;
icon04: string;
iconSvgFill: string;
iconError: string;
link01: string;
link01Active: string;
@@ -60,328 +58,6 @@ 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;
prejoinPreviewBackground: 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;
reactionsMenuButtonToggled: string;
reactionsMenuBoxShadow1: string;
reactionsMenuBoxShadow2: 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 {

View File

@@ -11,49 +11,13 @@ import * as tokens from './tokens.json';
*/
export function createColorTokens(colorMap: Object): any {
const allTokens = merge({}, tokens, jitsiTokens);
const result: any = {};
// 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.entries(colorMap)
.reduce((result, [ token, value ]: [any, string]) => {
const color = allTokens[value as keyof typeof allTokens] || value;
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;
return Object.assign(result, { [token]: color });
}, {});
}
/**

View File

@@ -24,13 +24,3 @@ const JITSI_MEET_APPS = [
export function isEmbedded(): boolean {
return !JITSI_MEET_APPS.includes(getBundleId());
}
/**
* React Native has no concept of same-domain embedding. SDK consumers are
* always treated as cross-domain embeddings.
*
* @returns {boolean} Always false in React Native.
*/
export function isEmbeddedFromSameDomain(): boolean {
return false;
}

View File

@@ -10,17 +10,3 @@ export function isEmbedded(): boolean {
return true;
}
}
/**
* Checks whether we are loaded in iframe with same parent domain.
*
* @returns {boolean} Whether the current page is loaded in an iframe with same parent domain.
*/
export function isEmbeddedFromSameDomain(): boolean {
try {
return window.self.location.host === window.parent.location.host;
} catch (e) {
return false;
}
}

View File

@@ -8,7 +8,7 @@ export default {
button: {
marginBottom: BaseTheme.spacing[4],
marginHorizontal: BaseTheme.spacing[3]
marginHorizontal: BaseTheme.spacing[2]
},
collapsibleList: {

View File

@@ -44,16 +44,6 @@ 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);
@@ -67,6 +57,7 @@ export const getRoomsInfo = (stateful: IStateful) => {
const conferenceParticipants = conference?.getParticipants()
.filter((participant: IJitsiParticipant) => !participant.isHidden());
const localParticipant = getLocalParticipant(stateful);
let localParticipantInfo;
if (localParticipant) {
@@ -74,8 +65,7 @@ export const getRoomsInfo = (stateful: IStateful) => {
role: localParticipant.role,
displayName: localParticipant.name,
avatarUrl: localParticipant.loadableAvatarUrl,
id: localParticipant.id,
userContext: localUserContext
id: localParticipant.id
};
}
@@ -96,8 +86,7 @@ export const getRoomsInfo = (stateful: IStateful) => {
role: participantItem.getRole(),
displayName: participantItem.getDisplayName(),
avatarUrl: storeParticipant?.loadableAvatarUrl,
id: participantItem.getId(),
userContext: storeParticipant?.userContext
id: participantItem.getId()
} as IRoomInfoParticipant;
}) ]
: [ localParticipantInfo ]
@@ -121,18 +110,13 @@ 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,
userContext
: participantLongId
} as IRoomInfoParticipant;
}) : []
} as IRoomInfo;

View File

@@ -44,59 +44,19 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
switch (type) {
case UPDATE_BREAKOUT_ROOMS: {
// Enrich participants with userContext from Redux store
// edit name if it was overwritten
if (!action.updatedNames) {
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
};
const { overwrittenNameList } = getState()['features/base/participants'];
// Get existing userContext cache
const existingCache = state['features/breakout-rooms'].userContextCache || {};
const newCache = { ...existingCache };
if (Object.keys(overwrittenNameList).length > 0) {
const newRooms: IRooms = {};
const newRooms: IRooms = {};
Object.entries(action.rooms as IRooms).forEach(([ key, r ]) => {
let participants = r?.participants || {};
let jid;
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;
}
enhancedParticipants[participantJid] = {
...participantData,
userContext
};
}
participants = enhancedParticipants;
// Apply overwritten display names
if (Object.keys(overwrittenNameList).length > 0) {
for (const id of Object.keys(overwrittenNameList)) {
const jid = Object.keys(participants).find(p => p.slice(p.indexOf('/') + 1) === id);
jid = Object.keys(participants).find(p => p.slice(p.indexOf('/') + 1) === id);
if (jid) {
participants = {
@@ -108,16 +68,15 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
};
}
}
}
newRooms[key] = {
...r,
participants
};
});
newRooms[key] = {
...r,
participants
};
});
action.rooms = newRooms;
action.userContextCache = newCache;
action.rooms = newRooms;
}
}
// edit the chat history to match names for participants in breakout rooms

View File

@@ -10,19 +10,12 @@ import { IRooms } from './types';
const DEFAULT_STATE = {
rooms: {},
roomCounter: 0,
userContextCache: {}
roomCounter: 0
};
export interface IBreakoutRoomsState {
roomCounter: number;
rooms: IRooms;
userContextCache: {
[participantId: string]: {
id?: string;
name?: string;
};
};
}
/**
@@ -36,13 +29,12 @@ ReducerRegistry.register<IBreakoutRoomsState>(FEATURE_KEY, (state = DEFAULT_STAT
roomCounter: action.roomCounter
};
case UPDATE_BREAKOUT_ROOMS: {
const { roomCounter, rooms, userContextCache } = action;
const { roomCounter, rooms } = action;
return {
...state,
roomCounter,
rooms,
userContextCache: userContextCache || state.userContextCache
rooms
};
}
case _RESET_BREAKOUT_ROOMS: {

Some files were not shown because too many files have changed in this diff Show More