mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-02-24 23:00:20 +00:00
Compare commits
50 Commits
debug-test
...
9058
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a461e867d5 | ||
|
|
96d02e8484 | ||
|
|
c48834a116 | ||
|
|
244b5f9dd3 | ||
|
|
18504d4ad8 | ||
|
|
fcf8a4bbe0 | ||
|
|
f66fe9fbbd | ||
|
|
caee687cc3 | ||
|
|
8530c1f2fc | ||
|
|
3b2e3526c4 | ||
|
|
aee5cb9200 | ||
|
|
df0e55eda3 | ||
|
|
f7d64341f8 | ||
|
|
156dd24b14 | ||
|
|
91481aa245 | ||
|
|
e94153df16 | ||
|
|
3c85aa2350 | ||
|
|
c9a220e1c3 | ||
|
|
5f11e2e9f4 | ||
|
|
45ee515b6b | ||
|
|
56f7792604 | ||
|
|
230559c493 | ||
|
|
900bece988 | ||
|
|
a06a338de5 | ||
|
|
eb1e4f01e7 | ||
|
|
641a401b4b | ||
|
|
6e3c256905 | ||
|
|
36a755776e | ||
|
|
7bd38be8a4 | ||
|
|
c88bfa1b9a | ||
|
|
6a443b0f8f | ||
|
|
b4cc466608 | ||
|
|
bde8dca825 | ||
|
|
2ab86cffb8 | ||
|
|
9ea9f4c899 | ||
|
|
28c72bfa7f | ||
|
|
24c78cf8ff | ||
|
|
147e64106d | ||
|
|
3d53f2e4c5 | ||
|
|
e60bfc573a | ||
|
|
796a7efa7f | ||
|
|
fb075c376d | ||
|
|
c457ed0d3c | ||
|
|
1efc5e40e1 | ||
|
|
dc84826d9c | ||
|
|
b4a64ebc44 | ||
|
|
dbb3ccc274 | ||
|
|
9a6ed65cb1 | ||
|
|
21ea67b29c | ||
|
|
ab4be2366f |
@@ -96,23 +96,53 @@ 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();
|
||||
|
||||
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;
|
||||
// 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;
|
||||
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
|
||||
@@ -136,10 +166,11 @@ 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);
|
||||
|
||||
|
||||
@@ -1600,6 +1600,8 @@ 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"
|
||||
|
||||
@@ -45,7 +45,7 @@ body {
|
||||
|
||||
.jitsi-icon {
|
||||
&-default svg {
|
||||
fill: white;
|
||||
fill: var(--icon-svg-fill, white);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.always-on-top-toolbox {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
background-color: var(--toolbox-background-color, $newToolbarBackgroundColor);
|
||||
border-radius: 3px;
|
||||
display: flex;
|
||||
z-index: $toolbarZ;
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
.reactions-menu {
|
||||
width: 330px;
|
||||
background: #242528;
|
||||
box-shadow: 0px 3px 16px rgba(0, 0, 0, 0.6), 0px 0px 4px 1px rgba(0, 0, 0, 0.25);
|
||||
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));
|
||||
border-radius: 6px;
|
||||
padding: 16px;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
top: 3px;
|
||||
|
||||
& .toolbox-icon.toggled {
|
||||
background-color: #000000;
|
||||
background-color: var(--reactions-menu-button-toggled, #000000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Round badge.
|
||||
*/
|
||||
.badge-round {
|
||||
background-color: #165ECC;
|
||||
background-color: var(--toolbar-badge-background, #165ECC);
|
||||
border-radius: 50%;
|
||||
box-sizing: border-box;
|
||||
color: #FFFFFF;
|
||||
@@ -93,7 +93,7 @@
|
||||
|
||||
.toolbox-content-wrapper::after {
|
||||
content: '';
|
||||
background: $newToolbarBackgroundColor;
|
||||
background: var(--toolbox-background-color, $newToolbarBackgroundColor);
|
||||
padding-bottom: env(safe-area-inset-bottom, 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
}
|
||||
|
||||
#notification-participant-list {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
background-color: var(--toolbox-background-color, $newToolbarBackgroundColor);
|
||||
border: 1px solid rgba(255, 255, 255, .4);
|
||||
border-radius: 8px;
|
||||
left: 0;
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
}
|
||||
|
||||
#preview {
|
||||
background: #040404;
|
||||
background: var(--prejoin-preview-background, #040404);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
2
debian/control
vendored
2
debian/control
vendored
@@ -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, lua-sec, lua-basexx, lua-luaossl, lua-cjson, lua-inspect
|
||||
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
|
||||
Replaces: jitsi-meet-tokens
|
||||
Description: Prosody configuration for Jitsi Meet
|
||||
Jitsi Meet is a WebRTC JavaScript application that uses Jitsi
|
||||
|
||||
12
debian/jitsi-meet-prosody.postinst
vendored
12
debian/jitsi-meet-prosody.postinst
vendored
@@ -284,13 +284,17 @@ case "$1" in
|
||||
# and drop the wait and the prosody restart
|
||||
sleep 1
|
||||
invoke-rc.d prosody restart || true
|
||||
|
||||
# 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
|
||||
fi
|
||||
;;
|
||||
triggered)
|
||||
for trigger in $2; do
|
||||
if [ "$trigger" = "update-ca-certificates-java" ]; then
|
||||
echo "Java certificates updated, restarting Jitsi components..."
|
||||
systemctl restart jitsi-videobridge2.service >/dev/null || true
|
||||
systemctl restart jicofo.service >/dev/null || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
exit 0
|
||||
;;
|
||||
|
||||
abort-upgrade|abort-remove|abort-deconfigure)
|
||||
|
||||
1
debian/jitsi-meet-prosody.triggers
vendored
Normal file
1
debian/jitsi-meet-prosody.triggers
vendored
Normal file
@@ -0,0 +1 @@
|
||||
interest-noawait update-ca-certificates-java
|
||||
@@ -227,6 +227,9 @@
|
||||
"video_ssrc": "Video-SSRC:",
|
||||
"yes": "Ja"
|
||||
},
|
||||
"customPanel": {
|
||||
"close": "Schließen"
|
||||
},
|
||||
"dateUtils": {
|
||||
"earlier": "Früher",
|
||||
"today": "Heute",
|
||||
@@ -589,6 +592,7 @@
|
||||
"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"
|
||||
@@ -1315,6 +1319,7 @@
|
||||
"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",
|
||||
@@ -1420,9 +1425,11 @@
|
||||
"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
1209
lang/main-fi.json
1209
lang/main-fi.json
File diff suppressed because it is too large
Load Diff
@@ -114,6 +114,9 @@
|
||||
"error": "Erreur : votre message n'a pas été envoyé. Raison : {{error}}",
|
||||
"everyone": "Tout le monde",
|
||||
"fieldPlaceHolder": "Tapez votre message ici",
|
||||
"fileAccessibleTitle": "{{user}} a téléversé un fichier",
|
||||
"fileAccessibleTitleMe": "j’ai téléversé un fichier",
|
||||
"fileDeleted": "Un fichier a été supprimé",
|
||||
"guestsChatIndicator": "(invité)",
|
||||
"lobbyChatMessageTo": "Message de salle d'attente à {{recipient}}",
|
||||
"message": "Message",
|
||||
@@ -123,8 +126,16 @@
|
||||
"messagebox": "Envoyer un message",
|
||||
"newMessages": "Nouveaux messages",
|
||||
"nickname": {
|
||||
"featureChat": "chat",
|
||||
"featureClosedCaptions": "sous-titres",
|
||||
"featureFileSharing": "partage de fichiers",
|
||||
"featurePolls": "sondages",
|
||||
"popover": "Choisissez un pseudonyme",
|
||||
"title": "Entrez un pseudonyme pour utiliser le chat",
|
||||
"titleWith1Features": "Entrez un pseudonyme pour utiliser {{feature1}}",
|
||||
"titleWith2Features": "Entrez un pseudonyme pour utiliser {{feature1}} et {{feature2}}",
|
||||
"titleWith3Features": "Entrez un pseudonyme pour utiliser {{feature1}}, {{feature2}} et {{feature3}}",
|
||||
"titleWith4Features": "Entrez un pseudonyme pour utiliser {{feature1}}, {{feature2}}, {{feature3}} et {{feature4}}",
|
||||
"titleWithCC": "Entrez un pseudonyme pour utiliser le chat et les sous-titres",
|
||||
"titleWithPolls": "Entrez un pseudonyme pour utiliser le chat et les sondages",
|
||||
"titleWithPollsAndCC": "Entrez un pseudonyme pour utiliser le chat, les sondages et les sous-titres",
|
||||
@@ -216,6 +227,9 @@
|
||||
"video_ssrc": "Video SSRC :",
|
||||
"yes": "oui"
|
||||
},
|
||||
"customPanel": {
|
||||
"close": "Fermer"
|
||||
},
|
||||
"dateUtils": {
|
||||
"earlier": "Plus tôt",
|
||||
"today": "Aujourd'hui",
|
||||
@@ -522,6 +536,7 @@
|
||||
"tokenAuthFailedWithReasons": "Désolé, vous n’êtes pas autorisé à rejoindre l’appel. La raison possible : {{reason}}.",
|
||||
"tokenAuthUnsupported": "Token URL n'est pas supporté.",
|
||||
"transcribing": "Transcription",
|
||||
"unauthenticatedAccessDisabled": "Cet appel nécessite une authentification. Veuillez vous connecter pour continuer.",
|
||||
"unlockRoom": "Supprimer le $t(lockRoomPassword) de la réunion",
|
||||
"user": "Utilisateur",
|
||||
"userIdentifier": "Identifiant utilisateur",
|
||||
@@ -569,10 +584,12 @@
|
||||
"downloadStarted": "Téléchargement de fichier démarré",
|
||||
"dragAndDrop": "Glissez et déposez des fichiers ici ou n'importe où sur l'écran",
|
||||
"fileAlreadyUploaded": "Le fichier a déjà été téléchargé vers cette réunion.",
|
||||
"fileRemovedByOther": "Votre fichier « {{ fileName }} » a été supprimé",
|
||||
"fileTooLargeDescription": "Veuillez vous assurer que le fichier ne dépasse pas {{ maxFileSize }}.",
|
||||
"fileTooLargeTitle": "Le fichier sélectionné est trop volumineux",
|
||||
"fileUploadProgress": "Progression du téléchargement de fichier",
|
||||
"fileUploadedSuccessfully": "Fichier téléchargé avec succès",
|
||||
"newFileNotification": "{{ participantName }} a partagé « {{ fileName }} »",
|
||||
"removeFile": "Supprimer",
|
||||
"removeFileSuccess": "Fichier supprimé avec succès",
|
||||
"uploadFailedDescription": "Veuillez réessayer.",
|
||||
@@ -964,6 +981,9 @@
|
||||
"by": "Par {{ name }}",
|
||||
"closeButton": "Fermer le sondage",
|
||||
"create": {
|
||||
"accessibilityLabel": {
|
||||
"send": "Envoyer le sondage"
|
||||
},
|
||||
"addOption": "Ajouter une option",
|
||||
"answerPlaceholder": "Option {{index}}",
|
||||
"cancel": "Annuler",
|
||||
@@ -972,8 +992,7 @@
|
||||
"pollQuestion": "Question du sondage",
|
||||
"questionPlaceholder": "Poser une question",
|
||||
"removeOption": "Supprimer l'option",
|
||||
"save": "Enregistrer",
|
||||
"send": "Envoyer"
|
||||
"save": "Enregistrer"
|
||||
},
|
||||
"errors": {
|
||||
"notUniqueOption": "Les options doivent être uniques"
|
||||
@@ -1299,6 +1318,7 @@
|
||||
"chat": "Afficher / Masquer la discussion instantanée",
|
||||
"clap": "Applaudir",
|
||||
"closeChat": "Fermer la discussion instantanée",
|
||||
"closeCustomPanel": "Fermer",
|
||||
"closeMoreActions": "Fermer le menu plus d'actions",
|
||||
"closeParticipantsPane": "Fermer le panneau des participants",
|
||||
"closedCaptions": "Sous-titres",
|
||||
@@ -1404,9 +1424,11 @@
|
||||
"chat": "Ouvrir / Fermer le chat",
|
||||
"clap": "Applaudir",
|
||||
"closeChat": "Fermer le chat",
|
||||
"closeCustomPanel": "Fermer",
|
||||
"closeParticipantsPane": "Fermer le panneau des participants",
|
||||
"closeReactionsMenu": "Fermer le menu réactions",
|
||||
"closedCaptions": "Sous-titres",
|
||||
"copilot": "Copilot",
|
||||
"disableNoiseSuppression": "Arrêter la suppression du bruit",
|
||||
"disableReactionSounds": "Vous pouvez interdire les réactions sonores à cette réunion",
|
||||
"documentClose": "Fermer le document partagé",
|
||||
@@ -1421,6 +1443,7 @@
|
||||
"exitFullScreen": "Quitter le mode plein écran",
|
||||
"exitTileView": "Quitter le mode mosaïque",
|
||||
"feedback": "Laisser des commentaires",
|
||||
"fileSharing": "Partage de fichiers",
|
||||
"giphy": "Activer/désactiver le menu GIPHY",
|
||||
"hangup": "Quitter",
|
||||
"help": "Aide",
|
||||
@@ -1456,6 +1479,7 @@
|
||||
"openReactionsMenu": "Ouvrir le menu Réactions",
|
||||
"participants": "Participants",
|
||||
"pip": "Entrer en mode Picture-in-Picture",
|
||||
"polls": "Sondages",
|
||||
"privateMessage": "Envoyer un message privé",
|
||||
"profile": "Éditer votre profil",
|
||||
"raiseHand": "Lever / Baisser la main",
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
{
|
||||
"addPeople": {
|
||||
"accessibilityLabel": {
|
||||
"meetingLink": "मीटिंग लिंक: {{url}}"
|
||||
},
|
||||
"add": "आमंत्रित करें",
|
||||
"addContacts": "संपर्क सूची से आमंत्रित करे",
|
||||
"contacts": "संपर्क",
|
||||
"copyInvite": "मीटिंग के आमंत्रण कि प्रतिलिपि बनाये",
|
||||
"copyLink": "मीटिंग कि लिंक कि प्रतिलिपि बनाये",
|
||||
"copyStream": "सीधे प्रसारण कि लिंक कि प्रीतिलिपि बनाये",
|
||||
"copyStream": "सीधे प्रसारण कि लिंक कि प्रतिलिपि बनाये",
|
||||
"countryNotSupported": "अभी हम इस गतव्य के लिये सक्षम नही है ।",
|
||||
"countryReminder": "यू.एस. के बाहर से काल कर रहे है तो कृपया सुनिश्चित करे कि अपने देश के कोड़ से प्रारंभ कर रहे है !",
|
||||
"defaultEmail": "अपना ई-मेल पता लिखें",
|
||||
@@ -35,6 +39,7 @@
|
||||
},
|
||||
"audioDevices": {
|
||||
"bluetooth": "ब्लूटूथ",
|
||||
"car": "कार ऑडियो",
|
||||
"headphones": "हेडफ़ोन",
|
||||
"none": "कोई ऑडियो डिवाइस उपलब्ध नहीं",
|
||||
"phone": "फ़ोन",
|
||||
@@ -43,9 +48,47 @@
|
||||
"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": "क्या आप इस इवेंट में एक Jitsi लिंक जोड़ना चाहते हैं?",
|
||||
"confirmAddLink": "क्या आप इस इवेंट में एक जित्सी लिंक जोड़ना चाहते हैं?",
|
||||
"error": {
|
||||
"appConfiguration": "कैलेंडर एकीकरण ठीक से कॉन्फ़िगर नहीं किया गया है।",
|
||||
"generic": "एक त्रुटि हुई है। कृपया अपनी कैलेंडर सेटिंग जांचें या कैलेंडर को रीफ़्रेश करने का प्रयास करें।",
|
||||
@@ -61,28 +104,72 @@
|
||||
"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": "चैट का उपयोग करने के लिए एक उपनाम दर्ज करें",
|
||||
"titleWithPolls": "चैट का उपयोग करने के लिए एक उपनाम दर्ज करें"
|
||||
"titleWith1Features": "{{feature1}} का उपयोग करने के लिए एक उपनाम दर्ज करें",
|
||||
"titleWith2Features": "{{feature1}} और {{feature2}} का उपयोग करने के लिए एक उपनाम दर्ज करें",
|
||||
"titleWith3Features": "{{feature1}}, {{feature2}} और {{feature3}} का उपयोग करने के लिए एक उपनाम दर्ज करें",
|
||||
"titleWith4Features": "{{feature1}}, {{feature2}}, {{feature3}} और {{feature4}} का उपयोग करने के लिए एक उपनाम दर्ज करें",
|
||||
"titleWithCC": "चैट और बंद कैप्शन का उपयोग करने के लिए एक उपनाम दर्ज करें",
|
||||
"titleWithPolls": "चैट और पोल का उपयोग करने के लिए एक उपनाम दर्ज करें",
|
||||
"titleWithPollsAndCC": "चैट, पोल और बंद कैप्शन का उपयोग करने के लिए एक उपनाम दर्ज करें",
|
||||
"titleWithPollsAndCCAndFileSharing": "चैट, पोल, बंद कैप्शन और फ़ाइलों का उपयोग करने के लिए एक उपनाम दर्ज करें"
|
||||
},
|
||||
"noMessagesMessage": "अभी तक मीटिंग में कोई संदेश नहीं आया है। वार्तालाप प्रारंभ करें!",
|
||||
"privateNotice": "{{recipient}} के लिए निजी संदेश",
|
||||
"sendButton": "भेजें",
|
||||
"smileysPanel": "इमोजी पैनल",
|
||||
"systemDisplayName": "सिस्टम",
|
||||
"tabs": {
|
||||
"chat": "चैट",
|
||||
"closedCaptions": "सीसी",
|
||||
"fileSharing": "फ़ाइलें",
|
||||
"polls": "पोल"
|
||||
},
|
||||
"title": "चैट",
|
||||
"titleWithPolls": "चैट",
|
||||
"titleWithCC": "सीसी",
|
||||
"titleWithFeatures": "चैट और",
|
||||
"titleWithFileSharing": "फ़ाइलें",
|
||||
"titleWithPolls": "पोल",
|
||||
"you": "आप"
|
||||
},
|
||||
"chromeExtensionBanner": {
|
||||
"buttonText": "क्रोम एक्सटेंशन इंस्टॉल करें",
|
||||
"dontShowAgain": "मुझे यह फिर से न दिखाएं",
|
||||
"installExtensionText": ",गूगल कैलेंडर और ऑफिस 365 एकीकरण के लिए एक्सटेंशन इंस्टॉल करें"
|
||||
"buttonTextEdge": "Edge एक्सटेंशन इंस्टॉल करें",
|
||||
"close": "बंद करें",
|
||||
"dontShowAgain": "मुझे यह फिर से न दिखाएं"
|
||||
},
|
||||
"closedCaptionsTab": {
|
||||
"emptyState": "मॉडरेटर द्वारा शुरू किए जाने पर बंद कैप्शन की सामग्री उपलब्ध होगी",
|
||||
"startClosedCaptionsButton": "बंद कैप्शन शुरू करें"
|
||||
},
|
||||
"connectingOverlay": {
|
||||
"joiningRoom": "आपको आपकी मीटिंग से कनेक्ट किया जा रहा है…"
|
||||
@@ -110,6 +197,7 @@
|
||||
"bridgeCount": "सर्वर गणना: ",
|
||||
"codecs": "कोडेक (ए/वी): ",
|
||||
"connectedTo": "से जुड़ा हुआ है :",
|
||||
"e2eeVerified": "E2EE सत्यापित:",
|
||||
"framerate": "फ्रेम दर:",
|
||||
"less": "कम दिखाएं",
|
||||
"localaddress": "स्थानीय पता:",
|
||||
@@ -136,7 +224,8 @@
|
||||
"status": "सम्पर्क:",
|
||||
"transport": "ट्रांसपोर्ट :",
|
||||
"transport_plural": "ट्रांसपोर्ट्स:",
|
||||
"video_ssrc": "वीडियो एस.आर.सी.सी.:"
|
||||
"video_ssrc": "वीडियो एस.आर.सी.सी.:",
|
||||
"yes": "हाँ"
|
||||
},
|
||||
"dateUtils": {
|
||||
"earlier": "पिछला कल",
|
||||
@@ -146,14 +235,25 @@
|
||||
"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}} में आपकी मीटिंग शुरू की जा रही हैं…",
|
||||
"tryAgainButton": "डेस्कटॉप में फिर से प्रयास करें"
|
||||
"titleNew": "आपकी मीटिंग शुरू की जा रही है…",
|
||||
"tryAgainButton": "डेस्कटॉप में फिर से प्रयास करें",
|
||||
"unsupportedBrowser": "ऐसा लगता है कि आप ऐसे ब्राउज़र का उपयोग कर रहे हैं जिसे हम सपोर्ट नहीं करते।"
|
||||
},
|
||||
"defaultLink": "उदाहरण {{url}}",
|
||||
"defaultNickname": "उदा. सतीष कुमार",
|
||||
@@ -164,6 +264,12 @@
|
||||
"microphonePermission": "माइक्रोफ़ोन अनुमति प्राप्त करने में त्रुटि"
|
||||
},
|
||||
"deviceSelection": {
|
||||
"hid": {
|
||||
"callControl": "कॉल नियंत्रण",
|
||||
"connectedDevices": "कनेक्टेड डिवाइस:",
|
||||
"deleteDevice": "डिवाइस हटाएँ",
|
||||
"pairDevice": "डिवाइस जोड़ें"
|
||||
},
|
||||
"noPermission": "अनुमति नहीं दी गई",
|
||||
"previewUnavailable": "पूर्वदर्शन अनुपलब्ध",
|
||||
"selectADevice": "डिवाइस का चयन करें",
|
||||
@@ -178,16 +284,23 @@
|
||||
"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": "एप्लिकेशन विंडो",
|
||||
@@ -212,7 +325,7 @@
|
||||
"connectErrorWithMsg": "उफ़! कुछ गड़बड़ हो गई और हम सम्मेलन से नहीं जुड़ सके: {{msg}}",
|
||||
"connecting": "संपर्क जोड़ा जा रहा है ",
|
||||
"contactSupport": "सहयोग के लिए संपर्क करें",
|
||||
"copied": "प्रतिलिपि बनाई गयी ",
|
||||
"copied": "प्रतिलिपि बनाई गयी",
|
||||
"copy": "प्रतिलिपि बनाये",
|
||||
"dismiss": "खारिज करें",
|
||||
"displayNameRequired": "नमस्ते! आपका नाम क्या है?",
|
||||
@@ -279,7 +392,7 @@
|
||||
"readMore": "अधिक",
|
||||
"recording": "रिकॉर्डिंग",
|
||||
"recordingDisabledBecauseOfActiveLiveStreamingTooltip": "संभव नहीं है जब एक लाइव स्ट्रीम सक्रिय है",
|
||||
"recordingDisabledTooltip": "रिकॉर्डिंग शुरू करना अक्षम करें.",
|
||||
"recordingDisabledTooltip": "रिकॉर्डिंग शुरू करना अक्षम करें|",
|
||||
"rejoinNow": "पुनः जुड़े",
|
||||
"remoteControlAllowedMessage": "{{user}} ने आपका रिमोट कंट्रोल अनुरोध स्वीकार कर लिया!",
|
||||
"remoteControlDeniedMessage": "{{user}} ने आपका रिमोट कंट्रोल अनुरोध अस्वीकार कर दिया!",
|
||||
@@ -295,7 +408,7 @@
|
||||
"reservationErrorMsg": "Error code: {{code}}, message: {{msg}}",
|
||||
"retry": "पुनः प्रयास करें",
|
||||
"screenSharingAudio": "ऑडियो साझा करें",
|
||||
"screenSharingFailed": "उफ़! कुछ गड़बड़ हो गई, हम स्क्रीन शेयरिंग शुरू करने में सक्षम नहीं थे!",
|
||||
"screenSharingFailed": "स्क्रीन शेयरिंग शुरू नहीं हो पाई।",
|
||||
"screenSharingFailedTitle": "स्क्रीन साझा करना विफल हुआ!",
|
||||
"screenSharingPermissionDeniedError": "उफ़! आपकी स्क्रीन शेयरिंग अनुमतियों में कुछ गड़बड़ हो गई है। कृपया पुनः लोड करें और पुनः प्रयास करें।",
|
||||
"sendPrivateMessage": "आपने हाल ही में एक निजी संदेश प्राप्त किया है। क्या आप उसका निजी रूप से जवाब देने का इरादा रखते हैं? या आप अपना संदेश समूह को भेजना चाहते हैं?",
|
||||
@@ -347,6 +460,34 @@
|
||||
"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": "सहायता केंद्र"
|
||||
},
|
||||
@@ -362,6 +503,7 @@
|
||||
"addPassword": "$t(lockRoomPassword)जोड़ें",
|
||||
"cancelPassword": "$t(lockRoomPassword)रद्द करें",
|
||||
"conferenceURL": "लिंक:",
|
||||
"copyNumber": "नंबर कॉपी करें",
|
||||
"country": "देश",
|
||||
"dialANumber": "अपनी मीटिंग में शामिल होने के लिए, इनमें से किसी एक नंबर को डायल करें और फिर पिन डालें।",
|
||||
"dialInConferenceID": "पिन:",
|
||||
@@ -384,11 +526,16 @@
|
||||
"noRoom": "डायल-इन करने के लिए कोई कक्ष निर्दिष्ट नहीं किया गया।",
|
||||
"numbers": "डायल-इन नंबर",
|
||||
"password": "$t(lockRoomPasswordUppercase):",
|
||||
"reachedLimit": "आप अपने प्लान की सीमा तक पहुँच चुके हैं।",
|
||||
"sip": "SIP पता",
|
||||
"sipAudioOnly": "केवल ऑडियो SIP पता",
|
||||
"title": "साझा करें",
|
||||
"tooltip": "इस मीटिंग के लिए लिंक और डायल-इन जानकारी साझा करें"
|
||||
"tooltip": "इस मीटिंग के लिए लिंक और डायल-इन जानकारी साझा करें",
|
||||
"upgradeOptions": "कृपया अपग्रेड विकल्पों की जाँच करें",
|
||||
"whiteboardError": "व्हाइटबोर्ड लोड करने में त्रुटि। कृपया बाद में पुनः प्रयास करें।"
|
||||
},
|
||||
"inlineDialogFailure": {
|
||||
"msg": "We stumbled a bit.",
|
||||
"msg": "कुछ समस्या हुई।",
|
||||
"retry": "पुनः प्रयास करें",
|
||||
"support": "सहायता",
|
||||
"supportMsg": "ऐसा बार बार हो रहा हो, तो सम्पर्क करे "
|
||||
@@ -417,6 +564,10 @@
|
||||
"toggleShortcuts": "कीबोर्ड शॉर्टकट दिखाएं या छिपाएं",
|
||||
"videoMute": "अपना कैमरा प्रारंभ या बंद करें"
|
||||
},
|
||||
"largeVideo": {
|
||||
"screenIsShared": "आप अपनी स्क्रीन साझा कर रहे हैं",
|
||||
"showMeWhatImSharing": "मुझे दिखाएँ कि मैं क्या साझा कर रहा हूँ"
|
||||
},
|
||||
"liveStreaming": {
|
||||
"busy": "हम स्ट्रीमिंग संसाधनों को मुक्त करने पर काम कर रहे हैं। कृपया कुछ मिनटों में पुनः प्रयास करें।",
|
||||
"busyTitle": "सभी स्ट्रीमर वर्तमान में व्यस्त हैं",
|
||||
@@ -461,7 +612,7 @@
|
||||
"emailField": "अपना ईमेल पता दर्ज करें",
|
||||
"enableDialogPasswordField": "पासवर्ड सेट करें (वैकल्पिक)",
|
||||
"enableDialogSubmit": "सक्षम करें",
|
||||
"enableDialogText": "Lobby mode lets you protect your meeting by only allowing people to enter after a formal approval by a moderator.",
|
||||
"enableDialogText": "लॉबी मोड से आप अपनी मीटिंग को सुरक्षित रख सकते हैं। केवल मॉडरेटर की अनुमति मिलने के बाद ही लोग इसमें शामिल हो पाएंगे।",
|
||||
"enterPasswordButton": "मीटिंग पासवर्ड दर्ज करें",
|
||||
"enterPasswordTitle": "मीटिंग में शामिल होने के लिए पासवर्ड दर्ज करें",
|
||||
"invalidPassword": "अमान्य पासवर्ड",
|
||||
@@ -475,8 +626,8 @@
|
||||
"knockTitle": "कोई व्यक्ति बैठक में शामिल होना चाहता है",
|
||||
"knockingParticipantList": "प्रतिभागी सूची दस्तक",
|
||||
"nameField": "अपना नाम दर्ज करें",
|
||||
"notificationLobbyAccessDenied": "{{targetParticipantName}} has been rejected to join by {{originParticipantName}}",
|
||||
"notificationLobbyAccessGranted": "{{targetParticipantName}} has been allowed to join by {{originParticipantName}}",
|
||||
"notificationLobbyAccessDenied": "{{targetParticipantName}} को {{originParticipantName}} ने मीटिंग में शामिल होने की अनुमति नहीं दी।",
|
||||
"notificationLobbyAccessGranted": "{{targetParticipantName}} को {{originParticipantName}} ने मीटिंग में शामिल होने की अनुमति दी।",
|
||||
"notificationLobbyDisabled": "लॉबी को {{originParticipantName}}द्वारा अक्षम कर दिया गया",
|
||||
"notificationLobbyEnabled": "लॉबी को {{originParticipantName}}द्वारा सक्षम किया गया",
|
||||
"notificationTitle": "लॉबी",
|
||||
@@ -509,9 +660,12 @@
|
||||
"no": "नहीं",
|
||||
"participant": "प्रतिभागी",
|
||||
"participantStats": "प्रतिभागी आँकड़े",
|
||||
"selectTabTitle": "🎥 रिकॉर्डिंग के लिए कृपया इस टैब को चुनें",
|
||||
"sessionToken": "सत्र टोकन",
|
||||
"start": "रिकॉर्डिंग प्रारंभ करें",
|
||||
"stop": "रिकॉर्डिंग बंद करें",
|
||||
"stopping": "रिकॉर्डिंग बंद की जा रही है",
|
||||
"wait": "कृपया प्रतीक्षा करें, आपकी रिकॉर्डिंग सेव की जा रही है",
|
||||
"yes": "हाँ"
|
||||
},
|
||||
"lockRoomPassword": "पासवर्ड",
|
||||
@@ -522,11 +676,30 @@
|
||||
},
|
||||
"me": "मैं",
|
||||
"notify": {
|
||||
"OldElectronAPPTitle": "Security vulnerability!",
|
||||
"OldElectronAPPTitle": "सुरक्षा में खामी",
|
||||
"allowAll": "सभी की अनुमति दें",
|
||||
"allowAudio": "ऑडियो की अनुमति दें",
|
||||
"allowDesktop": "स्क्रीन शेयरिंग की अनुमति दें",
|
||||
"allowVideo": "वीडियो की अनुमति दें",
|
||||
"allowedUnmute": "आप माइक्रोफोन अनम्यूट कर सकते हैं, कैमरा चालू कर सकते हैं या स्क्रीन साझा कर सकते हैं।",
|
||||
"audioUnmuteBlockedDescription": "सिस्टम सीमाओं के कारण माइक्रोफोन अनम्यूट अस्थायी रूप से ब्लॉक कर दिया गया है।",
|
||||
"audioUnmuteBlockedTitle": "माइक्रोफोन अनम्यूट ब्लॉक!",
|
||||
"chatMessages": "चैट संदेश",
|
||||
"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}}!",
|
||||
@@ -534,7 +707,29 @@
|
||||
"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": "जब आप बोलने के लिए तैयार हों, तो आप हमेशा अनम्यूट कर सकते हैं। बैठक में शोर कम रखने के लिए बोलने के बाद म्यूट कर दें।",
|
||||
@@ -543,24 +738,85 @@
|
||||
"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}}द्वारा अक्षम कर दिया गया है!"
|
||||
"videoMutedRemotelyTitle": "आपका कैमरा {{participantDisplayName}}द्वारा अक्षम कर दिया गया है!",
|
||||
"videoUnmuteBlockedDescription": "सिस्टम सीमाओं के कारण कैमरा अनम्यूट और डेस्कटॉप शेयरिंग अस्थायी रूप से ब्लॉक कर दी गई है।",
|
||||
"videoUnmuteBlockedTitle": "कैमरा अनम्यूट और डेस्कटॉप शेयरिंग ब्लॉक!",
|
||||
"viewParticipants": "प्रतिभागियों को देखें",
|
||||
"viewVisitors": "दर्शकों को देखें",
|
||||
"waitingParticipants": "{{waitingParticipants}} लोग",
|
||||
"waitingVisitors": "कतार में प्रतीक्षारत दर्शक: {{waitingVisitors}}",
|
||||
"waitingVisitorsTitle": "मीटिंग अभी लाइव नहीं है!",
|
||||
"whiteboardLimitDescription": "कृपया अपनी प्रगति सहेजें, क्योंकि उपयोगकर्ता सीमा जल्द ही पहुँच जाएगी और व्हाइटबोर्ड बंद हो जाएगा।",
|
||||
"whiteboardLimitTitle": "व्हाइटबोर्ड उपयोग"
|
||||
},
|
||||
"participantsPane": {
|
||||
"actions": {
|
||||
"reject": "अस्वीकार"
|
||||
"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}})"
|
||||
}
|
||||
},
|
||||
"passwordDigitsOnly": "अधिकतम {{number}} अंक",
|
||||
@@ -617,6 +873,7 @@
|
||||
"joinAudioByPhone": "फोन ऑडियो के साथ जुड़ें",
|
||||
"joinMeeting": "मीटिंग में शामिल हों",
|
||||
"joinWithoutAudio": "ऑडियो के बिना जुड़ें",
|
||||
"keyboardShortcuts": "कीबोर्ड शॉर्टकट सक्षम करें",
|
||||
"linkCopied": "लिंक क्लिपबोर्ड पर कॉपी किया गया",
|
||||
"lookGood": "ऐसा लगता है कि आपका माइक्रोफ़ोन ठीक से काम कर रहा है",
|
||||
"or": "या",
|
||||
@@ -642,9 +899,10 @@
|
||||
"ringing": "Ringing…"
|
||||
},
|
||||
"profile": {
|
||||
"avatar": "अवतार",
|
||||
"setDisplayNameLabel": "अपना नाम सेट करें",
|
||||
"setEmailInput": "ई-मेल दर्ज करें",
|
||||
"setEmailLabel": "Set अपना ग्रेवार्ट ईमेल सेट करें",
|
||||
"setEmailLabel": "Gravatar ईमेल",
|
||||
"title": "प्रोफ़ाइल"
|
||||
},
|
||||
"raisedHand": "बोलना चाहेंगे",
|
||||
@@ -660,6 +918,12 @@
|
||||
"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": "लाइव",
|
||||
@@ -677,18 +941,26 @@
|
||||
"signOut": "साइन आउट करें",
|
||||
"title": "रिकॉर्डिंग",
|
||||
"unavailable": "ओह! {{serviceName}} वर्तमान में अनुपलब्ध है। हम इस समस्या को हल करने पर काम कर रहे हैं। कृपया बाद में पुनः प्रयास करें।",
|
||||
"unavailableTitle": "रिकॉर्डिंग उपलब्ध नहीं है"
|
||||
"unavailableTitle": "रिकॉर्डिंग उपलब्ध नहीं है",
|
||||
"uploadToCloud": "क्लाउड पर अपलोड करें"
|
||||
},
|
||||
"sectionList": {
|
||||
"pullToRefresh": "रीफ़्रेश करने के लिए नीचे खींचें"
|
||||
},
|
||||
"security": {
|
||||
"about": "आप अपनी मीटिंग में $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.",
|
||||
"aboutReadOnly": "मॉडरेटर मीटिंग में $t(lockRoomPassword) जोड़ सकते हैं। प्रतिभागियों को मीटिंग में शामिल होने से पहले यह $t(lockRoomPassword) प्रदान करना होगा।",
|
||||
"insecureRoomNameWarning": "कमरे का नाम असुरक्षित है। अनचाहे सहभागियों की कॉन्फ्रेंस में शामिल हो सकते हैं। सुरक्षा बटन का उपयोग करके अपनी मीटिंग को सुरक्षित बनाने का विचार करें। ",
|
||||
"securityOptions": "Security options"
|
||||
"title": "सुरक्षा विकल्प",
|
||||
"unsafeRoomActions": {
|
||||
"meeting": "सुरक्षा बटन का उपयोग करके अपनी मीटिंग को सुरक्षित करने पर विचार करें।",
|
||||
"prejoin": "कृपया अधिक विशिष्ट मीटिंग नाम का उपयोग करने पर विचार करें।",
|
||||
"welcome": "कृपया अधिक विशिष्ट मीटिंग नाम का उपयोग करें या दिए गए सुझावों में से किसी एक को चुनें।"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"audio": "ऑडियो",
|
||||
"buttonLabel": "सेटिंग्स",
|
||||
"calendar": {
|
||||
"about": "{{appName}} कैलेंडर एकीकरण आपके कैलेंडर तक सुरक्षित रूप से पहुंचने के लिए उपयोग किया जाता है ताकि यह आगामी कार्यक्रम पढ़ सके।",
|
||||
"disconnect": "डिस्कनेक्ट करें",
|
||||
@@ -698,23 +970,35 @@
|
||||
},
|
||||
"devices": "डिवाइस",
|
||||
"followMe": "हर कोई मेरा अनुसरण करेगा",
|
||||
"incomingMessage": "आने वाला संदेश",
|
||||
"language": "भाषा",
|
||||
"loggedIn": "{{name}} के रूप में लॉग इन किया",
|
||||
"maxStageParticipants": "मुख्य स्टेज पर पिन किए जा सकने वाले प्रतिभागियों की अधिकतम संख्या",
|
||||
"microphones": "माइक्रोफोन",
|
||||
"moderator": "Moderator",
|
||||
"more": "More",
|
||||
"moderator": "होस्ट",
|
||||
"more": "अधिक",
|
||||
"name": "नाम",
|
||||
"noDevice": "कोई नहीं",
|
||||
"notifications": "सूचना",
|
||||
"participantJoined": "प्रतिभागी जुड़े",
|
||||
"participantKnocking": "प्रतिभागी लॉबी में आए",
|
||||
"participantLeft": "प्रतिभागी बाहर गए",
|
||||
"playSounds": "ध्वनि चलाएँ",
|
||||
"reactions": "मीटिंग प्रतिक्रियाएँ",
|
||||
"sameAsSystem": "सिस्टम के समान ({{label}})",
|
||||
"selectAudioOutput": "ऑडियो आउटपुट",
|
||||
"selectCamera": "कैमरा",
|
||||
"selectMic": "माइक्रोफोन",
|
||||
"speakers": "Speakers",
|
||||
"shortcuts": "शॉर्टकट्स",
|
||||
"speakers": "वक्ता",
|
||||
"startAudioMuted": "सभी लोग म्यूट से शुरू करेंगे",
|
||||
"startVideoMuted": "सभी लोग छिपे हुए शुरू करेंगे",
|
||||
"title": "सेटिंग"
|
||||
"talkWhileMuted": "म्यूट रहते हुए बोलें",
|
||||
"title": "सेटिंग",
|
||||
"video": "वीडियो"
|
||||
},
|
||||
"settingsView": {
|
||||
"advanced": "Advanced",
|
||||
"advanced": "उन्नत",
|
||||
"alertCancel": "रद्द करें",
|
||||
"alertOk": "ओके",
|
||||
"alertTitle": "चेतावनी",
|
||||
@@ -830,14 +1114,14 @@
|
||||
"exitTileView": "टाइल दृश्य से बाहर निकलें",
|
||||
"feedback": "प्रतिक्रिया छोड़ें",
|
||||
"hangup": "छोड़ें",
|
||||
"help": "Help",
|
||||
"help": "सहायता",
|
||||
"invite": "लोगों को आमंत्रित करें",
|
||||
"lobbyButtonDisable": "लॉबी मोड को अक्षम करें",
|
||||
"lobbyButtonEnable": "लॉबी मोड सक्षम करें",
|
||||
"login": "लॉग इन",
|
||||
"logout": "लॉगआउट",
|
||||
"lowerYourHand": "अपना हाथ नीचे करें",
|
||||
"moreActions": "More actions",
|
||||
"moreActions": "अधिक कार्रवाइयाँ",
|
||||
"moreOptions": "अधिक विकल्प",
|
||||
"mute": "म्यूट / अनम्यूट",
|
||||
"muteEveryone": "सभी को म्यूट करें",
|
||||
@@ -858,19 +1142,21 @@
|
||||
"security": "सुरक्षा विकल्प",
|
||||
"selectBackground": "पृष्ठभूमि का चयन करें",
|
||||
"shareRoom": "किसी को आमंत्रित करें",
|
||||
"sharedvideo": "एक YouTube वीडियो साझा करें",
|
||||
"shareaudio": "ऑडियो साझा करें",
|
||||
"sharedvideo": "एक वीडियो साझा करें",
|
||||
"shortcuts": "शॉर्टकट देखें",
|
||||
"speakerStats": "स्पीकर आँकड़े",
|
||||
"startScreenSharing": "स्क्रीन साझाकरण प्रारंभ करें",
|
||||
"startSubtitles": "Start subtitles",
|
||||
"startSubtitles": "सबटाइटल शुरू करें",
|
||||
"stopScreenSharing": "स्क्रीन शेयरिंग बंद करो",
|
||||
"stopSharedVideo": "YouTube वीडियो बंद करें",
|
||||
"stopSharedVideo": "वीडियो बंद करें",
|
||||
"stopSubtitles": "उपशीर्षक बंद करें",
|
||||
"talkWhileMutedPopup": "बोलने की कोशिश कर रहा है? आप मौन हैं",
|
||||
"tileViewToggle": "टॉगल टाइल दृश्य",
|
||||
"toggleCamera": "कैमरा टॉगल करें",
|
||||
"videoSettings": "वीडियो सेटिंग्स",
|
||||
"videomute": "स्टार्ट / स्टॉप कैमरा"
|
||||
"videomute": "स्टॉप कैमरा",
|
||||
"videounmute": "स्टार्ट कैमरा"
|
||||
},
|
||||
"transcribing": {
|
||||
"ccButtonTooltip": "सबटाइटल शुरू / बंद करें",
|
||||
@@ -919,10 +1205,11 @@
|
||||
"domuteOthers": "सभी को म्यूट करें",
|
||||
"domuteVideo": "कैमरा अक्षम करें",
|
||||
"domuteVideoOfOthers": "अन्य सभी के लिए कैमरा बंद करें",
|
||||
"flip": "Flip",
|
||||
"grantModerator": "Grant Moderator",
|
||||
"flip": "उलटना",
|
||||
"grantModerator": "संचालक बनाएं",
|
||||
"hideSelfView": "स्वयं का दृश्य छिपाएँ",
|
||||
"kick": "निकालें",
|
||||
"moderator": "Moderator",
|
||||
"moderator": "संचालक",
|
||||
"mute": "प्रतिभागी मौन है",
|
||||
"muted": "म्यूटेड",
|
||||
"remoteControl": "स्टार्ट / स्टॉप रिमोट कंट्रोल",
|
||||
@@ -958,8 +1245,20 @@
|
||||
"headerSubtitle": "सुरक्षित और उच्च गुणवत्ता बैठकें",
|
||||
"headerTitle": "जित्सी मीट",
|
||||
"info": "डायल-इन जानकारी",
|
||||
"jitsiOnMobile": "मोबाइल पर Jitsi – हमारे एप्लिकेशन डाउनलोड करें और कहीं से भी एक बैठक शुरू करें",
|
||||
"jitsiOnMobile": "मोबाइल पर जित्सी – हमारे एप्लिकेशन डाउनलोड करें और कहीं से भी एक बैठक शुरू करें",
|
||||
"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": "हाल का",
|
||||
@@ -972,6 +1271,13 @@
|
||||
"sendFeedback": "फ़ीडबैक भेजें",
|
||||
"startMeeting": "मीटिंग प्रारंभ करें",
|
||||
"terms": "शर्तें",
|
||||
"title": "सुरक्षित, पूरी तरह से चित्रित, और पूरी तरह से मुक्त वीडियो कॉन्फ्रेंसिंग"
|
||||
"title": "सुरक्षित, पूरी तरह से चित्रित, और पूरी तरह से मुक्त वीडियो कॉन्फ्रेंसिंग",
|
||||
"upcomingMeetings": "आपकी आगामी मीटिंग्स"
|
||||
},
|
||||
"whiteboard": {
|
||||
"accessibilityLabel": {
|
||||
"heading": "व्हाइटबोर्ड"
|
||||
},
|
||||
"screenTitle": "व्हाइटबोर्ड"
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -227,6 +227,9 @@
|
||||
"video_ssrc": "Video SSRC:",
|
||||
"yes": "yes"
|
||||
},
|
||||
"customPanel": {
|
||||
"close": "Close"
|
||||
},
|
||||
"dateUtils": {
|
||||
"earlier": "Earlier",
|
||||
"today": "Today",
|
||||
@@ -380,6 +383,8 @@
|
||||
"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",
|
||||
@@ -589,6 +594,7 @@
|
||||
"newFileNotification": "{{ participantName }} shared '{{ fileName }}'",
|
||||
"removeFile": "Remove",
|
||||
"removeFileSuccess": "File removed successfully",
|
||||
"uploadDisabled": "Not allowed to upload files. Ask a moderator for permission rights for that operation.",
|
||||
"uploadFailedDescription": "Please try again.",
|
||||
"uploadFailedTitle": "Upload failed",
|
||||
"uploadFile": "Share file"
|
||||
@@ -1315,6 +1321,7 @@
|
||||
"chat": "Open / Close chat",
|
||||
"clap": "Clap",
|
||||
"closeChat": "Close chat",
|
||||
"closeCustomPanel": "Close",
|
||||
"closeMoreActions": "Close more actions menu",
|
||||
"closeParticipantsPane": "Close participants pane",
|
||||
"closedCaptions": "Closed captions",
|
||||
@@ -1420,9 +1427,11 @@
|
||||
"chat": "Open / Close chat",
|
||||
"clap": "Clap",
|
||||
"closeChat": "Close chat",
|
||||
"closeCustomPanel": "Close",
|
||||
"closeParticipantsPane": "Close participants pane",
|
||||
"closeReactionsMenu": "Close reactions menu",
|
||||
"closedCaptions": "Closed captions",
|
||||
"copilot": "Copilot",
|
||||
"disableNoiseSuppression": "Disable extra noise suppression",
|
||||
"disableReactionSounds": "You can disable reaction sounds for this meeting",
|
||||
"documentClose": "Close shared document",
|
||||
|
||||
@@ -240,13 +240,27 @@ function initCommands() {
|
||||
APP.store.dispatch(muteAllParticipants(exclude, muteMediaType));
|
||||
},
|
||||
'mute-remote-participant': (participantId, mediaType) => {
|
||||
if (!isLocalParticipantModerator(APP.store.getState())) {
|
||||
logger.error('Missing moderator rights to mute remote participant');
|
||||
const state = APP.store.getState();
|
||||
const muteMediaType = mediaType ? mediaType : MEDIA_TYPE.AUDIO;
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
|
||||
// Check if targeting the local participant
|
||||
if (participantId === localParticipant?.id) {
|
||||
|
||||
if (muteMediaType === MEDIA_TYPE.AUDIO) {
|
||||
APP.conference.toggleAudioMuted(false);
|
||||
} else if (muteMediaType === MEDIA_TYPE.VIDEO) {
|
||||
APP.conference.toggleVideoMuted(false, true);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const muteMediaType = mediaType ? mediaType : MEDIA_TYPE.AUDIO;
|
||||
if (!isLocalParticipantModerator(state)) {
|
||||
logger.error('Missing moderator rights to mute remote participant');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
APP.store.dispatch(muteRemote(participantId, muteMediaType));
|
||||
},
|
||||
@@ -1417,17 +1431,15 @@ class API {
|
||||
*
|
||||
* @param {string} participantId - The ID of the participant.
|
||||
* @param {boolean} isMuted - True if muted, false if unmuted.
|
||||
* @param {string} mediaType - Media type that was muted ('audio', 'video', or 'desktop').
|
||||
* @param {boolean} isSelfMuted - True if participant muted themselves, false if muted by moderator.
|
||||
* @param {string} mediaType - Media type that was muted ('audio' or 'video').
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyParticipantMuted(participantId, isMuted, mediaType, isSelfMuted = true) {
|
||||
notifyParticipantMuted(participantId, isMuted, mediaType) {
|
||||
this._sendEvent({
|
||||
name: 'participant-muted',
|
||||
id: participantId,
|
||||
isMuted,
|
||||
mediaType,
|
||||
isSelfMuted
|
||||
mediaType
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2241,6 +2253,32 @@ class API {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the external application that a file has been uploaded.
|
||||
*
|
||||
* @param {Object} fileMetadata - The file metadata.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyFileUploaded(fileMetadata) {
|
||||
this._sendEvent({
|
||||
name: 'file-uploaded',
|
||||
file: fileMetadata
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the external application that a file has been deleted.
|
||||
*
|
||||
* @param {string} fileId - The ID of the deleted file.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyFileDeleted(fileId) {
|
||||
this._sendEvent({
|
||||
name: 'file-deleted',
|
||||
fileId
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the external application that the audio or video is being shared by a participant.
|
||||
*
|
||||
|
||||
2
modules/API/external/external_api.js
vendored
2
modules/API/external/external_api.js
vendored
@@ -133,6 +133,8 @@ const events = {
|
||||
'face-landmark-detected': 'faceLandmarkDetected',
|
||||
'feedback-submitted': 'feedbackSubmitted',
|
||||
'feedback-prompt-displayed': 'feedbackPromptDisplayed',
|
||||
'file-deleted': 'fileDeleted',
|
||||
'file-uploaded': 'fileUploaded',
|
||||
'filmstrip-display-changed': 'filmstripDisplayChanged',
|
||||
'incoming-message': 'incomingMessage',
|
||||
'knocking-participant': 'knockingParticipant',
|
||||
|
||||
2135
package-lock.json
generated
2135
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
@@ -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/v2124.0.0+80df84a1/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2131.0.0+6912eed8/lib-jitsi-meet.tgz",
|
||||
"lodash-es": "4.17.21",
|
||||
"null-loader": "4.0.1",
|
||||
"optional-require": "1.0.3",
|
||||
@@ -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.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",
|
||||
"@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",
|
||||
"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.95.0",
|
||||
"webpack": "5.105.0",
|
||||
"webpack-bundle-analyzer": "4.4.2",
|
||||
"webpack-cli": "5.1.4",
|
||||
"webpack-dev-server": "5.1.0"
|
||||
|
||||
@@ -113,12 +113,13 @@ 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(config)) {
|
||||
if (!isTokenAuthEnabled(state)) {
|
||||
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
|
||||
@@ -137,7 +138,8 @@ export function maybeRedirectToTokenAuthUrl(
|
||||
videoMuted
|
||||
},
|
||||
room,
|
||||
tenant
|
||||
tenant,
|
||||
refreshToken
|
||||
)
|
||||
.then((tokenAuthServiceUrl: string | undefined) => {
|
||||
if (!tokenAuthServiceUrl) {
|
||||
|
||||
@@ -11,8 +11,9 @@ const route = {
|
||||
* store.
|
||||
*
|
||||
* @param {any} _stateful - Used on web.
|
||||
* @param {any} _dispatch - Used on web.
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
export function _getRouteToRender(_stateful?: any) {
|
||||
export function _getRouteToRender(_stateful?: any): Promise<object> {
|
||||
return Promise.resolve(route);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
// @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';
|
||||
@@ -23,9 +20,10 @@ 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) {
|
||||
export function _getRouteToRender(stateful: IStateful): Promise<object> {
|
||||
const state = toState(stateful);
|
||||
|
||||
return _getWebConferenceRoute(state) || _getWebWelcomePageRoute(state);
|
||||
@@ -36,9 +34,10 @@ export function _getRouteToRender(stateful: IStateful) {
|
||||
* 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) {
|
||||
function _getWebConferenceRoute(state: IReduxState): Promise<any> | undefined {
|
||||
const room = state['features/base/conference'].room;
|
||||
|
||||
if (!isRoomValid(room)) {
|
||||
@@ -46,36 +45,6 @@ function _getWebConferenceRoute(state: IReduxState) {
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import '../base/app/middleware';
|
||||
import '../base/jwt/middleware.web';
|
||||
import '../base/config/middleware';
|
||||
import '../base/connection/middleware';
|
||||
import '../base/devices/middleware';
|
||||
@@ -26,5 +27,6 @@ import '../face-landmarks/middleware';
|
||||
import '../gifs/middleware';
|
||||
import '../whiteboard/middleware.web';
|
||||
import '../file-sharing/middleware.web';
|
||||
import '../custom-panel/middleware.web';
|
||||
|
||||
import './middlewares.any';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import '../base/devices/reducer';
|
||||
import '../base/premeeting/reducer';
|
||||
import '../custom-panel/reducer';
|
||||
import '../base/tooltip/reducer';
|
||||
import '../e2ee/reducer';
|
||||
import '../face-landmarks/reducer';
|
||||
|
||||
@@ -31,6 +31,7 @@ import { IUserInteractionState } from '../base/user-interaction/reducer';
|
||||
import { IBreakoutRoomsState } from '../breakout-rooms/reducer';
|
||||
import { ICalendarSyncState } from '../calendar-sync/reducer';
|
||||
import { IChatState } from '../chat/reducer';
|
||||
import { ICustomPanelState } from '../custom-panel/reducer';
|
||||
import { IDeepLinkingState } from '../deep-linking/reducer';
|
||||
import { IDropboxState } from '../dropbox/reducer';
|
||||
import { IDynamicBrandingState } from '../dynamic-branding/reducer';
|
||||
@@ -121,6 +122,7 @@ export interface IReduxState {
|
||||
'features/calendar-sync': ICalendarSyncState;
|
||||
'features/call-integration': ICallIntegrationState;
|
||||
'features/chat': IChatState;
|
||||
'features/custom-panel': ICustomPanelState;
|
||||
'features/deep-linking': IDeepLinkingState;
|
||||
'features/dropbox': IDropboxState;
|
||||
'features/dynamic-branding': IDynamicBrandingState;
|
||||
|
||||
@@ -88,3 +88,13 @@ 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);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
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';
|
||||
|
||||
@@ -46,6 +50,147 @@ 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.
|
||||
*
|
||||
@@ -63,6 +208,42 @@ 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, {
|
||||
|
||||
@@ -1,15 +1,30 @@
|
||||
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 is available.
|
||||
* 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.
|
||||
*
|
||||
* @param {Object} config - Configuration state object from store.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const isTokenAuthEnabled = (config: IConfig): boolean =>
|
||||
typeof config.tokenAuthUrl === 'string' && config.tokenAuthUrl.length > 0;
|
||||
export const isTokenAuthInline = (config: IConfig): boolean =>
|
||||
config.tokenAuthInline === true;
|
||||
|
||||
/**
|
||||
* Returns the state that we can add as a parameter to the tokenAuthUrl.
|
||||
@@ -23,6 +38,7 @@ export const isTokenAuthEnabled = (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.
|
||||
*/
|
||||
@@ -35,8 +51,10 @@ export const _getTokenAuthState = (
|
||||
videoMuted: boolean | undefined;
|
||||
},
|
||||
roomName: string | undefined,
|
||||
tenant: string | undefined): object => {
|
||||
tenant: string | undefined,
|
||||
refreshToken?: string): object => {
|
||||
const state = {
|
||||
refreshToken,
|
||||
room: roomName,
|
||||
roomSafe: getBackendSafeRoomName(roomName),
|
||||
tenant
|
||||
|
||||
@@ -23,6 +23,7 @@ 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
|
||||
@@ -39,7 +40,9 @@ export const getTokenAuthUrl = (
|
||||
},
|
||||
roomName: string | undefined,
|
||||
// eslint-disable-next-line max-params
|
||||
tenant: string | undefined): Promise<string | undefined> => {
|
||||
tenant: string | undefined,
|
||||
// eslint-disable-next-line max-params
|
||||
refreshToken?: string | undefined): Promise<string | undefined> => {
|
||||
|
||||
const {
|
||||
audioMuted = false,
|
||||
@@ -64,7 +67,8 @@ export const getTokenAuthUrl = (
|
||||
videoMuted
|
||||
},
|
||||
roomName,
|
||||
tenant
|
||||
tenant,
|
||||
refreshToken
|
||||
);
|
||||
|
||||
// Append ios=true or android=true to the token URL.
|
||||
|
||||
@@ -4,6 +4,7 @@ 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';
|
||||
|
||||
@@ -41,6 +42,7 @@ 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
|
||||
@@ -56,9 +58,10 @@ export const getTokenAuthUrl = (
|
||||
videoMuted: boolean | undefined;
|
||||
},
|
||||
roomName: string | undefined,
|
||||
// eslint-disable-next-line max-params
|
||||
tenant: string | undefined): Promise<string | undefined> => {
|
||||
|
||||
// eslint-disable max-params
|
||||
tenant: string | undefined,
|
||||
refreshToken?: string): Promise<string | undefined> => {
|
||||
// eslint-enable max-params
|
||||
const {
|
||||
audioMuted = false,
|
||||
audioOnlyEnabled = false,
|
||||
@@ -82,7 +85,8 @@ export const getTokenAuthUrl = (
|
||||
videoMuted
|
||||
},
|
||||
roomName,
|
||||
tenant
|
||||
tenant,
|
||||
refreshToken
|
||||
);
|
||||
|
||||
if (browser.isElectron()) {
|
||||
@@ -103,7 +107,17 @@ export const getTokenAuthUrl = (
|
||||
codeVerifier += POSSIBLE_CHARS.charAt(Math.floor(_cryptoRandom() * POSSIBLE_CHARS.length));
|
||||
}
|
||||
|
||||
window.sessionStorage.setItem('code_verifier', codeVerifier);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
return window.crypto.subtle.digest('SHA-256', new TextEncoder().encode(codeVerifier))
|
||||
.then(digest => {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { IStore } from '../app/types';
|
||||
import { APP_WILL_NAVIGATE } from '../base/app/actionTypes';
|
||||
import {
|
||||
CONFERENCE_FAILED,
|
||||
CONFERENCE_JOINED,
|
||||
@@ -17,6 +16,7 @@ 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(config)
|
||||
if (isTokenAuthEnabled(state)
|
||||
&& config.tokenAuthUrlAutoRedirect
|
||||
&& state['features/base/jwt'].jwt) {
|
||||
// auto redirect is turned on and we have successfully logged in
|
||||
@@ -187,7 +187,11 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
break;
|
||||
}
|
||||
|
||||
case APP_WILL_NAVIGATE: {
|
||||
case PREJOIN_JOINING_IN_PROGRESS: {
|
||||
if (!action.value) {
|
||||
break;
|
||||
}
|
||||
|
||||
const { dispatch, getState } = store;
|
||||
const state = getState();
|
||||
const config = state['features/base/config'];
|
||||
@@ -288,6 +292,7 @@ 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!');
|
||||
@@ -295,7 +300,7 @@ function _handleLogin({ dispatch, getState }: IStore) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isTokenAuthEnabled(config)) {
|
||||
if (!isTokenAuthEnabled(state)) {
|
||||
dispatch(openLoginDialog());
|
||||
|
||||
return;
|
||||
@@ -311,7 +316,8 @@ function _handleLogin({ dispatch, getState }: IStore) {
|
||||
videoMuted
|
||||
},
|
||||
room,
|
||||
tenant
|
||||
tenant,
|
||||
refreshToken
|
||||
)
|
||||
.then((tokenAuthServiceUrl: string | undefined) => {
|
||||
if (!tokenAuthServiceUrl) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -14,6 +14,7 @@ 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';
|
||||
@@ -266,8 +267,11 @@ 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';
|
||||
customActionNameKey = [ 'toolbar.login' ];
|
||||
customActionHandler = [ () => dispatch(login()) ];
|
||||
|
||||
if (isTokenAuthEnabled(getState())) {
|
||||
customActionNameKey = [ 'toolbar.login' ];
|
||||
customActionHandler = [ () => dispatch(login()) ]; // show login button if not jaas
|
||||
}
|
||||
}
|
||||
|
||||
dispatch(showErrorNotification({
|
||||
@@ -402,7 +406,8 @@ async function _connectionEstablished({ dispatch, getState }: IStore, next: Func
|
||||
email = getLocalParticipant(getState())?.email;
|
||||
}
|
||||
|
||||
dispatch(authStatusChanged(true, email || ''));
|
||||
// it may happen to be already set
|
||||
dispatch(authStatusChanged(true, email || getState()['features/base/conference'].authLogin || ''));
|
||||
}
|
||||
|
||||
// FIXME: Workaround for the web version. Currently, the creation of the
|
||||
|
||||
@@ -616,6 +616,7 @@ export interface IConfig {
|
||||
disabled?: boolean;
|
||||
numberOfVisibleTiles?: number;
|
||||
};
|
||||
tokenAuthInline?: boolean;
|
||||
tokenAuthUrl?: string;
|
||||
tokenAuthUrlAutoRedirect?: string;
|
||||
tokenGetUserInfoOutOfContext?: boolean;
|
||||
|
||||
@@ -387,7 +387,8 @@ export function setConfigFromURLParams(
|
||||
|
||||
// When not in an iframe, start without media if the pre-join page is not enabled.
|
||||
if (!isEmbedded()
|
||||
&& 'config.prejoinConfig.enabled' in params && config.prejoinConfig?.enabled === false) {
|
||||
&& ('config.prejoinConfig' in params || 'config.prejoinConfig.enabled' in params)
|
||||
&& config.prejoinConfig?.enabled === false) {
|
||||
logger.warn('Using prejoinConfig.enabled config URL overwrite implies starting without media.');
|
||||
config.disableInitialGUM = true;
|
||||
}
|
||||
|
||||
@@ -51,6 +51,16 @@ 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.
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
CONNECTION_ESTABLISHED,
|
||||
CONNECTION_FAILED,
|
||||
CONNECTION_PROPERTIES_UPDATED,
|
||||
CONNECTION_TOKEN_EXPIRED,
|
||||
CONNECTION_WILL_CONNECT,
|
||||
SET_LOCATION_URL,
|
||||
SET_PREFER_VISITOR
|
||||
@@ -239,6 +240,9 @@ 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
|
||||
@@ -323,6 +327,16 @@ 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.
|
||||
*
|
||||
@@ -364,6 +378,23 @@ 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.
|
||||
*
|
||||
|
||||
@@ -4,7 +4,7 @@ import { getCustomerDetails } from '../../jaas/actions.any';
|
||||
import { getJaasJWT, isVpaasMeeting } from '../../jaas/functions';
|
||||
import { navigateRoot } from '../../mobile/navigation/rootNavigationContainerRef';
|
||||
import { screen } from '../../mobile/navigation/routes';
|
||||
import { conferenceWillLeave } from '../conference/actions.native';
|
||||
import { conferenceLeft } from '../conference/actions.native';
|
||||
import { setJWT } from '../jwt/actions';
|
||||
import { JitsiConnectionErrors } from '../lib-jitsi-meet';
|
||||
|
||||
@@ -60,7 +60,7 @@ export function connect(id?: string, password?: string) {
|
||||
*/
|
||||
export function hangup(_requestFeedback = false) {
|
||||
return (dispatch: IStore['dispatch']) => {
|
||||
dispatch(conferenceWillLeave());
|
||||
dispatch(appNavigate(undefined));
|
||||
dispatch(conferenceLeft());
|
||||
};
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ export interface IConnectionState {
|
||||
getJid: () => string;
|
||||
getLogs: () => Object;
|
||||
initJitsiConference: Function;
|
||||
refreshToken: Function;
|
||||
removeFeature: Function;
|
||||
};
|
||||
error?: ConnectionFailedError;
|
||||
|
||||
@@ -29,4 +29,3 @@ export function isIpadMobileBrowser() {
|
||||
// @ts-ignore
|
||||
return isIosMobileBrowser() && Platform.isPad;
|
||||
}
|
||||
|
||||
1
react/features/base/environment/utils.native.ts
Normal file
1
react/features/base/environment/utils.native.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './utils.any';
|
||||
37
react/features/base/environment/utils.web.ts
Normal file
37
react/features/base/environment/utils.web.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { MIN_FILMSTRIP_RESIZE_WIDTH } from '../../filmstrip/constants';
|
||||
|
||||
/**
|
||||
* Detects if the current device has touch capability.
|
||||
* This includes smartphones, tablets, and laptops with touch screens.
|
||||
*
|
||||
* @returns {boolean} True if the device supports touch events.
|
||||
*/
|
||||
export function isTouchDevice(): boolean {
|
||||
// Check maxTouchPoints (most reliable for modern browsers)
|
||||
if ('maxTouchPoints' in navigator) {
|
||||
return navigator.maxTouchPoints > 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if resize functionality should be enabled based on device capabilities
|
||||
* and screen size. On touch devices, resize is only enabled for larger screens.
|
||||
* On non-touch devices (desktop), resize is always enabled.
|
||||
*
|
||||
* @returns {boolean} True if resize functionality should be available to the user.
|
||||
*/
|
||||
export function shouldEnableResize(): boolean {
|
||||
const hasTouch = isTouchDevice();
|
||||
|
||||
// On non-touch devices (desktop), always enable resize
|
||||
if (!hasTouch) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// On touch devices, only enable if screen is large enough.
|
||||
return window?.innerWidth >= MIN_FILMSTRIP_RESIZE_WIDTH;
|
||||
}
|
||||
|
||||
export * from './utils.any';
|
||||
1
react/features/base/icons/svg/AI.svg
Normal file
1
react/features/base/icons/svg/AI.svg
Normal file
@@ -0,0 +1 @@
|
||||
<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>
|
||||
|
After Width: | Height: | Size: 582 B |
@@ -1,3 +1,4 @@
|
||||
import { default as IconAI } from './AI.svg';
|
||||
import { default as IconRecordAccount } from './account-record.svg';
|
||||
import { default as IconAddUser } from './add-user.svg';
|
||||
import { default as IconArrowBack } from './arrow-back.svg';
|
||||
@@ -112,6 +113,7 @@ import { default as IconYahoo } from './yahoo.svg';
|
||||
*/
|
||||
export const DEFAULT_ICON: Record<string, any> = {
|
||||
IconAddUser,
|
||||
IconAI,
|
||||
IconArrowBack,
|
||||
IconArrowDown,
|
||||
IconArrowDownLarge,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { DEFAULT_ICON } from './constants';
|
||||
|
||||
const {
|
||||
IconAddUser,
|
||||
IconAI,
|
||||
IconArrowBack,
|
||||
IconArrowDown,
|
||||
IconArrowDownLarge,
|
||||
@@ -123,6 +124,7 @@ const {
|
||||
|
||||
export {
|
||||
IconAddUser,
|
||||
IconAI,
|
||||
IconArrowBack,
|
||||
IconArrowDown,
|
||||
IconArrowDownLarge,
|
||||
|
||||
@@ -20,15 +20,21 @@ 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)
|
||||
* jwt: (string|undefined),
|
||||
* idToken: (string|undefined),
|
||||
* refreshToken: (string|undefined)
|
||||
* }}
|
||||
*/
|
||||
export function setJWT(jwt?: string) {
|
||||
export function setJWT(jwt?: string, idToken?: string, refreshToken?: string) {
|
||||
return {
|
||||
type: SET_JWT,
|
||||
jwt
|
||||
jwt,
|
||||
idToken,
|
||||
refreshToken
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ 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';
|
||||
@@ -39,6 +40,8 @@ StateListenerRegistry.register(
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
const state = store.getState();
|
||||
|
||||
switch (action.type) {
|
||||
case SET_CONFIG:
|
||||
case SET_LOCATION_URL:
|
||||
@@ -46,7 +49,6 @@ 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) {
|
||||
@@ -56,6 +58,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
store.dispatch(setDelayedLoadOfAvatarUrl());
|
||||
store.dispatch(setKnownAvatarUrl(delayedLoadOfAvatarUrl));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SET_JWT:
|
||||
return _setJWT(store, next, action);
|
||||
@@ -149,7 +152,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 { jwt, type, ...actionPayload } = action;
|
||||
const { idToken, jwt, refreshToken, type, ...actionPayload } = action;
|
||||
|
||||
if (!Object.keys(actionPayload).length) {
|
||||
const state = store.getState();
|
||||
@@ -210,24 +213,32 @@ function _setJWT(store: IStore, next: Function, action: AnyAction) {
|
||||
if (context.user && context.user.role === 'visitor') {
|
||||
action.preferVisitor = true;
|
||||
}
|
||||
} 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 (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 (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);
|
||||
user && _undoOverwriteLocalParticipant(store, user);
|
||||
}
|
||||
|
||||
// clears authLogin
|
||||
store.dispatch(authStatusChanged(true));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
100
react/features/base/jwt/middleware.web.ts
Normal file
100
react/features/base/jwt/middleware.web.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
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: false,
|
||||
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);
|
||||
});
|
||||
@@ -11,8 +11,10 @@ export interface IJwtState {
|
||||
};
|
||||
delayedLoadOfAvatarUrl?: string;
|
||||
group?: string;
|
||||
idToken?: string;
|
||||
jwt?: string;
|
||||
knownAvatarUrl?: string;
|
||||
refreshToken?: string;
|
||||
server?: string;
|
||||
tenant?: string;
|
||||
user?: {
|
||||
|
||||
@@ -56,9 +56,9 @@ const useStyles = makeStyles()(theme => {
|
||||
label: {
|
||||
...theme.typography.labelRegular,
|
||||
alignItems: 'center',
|
||||
background: theme.palette.ui04,
|
||||
background: theme.palette.labelBackground,
|
||||
borderRadius: '4px',
|
||||
color: theme.palette.text01,
|
||||
color: theme.palette.labelText,
|
||||
display: 'flex',
|
||||
margin: '0 2px',
|
||||
padding: '6px',
|
||||
@@ -72,11 +72,11 @@ const useStyles = makeStyles()(theme => {
|
||||
cursor: 'pointer'
|
||||
},
|
||||
[COLORS.white]: {
|
||||
background: theme.palette.ui09,
|
||||
color: theme.palette.text04,
|
||||
background: theme.palette.labelWhiteBackground,
|
||||
color: theme.palette.labelWhiteText,
|
||||
|
||||
'& svg': {
|
||||
fill: theme.palette.icon04
|
||||
fill: theme.palette.labelWhiteIcon
|
||||
}
|
||||
},
|
||||
[COLORS.green]: {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { getLocalParticipant } from '../participants/functions';
|
||||
import StateListenerRegistry from '../redux/StateListenerRegistry';
|
||||
|
||||
/**
|
||||
@@ -13,6 +14,13 @@ StateListenerRegistry.register(
|
||||
|
||||
if (muted !== previousMuted) {
|
||||
APP.API.notifyAudioMutedStatusChanged(muted);
|
||||
|
||||
// Also fire the participantMuted event for consistency
|
||||
const localParticipant = getLocalParticipant(store.getState());
|
||||
|
||||
if (localParticipant) {
|
||||
APP.API.notifyParticipantMuted(localParticipant.id, muted, 'audio');
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -60,66 +60,6 @@ const AVATAR_CHECKER_FUNCTIONS = [
|
||||
return null;
|
||||
}
|
||||
];
|
||||
/* eslint-enable arrow-body-style */
|
||||
|
||||
/**
|
||||
* Returns the list of active speakers that should be moved to the top of the sorted list of participants so that the
|
||||
* dominant speaker is visible always on the vertical filmstrip in stage layout.
|
||||
*
|
||||
* @param {Function | Object} stateful - The (whole) redux state, or redux's {@code getState} function to be used to
|
||||
* retrieve the state.
|
||||
* @returns {Array<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.
|
||||
|
||||
@@ -68,6 +68,7 @@ const PARTICIPANT_PROPS_TO_OMIT_WHEN_UPDATE = [
|
||||
];
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
activeSpeakers: new Set<string>(),
|
||||
dominantSpeaker: undefined,
|
||||
fakeParticipants: new Map(),
|
||||
local: undefined,
|
||||
@@ -82,10 +83,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;
|
||||
@@ -100,7 +101,6 @@ export interface IParticipantsState {
|
||||
remoteVideoSources: Set<string>;
|
||||
sortedRemoteParticipants: Map<string, string>;
|
||||
sortedRemoteVirtualScreenshareParticipants: Map<string, string>;
|
||||
speakersList: Map<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,22 +157,8 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
|
||||
const { participant } = action;
|
||||
const { id, previousSpeakers = [] } = participant;
|
||||
const { dominantSpeaker, local } = state;
|
||||
const newSpeakers = [ id, ...previousSpeakers ];
|
||||
const sortedSpeakersList: Array<Array<string>> = [];
|
||||
|
||||
for (const speaker of newSpeakers) {
|
||||
if (speaker !== local?.id) {
|
||||
const remoteParticipant = state.remote.get(speaker);
|
||||
|
||||
remoteParticipant
|
||||
&& sortedSpeakersList.push(
|
||||
[ speaker, _getDisplayName(state, remoteParticipant?.name) ]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Keep the remote speaker list sorted alphabetically.
|
||||
sortedSpeakersList.sort((a, b) => a[1].localeCompare(b[1]));
|
||||
const activeSpeakers = new Set(previousSpeakers
|
||||
.filter((speakerId: string) => state.remote.has(speakerId) && (speakerId !== local?.id)));
|
||||
|
||||
// Only one dominant speaker is allowed.
|
||||
if (dominantSpeaker) {
|
||||
@@ -183,7 +169,7 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
|
||||
return {
|
||||
...state,
|
||||
dominantSpeaker: id, // @ts-ignore
|
||||
speakersList: new Map(sortedSpeakersList)
|
||||
activeSpeakers
|
||||
};
|
||||
}
|
||||
|
||||
@@ -438,7 +424,7 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
|
||||
}
|
||||
|
||||
// Remove the participant from the list of speakers.
|
||||
state.speakersList.has(id) && state.speakersList.delete(id);
|
||||
state.activeSpeakers.delete(id);
|
||||
|
||||
if (pinnedParticipant === id) {
|
||||
state.pinnedParticipant = undefined;
|
||||
|
||||
@@ -84,7 +84,7 @@ const useStyles = makeStyles()(theme => {
|
||||
...theme.typography.bodyLongBold,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
boxSizing: 'border-box',
|
||||
color: theme.palette.text01,
|
||||
color: theme.palette.actionButtonText,
|
||||
cursor: 'pointer',
|
||||
display: 'inline-block',
|
||||
marginBottom: '16px',
|
||||
@@ -95,20 +95,20 @@ const useStyles = makeStyles()(theme => {
|
||||
border: 0,
|
||||
|
||||
'&.primary': {
|
||||
background: theme.palette.action01,
|
||||
color: theme.palette.text01,
|
||||
background: theme.palette.prejoinActionButtonPrimary,
|
||||
color: theme.palette.prejoinActionButtonPrimaryText,
|
||||
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.action01Hover
|
||||
backgroundColor: theme.palette.prejoinActionButtonPrimaryHover
|
||||
}
|
||||
},
|
||||
|
||||
'&.secondary': {
|
||||
background: theme.palette.action02,
|
||||
color: theme.palette.text04,
|
||||
background: theme.palette.prejoinActionButtonSecondary,
|
||||
color: theme.palette.prejoinActionButtonSecondaryText,
|
||||
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.action02Hover
|
||||
backgroundColor: theme.palette.prejoinActionButtonSecondaryHover
|
||||
}
|
||||
},
|
||||
|
||||
@@ -120,7 +120,7 @@ const useStyles = makeStyles()(theme => {
|
||||
},
|
||||
|
||||
'&.disabled': {
|
||||
background: theme.palette.disabled01,
|
||||
background: theme.palette.prejoinActionButtonDisabled,
|
||||
border: '1px solid #5E6D7A',
|
||||
color: '#AFB6BC',
|
||||
cursor: 'initial',
|
||||
|
||||
@@ -109,7 +109,7 @@ const useStyles = makeStyles()(theme => {
|
||||
position: 'absolute',
|
||||
inset: '0 0 0 0',
|
||||
display: 'flex',
|
||||
backgroundColor: theme.palette.ui01,
|
||||
backgroundColor: theme.palette.preMeetingBackground,
|
||||
zIndex: 252,
|
||||
|
||||
'@media (max-width: 720px)': {
|
||||
@@ -163,7 +163,7 @@ const useStyles = makeStyles()(theme => {
|
||||
},
|
||||
title: {
|
||||
...theme.typography.heading4,
|
||||
color: `${theme.palette.text01}!important`,
|
||||
color: theme.palette.prejoinTitleText,
|
||||
marginBottom: theme.spacing(3),
|
||||
textAlign: 'center',
|
||||
|
||||
@@ -179,7 +179,7 @@ const useStyles = makeStyles()(theme => {
|
||||
|
||||
roomName: {
|
||||
...theme.typography.heading5,
|
||||
color: theme.palette.text01,
|
||||
color: theme.palette.prejoinRoomNameText,
|
||||
display: 'inline-block',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
|
||||
@@ -6,7 +6,7 @@ const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
warning: {
|
||||
bottom: 0,
|
||||
color: theme.palette.text03,
|
||||
color: theme.palette.prejoinRecordingWarningText,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
...theme.typography.bodyShortRegular,
|
||||
|
||||
@@ -11,8 +11,8 @@ import { setUnsafeRoomConsent } from '../../actions.web';
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
warning: {
|
||||
backgroundColor: theme.palette.warning01,
|
||||
color: theme.palette.text04,
|
||||
backgroundColor: theme.palette.prejoinWarningBackground,
|
||||
color: theme.palette.prejoinWarningText,
|
||||
...theme.typography.bodyShortRegular,
|
||||
padding: theme.spacing(3),
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
|
||||
@@ -9,11 +9,11 @@ import { getSupportUrl } from '../../functions';
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
dialog: {
|
||||
backgroundColor: theme.palette.ui01,
|
||||
border: `1px solid ${theme.palette.ui04}`,
|
||||
backgroundColor: theme.palette.dialogBackground,
|
||||
border: `1px solid ${theme.palette.inlineDialogBorder}`,
|
||||
borderRadius: `${Number(theme.shape.borderRadius)}px`,
|
||||
boxShadow: '0px 1px 2px rgba(41, 41, 41, 0.25)',
|
||||
color: theme.palette.text01,
|
||||
color: theme.palette.dialogText,
|
||||
...theme.typography.bodyShortRegular,
|
||||
padding: `${theme.spacing(3)} 10`,
|
||||
'& .retry-button': {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { batch } from 'react-redux';
|
||||
|
||||
import { IStore } from '../../app/types';
|
||||
import { CHAT_SIZE } from '../../chat/constants';
|
||||
import { getCustomPanelWidth } from '../../custom-panel/functions';
|
||||
import { getParticipantsPaneWidth } from '../../participants-pane/functions';
|
||||
|
||||
import {
|
||||
@@ -51,6 +52,7 @@ export function clientResized(clientWidth: number, clientHeight: number) {
|
||||
}
|
||||
|
||||
availableWidth -= getParticipantsPaneWidth(state);
|
||||
availableWidth -= getCustomPanelWidth(state);
|
||||
|
||||
reducedUIEnabled && dispatch(setReducedUI(availableWidth, clientHeight));
|
||||
}
|
||||
|
||||
@@ -22,11 +22,11 @@ interface IProps {
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
container: {
|
||||
backgroundColor: theme.palette.uiBackground,
|
||||
backgroundColor: theme.palette.tooltipBackground,
|
||||
borderRadius: '3px',
|
||||
padding: theme.spacing(2),
|
||||
...theme.typography.labelRegular,
|
||||
color: theme.palette.text01,
|
||||
color: theme.palette.tooltipText,
|
||||
position: 'relative',
|
||||
|
||||
'&.mounting-animation': {
|
||||
|
||||
@@ -51,7 +51,7 @@ import './subscriber.web';
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case TRACK_ADDED: {
|
||||
const { local } = action.track;
|
||||
const { local, jitsiTrack } = action.track;
|
||||
|
||||
// The devices list needs to be refreshed when no initial video permissions
|
||||
// were granted and a local video track is added by umuting the video.
|
||||
@@ -65,6 +65,16 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
|
||||
if (participantId) {
|
||||
logTracksForParticipant(store.getState()['features/base/tracks'], participantId, 'Track added');
|
||||
|
||||
// Fire participantMuted event for initial state of remote tracks
|
||||
if (typeof action.track?.muted !== 'undefined' && jitsiTrack) {
|
||||
const isVideoTrack = jitsiTrack.getType() !== MEDIA_TYPE.AUDIO;
|
||||
const mediaType = isVideoTrack
|
||||
? (jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP ? 'desktop' : 'video')
|
||||
: 'audio';
|
||||
|
||||
APP.API.notifyParticipantMuted(participantId, action.track.muted, mediaType);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -119,6 +129,16 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
// TODO Remove the following calls to APP.UI once components interested
|
||||
// in track mute changes are moved into React and/or redux.
|
||||
|
||||
const { jitsiTrack } = action.track;
|
||||
const participantID = jitsiTrack.getParticipantId();
|
||||
const isVideoTrack = jitsiTrack.type !== MEDIA_TYPE.AUDIO;
|
||||
const local = jitsiTrack.isLocal();
|
||||
|
||||
// Get old muted state BEFORE updating
|
||||
const tracks = store.getState()['features/base/tracks'];
|
||||
const oldTrack = tracks.find((t: ITrack) => t.jitsiTrack === jitsiTrack);
|
||||
const oldMutedState = oldTrack?.muted;
|
||||
|
||||
const result = next(action);
|
||||
const state = store.getState();
|
||||
|
||||
@@ -126,11 +146,6 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
return result;
|
||||
}
|
||||
|
||||
const { jitsiTrack } = action.track;
|
||||
const participantID = jitsiTrack.getParticipantId();
|
||||
const isVideoTrack = jitsiTrack.type !== MEDIA_TYPE.AUDIO;
|
||||
const local = jitsiTrack.isLocal();
|
||||
|
||||
if (isVideoTrack) {
|
||||
if (local && !(jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP)) {
|
||||
APP.conference.setVideoMuteStatus();
|
||||
@@ -144,12 +159,14 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
if (typeof action.track?.muted !== 'undefined' && participantID && !local) {
|
||||
logTracksForParticipant(store.getState()['features/base/tracks'], participantID, 'Track updated');
|
||||
|
||||
// Notify external API when remote participant mutes/unmutes themselves
|
||||
const mediaType = isVideoTrack
|
||||
? (jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP ? 'desktop' : 'video')
|
||||
: 'audio';
|
||||
// Fire participantMuted event only if muted state actually changed
|
||||
if (oldMutedState !== action.track.muted) {
|
||||
const mediaType = isVideoTrack
|
||||
? (jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP ? 'desktop' : 'video')
|
||||
: 'audio';
|
||||
|
||||
APP.API.notifyParticipantMuted(participantID, action.track.muted, mediaType, true);
|
||||
APP.API.notifyParticipantMuted(participantID, action.track.muted, mediaType);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { isEqual, sortBy } from 'lodash-es';
|
||||
import VideoLayout from '../../../../modules/UI/videolayout/VideoLayout';
|
||||
import { getAutoPinSetting } from '../../video-layout/functions.any';
|
||||
import { MEDIA_TYPE } from '../media/constants';
|
||||
import { getScreenshareParticipantIds } from '../participants/functions';
|
||||
import { getLocalParticipant, getScreenshareParticipantIds } from '../participants/functions';
|
||||
import StateListenerRegistry from '../redux/StateListenerRegistry';
|
||||
|
||||
import { isLocalTrackMuted } from './functions';
|
||||
@@ -47,6 +47,13 @@ StateListenerRegistry.register(
|
||||
/* listener */ (muted, store, previousMuted) => {
|
||||
if (muted !== previousMuted) {
|
||||
APP.API.notifyVideoMutedStatusChanged(muted);
|
||||
|
||||
// Also fire the participantMuted event for consistency
|
||||
const localParticipant = getLocalParticipant(store.getState());
|
||||
|
||||
if (localParticipant) {
|
||||
APP.API.notifyParticipantMuted(localParticipant.id, muted, 'video');
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @stylistic/no-multi-spaces */
|
||||
// Mapping between the token used and the color
|
||||
export const colorMap = {
|
||||
// ----- Surfaces -----
|
||||
@@ -7,7 +8,7 @@ export const colorMap = {
|
||||
// - JitsiMeetView.java
|
||||
uiBackground: 'surface01',
|
||||
|
||||
// Container backgrounds
|
||||
// Container backgrounds (legacy tokens)
|
||||
ui01: 'surface02',
|
||||
ui02: 'surface03',
|
||||
ui03: 'ui02',
|
||||
@@ -47,6 +48,430 @@ 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',
|
||||
@@ -84,6 +509,9 @@ export const colorMap = {
|
||||
// High-contrast
|
||||
icon04: 'surface01',
|
||||
|
||||
// SVG fill color
|
||||
iconSvgFill: 'icon01',
|
||||
|
||||
// Error
|
||||
iconError: 'action03',
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ const useStyles = makeStyles()(theme => {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
position: 'fixed',
|
||||
color: theme.palette.text01,
|
||||
color: theme.palette.dialogText,
|
||||
...theme.typography.bodyLongRegular,
|
||||
top: 0,
|
||||
left: 0,
|
||||
@@ -49,13 +49,13 @@ const useStyles = makeStyles()(theme => {
|
||||
height: '100%',
|
||||
top: 0,
|
||||
left: 0,
|
||||
backgroundColor: theme.palette.ui02,
|
||||
backgroundColor: theme.palette.dialogOverlay,
|
||||
opacity: 0.75
|
||||
},
|
||||
|
||||
modal: {
|
||||
backgroundColor: theme.palette.ui01,
|
||||
border: `1px solid ${theme.palette.ui03}`,
|
||||
backgroundColor: theme.palette.dialogBackground,
|
||||
border: `1px solid ${theme.palette.dialogBorder}`,
|
||||
boxShadow: '0px 4px 25px 4px rgba(20, 20, 20, 0.6)',
|
||||
borderRadius: `${theme.shape.borderRadius}px`,
|
||||
display: 'flex',
|
||||
|
||||
@@ -47,7 +47,7 @@ const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
formControl: {
|
||||
...theme.typography.bodyLongRegular,
|
||||
color: theme.palette.text01,
|
||||
color: theme.palette.checkboxLabel,
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
|
||||
@@ -76,10 +76,10 @@ const useStyles = makeStyles()(theme => {
|
||||
backgroundColor: 'transparent',
|
||||
margin: '3px',
|
||||
font: 'inherit',
|
||||
color: theme.palette.icon03,
|
||||
color: theme.palette.checkboxBorder,
|
||||
width: '18px',
|
||||
height: '18px',
|
||||
border: `2px solid ${theme.palette.icon03}`,
|
||||
border: `2px solid ${theme.palette.checkboxBorder}`,
|
||||
borderRadius: '3px',
|
||||
|
||||
display: 'grid',
|
||||
@@ -90,7 +90,7 @@ const useStyles = makeStyles()(theme => {
|
||||
width: '18px',
|
||||
height: '18px',
|
||||
opacity: 0,
|
||||
backgroundColor: theme.palette.action01,
|
||||
backgroundColor: theme.palette.checkboxChecked,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
@@ -104,11 +104,11 @@ const useStyles = makeStyles()(theme => {
|
||||
},
|
||||
|
||||
'&:disabled': {
|
||||
backgroundColor: theme.palette.ui03,
|
||||
borderColor: theme.palette.ui04,
|
||||
backgroundColor: theme.palette.checkboxDisabledBackground,
|
||||
borderColor: theme.palette.checkboxDisabledBorder,
|
||||
|
||||
'&::before': {
|
||||
backgroundColor: theme.palette.ui04
|
||||
backgroundColor: theme.palette.checkboxDisabledChecked
|
||||
}
|
||||
},
|
||||
|
||||
@@ -173,7 +173,7 @@ const Checkbox = ({
|
||||
<Icon
|
||||
aria-hidden = { true }
|
||||
className = 'checkmark'
|
||||
color = { disabled ? theme.palette.icon03 : theme.palette.icon01 }
|
||||
color = { disabled ? theme.palette.checkboxIconDisabled : theme.palette.checkboxIcon }
|
||||
size = { 18 }
|
||||
src = { IconCheck } />
|
||||
</div>
|
||||
|
||||
@@ -16,22 +16,22 @@ const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
button: {
|
||||
padding: '2px',
|
||||
backgroundColor: theme.palette.action03,
|
||||
backgroundColor: theme.palette.clickableIconBackground,
|
||||
border: 0,
|
||||
outline: 0,
|
||||
borderRadius: `${theme.shape.borderRadius}px`,
|
||||
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.ui02
|
||||
backgroundColor: theme.palette.clickableIconHover
|
||||
},
|
||||
|
||||
'&.focus-visible': {
|
||||
outline: 0,
|
||||
boxShadow: `0px 0px 0px 2px ${theme.palette.focus01}`
|
||||
boxShadow: `0px 0px 0px 2px ${theme.palette.clickableIconFocus}`
|
||||
},
|
||||
|
||||
'&:active': {
|
||||
backgroundColor: theme.palette.ui03
|
||||
backgroundColor: theme.palette.clickableIconActive
|
||||
},
|
||||
|
||||
'&.is-mobile': {
|
||||
|
||||
@@ -133,11 +133,11 @@ const MAX_HEIGHT = 400;
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
contextMenu: {
|
||||
backgroundColor: theme.palette.ui01,
|
||||
border: `1px solid ${theme.palette.ui04}`,
|
||||
backgroundColor: theme.palette.overflowMenuBackground,
|
||||
border: `1px solid ${theme.palette.overflowMenuBorder}`,
|
||||
borderRadius: `${Number(theme.shape.borderRadius)}px`,
|
||||
boxShadow: '0px 1px 2px rgba(41, 41, 41, 0.25)',
|
||||
color: theme.palette.text01,
|
||||
color: theme.palette.overflowMenuItemText,
|
||||
...theme.typography.bodyShortRegular,
|
||||
marginTop: '48px',
|
||||
position: 'absolute',
|
||||
|
||||
@@ -122,11 +122,11 @@ const useStyles = makeStyles()(theme => {
|
||||
},
|
||||
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.ui02
|
||||
backgroundColor: theme.palette.overflowMenuItemHover
|
||||
},
|
||||
|
||||
'&:active': {
|
||||
backgroundColor: theme.palette.ui03
|
||||
backgroundColor: theme.palette.overflowMenuItemHover
|
||||
},
|
||||
|
||||
'&.focus-visible': {
|
||||
@@ -137,7 +137,7 @@ const useStyles = makeStyles()(theme => {
|
||||
selected: {
|
||||
borderLeft: `3px solid ${theme.palette.action01Hover}`,
|
||||
paddingLeft: '13px',
|
||||
backgroundColor: theme.palette.ui02
|
||||
backgroundColor: theme.palette.overflowMenuItemHover
|
||||
},
|
||||
|
||||
contextMenuItemDisabled: {
|
||||
@@ -146,19 +146,19 @@ const useStyles = makeStyles()(theme => {
|
||||
|
||||
contextMenuItemIconDisabled: {
|
||||
'& svg': {
|
||||
fill: `${theme.palette.text03} !important`
|
||||
fill: `${theme.palette.overflowMenuItemDisabled} !important`
|
||||
}
|
||||
},
|
||||
|
||||
contextMenuItemLabelDisabled: {
|
||||
color: theme.palette.text03,
|
||||
color: theme.palette.overflowMenuItemDisabled,
|
||||
|
||||
'&:hover': {
|
||||
background: 'none'
|
||||
},
|
||||
|
||||
'& svg': {
|
||||
fill: theme.palette.text03
|
||||
fill: theme.palette.overflowMenuItemDisabled
|
||||
}
|
||||
},
|
||||
|
||||
@@ -168,13 +168,13 @@ const useStyles = makeStyles()(theme => {
|
||||
|
||||
contextMenuItemIcon: {
|
||||
'& svg': {
|
||||
fill: theme.palette.icon01
|
||||
fill: theme.palette.overflowMenuItemIcon
|
||||
}
|
||||
},
|
||||
|
||||
text: {
|
||||
...theme.typography.bodyShortRegular,
|
||||
color: theme.palette.text01
|
||||
color: theme.palette.overflowMenuItemText
|
||||
},
|
||||
|
||||
drawerText: {
|
||||
|
||||
@@ -30,7 +30,7 @@ const useStyles = makeStyles()(theme => {
|
||||
},
|
||||
|
||||
'& + &:not(:empty)': {
|
||||
borderTop: `1px solid ${theme.palette.ui03}`
|
||||
borderTop: `1px solid ${theme.palette.overflowMenuSeparator}`
|
||||
},
|
||||
|
||||
'&:first-of-type': {
|
||||
|
||||
@@ -24,7 +24,7 @@ const useStyles = makeStyles()(theme => {
|
||||
},
|
||||
|
||||
title: {
|
||||
color: theme.palette.text01,
|
||||
color: theme.palette.dialogText,
|
||||
...theme.typography.heading5,
|
||||
margin: 0,
|
||||
padding: 0
|
||||
|
||||
@@ -42,7 +42,7 @@ const useStyles = makeStyles()(theme => {
|
||||
flexDirection: 'column',
|
||||
minWidth: '211px',
|
||||
maxWidth: '100%',
|
||||
borderRight: `1px solid ${theme.palette.ui03}`,
|
||||
borderRight: `1px solid ${theme.palette.dialogBorder}`,
|
||||
|
||||
[`@media (max-width: ${MOBILE_BREAKPOINT}px)`]: {
|
||||
width: '100%',
|
||||
@@ -70,7 +70,7 @@ const useStyles = makeStyles()(theme => {
|
||||
|
||||
title: {
|
||||
...theme.typography.heading5,
|
||||
color: `${theme.palette.text01} !important`,
|
||||
color: `${theme.palette.dialogText} !important`,
|
||||
margin: 0,
|
||||
padding: 0
|
||||
},
|
||||
@@ -301,7 +301,7 @@ const DialogWithTabs = ({
|
||||
}
|
||||
|
||||
return null;
|
||||
}, [ selectedTabIndex, tabStates ]);
|
||||
}, [ selectedTabIndex, tabStates, tabs ]);
|
||||
|
||||
const closeIcon = useMemo(() => (
|
||||
<ClickableIcon
|
||||
|
||||
@@ -49,7 +49,7 @@ const useStyles = makeStyles()(theme => {
|
||||
},
|
||||
|
||||
label: {
|
||||
color: theme.palette.text01,
|
||||
color: theme.palette.inputLabel,
|
||||
...theme.typography.bodyShortRegular,
|
||||
marginBottom: theme.spacing(2),
|
||||
|
||||
@@ -64,9 +64,9 @@ const useStyles = makeStyles()(theme => {
|
||||
},
|
||||
|
||||
input: {
|
||||
backgroundColor: theme.palette.ui03,
|
||||
background: theme.palette.ui03,
|
||||
color: theme.palette.text01,
|
||||
backgroundColor: theme.palette.inputFieldBackground,
|
||||
background: theme.palette.inputFieldBackground,
|
||||
color: theme.palette.inputFieldText,
|
||||
...theme.typography.bodyShortRegular,
|
||||
padding: '10px 16px',
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
@@ -76,16 +76,20 @@ const useStyles = makeStyles()(theme => {
|
||||
width: '100%',
|
||||
|
||||
'&::placeholder': {
|
||||
color: theme.palette.text02
|
||||
color: theme.palette.inputFieldPlaceholder
|
||||
},
|
||||
|
||||
'&:focus': {
|
||||
outline: 0,
|
||||
boxShadow: `0px 0px 0px 2px ${theme.palette.focus01}`
|
||||
boxShadow: `0px 0px 0px 2px ${theme.palette.inputFieldFocus}`,
|
||||
|
||||
'&::placeholder': {
|
||||
opacity: 0
|
||||
}
|
||||
},
|
||||
|
||||
'&:disabled': {
|
||||
color: theme.palette.text03
|
||||
color: theme.palette.inputFieldDisabled
|
||||
},
|
||||
|
||||
'&.is-mobile': {
|
||||
@@ -99,7 +103,7 @@ const useStyles = makeStyles()(theme => {
|
||||
},
|
||||
|
||||
'&.error': {
|
||||
boxShadow: `0px 0px 0px 2px ${theme.palette.textError}`
|
||||
boxShadow: `0px 0px 0px 2px ${theme.palette.inputFieldError}`
|
||||
},
|
||||
'&.clearable-input': {
|
||||
paddingRight: '46px'
|
||||
@@ -131,7 +135,7 @@ const useStyles = makeStyles()(theme => {
|
||||
right: '16px',
|
||||
top: '10px',
|
||||
cursor: 'pointer',
|
||||
backgroundColor: theme.palette.action03,
|
||||
backgroundColor: theme.palette.inputClearButton,
|
||||
border: 0,
|
||||
padding: 0
|
||||
},
|
||||
@@ -139,14 +143,14 @@ const useStyles = makeStyles()(theme => {
|
||||
bottomLabel: {
|
||||
marginTop: theme.spacing(2),
|
||||
...theme.typography.labelRegular,
|
||||
color: theme.palette.text02,
|
||||
color: theme.palette.inputBottomLabel,
|
||||
|
||||
'&.is-mobile': {
|
||||
...theme.typography.bodyShortRegular
|
||||
},
|
||||
|
||||
'&.error': {
|
||||
color: theme.palette.textError
|
||||
color: theme.palette.inputBottomLabelError
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -83,7 +83,7 @@ const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
container: {
|
||||
alignItems: 'center',
|
||||
color: theme.palette.text01,
|
||||
color: theme.palette.listItemText,
|
||||
display: 'flex',
|
||||
...theme.typography.bodyShortBold,
|
||||
margin: `0 -${participantsPaneTheme.panePadding}px`,
|
||||
@@ -93,7 +93,7 @@ const useStyles = makeStyles()(theme => {
|
||||
minHeight: '40px',
|
||||
|
||||
'&:hover, &:focus-within': {
|
||||
backgroundColor: theme.palette.ui02,
|
||||
backgroundColor: theme.palette.listItemHover,
|
||||
|
||||
'& .indicators': {
|
||||
display: 'none'
|
||||
@@ -103,8 +103,8 @@ const useStyles = makeStyles()(theme => {
|
||||
display: 'flex',
|
||||
position: 'relative',
|
||||
top: 'auto',
|
||||
boxShadow: `-15px 0px 10px -5px ${theme.palette.ui02}`,
|
||||
backgroundColor: theme.palette.ui02
|
||||
boxShadow: `-15px 0px 10px -5px ${theme.palette.listItemBoxShadow}`,
|
||||
backgroundColor: theme.palette.listItemHover
|
||||
}
|
||||
},
|
||||
|
||||
@@ -115,14 +115,14 @@ const useStyles = makeStyles()(theme => {
|
||||
},
|
||||
|
||||
highlighted: {
|
||||
backgroundColor: theme.palette.ui02,
|
||||
backgroundColor: theme.palette.listItemHighlighted,
|
||||
|
||||
'& .actions': {
|
||||
display: 'flex',
|
||||
position: 'relative',
|
||||
top: 'auto',
|
||||
boxShadow: `-15px 0px 10px -5px ${theme.palette.ui02}`,
|
||||
backgroundColor: theme.palette.ui02
|
||||
boxShadow: `-15px 0px 10px -5px ${theme.palette.listItemBoxShadow}`,
|
||||
backgroundColor: theme.palette.listItemHighlighted
|
||||
}
|
||||
},
|
||||
|
||||
@@ -170,20 +170,20 @@ const useStyles = makeStyles()(theme => {
|
||||
actionsContainer: {
|
||||
position: 'absolute',
|
||||
top: '-1000px',
|
||||
boxShadow: `-15px 0px 10px -5px ${theme.palette.ui02}`,
|
||||
backgroundColor: theme.palette.ui02
|
||||
boxShadow: `-15px 0px 10px -5px ${theme.palette.listItemBoxShadow}`,
|
||||
backgroundColor: theme.palette.listItemHover
|
||||
},
|
||||
|
||||
actionsPermanent: {
|
||||
display: 'flex',
|
||||
boxShadow: `-15px 0px 10px -5px ${theme.palette.ui01}`,
|
||||
backgroundColor: theme.palette.ui01
|
||||
boxShadow: `-15px 0px 10px -5px ${theme.palette.listItemBackground}`,
|
||||
backgroundColor: theme.palette.listItemBackground
|
||||
},
|
||||
|
||||
actionsVisible: {
|
||||
display: 'flex',
|
||||
boxShadow: `-15px 0px 10px -5px ${theme.palette.ui02}`,
|
||||
backgroundColor: theme.palette.ui02
|
||||
boxShadow: `-15px 0px 10px -5px ${theme.palette.listItemBoxShadow}`,
|
||||
backgroundColor: theme.palette.listItemHighlighted
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -38,8 +38,8 @@ const useStyles = makeStyles()(theme => {
|
||||
},
|
||||
marginTop: theme.spacing(2),
|
||||
width: '100%',
|
||||
backgroundColor: theme.palette.ui01,
|
||||
border: `1px solid ${theme.palette.ui04}`,
|
||||
backgroundColor: theme.palette.multiSelectBackground,
|
||||
border: `1px solid ${theme.palette.multiSelectBorder}`,
|
||||
borderRadius: `${Number(theme.shape.borderRadius)}px`,
|
||||
...theme.typography.bodyShortRegular,
|
||||
zIndex: 2,
|
||||
@@ -57,7 +57,7 @@ const useStyles = makeStyles()(theme => {
|
||||
inlineSize: 'calc(100% - 38px)',
|
||||
overflowWrap: 'break-word',
|
||||
marginLeft: theme.spacing(2),
|
||||
color: theme.palette.text01,
|
||||
color: theme.palette.multiSelectItemText,
|
||||
'&.with-remove': {
|
||||
// 60px because of the icon before the content and the remove button
|
||||
inlineSize: 'calc(100% - 60px)',
|
||||
@@ -76,15 +76,15 @@ const useStyles = makeStyles()(theme => {
|
||||
cursor: 'pointer',
|
||||
padding: `10px ${theme.spacing(3)}`,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.ui02
|
||||
backgroundColor: theme.palette.multiSelectItemHover
|
||||
}
|
||||
},
|
||||
'&.disabled': {
|
||||
cursor: 'not-allowed',
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.ui01
|
||||
backgroundColor: theme.palette.multiSelectBackground
|
||||
},
|
||||
color: theme.palette.text03
|
||||
color: theme.palette.multiSelectItemDisabled
|
||||
}
|
||||
},
|
||||
errorMessage: {
|
||||
|
||||
@@ -70,7 +70,7 @@ const useStyles = makeStyles()(theme => {
|
||||
},
|
||||
|
||||
label: {
|
||||
color: theme.palette.text01,
|
||||
color: theme.palette.selectLabel,
|
||||
...theme.typography.bodyShortRegular,
|
||||
marginBottom: theme.spacing(2),
|
||||
|
||||
@@ -84,11 +84,11 @@ const useStyles = makeStyles()(theme => {
|
||||
},
|
||||
|
||||
select: {
|
||||
backgroundColor: theme.palette.ui03,
|
||||
backgroundColor: theme.palette.selectBackground,
|
||||
borderRadius: `${theme.shape.borderRadius}px`,
|
||||
width: '100%',
|
||||
...theme.typography.bodyShortRegular,
|
||||
color: theme.palette.text01,
|
||||
color: theme.palette.selectText,
|
||||
padding: '10px 16px',
|
||||
paddingRight: '42px',
|
||||
border: 0,
|
||||
@@ -99,11 +99,11 @@ const useStyles = makeStyles()(theme => {
|
||||
|
||||
'&:focus': {
|
||||
outline: 0,
|
||||
boxShadow: `0px 0px 0px 2px ${theme.palette.focus01}`
|
||||
boxShadow: `0px 0px 0px 2px ${theme.palette.selectFocus}`
|
||||
},
|
||||
|
||||
'&:disabled': {
|
||||
color: theme.palette.text03
|
||||
color: theme.palette.selectDisabled
|
||||
},
|
||||
|
||||
'&.is-mobile': {
|
||||
@@ -113,7 +113,7 @@ const useStyles = makeStyles()(theme => {
|
||||
},
|
||||
|
||||
'&.error': {
|
||||
boxShadow: `0px 0px 0px 2px ${theme.palette.textError}`
|
||||
boxShadow: `0px 0px 0px 2px ${theme.palette.selectError}`
|
||||
}
|
||||
},
|
||||
|
||||
@@ -132,14 +132,14 @@ const useStyles = makeStyles()(theme => {
|
||||
bottomLabel: {
|
||||
marginTop: theme.spacing(2),
|
||||
...theme.typography.labelRegular,
|
||||
color: theme.palette.text02,
|
||||
color: theme.palette.selectBottomLabel,
|
||||
|
||||
'&.is-mobile': {
|
||||
...theme.typography.bodyShortRegular
|
||||
},
|
||||
|
||||
'&.error': {
|
||||
color: theme.palette.textError
|
||||
color: theme.palette.selectBottomLabelError
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -180,7 +180,7 @@ const Select = ({
|
||||
</select>
|
||||
<Icon
|
||||
className = { cx(classes.icon, isMobile && 'is-mobile') }
|
||||
color = { disabled ? theme.palette.icon03 : theme.palette.icon01 }
|
||||
color = { disabled ? theme.palette.selectIconDisabled : theme.palette.selectIcon }
|
||||
size = { 22 }
|
||||
src = { IconArrowDown } />
|
||||
</div>
|
||||
|
||||
@@ -18,7 +18,7 @@ const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
container: {
|
||||
position: 'relative',
|
||||
backgroundColor: theme.palette.ui05,
|
||||
backgroundColor: theme.palette.switchBackground,
|
||||
borderRadius: '12px',
|
||||
width: '40px',
|
||||
height: '24px',
|
||||
@@ -29,11 +29,11 @@ const useStyles = makeStyles()(theme => {
|
||||
display: 'inline-block',
|
||||
|
||||
'&.disabled': {
|
||||
backgroundColor: theme.palette.ui05,
|
||||
backgroundColor: theme.palette.switchBackground,
|
||||
cursor: 'default',
|
||||
|
||||
'& .toggle': {
|
||||
backgroundColor: theme.palette.ui03
|
||||
backgroundColor: theme.palette.switchToggleDisabled
|
||||
}
|
||||
},
|
||||
|
||||
@@ -45,7 +45,7 @@ const useStyles = makeStyles()(theme => {
|
||||
},
|
||||
|
||||
containerOn: {
|
||||
backgroundColor: theme.palette.action01
|
||||
backgroundColor: theme.palette.switchBackgroundOn
|
||||
},
|
||||
|
||||
toggle: {
|
||||
@@ -55,7 +55,7 @@ const useStyles = makeStyles()(theme => {
|
||||
zIndex: 5,
|
||||
top: '4px',
|
||||
left: '4px',
|
||||
backgroundColor: theme.palette.ui10,
|
||||
backgroundColor: theme.palette.switchToggle,
|
||||
borderRadius: '100%',
|
||||
transition: '.3s',
|
||||
|
||||
@@ -87,7 +87,7 @@ const useStyles = makeStyles()(theme => {
|
||||
|
||||
'&.focus-visible + .toggle-checkbox-ring': {
|
||||
outline: 0,
|
||||
boxShadow: `0px 0px 0px 2px ${theme.palette.focus01}`
|
||||
boxShadow: `0px 0px 0px 2px ${theme.palette.switchFocus}`
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -29,13 +29,13 @@ const useStyles = makeStyles()(theme => {
|
||||
|
||||
tab: {
|
||||
...theme.typography.bodyShortBold,
|
||||
color: theme.palette.text02,
|
||||
color: theme.palette.tabText,
|
||||
flex: 1,
|
||||
padding: '14px',
|
||||
background: 'none',
|
||||
border: 0,
|
||||
appearance: 'none',
|
||||
borderBottom: `2px solid ${theme.palette.ui05}`,
|
||||
borderBottom: `2px solid ${theme.palette.tabBorder}`,
|
||||
transition: 'color, border-color 0.2s',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
@@ -43,25 +43,25 @@ const useStyles = makeStyles()(theme => {
|
||||
borderRadius: 0,
|
||||
|
||||
'&:hover': {
|
||||
color: theme.palette.text01,
|
||||
borderColor: theme.palette.ui10
|
||||
color: theme.palette.tabTextHover,
|
||||
borderColor: theme.palette.tabBorderHover
|
||||
},
|
||||
|
||||
'&.focus-visible': {
|
||||
outline: 0,
|
||||
boxShadow: `0px 0px 0px 2px ${theme.palette.focus01}`,
|
||||
boxShadow: `0px 0px 0px 2px ${theme.palette.tabFocus}`,
|
||||
border: 0,
|
||||
color: theme.palette.text01
|
||||
color: theme.palette.tabTextSelected
|
||||
},
|
||||
|
||||
'&.selected': {
|
||||
color: theme.palette.text01,
|
||||
borderColor: theme.palette.action01
|
||||
color: theme.palette.tabTextSelected,
|
||||
borderColor: theme.palette.tabBorderSelected
|
||||
},
|
||||
|
||||
'&:disabled': {
|
||||
color: theme.palette.text03,
|
||||
borderColor: theme.palette.ui05
|
||||
color: theme.palette.tabTextDisabled,
|
||||
borderColor: theme.palette.tabBorderDisabled
|
||||
},
|
||||
|
||||
'&.is-mobile': {
|
||||
@@ -72,9 +72,9 @@ const useStyles = makeStyles()(theme => {
|
||||
badge: {
|
||||
...theme.typography.labelBold,
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.palette.warning01,
|
||||
backgroundColor: theme.palette.tabBadgeBackground,
|
||||
borderRadius: theme.spacing(2),
|
||||
color: theme.palette.text04,
|
||||
color: theme.palette.tabBadgeText,
|
||||
display: 'inline-flex',
|
||||
height: theme.spacing(3),
|
||||
justifyContent: 'center',
|
||||
|
||||
@@ -11,6 +11,29 @@ 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,
|
||||
@@ -39,7 +62,7 @@ export const commonStyles = (theme: Theme) => {
|
||||
|
||||
'.overflow-menu-item': {
|
||||
alignItems: 'center',
|
||||
color: theme.palette.text01,
|
||||
color: theme.palette.overflowMenuItemText,
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
fontSize: '0.875rem',
|
||||
@@ -59,20 +82,20 @@ export const commonStyles = (theme: Theme) => {
|
||||
|
||||
'&.disabled': {
|
||||
cursor: 'initial',
|
||||
color: theme.palette.text03,
|
||||
color: theme.palette.overflowMenuItemDisabled,
|
||||
|
||||
'&:hover': {
|
||||
background: 'none'
|
||||
},
|
||||
|
||||
'& svg': {
|
||||
fill: theme.palette.text03
|
||||
fill: theme.palette.overflowMenuItemDisabled
|
||||
}
|
||||
},
|
||||
|
||||
'@media (hover: hover) and (pointer: fine)': {
|
||||
'&:hover': {
|
||||
background: theme.palette.action02Hover
|
||||
background: theme.palette.overflowMenuItemHover
|
||||
},
|
||||
'&.unclickable:hover': {
|
||||
background: 'inherit'
|
||||
@@ -100,14 +123,14 @@ export const commonStyles = (theme: Theme) => {
|
||||
},
|
||||
|
||||
'& svg': {
|
||||
fill: theme.palette.text01,
|
||||
fill: theme.palette.overflowMenuItemIcon,
|
||||
height: 20,
|
||||
width: 20
|
||||
}
|
||||
},
|
||||
|
||||
'.prejoin-dialog': {
|
||||
backgroundColor: theme.palette.uiBackground,
|
||||
backgroundColor: theme.palette.prejoinDialogBackground,
|
||||
boxShadow: '0px 2px 20px rgba(0, 0, 0, 0.5)',
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
color: '#fff',
|
||||
@@ -173,7 +196,7 @@ export const commonStyles = (theme: Theme) => {
|
||||
},
|
||||
|
||||
'.prejoin-dialog-delimiter': {
|
||||
background: theme.palette.ui03,
|
||||
background: theme.palette.prejoinDialogDelimiter,
|
||||
border: '0',
|
||||
height: '1px',
|
||||
margin: '0',
|
||||
@@ -194,8 +217,8 @@ export const commonStyles = (theme: Theme) => {
|
||||
},
|
||||
|
||||
'.prejoin-dialog-delimiter-txt': {
|
||||
background: theme.palette.uiBackground,
|
||||
color: theme.palette.text01,
|
||||
background: theme.palette.prejoinDialogBackground,
|
||||
color: theme.palette.prejoinDialogDelimiterText,
|
||||
fontSize: '0.75rem',
|
||||
textTransform: 'uppercase' as const,
|
||||
padding: `0 ${theme.spacing(2)}`
|
||||
@@ -219,11 +242,11 @@ export const commonStyles = (theme: Theme) => {
|
||||
|
||||
'@media (hover: hover) and (pointer: fine)': {
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.ui04
|
||||
backgroundColor: theme.palette.toolboxIconHover
|
||||
},
|
||||
|
||||
'&:active': {
|
||||
backgroundColor: theme.palette.ui03
|
||||
backgroundColor: theme.palette.toolboxIconActive
|
||||
}
|
||||
},
|
||||
[theme.breakpoints.down(320)]: {
|
||||
@@ -232,7 +255,7 @@ export const commonStyles = (theme: Theme) => {
|
||||
},
|
||||
|
||||
'&.toggled': {
|
||||
backgroundColor: theme.palette.ui03
|
||||
backgroundColor: theme.palette.toolboxIconToggled
|
||||
},
|
||||
|
||||
'&.disabled': {
|
||||
@@ -240,13 +263,13 @@ export const commonStyles = (theme: Theme) => {
|
||||
backgroundColor: `${theme.palette.disabled01} !important`,
|
||||
|
||||
'& svg': {
|
||||
fill: `${theme.palette.text03} !important`
|
||||
fill: `${theme.palette.icon03} !important`
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
'.toolbox-button': {
|
||||
color: theme.palette.text01,
|
||||
color: theme.palette.toolbarIcon,
|
||||
cursor: 'pointer',
|
||||
display: 'inline-block',
|
||||
lineHeight: '3rem',
|
||||
@@ -254,7 +277,7 @@ export const commonStyles = (theme: Theme) => {
|
||||
},
|
||||
|
||||
'.toolbox-content-items': {
|
||||
background: theme.palette.ui01,
|
||||
background: theme.palette.toolboxBackground,
|
||||
borderRadius: 6,
|
||||
margin: '0 auto',
|
||||
padding: 6,
|
||||
|
||||
11
react/features/base/ui/types.d.ts
vendored
Normal file
11
react/features/base/ui/types.d.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
import '@mui/material/styles';
|
||||
|
||||
import { IPalette, ITypography } from './types';
|
||||
|
||||
declare module '@mui/material/styles' {
|
||||
interface Palette extends IPalette {}
|
||||
interface PaletteOptions extends Partial<IPalette> {}
|
||||
|
||||
interface Typography extends ITypography {}
|
||||
interface TypographyOptions extends Partial<ITypography> {}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ interface ITypographyType {
|
||||
lineHeight: string;
|
||||
}
|
||||
|
||||
/* eslint-disable typescript-sort-keys/interface */
|
||||
export interface IPalette {
|
||||
action01: string;
|
||||
action01Active: string;
|
||||
@@ -25,6 +26,7 @@ export interface IPalette {
|
||||
icon02: string;
|
||||
icon03: string;
|
||||
icon04: string;
|
||||
iconSvgFill: string;
|
||||
iconError: string;
|
||||
link01: string;
|
||||
link01Active: string;
|
||||
@@ -58,6 +60,328 @@ 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 {
|
||||
|
||||
@@ -11,13 +11,49 @@ import * as tokens from './tokens.json';
|
||||
*/
|
||||
export function createColorTokens(colorMap: Object): any {
|
||||
const allTokens = merge({}, tokens, jitsiTokens);
|
||||
const result: any = {};
|
||||
|
||||
return Object.entries(colorMap)
|
||||
.reduce((result, [ token, value ]: [any, string]) => {
|
||||
const color = allTokens[value as keyof typeof allTokens] || value;
|
||||
// First pass: resolve tokens that reference allTokens directly
|
||||
Object.entries(colorMap).forEach(([ token, value ]: [any, string]) => {
|
||||
const color = allTokens[value as keyof typeof allTokens] || value;
|
||||
|
||||
return Object.assign(result, { [token]: color });
|
||||
}, {});
|
||||
result[token] = color;
|
||||
});
|
||||
|
||||
// Second pass: resolve semantic tokens that reference other colorMap entries
|
||||
// Recursively resolve until we get actual color values
|
||||
const resolveToken = (value: string, depth = 0): string => {
|
||||
// Prevent infinite loops
|
||||
if (depth > 10) {
|
||||
return value;
|
||||
}
|
||||
|
||||
// If it's already a color (starts with # or rgb/rgba), return it
|
||||
if (value.startsWith('#') || value.startsWith('rgb')) {
|
||||
return value;
|
||||
}
|
||||
|
||||
// Look up in the result map first (for colorMap token references)
|
||||
if (result[value]) {
|
||||
return resolveToken(result[value], depth + 1);
|
||||
}
|
||||
|
||||
// Then look up in allTokens
|
||||
const resolved = allTokens[value as keyof typeof allTokens];
|
||||
|
||||
if (resolved && resolved !== value && typeof resolved === 'string') {
|
||||
return resolveToken(resolved, depth + 1);
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
// Third pass: recursively resolve all values
|
||||
Object.entries(result).forEach(([ token, value ]) => {
|
||||
result[token] = resolveToken(String(value));
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,3 +24,13 @@ 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;
|
||||
}
|
||||
|
||||
@@ -10,3 +10,17 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { connect, useSelector } from 'react-redux';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { isTouchDevice, shouldEnableResize } from '../../../base/environment/utils';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { IconInfo, IconMessage, IconShareDoc, IconSubtitles } from '../../../base/icons/svg';
|
||||
import { getLocalParticipant, getRemoteParticipants, isPrivateChatEnabledSelf } from '../../../base/participants/functions';
|
||||
@@ -23,7 +24,16 @@ import {
|
||||
setUserChatWidth,
|
||||
toggleChat
|
||||
} from '../../actions.web';
|
||||
import { CHAT_SIZE, ChatTabs, OPTION_GROUPCHAT, SMALL_WIDTH_THRESHOLD } from '../../constants';
|
||||
import {
|
||||
CHAT_DRAG_HANDLE_HEIGHT,
|
||||
CHAT_DRAG_HANDLE_OFFSET,
|
||||
CHAT_DRAG_HANDLE_WIDTH,
|
||||
CHAT_SIZE,
|
||||
CHAT_TOUCH_HANDLE_SIZE,
|
||||
ChatTabs,
|
||||
OPTION_GROUPCHAT,
|
||||
SMALL_WIDTH_THRESHOLD
|
||||
} from '../../constants';
|
||||
import { getChatMaxSize, getFocusedTab, isChatDisabled } from '../../functions';
|
||||
import { IChatProps as AbstractProps } from '../../types';
|
||||
|
||||
@@ -104,10 +114,15 @@ interface IProps extends AbstractProps {
|
||||
_width: number;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles<{ _isResizing: boolean; width: number; }>()((theme, { _isResizing, width }) => {
|
||||
const useStyles = makeStyles<{
|
||||
_isResizing: boolean;
|
||||
isTouch: boolean;
|
||||
resizeEnabled: boolean;
|
||||
width: number;
|
||||
}>()((theme, { _isResizing, isTouch, resizeEnabled, width }) => {
|
||||
return {
|
||||
container: {
|
||||
backgroundColor: theme.palette.ui01,
|
||||
backgroundColor: theme.palette.chatBackground,
|
||||
flexShrink: 0,
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
@@ -115,11 +130,15 @@ const useStyles = makeStyles<{ _isResizing: boolean; width: number; }>()((theme,
|
||||
width: `${width}px`,
|
||||
zIndex: 300,
|
||||
|
||||
'&:hover, &:focus-within': {
|
||||
'& .dragHandleContainer': {
|
||||
visibility: 'visible'
|
||||
// On non-touch devices (desktop), show handle on hover
|
||||
// On touch devices, handle is always visible if resize is enabled
|
||||
...(!isTouch && {
|
||||
'&:hover, &:focus-within': {
|
||||
'& .dragHandleContainer': {
|
||||
visibility: 'visible'
|
||||
}
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
'@media (max-width: 580px)': {
|
||||
height: '100dvh',
|
||||
@@ -146,7 +165,7 @@ const useStyles = makeStyles<{ _isResizing: boolean; width: number; }>()((theme,
|
||||
padding: `${theme.spacing(3)} ${theme.spacing(4)}`,
|
||||
alignItems: 'center',
|
||||
boxSizing: 'border-box',
|
||||
color: theme.palette.text01,
|
||||
color: theme.palette.chatMessageText,
|
||||
...theme.typography.heading6,
|
||||
lineHeight: 'unset',
|
||||
fontWeight: theme.typography.heading6.fontWeight as any,
|
||||
@@ -183,16 +202,23 @@ const useStyles = makeStyles<{ _isResizing: boolean; width: number; }>()((theme,
|
||||
|
||||
dragHandleContainer: {
|
||||
height: '100%',
|
||||
width: '9px',
|
||||
// Touch devices need larger hit target but positioned to not take extra space
|
||||
width: isTouch ? `${CHAT_TOUCH_HANDLE_SIZE}px` : `${CHAT_DRAG_HANDLE_WIDTH}px`,
|
||||
backgroundColor: 'transparent',
|
||||
position: 'absolute',
|
||||
cursor: 'col-resize',
|
||||
display: 'flex',
|
||||
display: resizeEnabled ? 'flex' : 'none', // Hide if resize not enabled
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
visibility: 'hidden',
|
||||
right: '4px',
|
||||
// On touch devices, always visible if resize enabled. On desktop, hidden by default
|
||||
visibility: (isTouch && resizeEnabled) ? 'visible' : 'hidden',
|
||||
// Position touch handle centered on offset from edge, maintaining same gap as non-touch
|
||||
right: isTouch
|
||||
? `${CHAT_DRAG_HANDLE_OFFSET - Math.floor((CHAT_TOUCH_HANDLE_SIZE - CHAT_DRAG_HANDLE_WIDTH) / 2)}px`
|
||||
: `${CHAT_DRAG_HANDLE_OFFSET}px`,
|
||||
top: 0,
|
||||
// Prevent touch scrolling while dragging
|
||||
touchAction: 'none',
|
||||
|
||||
'&:hover': {
|
||||
'& .dragHandle': {
|
||||
@@ -210,10 +236,15 @@ const useStyles = makeStyles<{ _isResizing: boolean; width: number; }>()((theme,
|
||||
},
|
||||
|
||||
dragHandle: {
|
||||
// Keep the same visual appearance on all devices
|
||||
backgroundColor: theme.palette.icon02,
|
||||
height: '100px',
|
||||
width: '3px',
|
||||
borderRadius: '1px'
|
||||
height: `${CHAT_DRAG_HANDLE_HEIGHT}px`,
|
||||
width: `${CHAT_DRAG_HANDLE_WIDTH / 3}px`,
|
||||
borderRadius: '1px',
|
||||
// Make more visible when actively shown
|
||||
...(isTouch && resizeEnabled && {
|
||||
backgroundColor: theme.palette.icon01
|
||||
})
|
||||
},
|
||||
|
||||
privateMessageRecipientsList: {
|
||||
@@ -246,7 +277,10 @@ const Chat = ({
|
||||
return null;
|
||||
}
|
||||
|
||||
const { classes, cx } = useStyles({ _isResizing, width: _width });
|
||||
// Detect touch capability and screen size for resize functionality
|
||||
const isTouch = isTouchDevice();
|
||||
const resizeEnabled = shouldEnableResize();
|
||||
const { classes, cx } = useStyles({ _isResizing, width: _width, isTouch, resizeEnabled });
|
||||
const [ isMouseDown, setIsMouseDown ] = useState(false);
|
||||
const [ mousePosition, setMousePosition ] = useState<number | null>(null);
|
||||
const [ dragChatWidth, setDragChatWidth ] = useState<number | null>(null);
|
||||
@@ -282,16 +316,21 @@ const Chat = ({
|
||||
}, [ participants, defaultRemoteDisplayName, t, notifyTimestamp ]);
|
||||
|
||||
/**
|
||||
* Handles mouse down on the drag handle.
|
||||
* Handles pointer down on the drag handle.
|
||||
* Supports both mouse and touch events via Pointer Events API.
|
||||
*
|
||||
* @param {MouseEvent} e - The mouse down event.
|
||||
* @param {React.PointerEvent} e - The pointer down event.
|
||||
* @returns {void}
|
||||
*/
|
||||
const onDragHandleMouseDown = useCallback((e: React.MouseEvent) => {
|
||||
const onDragHandlePointerDown = useCallback((e: React.PointerEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Store the initial mouse position and chat width
|
||||
// Capture the pointer to ensure we receive all pointer events
|
||||
// even if the pointer moves outside the element
|
||||
(e.target as HTMLElement).setPointerCapture(e.pointerId);
|
||||
|
||||
// Store the initial pointer position and chat width
|
||||
setIsMouseDown(true);
|
||||
setMousePosition(e.clientX);
|
||||
setDragChatWidth(_width);
|
||||
@@ -299,7 +338,7 @@ const Chat = ({
|
||||
// Indicate that resizing is in progress
|
||||
dispatch(setChatIsResizing(true));
|
||||
|
||||
// Add visual feedback that we're dragging
|
||||
// Add visual feedback that we're dragging (cursor for mouse, not visible on touch)
|
||||
document.body.style.cursor = 'col-resize';
|
||||
|
||||
// Disable text selection during resize
|
||||
@@ -307,11 +346,12 @@ const Chat = ({
|
||||
}, [ _width, dispatch ]);
|
||||
|
||||
/**
|
||||
* Drag handle mouse up handler.
|
||||
* Drag handle pointer up handler.
|
||||
* Supports both mouse and touch events via Pointer Events API.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
const onDragMouseUp = useCallback(() => {
|
||||
const onDragPointerUp = useCallback(() => {
|
||||
if (isMouseDown) {
|
||||
setIsMouseDown(false);
|
||||
dispatch(setChatIsResizing(false));
|
||||
@@ -323,12 +363,13 @@ const Chat = ({
|
||||
}, [ isMouseDown, dispatch ]);
|
||||
|
||||
/**
|
||||
* Handles drag handle mouse move.
|
||||
* Handles drag handle pointer move.
|
||||
* Supports both mouse and touch events via Pointer Events API.
|
||||
*
|
||||
* @param {MouseEvent} e - The mousemove event.
|
||||
* @param {PointerEvent} e - The pointermove event.
|
||||
* @returns {void}
|
||||
*/
|
||||
const onChatResize = useCallback(throttle((e: MouseEvent) => {
|
||||
const onChatResize = useCallback(throttle((e: PointerEvent) => {
|
||||
if (isMouseDown && mousePosition !== null && dragChatWidth !== null) {
|
||||
// For chat panel resizing on the left edge:
|
||||
// - Dragging left (decreasing X coordinate) should make the panel wider
|
||||
@@ -352,14 +393,14 @@ const Chat = ({
|
||||
|
||||
// Set up event listeners when component mounts
|
||||
useEffect(() => {
|
||||
document.addEventListener('mouseup', onDragMouseUp);
|
||||
document.addEventListener('mousemove', onChatResize);
|
||||
document.addEventListener('pointerup', onDragPointerUp);
|
||||
document.addEventListener('pointermove', onChatResize);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mouseup', onDragMouseUp);
|
||||
document.removeEventListener('mousemove', onChatResize);
|
||||
document.removeEventListener('pointerup', onDragPointerUp);
|
||||
document.removeEventListener('pointermove', onChatResize);
|
||||
};
|
||||
}, [ onDragMouseUp, onChatResize ]);
|
||||
}, [ onDragPointerUp, onChatResize ]);
|
||||
|
||||
/**
|
||||
* Sends a text message.
|
||||
@@ -600,7 +641,7 @@ const Chat = ({
|
||||
(isMouseDown || _isResizing) && 'visible',
|
||||
'dragHandleContainer'
|
||||
) }
|
||||
onMouseDown = { onDragHandleMouseDown }>
|
||||
onPointerDown = { onDragHandlePointerDown }>
|
||||
<div className = { cx(classes.dragHandle, 'dragHandle') } />
|
||||
</div>
|
||||
</div> : null
|
||||
|
||||
@@ -37,7 +37,7 @@ const styles = (_theme: Theme, { _chatWidth }: IProps) => {
|
||||
}
|
||||
},
|
||||
chatDisabled: {
|
||||
borderTop: `1px solid ${_theme.palette.ui02}`,
|
||||
borderTop: `1px solid ${_theme.palette.chatInputBorder}`,
|
||||
boxSizing: 'border-box' as const,
|
||||
padding: _theme.spacing(4),
|
||||
textAlign: 'center' as const,
|
||||
|
||||
@@ -43,7 +43,7 @@ const useStyles = makeStyles()((theme: Theme) => {
|
||||
chatMessage: {
|
||||
display: 'inline-flex',
|
||||
padding: '12px',
|
||||
backgroundColor: theme.palette.ui02,
|
||||
backgroundColor: theme.palette.chatMessageRemote,
|
||||
borderRadius: '4px 12px 12px 12px',
|
||||
maxWidth: '100%',
|
||||
marginTop: '4px',
|
||||
@@ -66,21 +66,21 @@ const useStyles = makeStyles()((theme: Theme) => {
|
||||
},
|
||||
|
||||
'&.privatemessage': {
|
||||
backgroundColor: theme.palette.support05
|
||||
backgroundColor: theme.palette.chatMessagePrivate
|
||||
},
|
||||
'&.local': {
|
||||
backgroundColor: theme.palette.ui04,
|
||||
backgroundColor: theme.palette.chatMessageLocal,
|
||||
borderRadius: '12px 4px 12px 12px',
|
||||
|
||||
'&.privatemessage': {
|
||||
backgroundColor: theme.palette.support05
|
||||
backgroundColor: theme.palette.chatMessagePrivate
|
||||
},
|
||||
'&.local': {
|
||||
backgroundColor: theme.palette.ui04,
|
||||
backgroundColor: theme.palette.chatMessageLocal,
|
||||
borderRadius: '12px 4px 12px 12px',
|
||||
|
||||
'&.privatemessage': {
|
||||
backgroundColor: theme.palette.support05
|
||||
backgroundColor: theme.palette.chatMessagePrivate
|
||||
}
|
||||
},
|
||||
|
||||
@@ -91,7 +91,7 @@ const useStyles = makeStyles()((theme: Theme) => {
|
||||
},
|
||||
|
||||
'&.lobbymessage': {
|
||||
backgroundColor: theme.palette.support05
|
||||
backgroundColor: theme.palette.chatMessagePrivate
|
||||
}
|
||||
},
|
||||
'&.error': {
|
||||
@@ -100,7 +100,7 @@ const useStyles = makeStyles()((theme: Theme) => {
|
||||
fontWeight: 100
|
||||
},
|
||||
'&.lobbymessage': {
|
||||
backgroundColor: theme.palette.support05
|
||||
backgroundColor: theme.palette.chatMessagePrivate
|
||||
}
|
||||
},
|
||||
sideBySideContainer: {
|
||||
@@ -146,7 +146,7 @@ const useStyles = makeStyles()((theme: Theme) => {
|
||||
},
|
||||
displayName: {
|
||||
...theme.typography.labelBold,
|
||||
color: theme.palette.text02,
|
||||
color: theme.palette.chatSenderName,
|
||||
whiteSpace: 'nowrap',
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
@@ -155,18 +155,18 @@ const useStyles = makeStyles()((theme: Theme) => {
|
||||
},
|
||||
userMessage: {
|
||||
...theme.typography.bodyShortRegular,
|
||||
color: theme.palette.text01,
|
||||
color: theme.palette.chatMessageText,
|
||||
whiteSpace: 'pre-wrap',
|
||||
wordBreak: 'break-word'
|
||||
},
|
||||
privateMessageNotice: {
|
||||
...theme.typography.labelRegular,
|
||||
color: theme.palette.text02,
|
||||
color: theme.palette.chatPrivateNotice,
|
||||
marginTop: theme.spacing(1)
|
||||
},
|
||||
timestamp: {
|
||||
...theme.typography.labelRegular,
|
||||
color: theme.palette.text03,
|
||||
color: theme.palette.chatTimestamp,
|
||||
marginTop: theme.spacing(1),
|
||||
marginLeft: theme.spacing(1),
|
||||
whiteSpace: 'nowrap',
|
||||
@@ -174,12 +174,12 @@ const useStyles = makeStyles()((theme: Theme) => {
|
||||
},
|
||||
reactionsPopover: {
|
||||
padding: theme.spacing(2),
|
||||
backgroundColor: theme.palette.ui03,
|
||||
backgroundColor: theme.palette.chatInputBackground,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
maxWidth: '150px',
|
||||
maxHeight: '400px',
|
||||
overflowY: 'auto',
|
||||
color: theme.palette.text01
|
||||
color: theme.palette.chatMessageText
|
||||
},
|
||||
reactionItem: {
|
||||
display: 'flex',
|
||||
|
||||
@@ -24,7 +24,7 @@ const useStyles = makeStyles()(theme => {
|
||||
padding: '16px',
|
||||
flex: 1,
|
||||
boxSizing: 'border-box',
|
||||
color: theme.palette.text01
|
||||
color: theme.palette.chatMessageText
|
||||
},
|
||||
container: {
|
||||
display: 'flex',
|
||||
@@ -48,7 +48,7 @@ const useStyles = makeStyles()(theme => {
|
||||
boxSizing: 'border-box',
|
||||
flexDirection: 'column',
|
||||
gap: '16px',
|
||||
color: theme.palette.text01,
|
||||
color: theme.palette.chatMessageText,
|
||||
textAlign: 'center'
|
||||
},
|
||||
emptyIcon: {
|
||||
@@ -62,7 +62,7 @@ const useStyles = makeStyles()(theme => {
|
||||
},
|
||||
emptyState: {
|
||||
...theme.typography.bodyLongBold,
|
||||
color: theme.palette.text02
|
||||
color: theme.palette.chatSenderName
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -103,7 +103,7 @@ const ClosedCaptionsTab = ({
|
||||
<div className = { classes.emptyContent }>
|
||||
<Icon
|
||||
className = { classes.emptyIcon }
|
||||
color = { theme.palette.icon03 }
|
||||
color = { theme.palette.chatEmptyText }
|
||||
src = { IconSubtitles } />
|
||||
<span className = { classes.emptyState }>
|
||||
{ t('closedCaptionsTab.emptyState') }
|
||||
|
||||
@@ -12,7 +12,7 @@ const useStyles = makeStyles()((theme: Theme) => {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
borderRadius: '4px',
|
||||
backgroundColor: theme.palette.ui03
|
||||
backgroundColor: theme.palette.chatInputBackground
|
||||
},
|
||||
|
||||
emojiButton: {
|
||||
|
||||
@@ -46,12 +46,12 @@ const useStyles = makeStyles()(theme => {
|
||||
|
||||
// Add background to button container to hide text underneath in chat context
|
||||
'& > div:last-child': {
|
||||
backgroundColor: theme.palette.ui02,
|
||||
backgroundColor: theme.palette.chatMessageRemote,
|
||||
paddingLeft: theme.spacing(2)
|
||||
},
|
||||
|
||||
'&:hover > div:last-child': {
|
||||
backgroundColor: theme.palette.ui03
|
||||
backgroundColor: theme.palette.chatInputBackground
|
||||
}
|
||||
},
|
||||
|
||||
@@ -66,7 +66,7 @@ const useStyles = makeStyles()(theme => {
|
||||
deletedFileMessage: {
|
||||
...theme.typography.bodyShortRegular,
|
||||
fontStyle: 'italic',
|
||||
color: theme.palette.text02,
|
||||
color: theme.palette.fileSharingEmptyText,
|
||||
padding: theme.spacing(1, 0)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -39,14 +39,14 @@ const useStyles = makeStyles()(theme => {
|
||||
}
|
||||
},
|
||||
menuPanel: {
|
||||
backgroundColor: theme.palette.ui03,
|
||||
backgroundColor: theme.palette.chatInputBackground,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
boxShadow: theme.shadows[3],
|
||||
overflow: 'hidden'
|
||||
},
|
||||
copiedMessage: {
|
||||
position: 'fixed',
|
||||
backgroundColor: theme.palette.ui03,
|
||||
backgroundColor: theme.palette.chatInputBackground,
|
||||
color: 'white',
|
||||
padding: '4px 8px',
|
||||
borderRadius: '4px',
|
||||
|
||||
@@ -24,7 +24,7 @@ const useStyles = makeStyles()(theme => {
|
||||
backgroundColor: theme.palette.support05,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
...theme.typography.bodyShortRegular,
|
||||
color: theme.palette.text01
|
||||
color: theme.palette.chatRecipientText
|
||||
},
|
||||
|
||||
text: {
|
||||
|
||||
@@ -22,7 +22,7 @@ interface IProps extends ISubtitle {
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
messageContainer: {
|
||||
backgroundColor: theme.palette.ui02,
|
||||
backgroundColor: theme.palette.subtitleMessageBackground,
|
||||
borderRadius: '4px 12px 12px 12px',
|
||||
padding: '12px',
|
||||
maxWidth: '100%',
|
||||
@@ -39,7 +39,7 @@ const useStyles = makeStyles()(theme => {
|
||||
|
||||
messageHeader: {
|
||||
...theme.typography.labelBold,
|
||||
color: theme.palette.text02,
|
||||
color: theme.palette.subtitleMessageSender,
|
||||
whiteSpace: 'nowrap',
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
@@ -49,14 +49,14 @@ const useStyles = makeStyles()(theme => {
|
||||
|
||||
messageText: {
|
||||
...theme.typography.bodyShortRegular,
|
||||
color: theme.palette.text01,
|
||||
color: theme.palette.subtitleMessageText,
|
||||
whiteSpace: 'pre-wrap',
|
||||
wordBreak: 'break-word'
|
||||
},
|
||||
|
||||
timestamp: {
|
||||
...theme.typography.labelRegular,
|
||||
color: theme.palette.text03,
|
||||
color: theme.palette.subtitleMessageTime,
|
||||
marginTop: theme.spacing(1)
|
||||
},
|
||||
|
||||
|
||||
@@ -33,6 +33,23 @@ export const MESSAGE_TYPE_REMOTE = 'remote';
|
||||
|
||||
export const SMALL_WIDTH_THRESHOLD = 580;
|
||||
|
||||
/**
|
||||
* Drag handle dimensions for resizable chat.
|
||||
*/
|
||||
export const CHAT_DRAG_HANDLE_WIDTH = 9;
|
||||
export const CHAT_DRAG_HANDLE_HEIGHT = 100;
|
||||
|
||||
/**
|
||||
* Touch target size for chat drag handle on touch devices.
|
||||
* Provides adequate hit area (44px) for comfortable tapping.
|
||||
*/
|
||||
export const CHAT_TOUCH_HANDLE_SIZE = 44;
|
||||
|
||||
/**
|
||||
* Offset from edge for positioning the chat drag handle.
|
||||
*/
|
||||
export const CHAT_DRAG_HANDLE_OFFSET = 4;
|
||||
|
||||
|
||||
/**
|
||||
* Lobby message type.
|
||||
|
||||
@@ -227,7 +227,8 @@ class Conference extends AbstractConference<IProps, State> {
|
||||
const {
|
||||
_audioOnlyEnabled,
|
||||
_showLobby,
|
||||
_startCarMode
|
||||
_startCarMode,
|
||||
navigation
|
||||
} = this.props;
|
||||
|
||||
if (!prevProps._showLobby && _showLobby) {
|
||||
@@ -236,10 +237,10 @@ class Conference extends AbstractConference<IProps, State> {
|
||||
|
||||
if (prevProps._showLobby && !_showLobby) {
|
||||
if (_audioOnlyEnabled && _startCarMode) {
|
||||
return;
|
||||
navigation.navigate(screen.conference.carmode);
|
||||
} else {
|
||||
navigate(screen.conference.main);
|
||||
}
|
||||
|
||||
navigate(screen.conference.main);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import { setColorAlpha } from '../../../base/util/helpers';
|
||||
import { openChat, setFocusedTab } from '../../../chat/actions.web';
|
||||
import Chat from '../../../chat/components/web/Chat';
|
||||
import { ChatTabs } from '../../../chat/constants';
|
||||
import CustomPanel from '../../../custom-panel/components/web/CustomPanel';
|
||||
import { isFileUploadingEnabled, processFiles } from '../../../file-sharing/functions.any';
|
||||
import MainFilmstrip from '../../../filmstrip/components/web/MainFilmstrip';
|
||||
import ScreenshareFilmstrip from '../../../filmstrip/components/web/ScreenshareFilmstrip';
|
||||
@@ -326,6 +327,7 @@ class Conference extends AbstractConference<IProps, any> {
|
||||
{ _showVisitorsQueue && <VisitorsQueue />}
|
||||
</div>
|
||||
<ParticipantsPane />
|
||||
<CustomPanel />
|
||||
<ReactionAnimations />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -7,7 +7,7 @@ const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
timer: {
|
||||
...theme.typography.labelRegular,
|
||||
color: theme.palette.text01,
|
||||
color: theme.palette.conferenceTimerText,
|
||||
padding: '6px 8px',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
||||
boxSizing: 'border-box',
|
||||
|
||||
@@ -15,8 +15,8 @@ const useStyles = makeStyles()(theme => {
|
||||
},
|
||||
|
||||
message: {
|
||||
backgroundColor: theme.palette.uiBackground,
|
||||
color: theme.palette.text01,
|
||||
backgroundColor: theme.palette.conferenceNoticeBackground,
|
||||
color: theme.palette.conferenceNoticeText,
|
||||
padding: '3px',
|
||||
borderRadius: '5px'
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user