Compare commits
110 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3d21fa86c | ||
|
|
47a4c5aa88 | ||
|
|
efc25fd4ec | ||
|
|
8f8dd5f803 | ||
|
|
2a6981c58d | ||
|
|
eff8847586 | ||
|
|
166b9fede5 | ||
|
|
5dd7c3ad8d | ||
|
|
cc2402e61c | ||
|
|
66a582dd5f | ||
|
|
f0a6f5919e | ||
|
|
5ef0486eff | ||
|
|
b1a5417b63 | ||
|
|
e129e90dd8 | ||
|
|
5af7c9e7c7 | ||
|
|
9f924aef64 | ||
|
|
a0da11ea78 | ||
|
|
48cf604bd1 | ||
|
|
00c44fb38a | ||
|
|
062802b3e9 | ||
|
|
ce3d315d50 | ||
|
|
0e50679db5 | ||
|
|
3db081440e | ||
|
|
a9b2ca01a7 | ||
|
|
af4f27cbbf | ||
|
|
3f8848981d | ||
|
|
aa519f3c67 | ||
|
|
99f06b0322 | ||
|
|
e67290550c | ||
|
|
3c118f0cf7 | ||
|
|
9c2f4be17c | ||
|
|
dc8d0fd81b | ||
|
|
ae40dea7ec | ||
|
|
85380c8142 | ||
|
|
6560d9eb1a | ||
|
|
69099772e0 | ||
|
|
cf1a7f2e21 | ||
|
|
eeb1f4baaf | ||
|
|
568c989ff7 | ||
|
|
8451296f3a | ||
|
|
606358c51b | ||
|
|
fd0956bbc5 | ||
|
|
c01e363639 | ||
|
|
0114db7d2d | ||
|
|
e93dfb54d2 | ||
|
|
f1ee3604de | ||
|
|
d3111758b7 | ||
|
|
abdb85226f | ||
|
|
a56ef52eb0 | ||
|
|
01f20bf6c0 | ||
|
|
aae86ed830 | ||
|
|
172ff266a6 | ||
|
|
d7ecbff9ed | ||
|
|
f0f2ffe972 | ||
|
|
491b0991cb | ||
|
|
a961647e86 | ||
|
|
207554f067 | ||
|
|
b0ba366a2c | ||
|
|
047fc822d6 | ||
|
|
7d454645d0 | ||
|
|
fcb923f6db | ||
|
|
e3c34a7145 | ||
|
|
1c9b2a24d9 | ||
|
|
7ab94cb003 | ||
|
|
239095321a | ||
|
|
caf90d851e | ||
|
|
a74733f6bc | ||
|
|
440c617738 | ||
|
|
0f7ee3b860 | ||
|
|
dc15fbc8c9 | ||
|
|
d7e6c8c913 | ||
|
|
7c5c4d1870 | ||
|
|
2df8488c20 | ||
|
|
d270756443 | ||
|
|
8dd58d7e5c | ||
|
|
d148a81f91 | ||
|
|
9587dd7352 | ||
|
|
099dcd28c7 | ||
|
|
e925e7e060 | ||
|
|
e3d5c84b17 | ||
|
|
6f4ab0d3ab | ||
|
|
283b5d4504 | ||
|
|
6dc26392d7 | ||
|
|
4572e35339 | ||
|
|
6cad89b20c | ||
|
|
4c1168aaf7 | ||
|
|
57e79862a5 | ||
|
|
93a47e7009 | ||
|
|
d4f0300c82 | ||
|
|
4181780040 | ||
|
|
b19150bbba | ||
|
|
f11e1fac6b | ||
|
|
0269753f59 | ||
|
|
f6c95461e4 | ||
|
|
bfe65adb51 | ||
|
|
e031340ccb | ||
|
|
c70536996e | ||
|
|
a71a1c5c93 | ||
|
|
f7fc03cdc9 | ||
|
|
52dccff229 | ||
|
|
9c8692d99e | ||
|
|
69826b4de0 | ||
|
|
f2a7de2d8e | ||
|
|
2318f2c4a0 | ||
|
|
4e8146bc9c | ||
|
|
323dba620d | ||
|
|
8d56a8e8fc | ||
|
|
8ab7ee9298 | ||
|
|
33dd2758d7 | ||
|
|
d40e467b7d |
3
.github/workflows/build.yaml
vendored
3
.github/workflows/build.yaml
vendored
@@ -23,9 +23,6 @@ jobs:
|
||||
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
||||
SENTRY_URL: ${{ secrets.SENTRY_URL }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
# This appears to be necessary to stop Vite from OOMing
|
||||
# https://github.com/vitejs/vite/issues/2433
|
||||
NODE_OPTIONS: "--max-old-space-size=16384"
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
||||
3
.github/workflows/publish.yaml
vendored
3
.github/workflows/publish.yaml
vendored
@@ -40,9 +40,6 @@ jobs:
|
||||
SENTRY_URL: ${{ secrets.SENTRY_URL }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
VITE_APP_VERSION: ${{ github.event.release.tag_name }}
|
||||
# This appears to be necessary to stop Vite from OOMing
|
||||
# https://github.com/vitejs/vite/issues/2433
|
||||
NODE_OPTIONS: "--max-old-space-size=16384"
|
||||
|
||||
- name: Create Tarball
|
||||
env:
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"build": "NODE_OPTIONS=--max-old-space-size=16384 vite build",
|
||||
"serve": "vite preview",
|
||||
"storybook": "start-storybook -p 6006",
|
||||
"build-storybook": "build-storybook",
|
||||
@@ -53,7 +53,7 @@
|
||||
"i18next-browser-languagedetector": "^6.1.8",
|
||||
"i18next-http-backend": "^1.4.4",
|
||||
"lodash": "^4.17.21",
|
||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#261bc81554580b442769a65ceed2b154178fbe1c",
|
||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#3cfad3cdeb7b19b8e0e7015784efd803cb9542f1",
|
||||
"matrix-widget-api": "^1.3.1",
|
||||
"mermaid": "^8.13.8",
|
||||
"normalize.css": "^8.0.1",
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
"Create account": "Създай акаунт",
|
||||
"Debug log": "Debug логове",
|
||||
"Debug log request": "Заявка за debug логове",
|
||||
"Description (optional)": "Описание (незадължително)",
|
||||
"Details": "Детайли",
|
||||
"Developer": "Разработчик",
|
||||
"Display name": "Име/псевдоним",
|
||||
@@ -34,7 +33,6 @@
|
||||
"Full screen": "Цял екран",
|
||||
"Go": "Напред",
|
||||
"Grid layout menu": "Меню \"решетков изглед\"",
|
||||
"Having trouble? Help us fix it.": "Имате проблем? Помогнете да го поправим.",
|
||||
"Home": "Начало",
|
||||
"Include debug logs": "Включи debug логове",
|
||||
"Incompatible versions": "Несъвместими версии",
|
||||
@@ -46,7 +44,6 @@
|
||||
"Join call now": "Влез в разговора сега",
|
||||
"Join existing call?": "Присъединяване към съществуващ разговор?",
|
||||
"Leave": "Напусни",
|
||||
"Loading room…": "Напускане на стаята…",
|
||||
"Loading…": "Зареждане…",
|
||||
"Local volume": "Локална сила на звука",
|
||||
"Logging in…": "Влизане…",
|
||||
@@ -56,7 +53,6 @@
|
||||
"Microphone permissions needed to join the call.": "Необходими са разрешения за микрофона за да можете да се присъедините в разговора.",
|
||||
"Microphone {{n}}": "Микрофон {{n}}",
|
||||
"More": "Още",
|
||||
"More menu": "Мено \"още\"",
|
||||
"Mute microphone": "Заглуши микрофона",
|
||||
"No": "Не",
|
||||
"Not now, return to home screen": "Не сега, върни се на началния екран",
|
||||
@@ -77,8 +73,6 @@
|
||||
"Release to stop": "Отпуснете за да спрете",
|
||||
"Remove": "Премахни",
|
||||
"Return to home screen": "Връщане на началния екран",
|
||||
"Save": "Запази",
|
||||
"Saving…": "Запазване…",
|
||||
"Select an option": "Изберете опция",
|
||||
"Send debug logs": "Изпратете debug логове",
|
||||
"Sending…": "Изпращане…",
|
||||
@@ -93,7 +87,6 @@
|
||||
"Spotlight": "Прожектор",
|
||||
"Stop sharing screen": "Спри споделянето на екрана",
|
||||
"Submit feedback": "Изпрати обратна връзка",
|
||||
"Submitting feedback…": "Изпращане на обратна връзка…",
|
||||
"Take me Home": "Отиди в Начало",
|
||||
"Talk over speaker": "Говорете заедно с говорителя",
|
||||
"Talking…": "Говорене…",
|
||||
@@ -104,7 +97,6 @@
|
||||
"Turn off camera": "Изключи камерата",
|
||||
"Turn on camera": "Включи камерата",
|
||||
"Unmute microphone": "Включи микрофона",
|
||||
"User ID": "Потребителски идентификатор",
|
||||
"User menu": "Потребителско меню",
|
||||
"Username": "Потребителско име",
|
||||
"Version: {{version}}": "Версия: {{version}}",
|
||||
|
||||
@@ -26,14 +26,12 @@
|
||||
"Version: {{version}}": "Verze: {{version}}",
|
||||
"Username": "Uživatelské jméno",
|
||||
"User menu": "Uživatelské menu",
|
||||
"User ID": "ID uživatele",
|
||||
"Unmute microphone": "Zapnout mikrofon",
|
||||
"Turn on camera": "Zapnout kameru",
|
||||
"Turn off camera": "Vypnout kameru",
|
||||
"This call already exists, would you like to join?": "Tento hovor již existuje, chcete se připojit?",
|
||||
"Thanks! We'll get right on it.": "Děkujeme! Hned se na to vrhneme.",
|
||||
"Take me Home": "Domovská obrazovka",
|
||||
"Submitting feedback…": "Odesílání zpětné vazby…",
|
||||
"Submit feedback": "Dát feedback",
|
||||
"Stop sharing screen": "Zastavit sdílení obrazovek",
|
||||
"Speaker {{n}}": "Reproduktor {{n}}",
|
||||
@@ -48,8 +46,6 @@
|
||||
"Sending debug logs…": "Posílání ladícího záznamu…",
|
||||
"Send debug logs": "Poslat ladící záznam",
|
||||
"Select an option": "Vyberte možnost",
|
||||
"Saving…": "Ukládání…",
|
||||
"Save": "Uložit",
|
||||
"Return to home screen": "Vrátit se na domácí obrazovku",
|
||||
"Remove": "Odstranit",
|
||||
"Registering…": "Registrování…",
|
||||
@@ -71,7 +67,6 @@
|
||||
"Logging in…": "Přihlašování se…",
|
||||
"Local volume": "Lokální hlasitost",
|
||||
"Loading…": "Načítání…",
|
||||
"Loading room…": "Načítání místnosti…",
|
||||
"Leave": "Opustit hovor",
|
||||
"Join call now": "Připojit se k hovoru",
|
||||
"Join call": "Připojit se k hovoru",
|
||||
@@ -103,11 +98,9 @@
|
||||
"Press and hold spacebar to talk over {{name}}": "Zmáčkněte a držte mezerník, abyste mluvili přes {{name}}",
|
||||
"Other users are trying to join this call from incompatible versions. These users should ensure that they have refreshed their browsers:<1>{userLis}</1>": "Ostatní uživatelé se pokoušejí připojit k tomuto hovoru s nekompatibilních verzí. Tito uživatelé by se měli ujistit, že stránku načetli znovu:<1>{userLis}</1>",
|
||||
"Not registered yet? <2>Create an account</2>": "Nejste registrovaní? <2>Vytvořit účet</2>",
|
||||
"More menu": "Další možnosti",
|
||||
"Join existing call?": "Připojit se k existujícimu hovoru?",
|
||||
"Include debug logs": "Zahrnout ladící záznamy",
|
||||
"Home": "Domov",
|
||||
"Having trouble? Help us fix it.": "Máte problémy? Pomozte nám je spravit.",
|
||||
"Grid layout menu": "Menu rozložení",
|
||||
"Go": "Pokračovat",
|
||||
"Full screen": "Zvětšit na celou obrazovku",
|
||||
@@ -119,7 +112,6 @@
|
||||
"Display name": "Zobrazované jméno",
|
||||
"Developer": "Vývojář",
|
||||
"Details": "Detaily",
|
||||
"Description (optional)": "Popis (nepovinný)",
|
||||
"Debug log request": "Žádost o protokoly ladění",
|
||||
"Debug log": "Protokoly ladění",
|
||||
"Create account": "Vytvořit účet",
|
||||
@@ -134,5 +126,8 @@
|
||||
"{{name}} (Waiting for video...)": "{{name}} (Čekání na video...)",
|
||||
"This feature is only supported on Firefox.": "Tato funkce je podporována jen ve Firefoxu.",
|
||||
"<0>Submitting debug logs will help us track down the problem.</0>": "<0>Odeslání ladících záznamů nám pomůže diagnostikovat problém.</0>",
|
||||
"<0>Oops, something's gone wrong.</0>": "<0>Oops, něco se pokazilo.</0>"
|
||||
"<0>Oops, something's gone wrong.</0>": "<0>Oops, něco se pokazilo.</0>",
|
||||
"Use the upcoming grid system": "Používat nový systém pro zobrazení videí",
|
||||
"Expose developer settings in the settings window.": "Zobrazit vývojářské nastavení.",
|
||||
"Developer Settings": "Vývojářské nastavení"
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
"Create account": "Konto erstellen",
|
||||
"Debug log": "Debug-Protokoll",
|
||||
"Debug log request": "Debug-Log Anfrage",
|
||||
"Description (optional)": "Beschreibung (optional)",
|
||||
"Details": "Details",
|
||||
"Developer": "Entwickler",
|
||||
"Display name": "Anzeigename",
|
||||
@@ -33,7 +32,6 @@
|
||||
"Full screen": "Vollbild",
|
||||
"Go": "Los geht’s",
|
||||
"Grid layout menu": "Grid-Layout-Menü",
|
||||
"Having trouble? Help us fix it.": "Du hast ein Problem? Hilf uns, es zu beheben.",
|
||||
"Home": "Startseite",
|
||||
"Include debug logs": "Debug-Protokolle einschließen",
|
||||
"Incompatible versions": "Inkompatible Versionen",
|
||||
@@ -45,7 +43,6 @@
|
||||
"Join call now": "Anruf beitreten",
|
||||
"Join existing call?": "An bestehendem Anruf teilnehmen?",
|
||||
"Leave": "Verlassen",
|
||||
"Loading room…": "Lade Raum …",
|
||||
"Loading…": "Lade …",
|
||||
"Local volume": "Lokale Lautstärke",
|
||||
"Logging in…": "Anmelden …",
|
||||
@@ -55,7 +52,6 @@
|
||||
"Microphone permissions needed to join the call.": "Mikrofon-Berechtigung ist erforderlich, um dem Anruf beizutreten.",
|
||||
"Microphone {{n}}": "Mikrofon {{n}}",
|
||||
"More": "Mehr",
|
||||
"More menu": "Weiteres Menü",
|
||||
"Mute microphone": "Mikrofon stummschalten",
|
||||
"No": "Nein",
|
||||
"Not now, return to home screen": "Nicht jetzt, zurück zum Startbildschirm",
|
||||
@@ -76,8 +72,6 @@
|
||||
"Release to stop": "Loslassen zum Stoppen",
|
||||
"Remove": "Entfernen",
|
||||
"Return to home screen": "Zurück zum Startbildschirm",
|
||||
"Save": "Speichern",
|
||||
"Saving…": "Speichere …",
|
||||
"Select an option": "Wähle eine Option",
|
||||
"Send debug logs": "Debug-Logs senden",
|
||||
"Sending…": "Senden …",
|
||||
@@ -92,7 +86,6 @@
|
||||
"Spotlight": "Rampenlicht",
|
||||
"Stop sharing screen": "Beenden der Bildschirmfreigabe",
|
||||
"Submit feedback": "Rückmeldung geben",
|
||||
"Submitting feedback…": "Sende Rückmeldung …",
|
||||
"Take me Home": "Zurück zur Startseite",
|
||||
"Talk over speaker": "Aktiven Sprecher verdrängen und sprechen",
|
||||
"Talking…": "Sprechen …",
|
||||
@@ -103,7 +96,6 @@
|
||||
"Turn off camera": "Kamera ausschalten",
|
||||
"Turn on camera": "Kamera einschalten",
|
||||
"Unmute microphone": "Mikrofon aktivieren",
|
||||
"User ID": "Benutzer-ID",
|
||||
"User menu": "Benutzermenü",
|
||||
"Username": "Benutzername",
|
||||
"Version: {{version}}": "Version: {{version}}",
|
||||
@@ -139,5 +131,11 @@
|
||||
"Expose developer settings in the settings window.": "Zeige die Entwicklereinstellungen im Einstellungsfenster.",
|
||||
"Developer Settings": "Entwicklereinstellungen",
|
||||
"By participating in this beta, you consent to the collection of anonymous data, which we use to improve the product. You can find more information about which data we track in our <2>Privacy Policy</2> and our <5>Cookie Policy</5>.": "Mit der Teilnahme an der Beta akzeptierst du die Sammlung von anonymen Daten, die wir zur Verbesserung des Produkts verwenden. Weitere Informationen zu den von uns erhobenen Daten findest du in unserer <2>Datenschutzerklärung</2> und unseren <5>Cookie-Richtlinien</5>.",
|
||||
"<0></0><1></1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.": "<0></0><1></1>Du kannst deine Zustimmung durch Abwählen dieses Kästchens zurückziehen. Falls du dich aktuell in einem Anruf befindest, wird diese Einstellung nach dem Ende des Anrufs wirksam."
|
||||
"<0></0><1></1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.": "<0></0><1></1>Du kannst deine Zustimmung durch Abwählen dieses Kästchens zurückziehen. Falls du dich aktuell in einem Anruf befindest, wird diese Einstellung nach dem Ende des Anrufs wirksam.",
|
||||
"Feedback": "Rückmeldung",
|
||||
"If you are experiencing issues or simply would like to provide some feedback, please send us a short description below.": "Falls du auf Probleme stößt oder einfach nur eine Rückmeldung geben möchtest, sende uns bitte eine kurze Beschreibung.",
|
||||
"Your feedback": "Deine Rückmeldung",
|
||||
"Thanks, we received your feedback!": "Danke, wir haben deine Rückmeldung erhalten!",
|
||||
"Submitting…": "Sende …",
|
||||
"Submit": "Absenden"
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
"Share screen": "Κοινή χρήση οθόνης",
|
||||
"Sending…": "Αποστολή…",
|
||||
"Select an option": "Επιλέξτε μια επιλογή",
|
||||
"Saving…": "Αποθήκευση…",
|
||||
"Remove": "Αφαίρεση",
|
||||
"Registering…": "Εγγραφή…",
|
||||
"Press and hold to talk": "Πατήστε παρατεταμένα για να μιλήσετε",
|
||||
@@ -52,7 +51,6 @@
|
||||
"Spatial audio": "Χωρικός ήχος",
|
||||
"Sign out": "Αποσύνδεση",
|
||||
"Settings": "Ρυθμίσεις",
|
||||
"Save": "Αποθήκευση",
|
||||
"Return to home screen": "Επιστροφή στην αρχική οθόνη",
|
||||
"Register": "Εγγραφή",
|
||||
"Profile": "Προφίλ",
|
||||
@@ -62,14 +60,12 @@
|
||||
"Not now, return to home screen": "Όχι τώρα, επιστροφή στην αρχική οθόνη",
|
||||
"No": "Όχι",
|
||||
"Mute microphone": "Σίγαση μικροφώνου",
|
||||
"More menu": "Μενού περισσότερα",
|
||||
"More": "Περισσότερα",
|
||||
"Microphone permissions needed to join the call.": "Απαιτούνται δικαιώματα μικροφώνου για συμμετοχή στην κλήση.",
|
||||
"Microphone {{n}}": "Μικρόφωνο {{n}}",
|
||||
"Microphone": "Μικρόφωνο",
|
||||
"Login": "Σύνδεση",
|
||||
"Loading…": "Φόρτωση…",
|
||||
"Loading room…": "Φόρτωση δωματίου…",
|
||||
"Leave": "Αποχώρηση",
|
||||
"Join existing call?": "Συμμετοχή στην υπάρχουσα κλήση;",
|
||||
"Join call now": "Συμμετοχή στην κλήση τώρα",
|
||||
@@ -78,7 +74,6 @@
|
||||
"Full screen": "Πλήρη οθόνη",
|
||||
"Exit full screen": "Έξοδος από πλήρη οθόνη",
|
||||
"Details": "Λεπτομέρειες",
|
||||
"Description (optional)": "Περιγραφή (προαιρετική)",
|
||||
"Create account": "Δημιουργία λογαριασμού",
|
||||
"Copy and share this call link": "Αντιγράψτε και μοιραστείτε αυτόν τον σύνδεσμο κλήσης",
|
||||
"Copy": "Αντιγραφή",
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
{
|
||||
"{{count}} people connected|one": "{{count}} person connected",
|
||||
"{{count}} people connected|other": "{{count}} people connected",
|
||||
"{{displayName}}, your call is now ended": "{{displayName}}, your call is now ended",
|
||||
"{{count}} stars|one": "{{count}} star",
|
||||
"{{count}} stars|other": "{{count}} stars",
|
||||
"{{displayName}}, your call has ended.": "{{displayName}}, your call has ended.",
|
||||
"{{name}} (Connecting...)": "{{name}} (Connecting...)",
|
||||
"{{name}} (Waiting for video...)": "{{name}} (Waiting for video...)",
|
||||
"{{name}} is presenting": "{{name}} is presenting",
|
||||
@@ -14,6 +16,8 @@
|
||||
"<0>Join call now</0><1>Or</1><2>Copy call link and join later</2>": "<0>Join call now</0><1>Or</1><2>Copy call link and join later</2>",
|
||||
"<0>Oops, something's gone wrong.</0>": "<0>Oops, something's gone wrong.</0>",
|
||||
"<0>Submitting debug logs will help us track down the problem.</0>": "<0>Submitting debug logs will help us track down the problem.</0>",
|
||||
"<0>Thanks for your feedback!</0>": "<0>Thanks for your feedback!</0>",
|
||||
"<0>We'd love to hear your feedback so we can improve your experience.</0>": "<0>We'd love to hear your feedback so we can improve your experience.</0>",
|
||||
"<0>Why not finish by setting up a password to keep your account?</0><1>You'll be able to keep your name and set an avatar for use on future calls</1>": "<0>Why not finish by setting up a password to keep your account?</0><1>You'll be able to keep your name and set an avatar for use on future calls</1>",
|
||||
"Accept camera/microphone permissions to join the call.": "Accept camera/microphone permissions to join the call.",
|
||||
"Accept microphone permissions to join the call.": "Accept microphone permissions to join the call.",
|
||||
@@ -38,7 +42,6 @@
|
||||
"Create account": "Create account",
|
||||
"Debug log": "Debug log",
|
||||
"Debug log request": "Debug log request",
|
||||
"Description (optional)": "Description (optional)",
|
||||
"Details": "Details",
|
||||
"Developer": "Developer",
|
||||
"Developer Settings": "Developer Settings",
|
||||
@@ -47,13 +50,15 @@
|
||||
"Element Call Home": "Element Call Home",
|
||||
"Exit full screen": "Exit full screen",
|
||||
"Expose developer settings in the settings window.": "Expose developer settings in the settings window.",
|
||||
"Feedback": "Feedback",
|
||||
"Fetching group call timed out.": "Fetching group call timed out.",
|
||||
"Freedom": "Freedom",
|
||||
"Full screen": "Full screen",
|
||||
"Go": "Go",
|
||||
"Grid layout menu": "Grid layout menu",
|
||||
"Having trouble? Help us fix it.": "Having trouble? Help us fix it.",
|
||||
"Home": "Home",
|
||||
"How did it go?": "How did it go?",
|
||||
"If you are experiencing issues or simply would like to provide some feedback, please send us a short description below.": "If you are experiencing issues or simply would like to provide some feedback, please send us a short description below.",
|
||||
"Include debug logs": "Include debug logs",
|
||||
"Incompatible versions": "Incompatible versions",
|
||||
"Incompatible versions!": "Incompatible versions!",
|
||||
@@ -64,7 +69,6 @@
|
||||
"Join call now": "Join call now",
|
||||
"Join existing call?": "Join existing call?",
|
||||
"Leave": "Leave",
|
||||
"Loading room…": "Loading room…",
|
||||
"Loading…": "Loading…",
|
||||
"Local volume": "Local volume",
|
||||
"Logging in…": "Logging in…",
|
||||
@@ -74,7 +78,6 @@
|
||||
"Microphone {{n}}": "Microphone {{n}}",
|
||||
"Microphone permissions needed to join the call.": "Microphone permissions needed to join the call.",
|
||||
"More": "More",
|
||||
"More menu": "More menu",
|
||||
"Mute microphone": "Mute microphone",
|
||||
"No": "No",
|
||||
"Not now, return to home screen": "Not now, return to home screen",
|
||||
@@ -95,8 +98,6 @@
|
||||
"Release to stop": "Release to stop",
|
||||
"Remove": "Remove",
|
||||
"Return to home screen": "Return to home screen",
|
||||
"Save": "Save",
|
||||
"Saving…": "Saving…",
|
||||
"Select an option": "Select an option",
|
||||
"Send debug logs": "Send debug logs",
|
||||
"Sending debug logs…": "Sending debug logs…",
|
||||
@@ -111,11 +112,13 @@
|
||||
"Speaker {{n}}": "Speaker {{n}}",
|
||||
"Spotlight": "Spotlight",
|
||||
"Stop sharing screen": "Stop sharing screen",
|
||||
"Submit": "Submit",
|
||||
"Submit feedback": "Submit feedback",
|
||||
"Submitting feedback…": "Submitting feedback…",
|
||||
"Submitting…": "Submitting…",
|
||||
"Take me Home": "Take me Home",
|
||||
"Talk over speaker": "Talk over speaker",
|
||||
"Talking…": "Talking…",
|
||||
"Thanks, we received your feedback!": "Thanks, we received your feedback!",
|
||||
"Thanks! We'll get right on it.": "Thanks! We'll get right on it.",
|
||||
"This call already exists, would you like to join?": "This call already exists, would you like to join?",
|
||||
"This feature is only supported on Firefox.": "This feature is only supported on Firefox.",
|
||||
@@ -125,7 +128,6 @@
|
||||
"Turn on camera": "Turn on camera",
|
||||
"Unmute microphone": "Unmute microphone",
|
||||
"Use the upcoming grid system": "Use the upcoming grid system",
|
||||
"User ID": "User ID",
|
||||
"User menu": "User menu",
|
||||
"Username": "Username",
|
||||
"Version: {{version}}": "Version: {{version}}",
|
||||
@@ -139,5 +141,6 @@
|
||||
"WebRTC is not supported or is being blocked in this browser.": "WebRTC is not supported or is being blocked in this browser.",
|
||||
"Yes, join call": "Yes, join call",
|
||||
"You can't talk at the same time": "You can't talk at the same time",
|
||||
"Your feedback": "Your feedback",
|
||||
"Your recent calls": "Your recent calls"
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
"Version: {{version}}": "Versión: {{version}}",
|
||||
"Username": "Nombre de usuario",
|
||||
"User menu": "Menú de usuario",
|
||||
"User ID": "ID de usuario",
|
||||
"Unmute microphone": "Desilenciar el micrófono",
|
||||
"Turn on camera": "Encender la cámara",
|
||||
"Turn off camera": "Apagar la cámara",
|
||||
@@ -33,7 +32,6 @@
|
||||
"Talking…": "Hablando…",
|
||||
"Talk over speaker": "Hablar por encima",
|
||||
"Take me Home": "Volver al inicio",
|
||||
"Submitting feedback…": "Enviando comentarios…",
|
||||
"Submit feedback": "Enviar comentarios",
|
||||
"Stop sharing screen": "Dejar de compartir pantalla",
|
||||
"Spotlight": "Foco",
|
||||
@@ -49,8 +47,6 @@
|
||||
"Sending debug logs…": "Enviando registros de depuración…",
|
||||
"Send debug logs": "Enviar registros de depuración",
|
||||
"Select an option": "Selecciona una opción",
|
||||
"Saving…": "Guardando…",
|
||||
"Save": "Guardar",
|
||||
"Return to home screen": "Volver a la pantalla de inicio",
|
||||
"Remove": "Eliminar",
|
||||
"Release to stop": "Suelta para parar",
|
||||
@@ -68,7 +64,6 @@
|
||||
"Not now, return to home screen": "Ahora no, volver a la pantalla de inicio",
|
||||
"No": "No",
|
||||
"Mute microphone": "Silenciar micrófono",
|
||||
"More menu": "Menú Más",
|
||||
"More": "Más",
|
||||
"Microphone permissions needed to join the call.": "Se necesitan permisos del micrófono para unirse a la llamada.",
|
||||
"Microphone {{n}}": "Micrófono {{n}}",
|
||||
@@ -77,7 +72,6 @@
|
||||
"Logging in…": "Iniciando sesión…",
|
||||
"Local volume": "Volumen local",
|
||||
"Loading…": "Cargando…",
|
||||
"Loading room…": "Cargando sala…",
|
||||
"Leave": "Abandonar",
|
||||
"Join existing call?": "¿Unirse a llamada existente?",
|
||||
"Join call now": "Unirse a la llamada ahora",
|
||||
@@ -89,7 +83,6 @@
|
||||
"Incompatible versions": "Versiones incompatibles",
|
||||
"Include debug logs": "Incluir registros de depuración",
|
||||
"Home": "Inicio",
|
||||
"Having trouble? Help us fix it.": "¿Tienes problemas? Ayúdanos a resolverlos.",
|
||||
"Grid layout menu": "Menú de distribución de cuadrícula",
|
||||
"Go": "Comenzar",
|
||||
"Full screen": "Pantalla completa",
|
||||
@@ -100,7 +93,6 @@
|
||||
"Display name": "Nombre a mostrar",
|
||||
"Developer": "Desarrollador",
|
||||
"Details": "Detalles",
|
||||
"Description (optional)": "Descripción (opcional)",
|
||||
"Debug log request": "Petición de registros de depuración",
|
||||
"Debug log": "Registro de depuración",
|
||||
"Create account": "Crear cuenta",
|
||||
@@ -136,5 +128,8 @@
|
||||
"<0>Submitting debug logs will help us track down the problem.</0>": "<0>Subir los registros de depuración nos ayudará a encontrar el problema.</0>",
|
||||
"<0>Oops, something's gone wrong.</0>": "<0>Ups, algo ha salido mal.</0>",
|
||||
"Expose developer settings in the settings window.": "Muestra los ajustes de desarrollador en la ventana de ajustes.",
|
||||
"Developer Settings": "Ajustes de desarrollador"
|
||||
"Developer Settings": "Ajustes de desarrollador",
|
||||
"Use the upcoming grid system": "Utilizar el próximo sistema de cuadrícula",
|
||||
"By participating in this beta, you consent to the collection of anonymous data, which we use to improve the product. You can find more information about which data we track in our <2>Privacy Policy</2> and our <5>Cookie Policy</5>.": "Al participar en esta beta, consientes a la recogida de datos anónimos, los cuales usaremos para mejorar el producto. Puedes encontrar más información sobre que datos recogemos en nuestra <2>Política de privacidad</2> y en nuestra <5>Política sobre Cookies</5>.",
|
||||
"<0></0><1></1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.": "<0></0><1></1>Puedes retirar tu consentimiento desmarcando esta casilla. Si estás en una llamada, este ajuste se aplicará al final de esta."
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
"Incompatible versions": "Ühildumatud versioonid",
|
||||
"Include debug logs": "Lisa veatuvastuslogid",
|
||||
"Home": "Avavaatesse",
|
||||
"Having trouble? Help us fix it.": "Kas on probleeme? Aita meil asja parandada.",
|
||||
"Grid layout menu": "Ruudustikvaate menüü",
|
||||
"Go": "Jätka",
|
||||
"Full screen": "Täisekraan",
|
||||
@@ -31,7 +30,6 @@
|
||||
"Display name": "Kuvatav nimi",
|
||||
"Developer": "Arendaja",
|
||||
"Details": "Täpsemalt",
|
||||
"Description (optional)": "Kirjeldus (valikuline)",
|
||||
"Debug log request": "Veaotsingulogi päring",
|
||||
"Debug log": "Veaotsingulogi",
|
||||
"Create account": "Loo konto",
|
||||
@@ -60,7 +58,6 @@
|
||||
"Mute microphone": "Summuta mikrofon",
|
||||
"Your recent calls": "Hiljutised kõned",
|
||||
"You can't talk at the same time": "Üheaegselt ei saa rääkida",
|
||||
"More menu": "Rohkem valikuid",
|
||||
"More": "Rohkem",
|
||||
"Microphone permissions needed to join the call.": "Kõnega liitumiseks on vaja lubada mikrofoni kasutamine.",
|
||||
"Microphone {{n}}": "Mikrofon {{n}}",
|
||||
@@ -70,15 +67,12 @@
|
||||
"Logging in…": "Sisselogimine …",
|
||||
"Local volume": "Kohalik helitugevus",
|
||||
"Loading…": "Laadimine …",
|
||||
"Loading room…": "Ruumi laadimine …",
|
||||
"Leave": "Lahku",
|
||||
"Join existing call?": "Liitu juba käimasoleva kõnega?",
|
||||
"Join call now": "Kõnega liitumine",
|
||||
"Join call": "Kõnega liitumine",
|
||||
"User ID": "Kasutajatunnus",
|
||||
"Turn on camera": "Lülita kaamera sisse",
|
||||
"Turn off camera": "Lülita kaamera välja",
|
||||
"Submitting feedback…": "Tagasiside saatmine…",
|
||||
"Take me Home": "Mine avalehele",
|
||||
"Submit feedback": "Jaga tagasisidet",
|
||||
"Stop sharing screen": "Lõpeta ekraani jagamine",
|
||||
@@ -95,8 +89,6 @@
|
||||
"Sending debug logs…": "Veaotsingulogide saatmine…",
|
||||
"Send debug logs": "Saada veaotsingulogid",
|
||||
"Select an option": "Vali oma eelistus",
|
||||
"Saving…": "Salvestamine…",
|
||||
"Save": "Salvesta",
|
||||
"Return to home screen": "Tagasi avalehele",
|
||||
"Remove": "Eemalda",
|
||||
"Release to stop": "Peatamiseks vabasta klahv",
|
||||
@@ -139,5 +131,11 @@
|
||||
"Expose developer settings in the settings window.": "Näita seadistuste aknas arendajale vajalikke seadeid.",
|
||||
"Developer Settings": "Arendaja seadistused",
|
||||
"By participating in this beta, you consent to the collection of anonymous data, which we use to improve the product. You can find more information about which data we track in our <2>Privacy Policy</2> and our <5>Cookie Policy</5>.": "Nõustudes selle beetaversiooni kasutamisega sa nõustud ka toote arendamiseks kasutatavate anonüümsete andmete kogumisega. Täpsemat teavet kogutavate andmete kohta leiad meie <2>Privaatsuspoliitikast</2> ja meie <5>Küpsiste kasutamise reeglitest</5>.",
|
||||
"<0></0><1></1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.": "<0></0><1></1>Sa võid selle valiku eelmaldamisega alati oma nõusoleku tagasi võtta. Kui sul parasjagu on kõne pooleli, siis seadistuste muudatus jõustub pärast kõne lõppu."
|
||||
"<0></0><1></1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.": "<0></0><1></1>Sa võid selle valiku eelmaldamisega alati oma nõusoleku tagasi võtta. Kui sul parasjagu on kõne pooleli, siis seadistuste muudatus jõustub pärast kõne lõppu.",
|
||||
"Your feedback": "Sinu tagasiside",
|
||||
"Thanks, we received your feedback!": "Tänud, me oleme sinu tagasiside kätte saanud!",
|
||||
"Submitting…": "Saadan…",
|
||||
"Submit": "Saada",
|
||||
"If you are experiencing issues or simply would like to provide some feedback, please send us a short description below.": "Kui selle rakenduse kasutamisel tekib sul probleeme või lihtsalt soovid oma arvamust avaldada, siis palun täida alljärgnev lühike kirjeldus.",
|
||||
"Feedback": "Tagasiside"
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
"Video call": "تماس تصویری",
|
||||
"Video": "ویدیو",
|
||||
"Username": "نام کاربری",
|
||||
"User ID": "آی دی کاربر",
|
||||
"Turn on camera": "روشن کردن دوربین",
|
||||
"Turn off camera": "خاموش کردن دوربین",
|
||||
"Take me Home": "مرا به خانه ببر",
|
||||
@@ -11,7 +10,6 @@
|
||||
"Sign out": "خروج",
|
||||
"Sign in": "ورود",
|
||||
"Settings": "تنظیمات",
|
||||
"Save": "ذخیره",
|
||||
"Profile": "پروفایل",
|
||||
"Password": "رمز عبور",
|
||||
"No": "خیر",
|
||||
@@ -21,7 +19,6 @@
|
||||
"Login to your account": "به حساب کاربری خود وارد شوید",
|
||||
"Login": "ورود",
|
||||
"Loading…": "بارگزاری…",
|
||||
"Loading room…": "بارگزاری اتاق…",
|
||||
"Leave": "خروج",
|
||||
"Join existing call?": "پیوست به تماس؟",
|
||||
"Join call now": "الان به تماس بپیوند",
|
||||
@@ -37,7 +34,6 @@
|
||||
"Display name": "نام نمایشی",
|
||||
"Developer": "توسعه دهنده",
|
||||
"Details": "جزئیات",
|
||||
"Description (optional)": "توضیحات (اختیاری)",
|
||||
"Debug log request": "درخواست لاگ عیبیابی",
|
||||
"Debug log": "لاگ عیبیابی",
|
||||
"Create account": "ساخت حساب کاربری",
|
||||
@@ -81,7 +77,6 @@
|
||||
"Sending debug logs…": "در حال ارسال باگهای عیبیابی…",
|
||||
"Send debug logs": "ارسال لاگهای عیبیابی",
|
||||
"Select an option": "یک گزینه را انتخاب کنید",
|
||||
"Saving…": "در حال ذخیره…",
|
||||
"Return to home screen": "برگشت به صفحه اصلی",
|
||||
"Remove": "حذف",
|
||||
"Release to stop": "برای توقف رها کنید",
|
||||
@@ -98,12 +93,10 @@
|
||||
"Other users are trying to join this call from incompatible versions. These users should ensure that they have refreshed their browsers:<1>{userLis}</1>": "کاربران دیگر تلاش میکنند با ورژنهای ناسازگار به مکالمه بپیوندند. این کاربران باید از بروزرسانی مرورگرشان اطمینان داشته باشند:<1>{userLis}</1>",
|
||||
"Not registered yet? <2>Create an account</2>": "هنوز ثبتنام نکردهاید؟ <2>ساخت حساب کاربری</2>",
|
||||
"Not now, return to home screen": "الان نه، به صفحه اصلی برگردید",
|
||||
"More menu": "تنظیمات بیشتر",
|
||||
"Microphone permissions needed to join the call.": "برای پیوستن به مکالمه دسترسی به میکروفون نیاز است.",
|
||||
"Microphone {{n}}": "میکروفون {{n}}",
|
||||
"Logging in…": "ورود…",
|
||||
"Include debug logs": "شامل لاگهای عیبیابی",
|
||||
"Having trouble? Help us fix it.": "با مشکلی رو به رو شدید؟ به ما کمک کنید رفعش کنیم.",
|
||||
"Grid layout menu": "منوی طرحبندی شبکهای",
|
||||
"Fetching group call timed out.": "زمان اتصال به مکالمه گروهی تمام شد.",
|
||||
"You can't talk at the same time": "نمی توانید همزمان صحبت کنید",
|
||||
@@ -123,7 +116,6 @@
|
||||
"Thanks! We'll get right on it.": "با تشکر! ما به درستی آن را انجام خواهیم داد.",
|
||||
"Talking…": "در حال صحبت کردن…",
|
||||
"Talk over speaker": "روی بلندگو صحبت کنید",
|
||||
"Submitting feedback…": "در حال ارسال بازخورد…",
|
||||
"Submit feedback": "بازخورد ارائه دهید",
|
||||
"Stop sharing screen": "توقف اشتراکگذاری صفحه نمایش",
|
||||
"Spatial audio": "صدای جهتدار",
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
"Create account": "Créer un compte",
|
||||
"Debug log": "Journal de débogage",
|
||||
"Debug log request": "Demande d’un journal de débogage",
|
||||
"Description (optional)": "Description (facultatif)",
|
||||
"Details": "Informations",
|
||||
"Developer": "Développeur",
|
||||
"Display name": "Nom d’affichage",
|
||||
@@ -32,7 +31,6 @@
|
||||
"Full screen": "Plein écran",
|
||||
"Go": "Commencer",
|
||||
"Grid layout menu": "Menu en grille",
|
||||
"Having trouble? Help us fix it.": "Un problème ? Aidez nous à le résoudre.",
|
||||
"Home": "Accueil",
|
||||
"Include debug logs": "Inclure les journaux de débogage",
|
||||
"Incompatible versions": "Versions incompatibles",
|
||||
@@ -43,7 +41,6 @@
|
||||
"Join call now": "Rejoindre l’appel maintenant",
|
||||
"Join existing call?": "Rejoindre un appel existant ?",
|
||||
"Leave": "Partir",
|
||||
"Loading room…": "Chargement du salon…",
|
||||
"Loading…": "Chargement…",
|
||||
"Local volume": "Volume local",
|
||||
"Logging in…": "Connexion…",
|
||||
@@ -53,7 +50,6 @@
|
||||
"Microphone permissions needed to join the call.": "Accès au microphone requis pour rejoindre l’appel.",
|
||||
"Microphone {{n}}": "Microphone {{n}}",
|
||||
"More": "Plus",
|
||||
"More menu": "Menu plus",
|
||||
"Mute microphone": "Couper le micro",
|
||||
"No": "Non",
|
||||
"Not now, return to home screen": "Pas maintenant, retourner à l’accueil",
|
||||
@@ -74,8 +70,6 @@
|
||||
"Release to stop": "Relâcher pour arrêter",
|
||||
"Remove": "Supprimer",
|
||||
"Return to home screen": "Retour à l’accueil",
|
||||
"Save": "Enregistrer",
|
||||
"Saving…": "Enregistrement…",
|
||||
"Select an option": "Sélectionnez une option",
|
||||
"Send debug logs": "Envoyer les journaux de débogage",
|
||||
"Sending…": "Envoi…",
|
||||
@@ -88,7 +82,6 @@
|
||||
"Spotlight": "Premier plan",
|
||||
"Stop sharing screen": "Arrêter le partage d’écran",
|
||||
"Submit feedback": "Envoyer des retours",
|
||||
"Submitting feedback…": "Envoi des retours…",
|
||||
"Take me Home": "Retouner à l’accueil",
|
||||
"Talk over speaker": "Parler par dessus l’intervenant",
|
||||
"Thanks! We'll get right on it.": "Merci ! Nous allons nous y attaquer.",
|
||||
@@ -115,7 +108,6 @@
|
||||
"Version: {{version}}": "Version : {{version}}",
|
||||
"Username": "Nom d’utilisateur",
|
||||
"User menu": "Menu utilisateur",
|
||||
"User ID": "Identifiant utilisateur",
|
||||
"Unmute microphone": "Allumer le micro",
|
||||
"Turn on camera": "Allumer la caméra",
|
||||
"Turn off camera": "Couper la caméra",
|
||||
@@ -139,5 +131,11 @@
|
||||
"Expose developer settings in the settings window.": "Affiche les paramètres développeurs dans la fenêtre des paramètres.",
|
||||
"Developer Settings": "Paramètres développeurs",
|
||||
"By participating in this beta, you consent to the collection of anonymous data, which we use to improve the product. You can find more information about which data we track in our <2>Privacy Policy</2> and our <5>Cookie Policy</5>.": "En participant à cette beta, vous consentez à la collecte de données anonymes, qui seront utilisées pour améliorer le produit. Vous trouverez plus d’informations sur les données collectées dans notre <2>Politique de vie privée</2> et notre <5>Politique de cookies</5>.",
|
||||
"<0></0><1></1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.": "<0></0><1></1>Vous pouvez retirer votre consentement en décochant cette case. Si vous êtes actuellement en communication, ce paramètre prendra effet à la fin de l’appel."
|
||||
"<0></0><1></1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.": "<0></0><1></1>Vous pouvez retirer votre consentement en décochant cette case. Si vous êtes actuellement en communication, ce paramètre prendra effet à la fin de l’appel.",
|
||||
"Your feedback": "Votre commentaire",
|
||||
"Thanks, we received your feedback!": "Merci, nous avons reçu vos commentaires !",
|
||||
"Submitting…": "Envoi…",
|
||||
"Submit": "Envoyer",
|
||||
"If you are experiencing issues or simply would like to provide some feedback, please send us a short description below.": "Si vous rencontrez des problèmes, ou vous voulez simplement faire un commentaire, veuillez nous envoyer une courte description ci-dessous.",
|
||||
"Feedback": "Commentaires"
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
"Create account": "Buat akun",
|
||||
"Debug log": "Catatan pengawakutuan",
|
||||
"Debug log request": "Permintaan catatan pengawakutuan",
|
||||
"Description (optional)": "Deskripsi (opsional)",
|
||||
"Details": "Detail",
|
||||
"Developer": "Pengembang",
|
||||
"Display name": "Nama tampilan",
|
||||
@@ -34,7 +33,6 @@
|
||||
"Full screen": "Layar penuh",
|
||||
"Go": "Bergabung",
|
||||
"Grid layout menu": "Menu tata letak kisi",
|
||||
"Having trouble? Help us fix it.": "Mengalami masalah? Bantu kami memperbaikinya.",
|
||||
"Home": "Beranda",
|
||||
"Include debug logs": "Termasuk catatan pengawakutuan",
|
||||
"Incompatible versions": "Versi tidak kompatibel",
|
||||
@@ -46,7 +44,6 @@
|
||||
"Join call now": "Bergabung ke panggilan sekarang",
|
||||
"Join existing call?": "Bergabung ke panggilan yang sudah ada?",
|
||||
"Leave": "Keluar",
|
||||
"Loading room…": "Memuat ruangan…",
|
||||
"Loading…": "Memuat…",
|
||||
"Local volume": "Volume lokal",
|
||||
"Logging in…": "Memasuki…",
|
||||
@@ -56,7 +53,6 @@
|
||||
"Microphone permissions needed to join the call.": "Izin mikrofon dibutuhkan untuk bergabung ke panggilan ini.",
|
||||
"Microphone {{n}}": "Mikrofon {{n}}",
|
||||
"More": "Lainnya",
|
||||
"More menu": "Menu lainnya",
|
||||
"Mute microphone": "Bisukan mikrofon",
|
||||
"No": "Tidak",
|
||||
"Not now, return to home screen": "Tidak sekarang, kembali ke layar beranda",
|
||||
@@ -77,8 +73,6 @@
|
||||
"Release to stop": "Lepaskan untuk berhenti",
|
||||
"Remove": "Hapus",
|
||||
"Return to home screen": "Kembali ke layar beranda",
|
||||
"Save": "Simpan",
|
||||
"Saving…": "Menyimpan…",
|
||||
"Select an option": "Pilih sebuah opsi",
|
||||
"Send debug logs": "Kirim catatan pengawakutuan",
|
||||
"Sending…": "Mengirimkan…",
|
||||
@@ -93,7 +87,6 @@
|
||||
"Spotlight": "Sorotan",
|
||||
"Stop sharing screen": "Berhenti membagikan layar",
|
||||
"Submit feedback": "Kirim masukan",
|
||||
"Submitting feedback…": "Mengirimkan masukan…",
|
||||
"Take me Home": "Bawa saya ke Beranda",
|
||||
"Talk over speaker": "Bicara pada pembicara",
|
||||
"Talking…": "Berbicara…",
|
||||
@@ -104,7 +97,6 @@
|
||||
"Turn off camera": "Matikan kamera",
|
||||
"Turn on camera": "Nyalakan kamera",
|
||||
"Unmute microphone": "Suarakan mikrofon",
|
||||
"User ID": "ID pengguna",
|
||||
"User menu": "Menu pengguna",
|
||||
"Username": "Nama pengguna",
|
||||
"Version: {{version}}": "Versi: {{version}}",
|
||||
@@ -139,5 +131,11 @@
|
||||
"Expose developer settings in the settings window.": "Ekspos pengaturan pengembang dalam jendela pengaturan.",
|
||||
"Developer Settings": "Pengaturan Pengembang",
|
||||
"By participating in this beta, you consent to the collection of anonymous data, which we use to improve the product. You can find more information about which data we track in our <2>Privacy Policy</2> and our <5>Cookie Policy</5>.": "Dengan bergabung dalam beta ini, Anda mengizinkan kami untuk mengumpulkan data anonim, yang kami gunakan untuk meningkatkan produk ini. Anda dapat mempelajari lebih lanjut tentang data apa yang kami lacak dalam <2>Kebijakan Privasi</2> dan <5>Kebijakan Kuki</5> kami.",
|
||||
"<0></0><1></1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.": "<0></0><1></1>Anda dapat mengurungkan kembali izin dengan mencentang kotak ini. Jika Anda saat ini dalam panggilan, pengaturan ini akan diterapkan di akhir panggilan."
|
||||
"<0></0><1></1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.": "<0></0><1></1>Anda dapat mengurungkan kembali izin dengan mencentang kotak ini. Jika Anda saat ini dalam panggilan, pengaturan ini akan diterapkan di akhir panggilan.",
|
||||
"Feedback": "Masukan",
|
||||
"If you are experiencing issues or simply would like to provide some feedback, please send us a short description below.": "Jika Anda mengalami masalah atau hanya ingin memberikan masukan, silakan kirimkan kami deskripsi pendek di bawah.",
|
||||
"Submit": "Kirim",
|
||||
"Submitting…": "Mengirim",
|
||||
"Thanks, we received your feedback!": "Terima kasih, kami telah menerima masukan Anda!",
|
||||
"Your feedback": "Masukan Anda"
|
||||
}
|
||||
|
||||
@@ -24,10 +24,8 @@
|
||||
"Copied!": "コピーしました!",
|
||||
"Copy and share this call link": "通話リンクをコピーし共有",
|
||||
"Copy": "コピー",
|
||||
"Description (optional)": "概要(任意)",
|
||||
"Debug log": "デバッグログ",
|
||||
"Create account": "アカウントを作成",
|
||||
"Having trouble? Help us fix it.": "問題が起きましたか?修正にご協力ください。",
|
||||
"Go": "続行",
|
||||
"Fetching group call timed out.": "グループ通話の取得がタイムアウトしました。",
|
||||
"Element Call Home": "Element Call ホーム",
|
||||
@@ -53,16 +51,13 @@
|
||||
"Login": "ログイン",
|
||||
"Logging in…": "ログインしています…",
|
||||
"Loading…": "読み込んでいます…",
|
||||
"Loading room…": "ルームを読み込んでいます…",
|
||||
"Leave": "退出",
|
||||
"Version: {{version}}": "バージョン:{{version}}",
|
||||
"Username": "ユーザー名",
|
||||
"User menu": "ユーザーメニュー",
|
||||
"User ID": "ユーザーID",
|
||||
"Unmute microphone": "マイクのミュートを解除",
|
||||
"Turn on camera": "カメラをつける",
|
||||
"Turn off camera": "カメラを切る",
|
||||
"Submitting feedback…": "フィードバックを送信しています…",
|
||||
"Submit feedback": "フィードバックを送信",
|
||||
"Stop sharing screen": "画面共有を停止",
|
||||
"Spotlight": "スポットライト",
|
||||
@@ -73,8 +68,6 @@
|
||||
"Settings": "設定",
|
||||
"Sending…": "送信しています…",
|
||||
"Sending debug logs…": "デバッグログを送信しています…",
|
||||
"Saving…": "保存しています…",
|
||||
"Save": "保存",
|
||||
"Return to home screen": "ホーム画面に戻る",
|
||||
"Registering…": "登録しています…",
|
||||
"Register": "登録",
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
{
|
||||
"More menu": "Menu \"więcej\"",
|
||||
"Login": "Zaloguj się",
|
||||
"Go": "Kontynuuj",
|
||||
"By clicking \"Go\", you agree to our <2>Terms and conditions</2>": "Klikając \"Kontynuuj\", wyrażasz zgodę na nasze <2>Warunki</2>",
|
||||
"{{count}} people connected|other": "{{count}} ludzi połączono",
|
||||
"Go": "Przejdź",
|
||||
"By clicking \"Go\", you agree to our <2>Terms and conditions</2>": "Klikając \"Kontynuuj\", wyrażasz zgodę na nasze <2>Zasady i warunki</2>",
|
||||
"{{count}} people connected|other": "{{count}} osób połączonych",
|
||||
"Your recent calls": "Twoje ostatnie połączenia",
|
||||
"You can't talk at the same time": "Nie możesz mówić w tym samym czasie",
|
||||
"Yes, join call": "Tak, dołącz do połączenia",
|
||||
@@ -18,7 +17,6 @@
|
||||
"Version: {{version}}": "Wersja: {{version}}",
|
||||
"Username": "Nazwa użytkownika",
|
||||
"User menu": "Menu użytkownika",
|
||||
"User ID": "ID użytkownika",
|
||||
"Unmute microphone": "Wyłącz wyciszenie mikrofonu",
|
||||
"Turn on camera": "Włącz kamerę",
|
||||
"Turn off camera": "Wyłącz kamerę",
|
||||
@@ -26,8 +24,7 @@
|
||||
"This call already exists, would you like to join?": "Te połączenie już istnieje, czy chcesz do niego dołączyć?",
|
||||
"Thanks! We'll get right on it.": "Dziękujemy! Zaraz się tym zajmiemy.",
|
||||
"Talking…": "Mówienie…",
|
||||
"Take me Home": "Zabierz mnie do ekranu startowego",
|
||||
"Submitting feedback…": "Przesyłanie opinii…",
|
||||
"Take me Home": "Zabierz mnie do strony głównej",
|
||||
"Submit feedback": "Prześlij opinię",
|
||||
"Stop sharing screen": "Zatrzymaj udostępnianie ekranu",
|
||||
"Spotlight": "Centrum uwagi",
|
||||
@@ -43,12 +40,10 @@
|
||||
"Sending debug logs…": "Wysyłanie dzienników debugowania…",
|
||||
"Send debug logs": "Wyślij dzienniki debugowania",
|
||||
"Select an option": "Wybierz opcję",
|
||||
"Saving…": "Zapisywanie…",
|
||||
"Save": "Zapisz",
|
||||
"Return to home screen": "Powróć do ekranu domowego",
|
||||
"Return to home screen": "Powróć do strony głównej",
|
||||
"Remove": "Usuń",
|
||||
"Release to stop": "Puść przycisk, aby przestać",
|
||||
"Release spacebar key to stop": "Puść spację, aby przestać",
|
||||
"Release to stop": "Puść przycisk, aby zatrzymać",
|
||||
"Release spacebar key to stop": "Puść spację, aby zatrzymać",
|
||||
"Registering…": "Rejestrowanie…",
|
||||
"Register": "Zarejestruj",
|
||||
"Recaptcha not loaded": "Recaptcha nie została załadowana",
|
||||
@@ -58,7 +53,7 @@
|
||||
"Press and hold to talk": "Przytrzymaj, aby mówić",
|
||||
"Press and hold spacebar to talk over {{name}}": "Przytrzymaj spację, aby mówić wraz z {{name}}",
|
||||
"Press and hold spacebar to talk": "Przytrzymaj spację, aby mówić",
|
||||
"Passwords must match": "Hasła muszą być identyczne",
|
||||
"Passwords must match": "Hasła muszą pasować",
|
||||
"Password": "Hasło",
|
||||
"Other users are trying to join this call from incompatible versions. These users should ensure that they have refreshed their browsers:<1>{userLis}</1>": "Inni użytkownicy próbują dołączyć do tego połączenia przy użyciu niekompatybilnych wersji. Powinni oni upewnić się, że odświeżyli stronę w swoich przeglądarkach:<1>{userLis}</1>",
|
||||
"Not registered yet? <2>Create an account</2>": "Nie masz konta? <2>Utwórz je</2>",
|
||||
@@ -71,9 +66,8 @@
|
||||
"Microphone": "Mikrofon",
|
||||
"Login to your account": "Zaloguj się do swojego konta",
|
||||
"Logging in…": "Logowanie…",
|
||||
"Local volume": "Lokalna głośność",
|
||||
"Local volume": "Głośność lokalna",
|
||||
"Loading…": "Ładowanie…",
|
||||
"Loading room…": "Ładowanie pokoju…",
|
||||
"Leave": "Opuść",
|
||||
"Join existing call?": "Dołączyć do istniejącego połączenia?",
|
||||
"Join call now": "Dołącz do połączenia teraz",
|
||||
@@ -85,40 +79,38 @@
|
||||
"Incompatible versions": "Niekompatybilne wersje",
|
||||
"Include debug logs": "Dołącz dzienniki debugowania",
|
||||
"Home": "Strona domowa",
|
||||
"Having trouble? Help us fix it.": "Masz problem? Pomóż nam go naprawić.",
|
||||
"Grid layout menu": "Menu układu siatki",
|
||||
"Full screen": "Pełen ekran",
|
||||
"Full screen": "Pełny ekran",
|
||||
"Freedom": "Wolność",
|
||||
"Fetching group call timed out.": "Przekroczono limit czasu na uzyskanie połączenia grupowego.",
|
||||
"Exit full screen": "Zamknij pełny ekran",
|
||||
"Exit full screen": "Opuść pełny ekran",
|
||||
"Download debug logs": "Pobierz dzienniki debugowania",
|
||||
"Display name": "Wyświetlana nazwa",
|
||||
"Developer": "Deweloper",
|
||||
"Display name": "Nazwa wyświetlana",
|
||||
"Developer": "Programista",
|
||||
"Details": "Szczegóły",
|
||||
"Description (optional)": "Opis (opcjonalny)",
|
||||
"Debug log request": "Prośba o dzienniki debugowania",
|
||||
"Debug log": "Dzienniki debugowania",
|
||||
"Create account": "Utwórz konto",
|
||||
"Copy and share this call link": "Skopiuj i podziel się linkiem do połączenia",
|
||||
"Copy and share this call link": "Skopiuj i udostępnij link do rozmowy",
|
||||
"Copied!": "Skopiowano!",
|
||||
"Connection lost": "Połączenie utracone",
|
||||
"Confirm password": "Potwierdź hasło",
|
||||
"Close": "Zamknij",
|
||||
"Change layout": "Zmień układ",
|
||||
"Camera/microphone permissions needed to join the call.": "Aby dołączyć do tego połączenia, potrzebne są uprawnienia do kamery/mikrofonu.",
|
||||
"Camera/microphone permissions needed to join the call.": "Wymagane są uprawnienia do kamery/mikrofonu, aby dołączyć do rozmowy.",
|
||||
"Camera {{n}}": "Kamera {{n}}",
|
||||
"Camera": "Kamera",
|
||||
"Call type menu": "Menu rodzaju połączenia",
|
||||
"Call type menu": "Menu typu połączenia",
|
||||
"Call link copied": "Skopiowano link do połączenia",
|
||||
"By clicking \"Join call now\", you agree to our <2>Terms and conditions</2>": "Klikając \"Dołącz do rozmowy\", wyrażasz zgodę na nasze <2>Warunki</2>",
|
||||
"By clicking \"Join call now\", you agree to our <2>Terms and conditions</2>": "Klikając \"Dołącz do rozmowy\", wyrażasz zgodę na nasze <2>Zasady i warunki</2>",
|
||||
"Avatar": "Awatar",
|
||||
"Audio": "Dźwięk",
|
||||
"Another user on this call is having an issue. In order to better diagnose these issues we'd like to collect a debug log.": "Inny użytkownik w tym połączeniu napotkał problem. Aby lepiej zdiagnozować tę usterkę, chcielibyśmy zebrać dzienniki debugowania.",
|
||||
"Accept microphone permissions to join the call.": "Przyznaj uprawnienia do mikrofonu aby dołączyć do połączenia.",
|
||||
"Accept camera/microphone permissions to join the call.": "Przyznaj uprawnienia do kamery/mikrofonu aby dołączyć do połączenia.",
|
||||
"Accept microphone permissions to join the call.": "Akceptuj uprawnienia mikrofonu, aby dołączyć do połączenia.",
|
||||
"Accept camera/microphone permissions to join the call.": "Akceptuj uprawnienia kamery/mikrofonu, aby dołączyć do połączenia.",
|
||||
"<0>Why not finish by setting up a password to keep your account?</0><1>You'll be able to keep your name and set an avatar for use on future calls</1>": "<0>Może zechcesz ustawić hasło, aby zachować swoje konto?</0><1>Będziesz w stanie utrzymać swoją nazwę i ustawić awatar do wyświetlania podczas połączeń w przyszłości</1>",
|
||||
"<0>Create an account</0> Or <2>Access as a guest</2>": "<0>Utwórz konto</0> Albo <2>Dołącz jako gość</2>",
|
||||
"<0>Already have an account?</0><1><0>Log in</0> Or <2>Access as a guest</2></1>": "<0>Masz już konto?</0><1><0>Zaloguj się</0> Albo <2>Dołącz jako gość</2></1>",
|
||||
"<0>Create an account</0> Or <2>Access as a guest</2>": "<0>Utwórz konto</0> lub <2>Dołącz jako gość</2>",
|
||||
"<0>Already have an account?</0><1><0>Log in</0> Or <2>Access as a guest</2></1>": "<0>Masz już konto?</0><1><0>Zaloguj się</0> lub <2>Dołącz jako gość</2></1>",
|
||||
"{{roomName}} - Walkie-talkie call": "{{roomName}} - połączenie walkie-talkie",
|
||||
"{{names}}, {{name}}": "{{names}}, {{name}}",
|
||||
"{{name}} is talking…": "{{name}} mówi…",
|
||||
@@ -127,12 +119,17 @@
|
||||
"{{count}} people connected|one": "{{count}} osoba połączona",
|
||||
"This feature is only supported on Firefox.": "Ta funkcjonalność jest dostępna tylko w Firefox.",
|
||||
"Copy": "Kopiuj",
|
||||
"<0>Submitting debug logs will help us track down the problem.</0>": "<0>Wysłanie logów debuggowania pomoże nam ustalić przyczynę problemu.</0>",
|
||||
"<0>Submitting debug logs will help us track down the problem.</0>": "<0>Wysłanie dzienników debuggowania pomoże nam ustalić przyczynę problemu.</0>",
|
||||
"<0>Oops, something's gone wrong.</0>": "<0>Ojej, coś poszło nie tak.</0>",
|
||||
"<0>Join call now</0><1>Or</1><2>Copy call link and join later</2>": "<0>Dołącz do rozmowy teraz</0><1>Or</1><2>Skopiuj link do rozmowy i dołącz później</2>",
|
||||
"<0>Join call now</0><1>Or</1><2>Copy call link and join later</2>": "<0>Dołącz do rozmowy już teraz</0><1>lub</1><2>Skopiuj link do rozmowy i dołącz później</2>",
|
||||
"{{name}} (Waiting for video...)": "{{name}} (Oczekiwanie na wideo...)",
|
||||
"{{name}} (Connecting...)": "{{name}} (Łączenie...)",
|
||||
"Expose developer settings in the settings window.": "Wyświetlaj opcje programisty w oknie ustawień.",
|
||||
"Expose developer settings in the settings window.": "Wyświetl opcje programisty w oknie ustawień.",
|
||||
"Element Call Home": "Strona główna Element Call",
|
||||
"Developer Settings": "Opcje programisty"
|
||||
"Developer Settings": "Opcje programisty",
|
||||
"Talk over speaker": "Rozmowa przez głośnik",
|
||||
"Use the upcoming grid system": "Użyj nadchodzącego systemu siatek",
|
||||
"This site is protected by ReCAPTCHA and the Google <2>Privacy Policy</2> and <6>Terms of Service</6> apply.<9></9>By clicking \"Register\", you agree to our <12>Terms and conditions</12>": "Ta strona jest chroniona przez ReCAPTCHA, więc obowiązują na niej <2>Polityka prywatności</2> i <6>Warunki świadczenia usług</6> Google.<9></9>Klikając \"Zarejestruj się\", zgadzasz się na nasze <12>Warunki świadczenia usług</12>",
|
||||
"<0></0><1></1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.": "<0></0><1></1>Możesz wycofać swoją zgodę poprzez odznaczenie tego pola. Jeśli już jesteś w trakcie rozmowy, opcja zostanie zastosowana po jej zakończeniu.",
|
||||
"By participating in this beta, you consent to the collection of anonymous data, which we use to improve the product. You can find more information about which data we track in our <2>Privacy Policy</2> and our <5>Cookie Policy</5>.": "Uczestnicząc w tej becie, upoważniasz nas do zbierania anonimowych danych, które wykorzystamy do ulepszenia produktu. Dowiedz się więcej na temat danych, które zbieramy w naszej <2>Polityce prywatności</2> i <5>Polityce ciasteczek</5>."
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"Register": "Зарегистрироваться",
|
||||
"Saving…": "Сохранение…",
|
||||
"Registering…": "Регистрация…",
|
||||
"Logging in…": "Вход…",
|
||||
"{{names}}, {{name}}": "{{names}}, {{name}}",
|
||||
@@ -10,7 +9,6 @@
|
||||
"This call already exists, would you like to join?": "Этот звонок уже существует, хотите присоединиться?",
|
||||
"Thanks! We'll get right on it.": "Спасибо! Мы учтём ваш отзыв.",
|
||||
"Talking…": "Говорите…",
|
||||
"Submitting feedback…": "Отправка отзыва…",
|
||||
"Submit feedback": "Отправить отзыв",
|
||||
"Sending debug logs…": "Отправка журнала отладки…",
|
||||
"Select an option": "Выберите вариант",
|
||||
@@ -38,7 +36,6 @@
|
||||
"Version: {{version}}": "Версия: {{version}}",
|
||||
"Username": "Имя пользователя",
|
||||
"User menu": "Меню пользователя",
|
||||
"User ID": "ID пользователя",
|
||||
"Unmute microphone": "Включить микрофон",
|
||||
"Turn on camera": "Включить камеру",
|
||||
"Turn off camera": "Отключить камеру",
|
||||
@@ -57,7 +54,6 @@
|
||||
"Sending…": "Отправка…",
|
||||
"Local volume": "Местная громкость",
|
||||
"Call type menu": "Меню \"Тип звонка\"",
|
||||
"More menu": "Полное меню",
|
||||
"{{roomName}} - Walkie-talkie call": "{{roomName}} - Звонок-рация",
|
||||
"Include debug logs": "Приложить журнал отладки",
|
||||
"Download debug logs": "Скачать журнал отладки",
|
||||
@@ -65,7 +61,6 @@
|
||||
"Debug log": "Журнал отладки",
|
||||
"Another user on this call is having an issue. In order to better diagnose these issues we'd like to collect a debug log.": "У одного из участников звонка есть неполадки. Чтобы лучше диагностировать похожие проблемы, нам нужен журнал отладки.",
|
||||
"Send debug logs": "Отправить журнал отладки",
|
||||
"Save": "Сохранить",
|
||||
"Return to home screen": "Вернуться в Начало",
|
||||
"Remove": "Удалить",
|
||||
"Recaptcha not loaded": "Невозможно начать проверку",
|
||||
@@ -86,7 +81,6 @@
|
||||
"Login to your account": "Войдите в свой аккаунт",
|
||||
"Login": "Вход",
|
||||
"Loading…": "Загрузка…",
|
||||
"Loading room…": "Загрузка комнаты…",
|
||||
"Leave": "Покинуть",
|
||||
"Join existing call?": "Присоединиться к существующему звонку?",
|
||||
"Join call now": "Присоединиться сейчас",
|
||||
@@ -97,7 +91,6 @@
|
||||
"Incompatible versions!": "Несовместимые версии!",
|
||||
"Incompatible versions": "Несовместимые версии",
|
||||
"Home": "Начало",
|
||||
"Having trouble? Help us fix it.": "Есть проблема? Помогите нам её устранить.",
|
||||
"Go": "Далее",
|
||||
"Full screen": "Полноэкранный режим",
|
||||
"Freedom": "Свобода",
|
||||
@@ -106,7 +99,6 @@
|
||||
"Display name": "Видимое имя",
|
||||
"Developer": "Разработчику",
|
||||
"Details": "Подробности",
|
||||
"Description (optional)": "Описание (необязательно)",
|
||||
"Create account": "Создать аккаунт",
|
||||
"Copy and share this call link": "Скопируйте и поделитесь этой ссылкой на звонок",
|
||||
"Copied!": "Скопировано!",
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
"Talking…": "Rozprávanie…",
|
||||
"Talk over speaker": "Hovor cez reproduktor",
|
||||
"Take me Home": "Zober ma domov",
|
||||
"Submitting feedback…": "Odosielanie spätnej väzby…",
|
||||
"Submit feedback": "Odoslať spätnú väzbu",
|
||||
"Stop sharing screen": "Zastaviť zdieľanie obrazovky",
|
||||
"Show call inspector": "Zobraziť inšpektora hovorov",
|
||||
@@ -21,8 +20,6 @@
|
||||
"Sending debug logs…": "Odosielanie záznamov o ladení…",
|
||||
"Send debug logs": "Odoslať záznamy o ladení",
|
||||
"Select an option": "Vyberte možnosť",
|
||||
"Saving…": "Ukladanie…",
|
||||
"Save": "Uložiť",
|
||||
"Return to home screen": "Návrat na domovskú obrazovku",
|
||||
"Remove": "Odstrániť",
|
||||
"Release spacebar key to stop": "Pustite medzerník pre ukončenie",
|
||||
@@ -43,7 +40,6 @@
|
||||
"Not now, return to home screen": "Teraz nie, vrátiť sa na domovskú obrazovku",
|
||||
"No": "Nie",
|
||||
"Mute microphone": "Stlmiť mikrofón",
|
||||
"More menu": "Ponuka viac",
|
||||
"More": "Viac",
|
||||
"Microphone permissions needed to join the call.": "Povolenie mikrofónu je potrebné na pripojenie k hovoru.",
|
||||
"Microphone {{n}}": "Mikrofón {{n}}",
|
||||
@@ -52,7 +48,6 @@
|
||||
"Login": "Prihlásiť sa",
|
||||
"Logging in…": "Prihlasovanie…",
|
||||
"Loading…": "Načítanie…",
|
||||
"Loading room…": "Načítanie miestnosti…",
|
||||
"Leave": "Opustiť",
|
||||
"Join existing call?": "Pripojiť sa k existujúcemu hovoru?",
|
||||
"Join call now": "Pripojiť sa k hovoru teraz",
|
||||
@@ -63,7 +58,6 @@
|
||||
"Incompatible versions!": "Nekompatibilné verzie!",
|
||||
"Incompatible versions": "Nekompatibilné verzie",
|
||||
"Home": "Domov",
|
||||
"Having trouble? Help us fix it.": "Máte problém? Pomôžte nám ho opraviť.",
|
||||
"Grid layout menu": "Ponuka rozloženia mriežky",
|
||||
"Go": "Prejsť",
|
||||
"Full screen": "Zobrazenie na celú obrazovku",
|
||||
@@ -81,7 +75,6 @@
|
||||
"Version: {{version}}": "Verzia: {{version}}",
|
||||
"Username": "Meno používateľa",
|
||||
"User menu": "Používateľské menu",
|
||||
"User ID": "ID používateľa",
|
||||
"Unmute microphone": "Zrušiť stlmenie mikrofónu",
|
||||
"Turn on camera": "Zapnúť kameru",
|
||||
"Turn off camera": "Vypnúť kameru",
|
||||
@@ -96,7 +89,6 @@
|
||||
"Display name": "Zobrazované meno",
|
||||
"Developer": "Vývojár",
|
||||
"Details": "Podrobnosti",
|
||||
"Description (optional)": "Popis (voliteľné)",
|
||||
"Debug log request": "Žiadosť o záznam ladenia",
|
||||
"Debug log": "Záznam o ladení",
|
||||
"Create account": "Vytvoriť účet",
|
||||
@@ -139,5 +131,11 @@
|
||||
"Expose developer settings in the settings window.": "Zobraziť nastavenia pre vývojárov v okne nastavení.",
|
||||
"Developer Settings": "Nastavenia pre vývojárov",
|
||||
"By participating in this beta, you consent to the collection of anonymous data, which we use to improve the product. You can find more information about which data we track in our <2>Privacy Policy</2> and our <5>Cookie Policy</5>.": "Účasťou v tejto beta verzii súhlasíte so zhromažďovaním anonymných údajov, ktoré použijeme na zlepšenie produktu. Viac informácií o tom, ktoré údaje sledujeme, nájdete v našich <2>Zásadách ochrany osobných údajov</2> a <5>Zásadách používania súborov cookie</5>.",
|
||||
"<0></0><1></1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.": "<0></0><1></1>Súhlas môžete odvolať zrušením označenia tohto políčka. Ak práve prebieha hovor, toto nastavenie nadobudne platnosť po skončení hovoru."
|
||||
"<0></0><1></1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.": "<0></0><1></1>Súhlas môžete odvolať zrušením označenia tohto políčka. Ak práve prebieha hovor, toto nastavenie nadobudne platnosť po skončení hovoru.",
|
||||
"Your feedback": "Vaša spätná väzba",
|
||||
"Thanks, we received your feedback!": "Ďakujeme, dostali sme vašu spätnú väzbu!",
|
||||
"Submitting…": "Odosielanie…",
|
||||
"Submit": "Odoslať",
|
||||
"If you are experiencing issues or simply would like to provide some feedback, please send us a short description below.": "Ak máte problémy alebo jednoducho chcete poskytnúť spätnú väzbu, pošlite nám krátky popis nižšie.",
|
||||
"Feedback": "Spätná väzba"
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
"Create account": "Hesap aç",
|
||||
"Debug log": "Hata ayıklama kütüğü",
|
||||
"Debug log request": "Hata ayıklama kütük istemi",
|
||||
"Description (optional)": "Tanım (isteğe bağlı)",
|
||||
"Details": "Ayrıntı",
|
||||
"Developer": "Geliştirici",
|
||||
"Display name": "Ekran adı",
|
||||
@@ -32,7 +31,6 @@
|
||||
"Full screen": "Tam ekran",
|
||||
"Go": "Git",
|
||||
"Grid layout menu": "Izgara plan menü",
|
||||
"Having trouble? Help us fix it.": "Sorun mu var? Çözmemize yardım edin.",
|
||||
"Home": "Ev",
|
||||
"Include debug logs": "Hata ayıklama kütüğünü dahil et",
|
||||
"Incompatible versions": "Uyumsuz sürümler",
|
||||
@@ -43,7 +41,6 @@
|
||||
"Join call now": "Aramaya katıl",
|
||||
"Join existing call?": "Mevcut aramaya katıl?",
|
||||
"Leave": "Çık",
|
||||
"Loading room…": "Oda yükleniyor…",
|
||||
"Loading…": "Yükleniyor…",
|
||||
"Local volume": "Yerel ses seviyesi",
|
||||
"Logging in…": "Giriliyor…",
|
||||
@@ -53,7 +50,6 @@
|
||||
"Microphone permissions needed to join the call.": "Aramaya katılmak için mikrofon erişim izni gerek.",
|
||||
"Microphone {{n}}": "{{n}}. mikrofon",
|
||||
"More": "Daha",
|
||||
"More menu": "Daha fazla",
|
||||
"Mute microphone": "Mikrofonu kapat",
|
||||
"No": "Hayır",
|
||||
"Not now, return to home screen": "Şimdi değil, ev ekranına dön",
|
||||
@@ -71,8 +67,6 @@
|
||||
"Release to stop": "Kesmek için bırakın",
|
||||
"Remove": "Çıkar",
|
||||
"Return to home screen": "Ev ekranına geri dön",
|
||||
"Save": "Kaydet",
|
||||
"Saving…": "Kaydediliyor…",
|
||||
"Select an option": "Bir seçenek seç",
|
||||
"Send debug logs": "Hata ayıklama kütüğünü gönder",
|
||||
"Sending…": "Gönderiliyor…",
|
||||
@@ -84,7 +78,6 @@
|
||||
"Spatial audio": "Uzamsal ses",
|
||||
"Stop sharing screen": "Ekran paylaşmayı terk et",
|
||||
"Submit feedback": "Geri bildirim ver",
|
||||
"Submitting feedback…": "Geri bildirimler gönderiliyor…",
|
||||
"Take me Home": "Ev ekranına gir",
|
||||
"Talking…": "Konuşuyor…",
|
||||
"Thanks! We'll get right on it.": "Sağol! Bununla ilgileneceğiz.",
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
"Version: {{version}}": "Версія: {{version}}",
|
||||
"Username": "Ім'я користувача",
|
||||
"User menu": "Меню користувача",
|
||||
"User ID": "ID користувача",
|
||||
"Unmute microphone": "Увімкнути мікрофон",
|
||||
"Turn on camera": "Увімкнути камеру",
|
||||
"Turn off camera": "Вимкнути камеру",
|
||||
@@ -25,7 +24,6 @@
|
||||
"Talking…": "Говоріть…",
|
||||
"Talk over speaker": "Говорити через динамік",
|
||||
"Take me Home": "Перейти до Домівки",
|
||||
"Submitting feedback…": "Надсилання відгуку…",
|
||||
"Submit feedback": "Надіслати відгук",
|
||||
"Stop sharing screen": "Припинити показ екрана",
|
||||
"Spotlight": "У центрі уваги",
|
||||
@@ -41,8 +39,6 @@
|
||||
"Sending debug logs…": "Надсилання журналу налагодження…",
|
||||
"Send debug logs": "Надіслати журнал налагодження",
|
||||
"Select an option": "Вибрати опцію",
|
||||
"Saving…": "Збереження…",
|
||||
"Save": "Зберегти",
|
||||
"Return to home screen": "Повернутися на екран домівки",
|
||||
"Remove": "Вилучити",
|
||||
"Release to stop": "Відпустіть, щоб закінчити",
|
||||
@@ -63,7 +59,6 @@
|
||||
"Not now, return to home screen": "Не зараз, повернутися на екран домівки",
|
||||
"No": "Ні",
|
||||
"Mute microphone": "Заглушити мікрофон",
|
||||
"More menu": "Усе меню",
|
||||
"More": "Докладніше",
|
||||
"Microphone permissions needed to join the call.": "Для участі у виклику необхідний дозвіл на користування мікрофоном.",
|
||||
"Microphone {{n}}": "Мікрофон {{n}}",
|
||||
@@ -72,7 +67,6 @@
|
||||
"Login": "Увійти",
|
||||
"Logging in…": "Вхід…",
|
||||
"Local volume": "Локальна гучність",
|
||||
"Loading room…": "Завантаження кімнати…",
|
||||
"Leave": "Вийти",
|
||||
"Join existing call?": "Приєднатися до наявного виклику?",
|
||||
"Join call now": "Приєднатися до виклику зараз",
|
||||
@@ -84,7 +78,6 @@
|
||||
"Incompatible versions": "Несумісні версії",
|
||||
"Include debug logs": "Долучити журнали налагодження",
|
||||
"Home": "Домівка",
|
||||
"Having trouble? Help us fix it.": "Проблеми? Допоможіть нам це виправити.",
|
||||
"Grid layout menu": "Меню у вигляді сітки",
|
||||
"Go": "Далі",
|
||||
"Full screen": "Повноекранний режим",
|
||||
@@ -95,7 +88,6 @@
|
||||
"Display name": "Показуване ім'я",
|
||||
"Developer": "Розробнику",
|
||||
"Details": "Подробиці",
|
||||
"Description (optional)": "Опис (необов'язково)",
|
||||
"Debug log request": "Запит журналу налагодження",
|
||||
"Debug log": "Журнал налагодження",
|
||||
"Create account": "Створити обліковий запис",
|
||||
@@ -139,5 +131,11 @@
|
||||
"Expose developer settings in the settings window.": "Відкрийте налаштування розробника у вікні налаштувань.",
|
||||
"Developer Settings": "Налаштування розробника",
|
||||
"By participating in this beta, you consent to the collection of anonymous data, which we use to improve the product. You can find more information about which data we track in our <2>Privacy Policy</2> and our <5>Cookie Policy</5>.": "Користуючись дочасним доступом, ви даєте згоду на збір анонімних даних, які ми використовуємо для вдосконалення продукту. Ви можете знайти більше інформації про те, які дані ми відстежуємо в нашій <2>Політиці Приватності</2> і нашій <5>Політиці про куки</5>.",
|
||||
"<0></0><1></1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.": "<0></0><1></1>Ви можете відкликати згоду, прибравши цей прапорець. Якщо ви зараз розмовляєте, це налаштування застосується після завершення виклику."
|
||||
"<0></0><1></1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.": "<0></0><1></1>Ви можете відкликати згоду, прибравши цей прапорець. Якщо ви зараз розмовляєте, це налаштування застосується після завершення виклику.",
|
||||
"Your feedback": "Ваш відгук",
|
||||
"Thanks, we received your feedback!": "Дякуємо, ми отримали ваш відгук!",
|
||||
"Submitting…": "Надсилання…",
|
||||
"Submit": "Надіслати",
|
||||
"If you are experiencing issues or simply would like to provide some feedback, please send us a short description below.": "Якщо у вас виникли проблеми або ви просто хочете залишити відгук, надішліть нам короткий опис нижче.",
|
||||
"Feedback": "Відгук"
|
||||
}
|
||||
|
||||
38
public/locales/vi/app.json
Normal file
38
public/locales/vi/app.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"Login": "Đăng nhập",
|
||||
"{{count}} people connected|other": "{{count}} người đã kết nối",
|
||||
"{{name}} (Waiting for video...)": "{{name}} (Đang đợi truyền hình...)",
|
||||
"Join call": "Tham gia cuộc gọi",
|
||||
"Mute microphone": "Tắt micrô",
|
||||
"Password": "Mật khẩu",
|
||||
"Settings": "Cài đặt",
|
||||
"Sending…": "Đang gửi…",
|
||||
"Sign in": "Đăng nhập",
|
||||
"Submit": "Gửi",
|
||||
"Video call name": "Tên cuộc gọi truyền hình",
|
||||
"Video call": "Gọi truyền hình",
|
||||
"Video": "Truyền hình",
|
||||
"Username": "Tên người dùng",
|
||||
"Yes, join call": "Vâng, tham gia cuộc gọi",
|
||||
"Your feedback": "Phản hồi của bạn",
|
||||
"{{count}} people connected|one": "{{count}} người đã kết nối",
|
||||
"{{displayName}}, your call is now ended": "{{displayName}}, cuộc gọi của bạn đã kết thúc",
|
||||
"{{name}} (Connecting...)": "{{name}} (Đang kết nối...)",
|
||||
"Your recent calls": "Cuộc gọi gần đây",
|
||||
"You can't talk at the same time": "Bạn không thể nói cùng thời điểm",
|
||||
"WebRTC is not supported or is being blocked in this browser.": "WebRTC không được hỗ trợ hay bị chặn trong trình duyệt này.",
|
||||
"Waiting for network": "Đang đợi kết nối mạng",
|
||||
"Waiting for other participants…": "Đang đợi những người khác…",
|
||||
"Version: {{version}}": "Phiên bản: {{version}}",
|
||||
"Turn on camera": "Bật máy quay",
|
||||
"Turn off camera": "Tắt máy quay",
|
||||
"Submit feedback": "Gửi phản hồi",
|
||||
"Stop sharing screen": "Ngừng chia sẻ màn hình",
|
||||
"Speaker": "Loa",
|
||||
"Sign out": "Đăng xuất",
|
||||
"Share screen": "Chia sẻ màn hình",
|
||||
"No": "Không",
|
||||
"Invite people": "Mời mọi người",
|
||||
"Join call now": "Tham gia cuộc gọi",
|
||||
"Create account": "Tạo tài khoản"
|
||||
}
|
||||
@@ -13,7 +13,6 @@
|
||||
"Version: {{version}}": "版本:{{version}}",
|
||||
"Username": "用户名",
|
||||
"User menu": "用户菜单",
|
||||
"User ID": "用户ID",
|
||||
"Unmute microphone": "取消麦克风静音",
|
||||
"Turn on camera": "开启摄像头",
|
||||
"Turn off camera": "关闭摄像头",
|
||||
@@ -24,7 +23,6 @@
|
||||
"Talking…": "正在发言……",
|
||||
"Talk over speaker": "通过扬声器发言",
|
||||
"Take me Home": "返回主页",
|
||||
"Submitting feedback…": "正在提交反馈……",
|
||||
"Submit feedback": "提交反馈",
|
||||
"Stop sharing screen": "停止屏幕共享",
|
||||
"Spotlight": "聚焦模式",
|
||||
@@ -58,8 +56,6 @@
|
||||
"Sending debug logs…": "正在发送调试日志……",
|
||||
"Send debug logs": "发送调试日志",
|
||||
"Select an option": "选择一个选项",
|
||||
"Saving…": "正在保存……",
|
||||
"Save": "保存",
|
||||
"Return to home screen": "返回主页",
|
||||
"Remove": "移除",
|
||||
"Release to stop": "松开后停止",
|
||||
@@ -80,7 +76,6 @@
|
||||
"Not now, return to home screen": "暂不,先返回主页",
|
||||
"No": "否",
|
||||
"Mute microphone": "麦克风静音",
|
||||
"More menu": "更多",
|
||||
"More": "更多",
|
||||
"Microphone permissions needed to join the call.": "加入通话需要麦克风权限。",
|
||||
"Microphone {{n}}": "麦克风 {{n}}",
|
||||
@@ -90,7 +85,6 @@
|
||||
"Logging in…": "登录中……",
|
||||
"Local volume": "本地音量",
|
||||
"Loading…": "加载中……",
|
||||
"Loading room…": "加载房间中……",
|
||||
"Leave": "离开",
|
||||
"Join existing call?": "加入现有的通话?",
|
||||
"Join call now": "现在加入通话",
|
||||
@@ -101,7 +95,6 @@
|
||||
"Incompatible versions": "不兼容版本",
|
||||
"Include debug logs": "包含调试日志",
|
||||
"Home": "主页",
|
||||
"Having trouble? Help us fix it.": "遇到麻烦?帮助我们解决问题。",
|
||||
"Grid layout menu": "网格布局菜单",
|
||||
"Go": "开始",
|
||||
"Full screen": "全屏",
|
||||
@@ -113,7 +106,6 @@
|
||||
"Display name": "显示名称",
|
||||
"Developer": "开发者",
|
||||
"Details": "详情",
|
||||
"Description (optional)": "描述(可选)",
|
||||
"Debug log request": "调试日志请求",
|
||||
"Debug log": "调试日志",
|
||||
"Create account": "创建账户",
|
||||
@@ -131,5 +123,7 @@
|
||||
"Call type menu": "通话类型菜单",
|
||||
"Call link copied": "链接已复制",
|
||||
"By clicking \"Join call now\", you agree to our <2>Terms and conditions</2>": "点击“现在加入”则表示同意我们的<2>条款与条件<2>",
|
||||
"Avatar": "头像"
|
||||
"Avatar": "头像",
|
||||
"<0>Oops, something's gone wrong.</0>": "<0>哎哟,出问题了。</0>",
|
||||
"<0></0><1></1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.": ""
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
"Version: {{version}}": "版本: {{version}}",
|
||||
"Username": "使用者名稱",
|
||||
"User menu": "使用者選單",
|
||||
"User ID": "使用者 ID",
|
||||
"Unmute microphone": "取消麥克風靜音",
|
||||
"Turn on camera": "開啟相機",
|
||||
"Turn off camera": "關閉相機",
|
||||
@@ -42,7 +41,6 @@
|
||||
"Talking…": "對話中…",
|
||||
"Talk over speaker": "以擴音對話",
|
||||
"Take me Home": "帶我回主畫面",
|
||||
"Submitting feedback…": "遞交回饋…",
|
||||
"Submit feedback": "遞交回覆",
|
||||
"Stop sharing screen": "停止分享螢幕畫面",
|
||||
"Spotlight": "聚焦",
|
||||
@@ -58,8 +56,6 @@
|
||||
"Sending debug logs…": "傳送除錯記錄檔中…",
|
||||
"Send debug logs": "傳送除錯紀錄",
|
||||
"Select an option": "選擇一個選項",
|
||||
"Saving…": "儲存中…",
|
||||
"Save": "儲存",
|
||||
"Return to home screen": "回到首頁",
|
||||
"Remove": "移除",
|
||||
"Release to stop": "放開以停止",
|
||||
@@ -80,7 +76,6 @@
|
||||
"Not now, return to home screen": "現在不行,回到首頁",
|
||||
"No": "否",
|
||||
"Mute microphone": "麥克風靜音",
|
||||
"More menu": "更多選單",
|
||||
"More": "更多",
|
||||
"Microphone permissions needed to join the call.": "加入通話前需要取得麥克風的權限。",
|
||||
"Microphone {{n}}": "麥克風 {{n}}",
|
||||
@@ -90,7 +85,6 @@
|
||||
"Logging in…": "登入中…",
|
||||
"Local volume": "您的音量",
|
||||
"Loading…": "載入中…",
|
||||
"Loading room…": "載入聊天室…",
|
||||
"Leave": "離開",
|
||||
"Join existing call?": "加入已開始的通話嗎?",
|
||||
"Join call now": "現在加入通話",
|
||||
@@ -102,7 +96,6 @@
|
||||
"Incompatible versions": "不相容版本",
|
||||
"Include debug logs": "包含除錯紀錄",
|
||||
"Home": "首頁",
|
||||
"Having trouble? Help us fix it.": "遇到問題嗎?請讓我們協助您。",
|
||||
"Grid layout menu": "格框式清單",
|
||||
"Go": "前往",
|
||||
"Full screen": "全螢幕",
|
||||
@@ -114,7 +107,6 @@
|
||||
"Display name": "顯示名稱",
|
||||
"Developer": "開發者",
|
||||
"Details": "詳細說明",
|
||||
"Description (optional)": "描述(選擇性)",
|
||||
"Debug log request": "請求偵錯報告",
|
||||
"Debug log": "除錯紀錄",
|
||||
"Create account": "建立帳號",
|
||||
@@ -139,5 +131,11 @@
|
||||
"Accept camera/microphone permissions to join the call.": "請授權使用您的相機/麥克風以加入對話。",
|
||||
"<0>Why not finish by setting up a password to keep your account?</0><1>You'll be able to keep your name and set an avatar for use on future calls</1>": "<0>何不設定密碼以保留此帳號?</0><1>您可以保留暱稱並設定頭像,以便未來通話時使用</1>",
|
||||
"By participating in this beta, you consent to the collection of anonymous data, which we use to improve the product. You can find more information about which data we track in our <2>Privacy Policy</2> and our <5>Cookie Policy</5>.": "參與此測試版即表示您同意蒐集匿名資料,我們使用這些資料來改進產品。您可以在我們的<2>隱私政策</2>與我們的 <5>Cookie 政策</5> 中找到關於我們追蹤哪些資料的更多資訊。",
|
||||
"<0></0><1></1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.": "<0></0><1></1>您可以透過取消核取此方塊來撤回同意。若您目前正在通話中,此設定將在通話結束時生效。"
|
||||
"<0></0><1></1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.": "<0></0><1></1>您可以透過取消核取此方塊來撤回同意。若您目前正在通話中,此設定將在通話結束時生效。",
|
||||
"Your feedback": "您的回饋",
|
||||
"Thanks, we received your feedback!": "感謝,我們已經收到您的回饋了!",
|
||||
"Submitting…": "正在遞交……",
|
||||
"Submit": "遞交",
|
||||
"If you are experiencing issues or simply would like to provide some feedback, please send us a short description below.": "若您遇到問題或只是想提供一些回饋,請在下方傳送簡短說明給我們。",
|
||||
"Feedback": "回饋"
|
||||
}
|
||||
|
||||
57
src/App.tsx
57
src/App.tsx
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2021 New Vector Ltd
|
||||
Copyright 2021 - 2023 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -30,6 +30,7 @@ import { SequenceDiagramViewerPage } from "./SequenceDiagramViewerPage";
|
||||
import { InspectorContextProvider } from "./room/GroupCallInspector";
|
||||
import { CrashView, LoadingView } from "./FullScreenView";
|
||||
import { Initializer } from "./initializer";
|
||||
import { MediaHandlerProvider } from "./settings/useMediaHandler";
|
||||
|
||||
const SentryRoute = Sentry.withSentryRouting(Route);
|
||||
|
||||
@@ -55,32 +56,34 @@ export default function App({ history }: AppProps) {
|
||||
{loaded ? (
|
||||
<Suspense fallback={null}>
|
||||
<ClientProvider>
|
||||
<InspectorContextProvider>
|
||||
<Sentry.ErrorBoundary fallback={errorPage}>
|
||||
<OverlayProvider>
|
||||
<Switch>
|
||||
<SentryRoute exact path="/">
|
||||
<HomePage />
|
||||
</SentryRoute>
|
||||
<SentryRoute exact path="/login">
|
||||
<LoginPage />
|
||||
</SentryRoute>
|
||||
<SentryRoute exact path="/register">
|
||||
<RegisterPage />
|
||||
</SentryRoute>
|
||||
<SentryRoute path="/room/:roomId?">
|
||||
<RoomPage />
|
||||
</SentryRoute>
|
||||
<SentryRoute path="/inspector">
|
||||
<SequenceDiagramViewerPage />
|
||||
</SentryRoute>
|
||||
<SentryRoute path="*">
|
||||
<RoomRedirect />
|
||||
</SentryRoute>
|
||||
</Switch>
|
||||
</OverlayProvider>
|
||||
</Sentry.ErrorBoundary>
|
||||
</InspectorContextProvider>
|
||||
<MediaHandlerProvider>
|
||||
<InspectorContextProvider>
|
||||
<Sentry.ErrorBoundary fallback={errorPage}>
|
||||
<OverlayProvider>
|
||||
<Switch>
|
||||
<SentryRoute exact path="/">
|
||||
<HomePage />
|
||||
</SentryRoute>
|
||||
<SentryRoute exact path="/login">
|
||||
<LoginPage />
|
||||
</SentryRoute>
|
||||
<SentryRoute exact path="/register">
|
||||
<RegisterPage />
|
||||
</SentryRoute>
|
||||
<SentryRoute path="/room/:roomId?">
|
||||
<RoomPage />
|
||||
</SentryRoute>
|
||||
<SentryRoute path="/inspector">
|
||||
<SequenceDiagramViewerPage />
|
||||
</SentryRoute>
|
||||
<SentryRoute path="*">
|
||||
<RoomRedirect />
|
||||
</SentryRoute>
|
||||
</Switch>
|
||||
</OverlayProvider>
|
||||
</Sentry.ErrorBoundary>
|
||||
</InspectorContextProvider>
|
||||
</MediaHandlerProvider>
|
||||
</ClientProvider>
|
||||
</Suspense>
|
||||
) : (
|
||||
|
||||
@@ -134,7 +134,9 @@ export function RoomHeaderInfo({ roomName, avatarUrl }: RoomHeaderInfo) {
|
||||
/>
|
||||
<VideoIcon width={16} height={16} />
|
||||
</div>
|
||||
<Subtitle fontWeight="semiBold">{roomName}</Subtitle>
|
||||
<Subtitle data-testid="roomHeader_roomName" fontWeight="semiBold">
|
||||
{roomName}
|
||||
</Subtitle>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ limitations under the License.
|
||||
.modalHeader {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 34px 34px 0 34px;
|
||||
padding: 34px 32px 0 32px;
|
||||
}
|
||||
|
||||
.modalHeader h3 {
|
||||
@@ -72,7 +72,7 @@ limitations under the License.
|
||||
.modalHeader {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 24px 24px 0 24px;
|
||||
padding: 32px 20px 0 20px;
|
||||
}
|
||||
|
||||
.modal.mobileFullScreen {
|
||||
|
||||
@@ -92,6 +92,7 @@ export function Modal({
|
||||
{...closeButtonProps}
|
||||
ref={closeButtonRef}
|
||||
className={styles.closeButton}
|
||||
data-testid="modal_close"
|
||||
title={t("Close")}
|
||||
>
|
||||
<CloseIcon />
|
||||
|
||||
@@ -79,6 +79,11 @@ export interface UrlParams {
|
||||
* The Posthog analytics ID. It is only available if the user has given consent for sharing telemetry in element web.
|
||||
*/
|
||||
analyticsID: string | null;
|
||||
/**
|
||||
* Whether the app is allowed to use fallback STUN servers for ICE in case the
|
||||
* user's homeserver doesn't provide any.
|
||||
*/
|
||||
allowIceFallback: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -135,6 +140,7 @@ export const getUrlParams = (
|
||||
fonts: getAllParams("font"),
|
||||
fontScale: Number.isNaN(fontScale) ? null : fontScale,
|
||||
analyticsID: getParam("analyticsID"),
|
||||
allowIceFallback: hasParam("allowIceFallback"),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2022 New Vector Ltd
|
||||
Copyright 2022 - 2023 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -25,6 +25,7 @@ import { Menu } from "./Menu";
|
||||
import { TooltipTrigger } from "./Tooltip";
|
||||
import { Avatar, Size } from "./Avatar";
|
||||
import { ReactComponent as UserIcon } from "./icons/User.svg";
|
||||
import { ReactComponent as SettingsIcon } from "./icons/Settings.svg";
|
||||
import { ReactComponent as LoginIcon } from "./icons/Login.svg";
|
||||
import { ReactComponent as LogoutIcon } from "./icons/Logout.svg";
|
||||
import { Body } from "./typography/Typography";
|
||||
@@ -58,6 +59,12 @@ export function UserMenu({
|
||||
key: "user",
|
||||
icon: UserIcon,
|
||||
label: displayName,
|
||||
dataTestid: "usermenu_user",
|
||||
});
|
||||
arr.push({
|
||||
key: "settings",
|
||||
icon: SettingsIcon,
|
||||
label: t("Settings"),
|
||||
});
|
||||
|
||||
if (isPasswordlessUser && !preventNavigation) {
|
||||
@@ -65,6 +72,7 @@ export function UserMenu({
|
||||
key: "login",
|
||||
label: t("Sign in"),
|
||||
icon: LoginIcon,
|
||||
dataTestid: "usermenu_login",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -73,6 +81,7 @@ export function UserMenu({
|
||||
key: "logout",
|
||||
label: t("Sign out"),
|
||||
icon: LogoutIcon,
|
||||
dataTestid: "usermenu_logout",
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -93,7 +102,11 @@ export function UserMenu({
|
||||
return (
|
||||
<PopoverMenuTrigger placement="bottom right">
|
||||
<TooltipTrigger tooltip={tooltip} placement="bottom left">
|
||||
<Button variant="icon" className={styles.userButton}>
|
||||
<Button
|
||||
variant="icon"
|
||||
className={styles.userButton}
|
||||
data-testid="usermenu_open"
|
||||
>
|
||||
{isAuthenticated && (!isPasswordlessUser || avatarUrl) ? (
|
||||
<Avatar
|
||||
size={Size.SM}
|
||||
@@ -108,9 +121,14 @@ export function UserMenu({
|
||||
</TooltipTrigger>
|
||||
{(props) => (
|
||||
<Menu {...props} label={t("User menu")} onAction={onAction}>
|
||||
{items.map(({ key, icon: Icon, label }) => (
|
||||
{items.map(({ key, icon: Icon, label, dataTestid }) => (
|
||||
<Item key={key} textValue={label}>
|
||||
<Icon width={24} height={24} className={styles.menuIcon} />
|
||||
<Icon
|
||||
width={24}
|
||||
height={24}
|
||||
className={styles.menuIcon}
|
||||
data-testid={dataTestid}
|
||||
/>
|
||||
<Body overflowEllipsis>{label}</Body>
|
||||
</Item>
|
||||
))}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2022 New Vector Ltd
|
||||
Copyright 2022 - 2023 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -14,13 +14,13 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useCallback } from "react";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { useHistory, useLocation } from "react-router-dom";
|
||||
|
||||
import { useClient } from "./ClientContext";
|
||||
import { useProfile } from "./profile/useProfile";
|
||||
import { useModalTriggerState } from "./Modal";
|
||||
import { ProfileModal } from "./profile/ProfileModal";
|
||||
import { SettingsModal } from "./settings/SettingsModal";
|
||||
import { UserMenu } from "./UserMenu";
|
||||
|
||||
interface Props {
|
||||
@@ -35,10 +35,17 @@ export function UserMenuContainer({ preventNavigation = false }: Props) {
|
||||
const { displayName, avatarUrl } = useProfile(client);
|
||||
const { modalState, modalProps } = useModalTriggerState();
|
||||
|
||||
const [defaultSettingsTab, setDefaultSettingsTab] = useState<string>();
|
||||
|
||||
const onAction = useCallback(
|
||||
(value: string) => {
|
||||
async (value: string) => {
|
||||
switch (value) {
|
||||
case "user":
|
||||
setDefaultSettingsTab("profile");
|
||||
modalState.open();
|
||||
break;
|
||||
case "settings":
|
||||
setDefaultSettingsTab("audio");
|
||||
modalState.open();
|
||||
break;
|
||||
case "logout":
|
||||
@@ -64,7 +71,13 @@ export function UserMenuContainer({ preventNavigation = false }: Props) {
|
||||
displayName || (userName ? userName.replace("@", "") : undefined)
|
||||
}
|
||||
/>
|
||||
{modalState.isOpen && <ProfileModal client={client} {...modalProps} />}
|
||||
{modalState.isOpen && (
|
||||
<SettingsModal
|
||||
client={client}
|
||||
defaultTab={defaultSettingsTab}
|
||||
{...modalProps}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
MuteCameraTracker,
|
||||
MuteMicrophoneTracker,
|
||||
UndecryptableToDeviceEventTracker,
|
||||
QualitySurveyEventTracker,
|
||||
} from "./PosthogEvents";
|
||||
import { Config } from "../config/Config";
|
||||
import { getUrlParams } from "../UrlParams";
|
||||
@@ -431,4 +432,5 @@ export class PosthogAnalytics {
|
||||
public eventMuteMicrophone = new MuteMicrophoneTracker();
|
||||
public eventMuteCamera = new MuteCameraTracker();
|
||||
public eventUndecryptableToDevice = new UndecryptableToDeviceEventTracker();
|
||||
public eventQualitySurvey = new QualitySurveyEventTracker();
|
||||
}
|
||||
|
||||
@@ -163,3 +163,21 @@ export class UndecryptableToDeviceEventTracker {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
interface QualitySurveyEvent {
|
||||
eventName: "QualitySurvey";
|
||||
callId: string;
|
||||
feedbackText: string;
|
||||
stars: number;
|
||||
}
|
||||
|
||||
export class QualitySurveyEventTracker {
|
||||
track(callId: string, feedbackText: string, stars: number) {
|
||||
PosthogAnalytics.instance.trackEvent<QualitySurveyEvent>({
|
||||
eventName: "QualitySurvey",
|
||||
callId,
|
||||
feedbackText,
|
||||
stars,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,6 +124,13 @@ export class PosthogSpanProcessor implements SpanProcessor {
|
||||
const audioReceived = `${attributes["matrix.stats.summary.percentageReceivedAudioMedia"]}`;
|
||||
const maxJitter = `${attributes["matrix.stats.summary.maxJitter"]}`;
|
||||
const maxPacketLoss = `${attributes["matrix.stats.summary.maxPacketLoss"]}`;
|
||||
const peerConnections = `${attributes["matrix.stats.summary.peerConnections"]}`;
|
||||
const percentageConcealedAudio = `${attributes["matrix.stats.summary.percentageConcealedAudio"]}`;
|
||||
const opponentUsersInCall = `${attributes["matrix.stats.summary.opponentUsersInCall"]}`;
|
||||
const opponentDevicesInCall = `${attributes["matrix.stats.summary.opponentDevicesInCall"]}`;
|
||||
const diffDevicesToPeerConnections = `${attributes["matrix.stats.summary.diffDevicesToPeerConnections"]}`;
|
||||
const ratioPeerConnectionToDevices = `${attributes["matrix.stats.summary.ratioPeerConnectionToDevices"]}`;
|
||||
|
||||
PosthogAnalytics.instance.trackEvent(
|
||||
{
|
||||
eventName: "MediaReceived",
|
||||
@@ -133,6 +140,12 @@ export class PosthogSpanProcessor implements SpanProcessor {
|
||||
videoReceived: videoReceived,
|
||||
maxJitter: maxJitter,
|
||||
maxPacketLoss: maxPacketLoss,
|
||||
peerConnections: peerConnections,
|
||||
percentageConcealedAudio: percentageConcealedAudio,
|
||||
opponentUsersInCall: opponentUsersInCall,
|
||||
opponentDevicesInCall: opponentDevicesInCall,
|
||||
diffDevicesToPeerConnections: diffDevicesToPeerConnections,
|
||||
ratioPeerConnectionToDevices: ratioPeerConnectionToDevices,
|
||||
},
|
||||
// Send instantly because the window might be closing
|
||||
{ send_instantly: true }
|
||||
|
||||
@@ -88,6 +88,7 @@ export const LoginPage: FC = () => {
|
||||
autoCapitalize="none"
|
||||
prefix="@"
|
||||
suffix={`:${Config.defaultServerName()}`}
|
||||
data-testid="login_username"
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
@@ -96,6 +97,7 @@ export const LoginPage: FC = () => {
|
||||
ref={passwordRef}
|
||||
placeholder={t("Password")}
|
||||
label={t("Password")}
|
||||
data-testid="login_password"
|
||||
/>
|
||||
</FieldRow>
|
||||
{error && (
|
||||
@@ -104,7 +106,11 @@ export const LoginPage: FC = () => {
|
||||
</FieldRow>
|
||||
)}
|
||||
<FieldRow>
|
||||
<Button type="submit" disabled={loading}>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
data-testid="login_login"
|
||||
>
|
||||
{loading ? t("Logging in…") : t("Login")}
|
||||
</Button>
|
||||
</FieldRow>
|
||||
|
||||
@@ -166,6 +166,7 @@ export const RegisterPage: FC = () => {
|
||||
autoCapitalize="none"
|
||||
prefix="@"
|
||||
suffix={`:${Config.defaultServerName()}`}
|
||||
data-testid="register_username"
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
@@ -179,6 +180,7 @@ export const RegisterPage: FC = () => {
|
||||
value={password}
|
||||
placeholder={t("Password")}
|
||||
label={t("Password")}
|
||||
data-testid="register_password"
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
@@ -193,6 +195,7 @@ export const RegisterPage: FC = () => {
|
||||
placeholder={t("Confirm password")}
|
||||
label={t("Confirm password")}
|
||||
ref={confirmPasswordRef}
|
||||
data-testid="register_confirm_password"
|
||||
/>
|
||||
</FieldRow>
|
||||
<Caption>
|
||||
@@ -217,7 +220,11 @@ export const RegisterPage: FC = () => {
|
||||
</FieldRow>
|
||||
)}
|
||||
<FieldRow>
|
||||
<Button type="submit" disabled={registering}>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={registering}
|
||||
data-testid="register_register"
|
||||
>
|
||||
{registering ? t("Registering…") : t("Register")}
|
||||
</Button>
|
||||
</FieldRow>
|
||||
|
||||
@@ -39,10 +39,10 @@ limitations under the License.
|
||||
.secondaryHangup,
|
||||
.button,
|
||||
.copyButton {
|
||||
padding: 7px 15px;
|
||||
padding: 8px 20px;
|
||||
border-radius: 8px;
|
||||
font-size: var(--font-size-body);
|
||||
font-weight: 700;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.button {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2022 New Vector Ltd
|
||||
Copyright 2022 - 2023 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -238,7 +238,7 @@ export function SettingsButton({
|
||||
return (
|
||||
<TooltipTrigger tooltip={tooltip}>
|
||||
<Button variant="toolbar" {...rest}>
|
||||
<SettingsIcon />
|
||||
<SettingsIcon width={20} height={20} />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
);
|
||||
@@ -246,9 +246,11 @@ export function SettingsButton({
|
||||
|
||||
export function InviteButton({
|
||||
className,
|
||||
variant = "toolbar",
|
||||
...rest
|
||||
}: {
|
||||
className?: string;
|
||||
variant?: string;
|
||||
// TODO: add all props for <Button>
|
||||
[index: string]: unknown;
|
||||
}) {
|
||||
@@ -257,7 +259,7 @@ export function InviteButton({
|
||||
|
||||
return (
|
||||
<TooltipTrigger tooltip={tooltip}>
|
||||
<Button variant="toolbar" {...rest}>
|
||||
<Button variant={variant} {...rest}>
|
||||
<AddUserIcon />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
|
||||
@@ -43,7 +43,9 @@ export function JoinExistingCallModal({ onJoin, onClose, ...rest }: Props) {
|
||||
<p>{t("This call already exists, would you like to join?")}</p>
|
||||
<FieldRow rightAlign className={styles.buttons}>
|
||||
<Button onPress={onClose}>{t("No")}</Button>
|
||||
<Button onPress={onJoin}>{t("Yes, join call")}</Button>
|
||||
<Button onPress={onJoin} data-testid="home_joinExistingRoom">
|
||||
{t("Yes, join call")}
|
||||
</Button>
|
||||
</FieldRow>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
|
||||
@@ -133,6 +133,7 @@ export function RegisteredView({ client, isPasswordlessUser }: Props) {
|
||||
type="text"
|
||||
required
|
||||
autoComplete="off"
|
||||
data-testid="home_callName"
|
||||
/>
|
||||
|
||||
<Button
|
||||
@@ -140,6 +141,7 @@ export function RegisteredView({ client, isPasswordlessUser }: Props) {
|
||||
size="lg"
|
||||
className={styles.button}
|
||||
disabled={loading}
|
||||
data-testid="home_go"
|
||||
>
|
||||
{loading ? t("Loading…") : t("Go")}
|
||||
</Button>
|
||||
|
||||
@@ -142,6 +142,7 @@ export const UnauthenticatedView: FC = () => {
|
||||
type="text"
|
||||
required
|
||||
autoComplete="off"
|
||||
data-testid="home_callName"
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
@@ -152,6 +153,7 @@ export const UnauthenticatedView: FC = () => {
|
||||
placeholder={t("Display name")}
|
||||
type="text"
|
||||
required
|
||||
data-testid="home_displayName"
|
||||
autoComplete="off"
|
||||
/>
|
||||
</FieldRow>
|
||||
@@ -171,7 +173,12 @@ export const UnauthenticatedView: FC = () => {
|
||||
<ErrorMessage error={error} />
|
||||
</FieldRow>
|
||||
)}
|
||||
<Button type="submit" size="lg" disabled={loading}>
|
||||
<Button
|
||||
type="submit"
|
||||
size="lg"
|
||||
disabled={loading}
|
||||
data-testid="home_go"
|
||||
>
|
||||
{loading ? t("Loading…") : t("Go")}
|
||||
</Button>
|
||||
<div id={recaptchaId} />
|
||||
@@ -179,14 +186,14 @@ export const UnauthenticatedView: FC = () => {
|
||||
</main>
|
||||
<footer className={styles.footer}>
|
||||
<Body className={styles.mobileLoginLink}>
|
||||
<Link color="primary" to="/login">
|
||||
<Link color="primary" to="/login" data-testid="home_login">
|
||||
{t("Login to your account")}
|
||||
</Link>
|
||||
</Body>
|
||||
<Body>
|
||||
<Trans>
|
||||
Not registered yet?{" "}
|
||||
<Link color="primary" to="/register">
|
||||
<Link color="primary" to="/register" data-testid="home_register">
|
||||
Create an account
|
||||
</Link>
|
||||
</Trans>
|
||||
|
||||
3
src/icons/StarSelected.svg
Normal file
3
src/icons/StarSelected.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="28" height="26" viewBox="0 0 28 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14 21.0267L22.24 26.0001L20.0533 16.6267L27.3333 10.3201L17.7466 9.50675L14 0.666748L10.2533 9.50675L0.666626 10.3201L7.94663 16.6267L5.75996 26.0001L14 21.0267Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 290 B |
4
src/icons/StarUnselected.svg
Normal file
4
src/icons/StarUnselected.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="28" height="26" viewBox="0 0 28 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path id="Vector" d="M14 7.50675L15.2933 10.5601L15.92 12.0401L17.52 12.1734L20.8133 12.4534L18.3066 14.6267L17.0933 15.6801L17.4533 17.2534L18.2 20.4667L15.3733 18.7601L14 17.9067L12.6266 18.7334L9.79996 20.4401L10.5466 17.2267L10.9066 15.6534L9.69329 14.6001L7.18663 12.4267L10.48 12.1467L12.08 12.0134L12.7066 10.5334L14 7.50675M14 0.666748L10.2533 9.50675L0.666626 10.3201L7.94663 16.6267L5.75996 26.0001L14 21.0267L22.24 26.0001L20.0533 16.6267L27.3333 10.3201L17.7466 9.50675L14 0.666748Z" fill="white"/>
|
||||
</svg>
|
||||
|
||||
|
After Width: | Height: | Size: 620 B |
@@ -1,4 +1,4 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg data-testid="videoTile_muted" width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0.20333 0.963373C0.474437 0.690007 0.913989 0.690007 1.1851 0.963373L11.5983 11.4633C11.8694 11.7367 11.8694 12.1799 11.5983 12.4533C11.3272 12.7267 10.8876 12.7267 10.6165 12.4533L0.20333 1.95332C-0.0677768 1.67995 -0.0677768 1.23674 0.20333 0.963373Z" fill="white"/>
|
||||
<path d="M0.418261 3.63429C0.226267 3.95219 0.115674 4.32557 0.115674 4.725V9.85832C0.115674 11.0181 1.0481 11.9583 2.19831 11.9583H8.65411L0.447396 3.66596C0.437225 3.65568 0.427513 3.64511 0.418261 3.63429Z" fill="white"/>
|
||||
<path d="M9.95036 4.725V8.33212L4.30219 2.625H7.86772C9.01793 2.625 9.95036 3.5652 9.95036 4.725Z" fill="white"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 892 B After Width: | Height: | Size: 922 B |
@@ -180,10 +180,16 @@ h2 {
|
||||
|
||||
/* Subtitle */
|
||||
h3 {
|
||||
font-weight: 400;
|
||||
font-weight: 600;
|
||||
font-size: var(--font-size-subtitle);
|
||||
}
|
||||
|
||||
/* Body Semi Bold */
|
||||
h4 {
|
||||
font-weight: 600;
|
||||
font-size: var(--font-size-body);
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
|
||||
@@ -54,4 +54,6 @@ limitations under the License.
|
||||
|
||||
.removeButton {
|
||||
color: var(--accent);
|
||||
font-size: var(--font-size-caption);
|
||||
padding: 6px 0;
|
||||
}
|
||||
|
||||
23
src/input/FeedbackInput.module.css
Normal file
23
src/input/FeedbackInput.module.css
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
Copyright 2023 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
.feedback textarea {
|
||||
height: 75px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.feedback {
|
||||
border-radius: 8px;
|
||||
}
|
||||
@@ -72,6 +72,7 @@ interface InputFieldProps {
|
||||
autoCorrect?: string;
|
||||
autoCapitalize?: string;
|
||||
value?: string;
|
||||
defaultValue?: string;
|
||||
placeholder?: string;
|
||||
defaultChecked?: boolean;
|
||||
onChange?: (event: ChangeEvent) => void;
|
||||
|
||||
@@ -22,8 +22,6 @@ limitations under the License.
|
||||
}
|
||||
|
||||
.label {
|
||||
font-weight: 600;
|
||||
font-size: var(--font-size-subtitle);
|
||||
margin-top: 0;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
41
src/input/StarRatingInput.module.css
Normal file
41
src/input/StarRatingInput.module.css
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
Copyright 2023 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
.starIcon {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.starRating {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.inputContainer {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.hideElement {
|
||||
border: 0;
|
||||
clip-path: content-box;
|
||||
height: 0px;
|
||||
width: 0px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
width: 1px;
|
||||
display: inline-block;
|
||||
}
|
||||
85
src/input/StarRatingInput.tsx
Normal file
85
src/input/StarRatingInput.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
Copyright 2023 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import styles from "./StarRatingInput.module.css";
|
||||
import { ReactComponent as StarSelected } from "../icons/StarSelected.svg";
|
||||
import { ReactComponent as StarUnselected } from "../icons/StarUnselected.svg";
|
||||
|
||||
interface Props {
|
||||
starCount: number;
|
||||
onChange: (stars: number) => void;
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
export function StarRatingInput({
|
||||
starCount,
|
||||
onChange,
|
||||
required,
|
||||
}: Props): JSX.Element {
|
||||
const [rating, setRating] = useState(0);
|
||||
const [hover, setHover] = useState(0);
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className={styles.starRating}>
|
||||
{[...Array(starCount)].map((_star, index) => {
|
||||
index += 1;
|
||||
return (
|
||||
<div
|
||||
className={styles.inputContainer}
|
||||
onMouseEnter={() => setHover(index)}
|
||||
onMouseLeave={() => setHover(rating)}
|
||||
key={index}
|
||||
>
|
||||
<input
|
||||
className={styles.hideElement}
|
||||
type="radio"
|
||||
id={"starInput" + String(index)}
|
||||
value={String(index) + "Star"}
|
||||
name="star rating"
|
||||
onChange={(_ev) => {
|
||||
setRating(index);
|
||||
onChange(index);
|
||||
}}
|
||||
required
|
||||
/>
|
||||
<label
|
||||
className={styles.hideElement}
|
||||
id={"starInvisibleLabel" + String(index)}
|
||||
htmlFor={"starInput" + String(index)}
|
||||
>
|
||||
{t("{{count}} stars", {
|
||||
count: index,
|
||||
})}
|
||||
</label>
|
||||
<label
|
||||
className={styles.starIcon}
|
||||
id={"starIcon" + String(index)}
|
||||
htmlFor={"starInput" + String(index)}
|
||||
>
|
||||
{index <= (hover || rating) ? (
|
||||
<StarSelected />
|
||||
) : (
|
||||
<StarUnselected />
|
||||
)}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -47,7 +47,7 @@ export async function findDeviceByName(
|
||||
*
|
||||
* @return The available media devices
|
||||
*/
|
||||
export async function getDevices(): Promise<MediaDeviceInfo[]> {
|
||||
export async function getNamedDevices(): Promise<MediaDeviceInfo[]> {
|
||||
// First get the devices without their labels, to learn what kinds of streams
|
||||
// we can request
|
||||
let devices: MediaDeviceInfo[];
|
||||
|
||||
@@ -17,13 +17,33 @@ limitations under the License.
|
||||
import { Span } from "@opentelemetry/api";
|
||||
import { MatrixCall } from "matrix-js-sdk";
|
||||
import { CallEvent } from "matrix-js-sdk/src/webrtc/call";
|
||||
import {
|
||||
TransceiverStats,
|
||||
CallFeedStats,
|
||||
} from "matrix-js-sdk/src/webrtc/stats/statsReport";
|
||||
|
||||
import { ObjectFlattener } from "./ObjectFlattener";
|
||||
import { ElementCallOpenTelemetry } from "./otel";
|
||||
import { OTelCallAbstractMediaStreamSpan } from "./OTelCallAbstractMediaStreamSpan";
|
||||
import { OTelCallTransceiverMediaStreamSpan } from "./OTelCallTransceiverMediaStreamSpan";
|
||||
import { OTelCallFeedMediaStreamSpan } from "./OTelCallFeedMediaStreamSpan";
|
||||
|
||||
type StreamId = string;
|
||||
type MID = string;
|
||||
|
||||
/**
|
||||
* Tracks an individual call within a group call, either to a full-mesh peer or a focus
|
||||
*/
|
||||
export class OTelCall {
|
||||
private readonly trackFeedSpan = new Map<
|
||||
StreamId,
|
||||
OTelCallAbstractMediaStreamSpan
|
||||
>();
|
||||
private readonly trackTransceiverSpan = new Map<
|
||||
MID,
|
||||
OTelCallAbstractMediaStreamSpan
|
||||
>();
|
||||
|
||||
constructor(
|
||||
public userId: string,
|
||||
public deviceId: string,
|
||||
@@ -116,4 +136,62 @@ export class OTelCall {
|
||||
|
||||
this.span.addEvent("matrix.call.iceCandidateError", flatObject);
|
||||
};
|
||||
|
||||
public onCallFeedStats(callFeeds: CallFeedStats[]): void {
|
||||
let prvFeeds: StreamId[] = [...this.trackFeedSpan.keys()];
|
||||
|
||||
callFeeds.forEach((feed) => {
|
||||
if (!this.trackFeedSpan.has(feed.stream)) {
|
||||
this.trackFeedSpan.set(
|
||||
feed.stream,
|
||||
new OTelCallFeedMediaStreamSpan(
|
||||
ElementCallOpenTelemetry.instance,
|
||||
this.span,
|
||||
feed
|
||||
)
|
||||
);
|
||||
}
|
||||
this.trackFeedSpan.get(feed.stream)?.update(feed);
|
||||
prvFeeds = prvFeeds.filter((prvStreamId) => prvStreamId !== feed.stream);
|
||||
});
|
||||
|
||||
prvFeeds.forEach((prvStreamId) => {
|
||||
this.trackFeedSpan.get(prvStreamId)?.end();
|
||||
this.trackFeedSpan.delete(prvStreamId);
|
||||
});
|
||||
}
|
||||
|
||||
public onTransceiverStats(transceiverStats: TransceiverStats[]): void {
|
||||
let prvTransSpan: MID[] = [...this.trackTransceiverSpan.keys()];
|
||||
|
||||
transceiverStats.forEach((transStats) => {
|
||||
if (!this.trackTransceiverSpan.has(transStats.mid)) {
|
||||
this.trackTransceiverSpan.set(
|
||||
transStats.mid,
|
||||
new OTelCallTransceiverMediaStreamSpan(
|
||||
ElementCallOpenTelemetry.instance,
|
||||
this.span,
|
||||
transStats
|
||||
)
|
||||
);
|
||||
}
|
||||
this.trackTransceiverSpan.get(transStats.mid)?.update(transStats);
|
||||
prvTransSpan = prvTransSpan.filter(
|
||||
(prvStreamId) => prvStreamId !== transStats.mid
|
||||
);
|
||||
});
|
||||
|
||||
prvTransSpan.forEach((prvMID) => {
|
||||
this.trackTransceiverSpan.get(prvMID)?.end();
|
||||
this.trackTransceiverSpan.delete(prvMID);
|
||||
});
|
||||
}
|
||||
|
||||
public end(): void {
|
||||
this.trackFeedSpan.forEach((feedSpan) => feedSpan.end());
|
||||
this.trackTransceiverSpan.forEach((transceiverSpan) =>
|
||||
transceiverSpan.end()
|
||||
);
|
||||
this.span.end();
|
||||
}
|
||||
}
|
||||
|
||||
62
src/otel/OTelCallAbstractMediaStreamSpan.ts
Normal file
62
src/otel/OTelCallAbstractMediaStreamSpan.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import opentelemetry, { Span } from "@opentelemetry/api";
|
||||
import { TrackStats } from "matrix-js-sdk/src/webrtc/stats/statsReport";
|
||||
|
||||
import { ElementCallOpenTelemetry } from "./otel";
|
||||
import { OTelCallMediaStreamTrackSpan } from "./OTelCallMediaStreamTrackSpan";
|
||||
|
||||
type TrackId = string;
|
||||
|
||||
export abstract class OTelCallAbstractMediaStreamSpan {
|
||||
protected readonly trackSpans = new Map<
|
||||
TrackId,
|
||||
OTelCallMediaStreamTrackSpan
|
||||
>();
|
||||
public readonly span;
|
||||
|
||||
public constructor(
|
||||
readonly oTel: ElementCallOpenTelemetry,
|
||||
readonly callSpan: Span,
|
||||
protected readonly type: string
|
||||
) {
|
||||
const ctx = opentelemetry.trace.setSpan(
|
||||
opentelemetry.context.active(),
|
||||
callSpan
|
||||
);
|
||||
const options = {
|
||||
links: [
|
||||
{
|
||||
context: callSpan.spanContext(),
|
||||
},
|
||||
],
|
||||
};
|
||||
this.span = oTel.tracer.startSpan(this.type, options, ctx);
|
||||
}
|
||||
|
||||
protected upsertTrackSpans(tracks: TrackStats[]) {
|
||||
let prvTracks: TrackId[] = [...this.trackSpans.keys()];
|
||||
tracks.forEach((t) => {
|
||||
if (!this.trackSpans.has(t.id)) {
|
||||
this.trackSpans.set(
|
||||
t.id,
|
||||
new OTelCallMediaStreamTrackSpan(this.oTel, this.span, t)
|
||||
);
|
||||
}
|
||||
this.trackSpans.get(t.id)?.update(t);
|
||||
prvTracks = prvTracks.filter((prvTrackId) => prvTrackId !== t.id);
|
||||
});
|
||||
|
||||
prvTracks.forEach((prvTrackId) => {
|
||||
this.trackSpans.get(prvTrackId)?.end();
|
||||
this.trackSpans.delete(prvTrackId);
|
||||
});
|
||||
}
|
||||
|
||||
public abstract update(data: Object): void;
|
||||
|
||||
public end(): void {
|
||||
this.trackSpans.forEach((tSpan) => {
|
||||
tSpan.end();
|
||||
});
|
||||
this.span.end();
|
||||
}
|
||||
}
|
||||
57
src/otel/OTelCallFeedMediaStreamSpan.ts
Normal file
57
src/otel/OTelCallFeedMediaStreamSpan.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { Span } from "@opentelemetry/api";
|
||||
import {
|
||||
CallFeedStats,
|
||||
TrackStats,
|
||||
} from "matrix-js-sdk/src/webrtc/stats/statsReport";
|
||||
|
||||
import { ElementCallOpenTelemetry } from "./otel";
|
||||
import { OTelCallAbstractMediaStreamSpan } from "./OTelCallAbstractMediaStreamSpan";
|
||||
|
||||
export class OTelCallFeedMediaStreamSpan extends OTelCallAbstractMediaStreamSpan {
|
||||
private readonly prev: { isAudioMuted: boolean; isVideoMuted: boolean };
|
||||
|
||||
constructor(
|
||||
readonly oTel: ElementCallOpenTelemetry,
|
||||
readonly callSpan: Span,
|
||||
callFeed: CallFeedStats
|
||||
) {
|
||||
const postFix =
|
||||
callFeed.type === "local" && callFeed.prefix === "from-call-feed"
|
||||
? "(clone)"
|
||||
: "";
|
||||
super(oTel, callSpan, `matrix.call.feed.${callFeed.type}${postFix}`);
|
||||
this.span.setAttribute("feed.streamId", callFeed.stream);
|
||||
this.span.setAttribute("feed.type", callFeed.type);
|
||||
this.span.setAttribute("feed.readFrom", callFeed.prefix);
|
||||
this.span.setAttribute("feed.purpose", callFeed.purpose);
|
||||
this.prev = {
|
||||
isAudioMuted: callFeed.isAudioMuted,
|
||||
isVideoMuted: callFeed.isVideoMuted,
|
||||
};
|
||||
this.span.addEvent("matrix.call.feed.initState", this.prev);
|
||||
}
|
||||
|
||||
public update(callFeed: CallFeedStats): void {
|
||||
if (this.prev.isAudioMuted !== callFeed.isAudioMuted) {
|
||||
this.span.addEvent("matrix.call.feed.audioMuted", {
|
||||
isAudioMuted: callFeed.isAudioMuted,
|
||||
});
|
||||
this.prev.isAudioMuted = callFeed.isAudioMuted;
|
||||
}
|
||||
if (this.prev.isVideoMuted !== callFeed.isVideoMuted) {
|
||||
this.span.addEvent("matrix.call.feed.isVideoMuted", {
|
||||
isVideoMuted: callFeed.isVideoMuted,
|
||||
});
|
||||
this.prev.isVideoMuted = callFeed.isVideoMuted;
|
||||
}
|
||||
|
||||
const trackStats: TrackStats[] = [];
|
||||
if (callFeed.video) {
|
||||
trackStats.push(callFeed.video);
|
||||
}
|
||||
if (callFeed.audio) {
|
||||
trackStats.push(callFeed.audio);
|
||||
}
|
||||
this.upsertTrackSpans(trackStats);
|
||||
}
|
||||
}
|
||||
62
src/otel/OTelCallMediaStreamTrackSpan.ts
Normal file
62
src/otel/OTelCallMediaStreamTrackSpan.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { TrackStats } from "matrix-js-sdk/src/webrtc/stats/statsReport";
|
||||
import opentelemetry, { Span } from "@opentelemetry/api";
|
||||
|
||||
import { ElementCallOpenTelemetry } from "./otel";
|
||||
|
||||
export class OTelCallMediaStreamTrackSpan {
|
||||
private readonly span: Span;
|
||||
private prev: TrackStats;
|
||||
|
||||
public constructor(
|
||||
readonly oTel: ElementCallOpenTelemetry,
|
||||
readonly streamSpan: Span,
|
||||
data: TrackStats
|
||||
) {
|
||||
const ctx = opentelemetry.trace.setSpan(
|
||||
opentelemetry.context.active(),
|
||||
streamSpan
|
||||
);
|
||||
const options = {
|
||||
links: [
|
||||
{
|
||||
context: streamSpan.spanContext(),
|
||||
},
|
||||
],
|
||||
};
|
||||
const type = `matrix.call.track.${data.label}.${data.kind}`;
|
||||
this.span = oTel.tracer.startSpan(type, options, ctx);
|
||||
this.span.setAttribute("track.trackId", data.id);
|
||||
this.span.setAttribute("track.kind", data.kind);
|
||||
this.span.setAttribute("track.constrainDeviceId", data.constrainDeviceId);
|
||||
this.span.setAttribute("track.settingDeviceId", data.settingDeviceId);
|
||||
this.span.setAttribute("track.label", data.label);
|
||||
|
||||
this.span.addEvent("matrix.call.track.initState", {
|
||||
readyState: data.readyState,
|
||||
muted: data.muted,
|
||||
enabled: data.enabled,
|
||||
});
|
||||
this.prev = data;
|
||||
}
|
||||
|
||||
public update(data: TrackStats): void {
|
||||
if (this.prev.muted !== data.muted) {
|
||||
this.span.addEvent("matrix.call.track.muted", { muted: data.muted });
|
||||
}
|
||||
if (this.prev.enabled !== data.enabled) {
|
||||
this.span.addEvent("matrix.call.track.enabled", {
|
||||
enabled: data.enabled,
|
||||
});
|
||||
}
|
||||
if (this.prev.readyState !== data.readyState) {
|
||||
this.span.addEvent("matrix.call.track.readyState", {
|
||||
readyState: data.readyState,
|
||||
});
|
||||
}
|
||||
this.prev = data;
|
||||
}
|
||||
|
||||
public end(): void {
|
||||
this.span.end();
|
||||
}
|
||||
}
|
||||
54
src/otel/OTelCallTransceiverMediaStreamSpan.ts
Normal file
54
src/otel/OTelCallTransceiverMediaStreamSpan.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { Span } from "@opentelemetry/api";
|
||||
import {
|
||||
TrackStats,
|
||||
TransceiverStats,
|
||||
} from "matrix-js-sdk/src/webrtc/stats/statsReport";
|
||||
|
||||
import { ElementCallOpenTelemetry } from "./otel";
|
||||
import { OTelCallAbstractMediaStreamSpan } from "./OTelCallAbstractMediaStreamSpan";
|
||||
|
||||
export class OTelCallTransceiverMediaStreamSpan extends OTelCallAbstractMediaStreamSpan {
|
||||
private readonly prev: {
|
||||
direction: string;
|
||||
currentDirection: string;
|
||||
};
|
||||
|
||||
constructor(
|
||||
readonly oTel: ElementCallOpenTelemetry,
|
||||
readonly callSpan: Span,
|
||||
stats: TransceiverStats
|
||||
) {
|
||||
super(oTel, callSpan, `matrix.call.transceiver.${stats.mid}`);
|
||||
this.span.setAttribute("transceiver.mid", stats.mid);
|
||||
|
||||
this.prev = {
|
||||
direction: stats.direction,
|
||||
currentDirection: stats.currentDirection,
|
||||
};
|
||||
this.span.addEvent("matrix.call.transceiver.initState", this.prev);
|
||||
}
|
||||
|
||||
public update(stats: TransceiverStats): void {
|
||||
if (this.prev.currentDirection !== stats.currentDirection) {
|
||||
this.span.addEvent("matrix.call.transceiver.currentDirection", {
|
||||
currentDirection: stats.currentDirection,
|
||||
});
|
||||
this.prev.currentDirection = stats.currentDirection;
|
||||
}
|
||||
if (this.prev.direction !== stats.direction) {
|
||||
this.span.addEvent("matrix.call.transceiver.direction", {
|
||||
direction: stats.direction,
|
||||
});
|
||||
this.prev.direction = stats.direction;
|
||||
}
|
||||
|
||||
const trackStats: TrackStats[] = [];
|
||||
if (stats.sender) {
|
||||
trackStats.push(stats.sender);
|
||||
}
|
||||
if (stats.receiver) {
|
||||
trackStats.push(stats.receiver);
|
||||
}
|
||||
this.upsertTrackSpans(trackStats);
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
ConnectionStatsReport,
|
||||
ByteSentStatsReport,
|
||||
SummaryStatsReport,
|
||||
CallFeedReport,
|
||||
} from "matrix-js-sdk/src/webrtc/stats/statsReport";
|
||||
import { setSpan } from "@opentelemetry/api/build/esm/trace/context-utils";
|
||||
|
||||
@@ -174,7 +175,7 @@ export class OTelGroupCallMembership {
|
||||
userCalls.get(callTrackingInfo.deviceId).callId !==
|
||||
callTrackingInfo.call.callId
|
||||
) {
|
||||
callTrackingInfo.span.end();
|
||||
callTrackingInfo.end();
|
||||
this.callsByCallId.delete(callTrackingInfo.call.callId);
|
||||
}
|
||||
}
|
||||
@@ -330,25 +331,102 @@ export class OTelGroupCallMembership {
|
||||
});
|
||||
}
|
||||
|
||||
public onCallFeedStatsReport(report: GroupCallStatsReport<CallFeedReport>) {
|
||||
if (!ElementCallOpenTelemetry.instance) return;
|
||||
let call: OTelCall | undefined;
|
||||
const callId = report.report?.callId;
|
||||
|
||||
if (callId) {
|
||||
call = this.callsByCallId.get(callId);
|
||||
}
|
||||
|
||||
if (!call) {
|
||||
this.callMembershipSpan?.addEvent(
|
||||
OTelStatsReportType.CallFeedReport + "_unknown_callId",
|
||||
{
|
||||
"call.callId": callId,
|
||||
"call.opponentMemberId": report.report?.opponentMemberId
|
||||
? report.report?.opponentMemberId
|
||||
: "unknown",
|
||||
}
|
||||
);
|
||||
logger.error(
|
||||
`Received ${OTelStatsReportType.CallFeedReport} with unknown call ID: ${callId}`
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
call.onCallFeedStats(report.report.callFeeds);
|
||||
call.onTransceiverStats(report.report.transceiver);
|
||||
}
|
||||
}
|
||||
|
||||
public onConnectionStatsReport(
|
||||
statsReport: GroupCallStatsReport<ConnectionStatsReport>
|
||||
) {
|
||||
if (!ElementCallOpenTelemetry.instance) return;
|
||||
|
||||
const type = OTelStatsReportType.ConnectionReport;
|
||||
const data =
|
||||
ObjectFlattener.flattenConnectionStatsReportObject(statsReport);
|
||||
this.buildStatsEventSpan({ type, data });
|
||||
this.buildCallStatsSpan(
|
||||
OTelStatsReportType.ConnectionReport,
|
||||
statsReport.report
|
||||
);
|
||||
}
|
||||
|
||||
public onByteSentStatsReport(
|
||||
statsReport: GroupCallStatsReport<ByteSentStatsReport>
|
||||
) {
|
||||
if (!ElementCallOpenTelemetry.instance) return;
|
||||
this.buildCallStatsSpan(
|
||||
OTelStatsReportType.ByteSentReport,
|
||||
statsReport.report
|
||||
);
|
||||
}
|
||||
|
||||
const type = OTelStatsReportType.ByteSentReport;
|
||||
const data = ObjectFlattener.flattenByteSentStatsReportObject(statsReport);
|
||||
this.buildStatsEventSpan({ type, data });
|
||||
public buildCallStatsSpan(
|
||||
type: OTelStatsReportType,
|
||||
report: ByteSentStatsReport | ConnectionStatsReport
|
||||
): void {
|
||||
if (!ElementCallOpenTelemetry.instance) return;
|
||||
let call: OTelCall | undefined;
|
||||
const callId = report?.callId;
|
||||
|
||||
if (callId) {
|
||||
call = this.callsByCallId.get(callId);
|
||||
}
|
||||
|
||||
if (!call) {
|
||||
this.callMembershipSpan?.addEvent(type + "_unknown_callid", {
|
||||
"call.callId": callId,
|
||||
"call.opponentMemberId": report.opponentMemberId
|
||||
? report.opponentMemberId
|
||||
: "unknown",
|
||||
});
|
||||
logger.error(`Received ${type} with unknown call ID: ${callId}`);
|
||||
return;
|
||||
}
|
||||
const data = ObjectFlattener.flattenReportObject(type, report);
|
||||
const ctx = opentelemetry.trace.setSpan(
|
||||
opentelemetry.context.active(),
|
||||
call.span
|
||||
);
|
||||
|
||||
const options = {
|
||||
links: [
|
||||
{
|
||||
context: call.span.spanContext(),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const span = ElementCallOpenTelemetry.instance.tracer.startSpan(
|
||||
type,
|
||||
options,
|
||||
ctx
|
||||
);
|
||||
|
||||
span.setAttribute("matrix.callId", callId);
|
||||
span.setAttribute(
|
||||
"matrix.opponentMemberId",
|
||||
report.opponentMemberId ? report.opponentMemberId : "unknown"
|
||||
);
|
||||
span.addEvent("matrix.call.connection_stats_event", data);
|
||||
span.end();
|
||||
}
|
||||
|
||||
public onSummaryStatsReport(
|
||||
@@ -381,45 +459,6 @@ export class OTelGroupCallMembership {
|
||||
span.end();
|
||||
}
|
||||
}
|
||||
|
||||
private buildStatsEventSpan(event: OTelStatsReportEvent): void {
|
||||
// @ TODO: fix this - Because on multiple calls we receive multiple stats report spans.
|
||||
// This could be break if stats arrived in same time from different call objects.
|
||||
if (this.statsReportSpan.span === undefined && this.callMembershipSpan) {
|
||||
const ctx = setSpan(
|
||||
opentelemetry.context.active(),
|
||||
this.callMembershipSpan
|
||||
);
|
||||
this.statsReportSpan.span =
|
||||
ElementCallOpenTelemetry.instance?.tracer.startSpan(
|
||||
"matrix.groupCallMembership.statsReport",
|
||||
undefined,
|
||||
ctx
|
||||
);
|
||||
if (this.statsReportSpan.span === undefined) {
|
||||
return;
|
||||
}
|
||||
this.statsReportSpan.span.setAttribute(
|
||||
"matrix.confId",
|
||||
this.groupCall.groupCallId
|
||||
);
|
||||
this.statsReportSpan.span.setAttribute("matrix.userId", this.myUserId);
|
||||
this.statsReportSpan.span.setAttribute(
|
||||
"matrix.displayName",
|
||||
this.myMember ? this.myMember.name : "unknown-name"
|
||||
);
|
||||
|
||||
this.statsReportSpan.span.addEvent(event.type, event.data);
|
||||
this.statsReportSpan.stats.push(event);
|
||||
} else if (
|
||||
this.statsReportSpan.span !== undefined &&
|
||||
this.callMembershipSpan
|
||||
) {
|
||||
this.statsReportSpan.span.addEvent(event.type, event.data);
|
||||
this.statsReportSpan.span.end();
|
||||
this.statsReportSpan = { span: undefined, stats: [] };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface OTelStatsReportEvent {
|
||||
@@ -428,7 +467,8 @@ interface OTelStatsReportEvent {
|
||||
}
|
||||
|
||||
enum OTelStatsReportType {
|
||||
ConnectionReport = "matrix.stats.connection",
|
||||
ByteSentReport = "matrix.stats.byteSent",
|
||||
ConnectionReport = "matrix.call.stats.connection",
|
||||
ByteSentReport = "matrix.call.stats.byteSent",
|
||||
SummaryReport = "matrix.stats.summary",
|
||||
CallFeedReport = "matrix.stats.call_feed",
|
||||
}
|
||||
|
||||
@@ -23,16 +23,12 @@ import {
|
||||
} from "matrix-js-sdk/src/webrtc/stats/statsReport";
|
||||
|
||||
export class ObjectFlattener {
|
||||
public static flattenConnectionStatsReportObject(
|
||||
statsReport: GroupCallStatsReport<ConnectionStatsReport>
|
||||
public static flattenReportObject(
|
||||
prefix: string,
|
||||
report: ConnectionStatsReport | ByteSentStatsReport
|
||||
): Attributes {
|
||||
const flatObject = {};
|
||||
ObjectFlattener.flattenObjectRecursive(
|
||||
statsReport.report,
|
||||
flatObject,
|
||||
"matrix.stats.conn.",
|
||||
0
|
||||
);
|
||||
ObjectFlattener.flattenObjectRecursive(report, flatObject, `${prefix}.`, 0);
|
||||
return flatObject;
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ export class ElementCallOpenTelemetry {
|
||||
const config = Config.get();
|
||||
// we always enable opentelemetry in general. We only enable the OTLP
|
||||
// collector if a URL is defined (and in future if another setting is defined)
|
||||
// The posthog exporter is always enabled, posthog reporting is enabled or disabled
|
||||
// Posthog reporting is enabled or disabled
|
||||
// within the posthog code.
|
||||
const shouldEnableOtlp = Boolean(config.opentelemetry?.collector_url);
|
||||
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
/*
|
||||
Copyright 2022 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { ChangeEvent, useCallback, useEffect, useState } from "react";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { Button } from "../button";
|
||||
import { useProfile } from "./useProfile";
|
||||
import { FieldRow, InputField, ErrorMessage } from "../input/Input";
|
||||
import { Modal, ModalContent } from "../Modal";
|
||||
import { AvatarInputField } from "../input/AvatarInputField";
|
||||
import styles from "./ProfileModal.module.css";
|
||||
|
||||
interface Props {
|
||||
client: MatrixClient;
|
||||
onClose: () => void;
|
||||
[rest: string]: unknown;
|
||||
}
|
||||
export function ProfileModal({ client, ...rest }: Props) {
|
||||
const { onClose } = rest;
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
success,
|
||||
error,
|
||||
loading,
|
||||
displayName: initialDisplayName,
|
||||
avatarUrl,
|
||||
saveProfile,
|
||||
} = useProfile(client);
|
||||
const [displayName, setDisplayName] = useState(initialDisplayName || "");
|
||||
const [removeAvatar, setRemoveAvatar] = useState(false);
|
||||
|
||||
const onRemoveAvatar = useCallback(() => {
|
||||
setRemoveAvatar(true);
|
||||
}, []);
|
||||
|
||||
const onChangeDisplayName = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
setDisplayName(e.target.value);
|
||||
},
|
||||
[setDisplayName]
|
||||
);
|
||||
|
||||
const onSubmit = useCallback(
|
||||
(e) => {
|
||||
e.preventDefault();
|
||||
const data = new FormData(e.target);
|
||||
const displayNameDataEntry = data.get("displayName");
|
||||
const avatar: File | string = data.get("avatar");
|
||||
|
||||
const avatarSize =
|
||||
typeof avatar == "string" ? avatar.length : avatar.size;
|
||||
const displayName =
|
||||
typeof displayNameDataEntry == "string"
|
||||
? displayNameDataEntry
|
||||
: displayNameDataEntry.name;
|
||||
|
||||
saveProfile({
|
||||
displayName,
|
||||
avatar: avatar && avatarSize > 0 ? avatar : undefined,
|
||||
removeAvatar: removeAvatar && (!avatar || avatarSize === 0),
|
||||
});
|
||||
},
|
||||
[saveProfile, removeAvatar]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (success) {
|
||||
onClose();
|
||||
}
|
||||
}, [success, onClose]);
|
||||
|
||||
return (
|
||||
<Modal title={t("Profile")} isDismissable {...rest}>
|
||||
<ModalContent>
|
||||
<form onSubmit={onSubmit}>
|
||||
<FieldRow className={styles.avatarFieldRow}>
|
||||
<AvatarInputField
|
||||
id="avatar"
|
||||
name="avatar"
|
||||
label={t("Avatar")}
|
||||
avatarUrl={avatarUrl}
|
||||
displayName={displayName}
|
||||
onRemoveAvatar={onRemoveAvatar}
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="userId"
|
||||
name="userId"
|
||||
label={t("User ID")}
|
||||
type="text"
|
||||
disabled
|
||||
value={client.getUserId()}
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="displayName"
|
||||
name="displayName"
|
||||
label={t("Display name")}
|
||||
type="text"
|
||||
required
|
||||
autoComplete="off"
|
||||
placeholder={t("Display name")}
|
||||
value={displayName}
|
||||
onChange={onChangeDisplayName}
|
||||
/>
|
||||
</FieldRow>
|
||||
{error && (
|
||||
<FieldRow>
|
||||
<ErrorMessage error={error} />
|
||||
</FieldRow>
|
||||
)}
|
||||
<FieldRow rightAlign>
|
||||
<Button type="button" variant="secondary" onPress={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" disabled={loading}>
|
||||
{loading ? t("Saving…") : t("Save")}
|
||||
</Button>
|
||||
</FieldRow>
|
||||
</form>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -17,20 +17,31 @@ limitations under the License.
|
||||
.headline {
|
||||
text-align: center;
|
||||
margin-bottom: 60px;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.callEndedContent {
|
||||
text-align: center;
|
||||
max-width: 360px;
|
||||
max-width: 450px;
|
||||
}
|
||||
.callEndedContent p {
|
||||
font-size: var(--font-size-subtitle);
|
||||
}
|
||||
|
||||
.callEndedContent h3 {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.callEndedButton {
|
||||
margin-top: 54px;
|
||||
margin-left: 30px;
|
||||
margin-right: 30px !important;
|
||||
}
|
||||
|
||||
.submitButton {
|
||||
width: 100%;
|
||||
margin-top: 54px;
|
||||
margin-left: 30px;
|
||||
margin-right: 30px !important;
|
||||
}
|
||||
|
||||
.container {
|
||||
|
||||
@@ -14,19 +14,130 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { FormEventHandler, useCallback, useState } from "react";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { useHistory } from "react-router-dom";
|
||||
|
||||
import styles from "./CallEndedView.module.css";
|
||||
import { LinkButton } from "../button";
|
||||
import feedbackStyle from "../input/FeedbackInput.module.css";
|
||||
import { Button, LinkButton } from "../button";
|
||||
import { useProfile } from "../profile/useProfile";
|
||||
import { Subtitle, Body, Link, Headline } from "../typography/Typography";
|
||||
import { Body, Link, Headline } from "../typography/Typography";
|
||||
import { Header, HeaderLogo, LeftNav, RightNav } from "../Header";
|
||||
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
|
||||
import { FieldRow, InputField } from "../input/Input";
|
||||
import { StarRatingInput } from "../input/StarRatingInput";
|
||||
|
||||
export function CallEndedView({ client }: { client: MatrixClient }) {
|
||||
export function CallEndedView({
|
||||
client,
|
||||
isPasswordlessUser,
|
||||
endedCallId,
|
||||
}: {
|
||||
client: MatrixClient;
|
||||
isPasswordlessUser: boolean;
|
||||
endedCallId: string;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const history = useHistory();
|
||||
|
||||
const { displayName } = useProfile(client);
|
||||
const [surveySubmitted, setSurverySubmitted] = useState(false);
|
||||
const [starRating, setStarRating] = useState(0);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [submitDone, setSubmitDone] = useState(false);
|
||||
const submitSurvery: FormEventHandler<HTMLFormElement> = useCallback(
|
||||
(e) => {
|
||||
e.preventDefault();
|
||||
const data = new FormData(e.target as HTMLFormElement);
|
||||
const feedbackText = data.get("feedbackText") as string;
|
||||
|
||||
PosthogAnalytics.instance.eventQualitySurvey.track(
|
||||
endedCallId,
|
||||
feedbackText,
|
||||
starRating
|
||||
);
|
||||
|
||||
setSubmitting(true);
|
||||
|
||||
setTimeout(() => {
|
||||
setSubmitDone(true);
|
||||
|
||||
setTimeout(() => {
|
||||
if (isPasswordlessUser) {
|
||||
// setting this renders the callEndedView with the invitation to create an account
|
||||
setSurverySubmitted(true);
|
||||
} else {
|
||||
// if the user already has an account immediately go back to the home screen
|
||||
history.push("/");
|
||||
}
|
||||
}, 1000);
|
||||
}, 1000);
|
||||
},
|
||||
[endedCallId, history, isPasswordlessUser, starRating]
|
||||
);
|
||||
const createAccountDialog = isPasswordlessUser && (
|
||||
<div className={styles.callEndedContent}>
|
||||
<Trans>
|
||||
<p>Why not finish by setting up a password to keep your account?</p>
|
||||
<p>
|
||||
You'll be able to keep your name and set an avatar for use on future
|
||||
calls
|
||||
</p>
|
||||
</Trans>
|
||||
<LinkButton
|
||||
className={styles.callEndedButton}
|
||||
size="lg"
|
||||
variant="default"
|
||||
to="/register"
|
||||
>
|
||||
{t("Create account")}
|
||||
</LinkButton>
|
||||
</div>
|
||||
);
|
||||
|
||||
const qualitySurveyDialog = (
|
||||
<div className={styles.callEndedContent}>
|
||||
<Trans>
|
||||
<p>
|
||||
We'd love to hear your feedback so we can improve your experience.
|
||||
</p>
|
||||
</Trans>
|
||||
<form onSubmit={submitSurvery}>
|
||||
<FieldRow>
|
||||
<StarRatingInput starCount={5} onChange={setStarRating} required />
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
className={feedbackStyle.feedback}
|
||||
id="feedbackText"
|
||||
name="feedbackText"
|
||||
label={t("Your feedback")}
|
||||
placeholder={t("Your feedback")}
|
||||
type="textarea"
|
||||
required
|
||||
/>
|
||||
</FieldRow>{" "}
|
||||
<FieldRow>
|
||||
{submitDone ? (
|
||||
<Trans>
|
||||
<p>Thanks for your feedback!</p>
|
||||
</Trans>
|
||||
) : (
|
||||
<Button
|
||||
type="submit"
|
||||
className={styles.submitButton}
|
||||
size="lg"
|
||||
variant="default"
|
||||
data-testid="home_go"
|
||||
>
|
||||
{submitting ? t("Submitting…") : t("Submit")}
|
||||
</Button>
|
||||
)}
|
||||
</FieldRow>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -39,27 +150,19 @@ export function CallEndedView({ client }: { client: MatrixClient }) {
|
||||
<div className={styles.container}>
|
||||
<main className={styles.main}>
|
||||
<Headline className={styles.headline}>
|
||||
{t("{{displayName}}, your call is now ended", { displayName })}
|
||||
{surveySubmitted
|
||||
? t("{{displayName}}, your call has ended.", {
|
||||
displayName,
|
||||
})
|
||||
: t("{{displayName}}, your call has ended.", {
|
||||
displayName,
|
||||
}) +
|
||||
"\n" +
|
||||
t("How did it go?")}
|
||||
</Headline>
|
||||
<div className={styles.callEndedContent}>
|
||||
<Trans>
|
||||
<Subtitle>
|
||||
Why not finish by setting up a password to keep your account?
|
||||
</Subtitle>
|
||||
<Subtitle>
|
||||
You'll be able to keep your name and set an avatar for use on
|
||||
future calls
|
||||
</Subtitle>
|
||||
</Trans>
|
||||
<LinkButton
|
||||
className={styles.callEndedButton}
|
||||
size="lg"
|
||||
variant="default"
|
||||
to="/register"
|
||||
>
|
||||
{t("Create account")}
|
||||
</LinkButton>
|
||||
</div>
|
||||
{!surveySubmitted && PosthogAnalytics.instance.isEnabled()
|
||||
? qualitySurveyDialog
|
||||
: createAccountDialog}
|
||||
</main>
|
||||
<Body className={styles.footer}>
|
||||
<Link color="primary" to="/">
|
||||
|
||||
@@ -51,7 +51,7 @@ export function GroupCallLoader({
|
||||
if (loading) {
|
||||
return (
|
||||
<FullScreenView>
|
||||
<h1>{t("Loading room…")}</h1>
|
||||
<h1>{t("Loading…")}</h1>
|
||||
</FullScreenView>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ import { useSentryGroupCallHandler } from "./useSentryGroupCallHandler";
|
||||
import { useLocationNavigation } from "../useLocationNavigation";
|
||||
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
|
||||
import { useMediaHandler } from "../settings/useMediaHandler";
|
||||
import { findDeviceByName, getDevices } from "../media-utils";
|
||||
import { findDeviceByName, getNamedDevices } from "../media-utils";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@@ -102,7 +102,7 @@ export function GroupCallView({
|
||||
// Get the available devices so we can match the selected device
|
||||
// to its ID. This involves getting a media stream (see docs on
|
||||
// the function) so we only do it once and re-use the result.
|
||||
const devices = await getDevices();
|
||||
const devices = await getNamedDevices();
|
||||
|
||||
const { audioInput, videoInput } = ev.detail
|
||||
.data as unknown as JoinCallData;
|
||||
@@ -203,7 +203,11 @@ export function GroupCallView({
|
||||
widget.api.transport.send(ElementWidgetActions.HangupCall, {});
|
||||
}
|
||||
|
||||
if (!isPasswordlessUser && !isEmbedded) {
|
||||
if (
|
||||
!isPasswordlessUser &&
|
||||
!isEmbedded &&
|
||||
!PosthogAnalytics.instance.isEnabled()
|
||||
) {
|
||||
history.push("/");
|
||||
}
|
||||
}, [groupCall, leave, isPasswordlessUser, isEmbedded, history]);
|
||||
@@ -268,8 +272,23 @@ export function GroupCallView({
|
||||
);
|
||||
}
|
||||
} else if (left) {
|
||||
if (isPasswordlessUser) {
|
||||
return <CallEndedView client={client} />;
|
||||
// The call ended view is shown for two reasons: prompting guests to create
|
||||
// an account, and prompting users that have opted into analytics to provide
|
||||
// feedback. We don't show a feedback prompt to widget users however (at
|
||||
// least for now), because we don't yet have designs that would allow widget
|
||||
// users to dismiss the feedback prompt and close the call window without
|
||||
// submitting anything.
|
||||
if (
|
||||
isPasswordlessUser ||
|
||||
(PosthogAnalytics.instance.isEnabled() && !isEmbedded)
|
||||
) {
|
||||
return (
|
||||
<CallEndedView
|
||||
endedCallId={groupCall.groupCallId}
|
||||
client={client}
|
||||
isPasswordlessUser={isPasswordlessUser}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
// If the user is a regular user, we'll have sent them back to the homepage,
|
||||
// so just sit here & do nothing: otherwise we would (briefly) mount the
|
||||
@@ -281,7 +300,7 @@ export function GroupCallView({
|
||||
} else if (isEmbedded) {
|
||||
return (
|
||||
<FullScreenView>
|
||||
<h1>{t("Loading room…")}</h1>
|
||||
<h1>{t("Loading…")}</h1>
|
||||
</FullScreenView>
|
||||
);
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2022 New Vector Ltd
|
||||
Copyright 2022 - 2023 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -24,6 +24,7 @@ import { GroupCall } from "matrix-js-sdk/src/webrtc/groupCall";
|
||||
import { CallFeed } from "matrix-js-sdk/src/webrtc/callFeed";
|
||||
import classNames from "classnames";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { OverlayTriggerState } from "@react-stately/overlays";
|
||||
import { JoinRule } from "matrix-js-sdk/src/@types/partials";
|
||||
|
||||
import type { IWidgetApiRequest } from "matrix-widget-api";
|
||||
@@ -33,6 +34,8 @@ import {
|
||||
MicButton,
|
||||
VideoButton,
|
||||
ScreenshareButton,
|
||||
SettingsButton,
|
||||
InviteButton,
|
||||
} from "../button";
|
||||
import {
|
||||
Header,
|
||||
@@ -48,12 +51,8 @@ import {
|
||||
} from "../video-grid/VideoGrid";
|
||||
import { VideoTileContainer } from "../video-grid/VideoTileContainer";
|
||||
import { GroupCallInspector } from "./GroupCallInspector";
|
||||
import { OverflowMenu } from "./OverflowMenu";
|
||||
import { GridLayoutMenu } from "./GridLayoutMenu";
|
||||
import { Avatar } from "../Avatar";
|
||||
import { UserMenuContainer } from "../UserMenuContainer";
|
||||
import { useRageshakeRequestModal } from "../settings/submit-rageshake";
|
||||
import { RageshakeRequestModal } from "./RageshakeRequestModal";
|
||||
import { useMediaHandler } from "../settings/useMediaHandler";
|
||||
import {
|
||||
useNewGrid,
|
||||
@@ -74,6 +73,10 @@ import { AudioSink } from "../video-grid/AudioSink";
|
||||
import { useCallViewKeyboardShortcuts } from "../useCallViewKeyboardShortcuts";
|
||||
import { NewVideoGrid } from "../video-grid/NewVideoGrid";
|
||||
import { OTelGroupCallMembership } from "../otel/OTelGroupCallMembership";
|
||||
import { SettingsModal } from "../settings/SettingsModal";
|
||||
import { InviteModal } from "./InviteModal";
|
||||
import { useRageshakeRequestModal } from "../settings/submit-rageshake";
|
||||
import { RageshakeRequestModal } from "./RageshakeRequestModal";
|
||||
|
||||
const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {});
|
||||
// There is currently a bug in Safari our our code with cloning and sending MediaStreams
|
||||
@@ -128,7 +131,6 @@ export function InCallView({
|
||||
}: Props) {
|
||||
const { t } = useTranslation();
|
||||
usePreventScroll();
|
||||
const joinRule = useJoinRule(groupCall.room);
|
||||
|
||||
const containerRef1 = useRef<HTMLDivElement | null>(null);
|
||||
const [containerRef2, bounds] = useMeasure({ polyfill: ResizeObserver });
|
||||
@@ -151,11 +153,10 @@ export function InCallView({
|
||||
const [audioContext, audioDestination] = useAudioContext();
|
||||
const [showInspector] = useShowInspector();
|
||||
|
||||
const { modalState: feedbackModalState, modalProps: feedbackModalProps } =
|
||||
useModalTriggerState();
|
||||
|
||||
const { hideScreensharing } = useUrlParams();
|
||||
|
||||
const joinRule = useJoinRule(groupCall.room);
|
||||
|
||||
useCallViewKeyboardShortcuts(
|
||||
containerRef1,
|
||||
toggleMicrophoneMuted,
|
||||
@@ -346,6 +347,36 @@ export function InCallView({
|
||||
modalProps: rageshakeRequestModalProps,
|
||||
} = useRageshakeRequestModal(groupCall.room.roomId);
|
||||
|
||||
const {
|
||||
modalState: settingsModalState,
|
||||
modalProps: settingsModalProps,
|
||||
}: {
|
||||
modalState: OverlayTriggerState;
|
||||
modalProps: {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
};
|
||||
} = useModalTriggerState();
|
||||
|
||||
const openSettings = useCallback(() => {
|
||||
settingsModalState.open();
|
||||
}, [settingsModalState]);
|
||||
|
||||
const {
|
||||
modalState: inviteModalState,
|
||||
modalProps: inviteModalProps,
|
||||
}: {
|
||||
modalState: OverlayTriggerState;
|
||||
modalProps: {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
};
|
||||
} = useModalTriggerState();
|
||||
|
||||
const openInvite = useCallback(() => {
|
||||
inviteModalState.open();
|
||||
}, [inviteModalState]);
|
||||
|
||||
const containerClasses = classNames(styles.inRoom, {
|
||||
[styles.maximised]: maximisedParticipant,
|
||||
});
|
||||
@@ -383,11 +414,13 @@ export function InCallView({
|
||||
key="1"
|
||||
muted={microphoneMuted}
|
||||
onPress={toggleMicrophoneMuted}
|
||||
data-testid="incall_mute"
|
||||
/>,
|
||||
<VideoButton
|
||||
key="2"
|
||||
muted={localVideoMuted}
|
||||
onPress={toggleLocalVideoMuted}
|
||||
data-testid="incall_videomute"
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -398,25 +431,18 @@ export function InCallView({
|
||||
key="3"
|
||||
enabled={isScreensharing}
|
||||
onPress={toggleScreensharing}
|
||||
data-testid="incall_screenshare"
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (!maximisedParticipant) {
|
||||
buttons.push(
|
||||
<OverflowMenu
|
||||
key="4"
|
||||
inCall
|
||||
roomIdOrAlias={roomIdOrAlias}
|
||||
groupCall={groupCall}
|
||||
showInvite={joinRule === JoinRule.Public}
|
||||
feedbackModalState={feedbackModalState}
|
||||
feedbackModalProps={feedbackModalProps}
|
||||
/>
|
||||
);
|
||||
buttons.push(<SettingsButton key="4" onPress={openSettings} />);
|
||||
}
|
||||
}
|
||||
|
||||
buttons.push(<HangupButton key="6" onPress={onLeave} />);
|
||||
buttons.push(
|
||||
<HangupButton key="6" onPress={onLeave} data-testid="incall_leave" />
|
||||
);
|
||||
footer = <div className={styles.footer}>{buttons}</div>;
|
||||
}
|
||||
|
||||
@@ -434,7 +460,9 @@ export function InCallView({
|
||||
</LeftNav>
|
||||
<RightNav>
|
||||
<GridLayoutMenu layout={layout} setLayout={setLayout} />
|
||||
<UserMenuContainer preventNavigation />
|
||||
{joinRule === JoinRule.Public && (
|
||||
<InviteButton variant="icon" onClick={openInvite} />
|
||||
)}
|
||||
</RightNav>
|
||||
</Header>
|
||||
)}
|
||||
@@ -448,12 +476,22 @@ export function InCallView({
|
||||
otelGroupCallMembership={otelGroupCallMembership}
|
||||
show={showInspector}
|
||||
/>
|
||||
{rageshakeRequestModalState.isOpen && (
|
||||
{rageshakeRequestModalState.isOpen && !noControls && (
|
||||
<RageshakeRequestModal
|
||||
{...rageshakeRequestModalProps}
|
||||
roomIdOrAlias={roomIdOrAlias}
|
||||
/>
|
||||
)}
|
||||
{settingsModalState.isOpen && (
|
||||
<SettingsModal
|
||||
client={client}
|
||||
roomId={groupCall.room.roomId}
|
||||
{...settingsModalProps}
|
||||
/>
|
||||
)}
|
||||
{inviteModalState.isOpen && (
|
||||
<InviteModal roomIdOrAlias={roomIdOrAlias} {...inviteModalProps} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ export const InviteModal: FC<Props> = ({ roomIdOrAlias, ...rest }) => {
|
||||
<CopyButton
|
||||
className={styles.copyButton}
|
||||
value={getRoomUrl(roomIdOrAlias)}
|
||||
data-testid="modal_inviteLink"
|
||||
/>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
|
||||
@@ -137,6 +137,7 @@ export function LobbyView({
|
||||
size="lg"
|
||||
disabled={state !== GroupCallState.LocalCallFeedInitialized}
|
||||
onPress={onEnter}
|
||||
data-testid="lobby_joinCall"
|
||||
>
|
||||
Join call now
|
||||
</Button>
|
||||
@@ -146,6 +147,7 @@ export function LobbyView({
|
||||
value={getRoomUrl(roomIdOrAlias)}
|
||||
className={styles.copyButton}
|
||||
copiedMessage={t("Call link copied")}
|
||||
data-testid="lobby_inviteLink"
|
||||
>
|
||||
Copy call link and join later
|
||||
</CopyButton>
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
/*
|
||||
Copyright 2022 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useCallback } from "react";
|
||||
import { Item } from "@react-stately/collections";
|
||||
import { GroupCall } from "matrix-js-sdk/src/webrtc/groupCall";
|
||||
import { OverlayTriggerState } from "@react-stately/overlays";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { Button } from "../button";
|
||||
import { Menu } from "../Menu";
|
||||
import { PopoverMenuTrigger } from "../popover/PopoverMenu";
|
||||
import { ReactComponent as SettingsIcon } from "../icons/Settings.svg";
|
||||
import { ReactComponent as AddUserIcon } from "../icons/AddUser.svg";
|
||||
import { ReactComponent as OverflowIcon } from "../icons/Overflow.svg";
|
||||
import { ReactComponent as FeedbackIcon } from "../icons/Feedback.svg";
|
||||
import { useModalTriggerState } from "../Modal";
|
||||
import { SettingsModal } from "../settings/SettingsModal";
|
||||
import { InviteModal } from "./InviteModal";
|
||||
import { TooltipTrigger } from "../Tooltip";
|
||||
import { FeedbackModal } from "./FeedbackModal";
|
||||
import { Config } from "../config/Config";
|
||||
|
||||
interface Props {
|
||||
roomIdOrAlias: string;
|
||||
inCall: boolean;
|
||||
groupCall: GroupCall;
|
||||
showInvite: boolean;
|
||||
feedbackModalState: OverlayTriggerState;
|
||||
feedbackModalProps: {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
};
|
||||
}
|
||||
|
||||
export function OverflowMenu({
|
||||
roomIdOrAlias,
|
||||
inCall,
|
||||
groupCall,
|
||||
showInvite,
|
||||
feedbackModalState,
|
||||
feedbackModalProps,
|
||||
}: Props) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
modalState: inviteModalState,
|
||||
modalProps: inviteModalProps,
|
||||
}: {
|
||||
modalState: OverlayTriggerState;
|
||||
modalProps: {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
};
|
||||
} = useModalTriggerState();
|
||||
const {
|
||||
modalState: settingsModalState,
|
||||
modalProps: settingsModalProps,
|
||||
}: {
|
||||
modalState: OverlayTriggerState;
|
||||
modalProps: {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
};
|
||||
} = useModalTriggerState();
|
||||
|
||||
// TODO: On closing modal, focus should be restored to the trigger button
|
||||
// https://github.com/adobe/react-spectrum/issues/2444
|
||||
const onAction = useCallback(
|
||||
(key) => {
|
||||
switch (key) {
|
||||
case "invite":
|
||||
inviteModalState.open();
|
||||
break;
|
||||
case "settings":
|
||||
settingsModalState.open();
|
||||
break;
|
||||
case "feedback":
|
||||
feedbackModalState.open();
|
||||
break;
|
||||
}
|
||||
},
|
||||
[feedbackModalState, inviteModalState, settingsModalState]
|
||||
);
|
||||
|
||||
const tooltip = useCallback(() => t("More"), [t]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PopoverMenuTrigger disableOnState>
|
||||
<TooltipTrigger tooltip={tooltip} placement="top">
|
||||
<Button variant="toolbar">
|
||||
<OverflowIcon />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
{(props: JSX.IntrinsicAttributes) => (
|
||||
<Menu {...props} label={t("More menu")} onAction={onAction}>
|
||||
{showInvite && (
|
||||
<Item key="invite" textValue={t("Invite people")}>
|
||||
<AddUserIcon />
|
||||
<span>{t("Invite people")}</span>
|
||||
</Item>
|
||||
)}
|
||||
<Item key="settings" textValue={t("Settings")}>
|
||||
<SettingsIcon />
|
||||
<span>{t("Settings")}</span>
|
||||
</Item>
|
||||
{Config.get().rageshake?.submit_url && (
|
||||
<Item key="feedback" textValue={t("Submit feedback")}>
|
||||
<FeedbackIcon />
|
||||
<span>{t("Submit feedback")}</span>
|
||||
</Item>
|
||||
)}
|
||||
</Menu>
|
||||
)}
|
||||
</PopoverMenuTrigger>
|
||||
{settingsModalState.isOpen && <SettingsModal {...settingsModalProps} />}
|
||||
{inviteModalState.isOpen && (
|
||||
<InviteModal roomIdOrAlias={roomIdOrAlias} {...inviteModalProps} />
|
||||
)}
|
||||
{feedbackModalState.isOpen && (
|
||||
<FeedbackModal
|
||||
{...feedbackModalProps}
|
||||
roomId={groupCall?.room.roomId}
|
||||
inCall={inCall}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2022 New Vector Ltd
|
||||
Copyright 2022 - 2023 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -27,7 +27,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { useDelayedState } from "../useDelayedState";
|
||||
import { useModalTriggerState } from "../Modal";
|
||||
import { InviteModal } from "./InviteModal";
|
||||
import { HangupButton, InviteButton } from "../button";
|
||||
import { HangupButton, InviteButton, SettingsButton } from "../button";
|
||||
import { Header, LeftNav, RightNav, RoomSetupHeaderInfo } from "../Header";
|
||||
import styles from "./PTTCallView.module.css";
|
||||
import { Facepile } from "../Facepile";
|
||||
@@ -41,10 +41,10 @@ import { ReactComponent as AudioIcon } from "../icons/Audio.svg";
|
||||
import { usePTTSounds } from "../sound/usePttSounds";
|
||||
import { PTTClips } from "../sound/PTTClips";
|
||||
import { GroupCallInspector } from "./GroupCallInspector";
|
||||
import { OverflowMenu } from "./OverflowMenu";
|
||||
import { Size } from "../Avatar";
|
||||
import { ParticipantInfo } from "./useGroupCall";
|
||||
import { OTelGroupCallMembership } from "../otel/OTelGroupCallMembership";
|
||||
import { SettingsModal } from "../settings/SettingsModal";
|
||||
|
||||
function getPromptText(
|
||||
networkWaiting: boolean,
|
||||
@@ -126,8 +126,9 @@ export const PTTCallView: React.FC<Props> = ({
|
||||
const { t } = useTranslation();
|
||||
const { modalState: inviteModalState, modalProps: inviteModalProps } =
|
||||
useModalTriggerState();
|
||||
const { modalState: feedbackModalState, modalProps: feedbackModalProps } =
|
||||
const { modalState: settingsModalState, modalProps: settingsModalProps } =
|
||||
useModalTriggerState();
|
||||
|
||||
const [containerRef, bounds] = useMeasure({ polyfill: ResizeObserver });
|
||||
const facepileSize = bounds.width < 800 ? Size.SM : Size.MD;
|
||||
const showControls = bounds.height > 500;
|
||||
@@ -232,14 +233,7 @@ export const PTTCallView: React.FC<Props> = ({
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.footer}>
|
||||
<OverflowMenu
|
||||
inCall
|
||||
roomIdOrAlias={roomIdOrAlias}
|
||||
groupCall={groupCall}
|
||||
showInvite={false}
|
||||
feedbackModalState={feedbackModalState}
|
||||
feedbackModalProps={feedbackModalProps}
|
||||
/>
|
||||
<SettingsButton onPress={() => settingsModalState.open()} />
|
||||
{!isEmbedded && <HangupButton onPress={onLeave} />}
|
||||
<InviteButton onPress={() => inviteModalState.open()} />
|
||||
</div>
|
||||
@@ -265,7 +259,7 @@ export const PTTCallView: React.FC<Props> = ({
|
||||
<div className={styles.talkingInfo} />
|
||||
))}
|
||||
<PTTButton
|
||||
enabled={!feedbackModalState.isOpen}
|
||||
enabled={!inviteModalState.isOpen && !settingsModalState.isOpen}
|
||||
showTalkOverError={showTalkOverError}
|
||||
activeSpeakerUserId={activeSpeakerUserId}
|
||||
activeSpeakerDisplayName={activeSpeakerDisplayName}
|
||||
@@ -312,6 +306,13 @@ export const PTTCallView: React.FC<Props> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{settingsModalState.isOpen && (
|
||||
<SettingsModal
|
||||
client={client}
|
||||
roomId={groupCall.room.roomId}
|
||||
{...settingsModalProps}
|
||||
/>
|
||||
)}
|
||||
{inviteModalState.isOpen && showControls && (
|
||||
<InviteModal roomIdOrAlias={roomIdOrAlias} {...inviteModalProps} />
|
||||
)}
|
||||
|
||||
@@ -74,6 +74,7 @@ export function RoomAuthView() {
|
||||
name="displayName"
|
||||
label={t("Display name")}
|
||||
placeholder={t("Display name")}
|
||||
data-testid="joincall_displayName"
|
||||
type="text"
|
||||
required
|
||||
autoComplete="off"
|
||||
@@ -90,7 +91,12 @@ export function RoomAuthView() {
|
||||
<ErrorMessage error={error} />
|
||||
</FieldRow>
|
||||
)}
|
||||
<Button type="submit" size="lg" disabled={loading}>
|
||||
<Button
|
||||
type="submit"
|
||||
size="lg"
|
||||
disabled={loading}
|
||||
data-testid="joincall_joincall"
|
||||
>
|
||||
{loading ? t("Loading…") : t("Join call now")}
|
||||
</Button>
|
||||
<div id={recaptchaId} />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2021-2022 New Vector Ltd
|
||||
Copyright 2021-2023 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -24,7 +24,6 @@ import { RoomAuthView } from "./RoomAuthView";
|
||||
import { GroupCallLoader } from "./GroupCallLoader";
|
||||
import { GroupCallView } from "./GroupCallView";
|
||||
import { useUrlParams } from "../UrlParams";
|
||||
import { MediaHandlerProvider } from "../settings/useMediaHandler";
|
||||
import { useRegisterPasswordlessUser } from "../auth/useRegisterPasswordlessUser";
|
||||
import { translatedError } from "../TranslatedError";
|
||||
import { useOptInAnalytics } from "../settings/useSetting";
|
||||
@@ -101,15 +100,13 @@ export const RoomPage: FC = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<MediaHandlerProvider client={client}>
|
||||
<GroupCallLoader
|
||||
client={client}
|
||||
roomIdOrAlias={roomIdOrAlias}
|
||||
viaServers={viaServers}
|
||||
createPtt={isPtt}
|
||||
>
|
||||
{groupCallView}
|
||||
</GroupCallLoader>
|
||||
</MediaHandlerProvider>
|
||||
<GroupCallLoader
|
||||
client={client}
|
||||
roomIdOrAlias={roomIdOrAlias}
|
||||
viaServers={viaServers}
|
||||
createPtt={isPtt}
|
||||
>
|
||||
{groupCallView}
|
||||
</GroupCallLoader>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2022 New Vector Ltd
|
||||
Copyright 2022 - 2023 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -14,21 +14,22 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { useCallback } from "react";
|
||||
import useMeasure from "react-use-measure";
|
||||
import { ResizeObserver } from "@juggle/resize-observer";
|
||||
import { GroupCallState } from "matrix-js-sdk/src/webrtc/groupCall";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { OverlayTriggerState } from "@react-stately/overlays";
|
||||
|
||||
import { MicButton, VideoButton } from "../button";
|
||||
import { MicButton, SettingsButton, VideoButton } from "../button";
|
||||
import { useMediaStream } from "../video-grid/useMediaStream";
|
||||
import { OverflowMenu } from "./OverflowMenu";
|
||||
import { Avatar } from "../Avatar";
|
||||
import { useProfile } from "../profile/useProfile";
|
||||
import styles from "./VideoPreview.module.css";
|
||||
import { Body } from "../typography/Typography";
|
||||
import { useModalTriggerState } from "../Modal";
|
||||
import { SettingsModal } from "../settings/SettingsModal";
|
||||
|
||||
interface Props {
|
||||
client: MatrixClient;
|
||||
@@ -59,12 +60,30 @@ export function VideoPreview({
|
||||
const [previewRef, previewBounds] = useMeasure({ polyfill: ResizeObserver });
|
||||
const avatarSize = (previewBounds.height - 66) / 2;
|
||||
|
||||
const { modalState: feedbackModalState, modalProps: feedbackModalProps } =
|
||||
useModalTriggerState();
|
||||
const {
|
||||
modalState: settingsModalState,
|
||||
modalProps: settingsModalProps,
|
||||
}: {
|
||||
modalState: OverlayTriggerState;
|
||||
modalProps: {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
};
|
||||
} = useModalTriggerState();
|
||||
|
||||
const openSettings = useCallback(() => {
|
||||
settingsModalState.open();
|
||||
}, [settingsModalState]);
|
||||
|
||||
return (
|
||||
<div className={styles.preview} ref={previewRef}>
|
||||
<video ref={videoRef} muted playsInline disablePictureInPicture />
|
||||
<video
|
||||
ref={videoRef}
|
||||
muted
|
||||
playsInline
|
||||
disablePictureInPicture
|
||||
data-testid="preview_video"
|
||||
/>
|
||||
{state === GroupCallState.LocalCallFeedUninitialized && (
|
||||
<Body fontWeight="semiBold" className={styles.cameraPermissions}>
|
||||
{t("Camera/microphone permissions needed to join the call.")}
|
||||
@@ -95,17 +114,13 @@ export function VideoPreview({
|
||||
muted={localVideoMuted}
|
||||
onPress={toggleLocalVideoMuted}
|
||||
/>
|
||||
<OverflowMenu
|
||||
roomIdOrAlias={roomIdOrAlias}
|
||||
feedbackModalState={feedbackModalState}
|
||||
feedbackModalProps={feedbackModalProps}
|
||||
inCall={false}
|
||||
groupCall={undefined}
|
||||
showInvite={false}
|
||||
/>
|
||||
<SettingsButton onPress={openSettings} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{settingsModalState.isOpen && (
|
||||
<SettingsModal client={client} {...settingsModalProps} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
ByteSentStatsReport,
|
||||
ConnectionStatsReport,
|
||||
SummaryStatsReport,
|
||||
CallFeedReport,
|
||||
} from "matrix-js-sdk/src/webrtc/stats/statsReport";
|
||||
|
||||
import { usePageUnload } from "./usePageUnload";
|
||||
@@ -363,6 +364,12 @@ export function useGroupCall(
|
||||
groupCallOTelMembership?.onSummaryStatsReport(report);
|
||||
}
|
||||
|
||||
function onCallFeedStatsReport(
|
||||
report: GroupCallStatsReport<CallFeedReport>
|
||||
): void {
|
||||
groupCallOTelMembership?.onCallFeedStatsReport(report);
|
||||
}
|
||||
|
||||
groupCall.on(GroupCallEvent.GroupCallStateChanged, onGroupCallStateChanged);
|
||||
groupCall.on(GroupCallEvent.UserMediaFeedsChanged, onUserMediaFeedsChanged);
|
||||
groupCall.on(
|
||||
@@ -387,6 +394,11 @@ export function useGroupCall(
|
||||
onByteSentStatsReport
|
||||
);
|
||||
groupCall.on(GroupCallStatsReportEvent.SummaryStats, onSummaryStatsReport);
|
||||
groupCall.on(
|
||||
GroupCallStatsReportEvent.CallFeedStats,
|
||||
onCallFeedStatsReport
|
||||
);
|
||||
|
||||
groupCall.room.currentState.on(
|
||||
RoomStateEvent.Update,
|
||||
checkForParallelCalls
|
||||
@@ -450,6 +462,10 @@ export function useGroupCall(
|
||||
GroupCallStatsReportEvent.SummaryStats,
|
||||
onSummaryStatsReport
|
||||
);
|
||||
groupCall.removeListener(
|
||||
GroupCallStatsReportEvent.CallFeedStats,
|
||||
onCallFeedStatsReport
|
||||
);
|
||||
groupCall.room.currentState.off(
|
||||
RoomStateEvent.Update,
|
||||
checkForParallelCalls
|
||||
|
||||
@@ -32,7 +32,7 @@ import { isLocalRoomId, createRoom, roomNameFromRoomId } from "../matrix-utils";
|
||||
import { translatedError } from "../TranslatedError";
|
||||
import { widget } from "../widget";
|
||||
|
||||
const STATS_COLLECT_INTERVAL_TIME_MS = 30000;
|
||||
const STATS_COLLECT_INTERVAL_TIME_MS = 10000;
|
||||
|
||||
export interface GroupCallLoadState {
|
||||
loading: boolean;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2022 New Vector Ltd
|
||||
Copyright 2022 - 2023 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -14,28 +14,22 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect } from "react";
|
||||
import React, { useCallback } from "react";
|
||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { Modal, ModalContent } from "../Modal";
|
||||
import { Button } from "../button";
|
||||
import { FieldRow, InputField, ErrorMessage } from "../input/Input";
|
||||
import {
|
||||
useSubmitRageshake,
|
||||
useRageshakeRequest,
|
||||
} from "../settings/submit-rageshake";
|
||||
import { useSubmitRageshake, useRageshakeRequest } from "./submit-rageshake";
|
||||
import { Body } from "../typography/Typography";
|
||||
import styles from "../input/SelectInput.module.css";
|
||||
import feedbackStyles from "../input/FeedbackInput.module.css";
|
||||
|
||||
interface Props {
|
||||
inCall: boolean;
|
||||
roomId: string;
|
||||
onClose?: () => void;
|
||||
// TODO: add all props for for <Modal>
|
||||
[index: string]: unknown;
|
||||
roomId?: string;
|
||||
}
|
||||
|
||||
export function FeedbackModal({ inCall, roomId, onClose, ...rest }: Props) {
|
||||
export function FeedbackSettingsTab({ roomId }: Props) {
|
||||
const { t } = useTranslation();
|
||||
const { submitRageshake, sending, sent, error } = useSubmitRageshake();
|
||||
const sendRageshakeRequest = useRageshakeRequest();
|
||||
@@ -57,37 +51,36 @@ export function FeedbackModal({ inCall, roomId, onClose, ...rest }: Props) {
|
||||
roomId,
|
||||
});
|
||||
|
||||
if (inCall && sendLogs) {
|
||||
if (roomId && sendLogs) {
|
||||
sendRageshakeRequest(roomId, rageshakeRequestId);
|
||||
}
|
||||
},
|
||||
[inCall, submitRageshake, roomId, sendRageshakeRequest]
|
||||
[submitRageshake, roomId, sendRageshakeRequest]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (sent) {
|
||||
onClose();
|
||||
}
|
||||
}, [sent, onClose]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t("Submit feedback")}
|
||||
isDismissable
|
||||
onClose={onClose}
|
||||
{...rest}
|
||||
>
|
||||
<ModalContent>
|
||||
<Body>{t("Having trouble? Help us fix it.")}</Body>
|
||||
<form onSubmit={onSubmitFeedback}>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="description"
|
||||
name="description"
|
||||
label={t("Description (optional)")}
|
||||
type="textarea"
|
||||
/>
|
||||
</FieldRow>
|
||||
<div>
|
||||
<h4 className={styles.label}>{t("Submit feedback")}</h4>
|
||||
<Body>
|
||||
{t(
|
||||
"If you are experiencing issues or simply would like to provide some feedback, please send us a short description below."
|
||||
)}
|
||||
</Body>
|
||||
<form onSubmit={onSubmitFeedback}>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
className={feedbackStyles.feedback}
|
||||
id="description"
|
||||
name="description"
|
||||
label={t("Your feedback")}
|
||||
placeholder={t("Your feedback")}
|
||||
type="textarea"
|
||||
disabled={sending || sent}
|
||||
/>
|
||||
</FieldRow>
|
||||
{sent ? (
|
||||
<Body> {t("Thanks, we received your feedback!")}</Body>
|
||||
) : (
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="sendLogs"
|
||||
@@ -96,19 +89,17 @@ export function FeedbackModal({ inCall, roomId, onClose, ...rest }: Props) {
|
||||
type="checkbox"
|
||||
defaultChecked
|
||||
/>
|
||||
</FieldRow>
|
||||
{error && (
|
||||
<FieldRow>
|
||||
<ErrorMessage error={error} />
|
||||
</FieldRow>
|
||||
)}
|
||||
<FieldRow>
|
||||
{error && (
|
||||
<FieldRow>
|
||||
<ErrorMessage error={error} />
|
||||
</FieldRow>
|
||||
)}
|
||||
<Button type="submit" disabled={sending}>
|
||||
{sending ? t("Submitting feedback…") : t("Submit feedback")}
|
||||
{sending ? t("Submitting…") : t("Submit")}
|
||||
</Button>
|
||||
</FieldRow>
|
||||
</form>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
)}
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2022 New Vector Ltd
|
||||
Copyright 2022 - 2023 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -14,6 +14,12 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
.content {
|
||||
width: 100%;
|
||||
max-width: 350px;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.avatarFieldRow {
|
||||
justify-content: center;
|
||||
}
|
||||
113
src/settings/ProfileSettingsTab.tsx
Normal file
113
src/settings/ProfileSettingsTab.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
Copyright 2022 - 2023 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useRef } from "react";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { useProfile } from "../profile/useProfile";
|
||||
import { FieldRow, InputField, ErrorMessage } from "../input/Input";
|
||||
import { AvatarInputField } from "../input/AvatarInputField";
|
||||
import styles from "./ProfileSettingsTab.module.css";
|
||||
|
||||
interface Props {
|
||||
client: MatrixClient;
|
||||
}
|
||||
export function ProfileSettingsTab({ client }: Props) {
|
||||
const { t } = useTranslation();
|
||||
const { error, displayName, avatarUrl, saveProfile } = useProfile(client);
|
||||
|
||||
const formRef = useRef<HTMLFormElement | null>(null);
|
||||
|
||||
const formChanged = useRef(false);
|
||||
const onFormChange = useCallback(() => {
|
||||
formChanged.current = true;
|
||||
}, []);
|
||||
|
||||
const removeAvatar = useRef(false);
|
||||
const onRemoveAvatar = useCallback(() => {
|
||||
removeAvatar.current = true;
|
||||
formChanged.current = true;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const form = formRef.current!;
|
||||
// Auto-save when the user dismisses this component
|
||||
return () => {
|
||||
if (formChanged.current) {
|
||||
const data = new FormData(form);
|
||||
const displayNameDataEntry = data.get("displayName");
|
||||
const avatar = data.get("avatar");
|
||||
|
||||
const avatarSize =
|
||||
typeof avatar == "string" ? avatar.length : avatar?.size ?? 0;
|
||||
const displayName =
|
||||
typeof displayNameDataEntry == "string"
|
||||
? displayNameDataEntry
|
||||
: displayNameDataEntry?.name ?? null;
|
||||
|
||||
saveProfile({
|
||||
displayName,
|
||||
avatar: avatar && avatarSize > 0 ? avatar : undefined,
|
||||
removeAvatar: removeAvatar.current && (!avatar || avatarSize === 0),
|
||||
});
|
||||
}
|
||||
};
|
||||
}, [saveProfile]);
|
||||
|
||||
return (
|
||||
<form onChange={onFormChange} ref={formRef} className={styles.content}>
|
||||
<FieldRow className={styles.avatarFieldRow}>
|
||||
<AvatarInputField
|
||||
id="avatar"
|
||||
name="avatar"
|
||||
label={t("Avatar")}
|
||||
avatarUrl={avatarUrl}
|
||||
displayName={displayName}
|
||||
onRemoveAvatar={onRemoveAvatar}
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="userId"
|
||||
name="userId"
|
||||
label={t("Username")}
|
||||
type="text"
|
||||
disabled
|
||||
value={client.getUserId()!}
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="displayName"
|
||||
name="displayName"
|
||||
label={t("Display name")}
|
||||
type="text"
|
||||
required
|
||||
autoComplete="off"
|
||||
placeholder={t("Display name")}
|
||||
defaultValue={displayName}
|
||||
data-testid="profile_displayname"
|
||||
/>
|
||||
</FieldRow>
|
||||
{error && (
|
||||
<FieldRow>
|
||||
<ErrorMessage error={error} />
|
||||
</FieldRow>
|
||||
)}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -19,6 +19,10 @@ limitations under the License.
|
||||
height: 480px;
|
||||
}
|
||||
|
||||
.settingsModal p {
|
||||
color: var(--secondary-content);
|
||||
}
|
||||
|
||||
.tabContainer {
|
||||
padding: 27px 20px;
|
||||
}
|
||||
@@ -26,12 +30,3 @@ limitations under the License.
|
||||
.fieldRowText {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
This style guarantees a fixed width of the tab bar in the settings window.
|
||||
The "Developer" item in the tab bar can be toggled.
|
||||
Without a defined width activating the developer tab makes the tab container jump to the right.
|
||||
*/
|
||||
.tabLabel {
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2022 New Vector Ltd
|
||||
Copyright 2022 - 2023 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -14,9 +14,10 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { Item } from "@react-stately/collections";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { MatrixClient } from "matrix-js-sdk";
|
||||
|
||||
import { Modal } from "../Modal";
|
||||
import styles from "./SettingsModal.module.css";
|
||||
@@ -25,6 +26,8 @@ import { ReactComponent as AudioIcon } from "../icons/Audio.svg";
|
||||
import { ReactComponent as VideoIcon } from "../icons/Video.svg";
|
||||
import { ReactComponent as DeveloperIcon } from "../icons/Developer.svg";
|
||||
import { ReactComponent as OverflowIcon } from "../icons/Overflow.svg";
|
||||
import { ReactComponent as UserIcon } from "../icons/User.svg";
|
||||
import { ReactComponent as FeedbackIcon } from "../icons/Feedback.svg";
|
||||
import { SelectInput } from "../input/SelectInput";
|
||||
import { useMediaHandler } from "./useMediaHandler";
|
||||
import {
|
||||
@@ -39,9 +42,14 @@ import { Button } from "../button";
|
||||
import { useDownloadDebugLog } from "./submit-rageshake";
|
||||
import { Body, Caption } from "../typography/Typography";
|
||||
import { AnalyticsNotice } from "../analytics/AnalyticsNotice";
|
||||
import { ProfileSettingsTab } from "./ProfileSettingsTab";
|
||||
import { FeedbackSettingsTab } from "./FeedbackSettingsTab";
|
||||
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
client: MatrixClient;
|
||||
roomId?: string;
|
||||
defaultTab?: string;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
@@ -57,7 +65,9 @@ export const SettingsModal = (props: Props) => {
|
||||
audioOutput,
|
||||
audioOutputs,
|
||||
setAudioOutput,
|
||||
useDeviceNames,
|
||||
} = useMediaHandler();
|
||||
useDeviceNames();
|
||||
|
||||
const [spatialAudio, setSpatialAudio] = useSpatialAudio();
|
||||
const [showInspector, setShowInspector] = useShowInspector();
|
||||
@@ -68,6 +78,15 @@ export const SettingsModal = (props: Props) => {
|
||||
|
||||
const downloadDebugLog = useDownloadDebugLog();
|
||||
|
||||
const [selectedTab, setSelectedTab] = useState<string | undefined>();
|
||||
|
||||
const onSelectedTabChanged = useCallback(
|
||||
(tab) => {
|
||||
setSelectedTab(tab);
|
||||
},
|
||||
[setSelectedTab]
|
||||
);
|
||||
|
||||
const optInDescription = (
|
||||
<Caption>
|
||||
<Trans>
|
||||
@@ -87,8 +106,13 @@ export const SettingsModal = (props: Props) => {
|
||||
className={styles.settingsModal}
|
||||
{...props}
|
||||
>
|
||||
<TabContainer className={styles.tabContainer}>
|
||||
<TabContainer
|
||||
onSelectionChange={onSelectedTabChanged}
|
||||
selectedKey={selectedTab ?? props.defaultTab ?? "audio"}
|
||||
className={styles.tabContainer}
|
||||
>
|
||||
<TabItem
|
||||
key="audio"
|
||||
title={
|
||||
<>
|
||||
<AudioIcon width={16} height={16} />
|
||||
@@ -145,6 +169,7 @@ export const SettingsModal = (props: Props) => {
|
||||
</FieldRow>
|
||||
</TabItem>
|
||||
<TabItem
|
||||
key="video"
|
||||
title={
|
||||
<>
|
||||
<VideoIcon width={16} height={16} />
|
||||
@@ -167,6 +192,29 @@ export const SettingsModal = (props: Props) => {
|
||||
</SelectInput>
|
||||
</TabItem>
|
||||
<TabItem
|
||||
key="profile"
|
||||
title={
|
||||
<>
|
||||
<UserIcon width={15} height={15} />
|
||||
<span>{t("Profile")}</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<ProfileSettingsTab client={props.client} />
|
||||
</TabItem>
|
||||
<TabItem
|
||||
key="feedback"
|
||||
title={
|
||||
<>
|
||||
<FeedbackIcon width={16} height={16} />
|
||||
<span>{t("Feedback")}</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<FeedbackSettingsTab roomId={props.roomId} />
|
||||
</TabItem>
|
||||
<TabItem
|
||||
key="more"
|
||||
title={
|
||||
<>
|
||||
<OverflowIcon width={16} height={16} />
|
||||
@@ -174,18 +222,10 @@ export const SettingsModal = (props: Props) => {
|
||||
</>
|
||||
}
|
||||
>
|
||||
<h4>Analytics</h4>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="optInAnalytics"
|
||||
type="checkbox"
|
||||
checked={optInAnalytics}
|
||||
description={optInDescription}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setOptInAnalytics(event.target.checked)
|
||||
}
|
||||
/>
|
||||
</FieldRow>
|
||||
<h4>Developer</h4>
|
||||
<p>
|
||||
Version: {(import.meta.env.VITE_APP_VERSION as string) || "dev"}
|
||||
</p>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="developerSettingsTab"
|
||||
@@ -200,9 +240,22 @@ export const SettingsModal = (props: Props) => {
|
||||
}
|
||||
/>
|
||||
</FieldRow>
|
||||
<h4>Analytics</h4>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="optInAnalytics"
|
||||
type="checkbox"
|
||||
checked={optInAnalytics}
|
||||
description={optInDescription}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setOptInAnalytics(event.target.checked)
|
||||
}
|
||||
/>
|
||||
</FieldRow>
|
||||
</TabItem>
|
||||
{developerSettingsTab && (
|
||||
<TabItem
|
||||
key="developer"
|
||||
title={
|
||||
<>
|
||||
<DeveloperIcon width={16} height={16} />
|
||||
|
||||
@@ -249,7 +249,7 @@ export function useSubmitRageshake(): {
|
||||
body.append(
|
||||
"file",
|
||||
gzip(ElementCallOpenTelemetry.instance.rageshakeProcessor!.dump()),
|
||||
"traces.json"
|
||||
"traces.json.gz"
|
||||
);
|
||||
|
||||
if (inspectorState) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2022 New Vector Ltd
|
||||
Copyright 2022 - 2023 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -14,25 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
/*
|
||||
Copyright 2022 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { MediaHandlerEvent } from "matrix-js-sdk/src/webrtc/mediaHandler";
|
||||
import React, {
|
||||
useState,
|
||||
useEffect,
|
||||
@@ -41,18 +23,27 @@ import React, {
|
||||
useContext,
|
||||
createContext,
|
||||
ReactNode,
|
||||
useRef,
|
||||
} from "react";
|
||||
|
||||
import { useClient } from "../ClientContext";
|
||||
import { getNamedDevices } from "../media-utils";
|
||||
|
||||
export interface MediaHandlerContextInterface {
|
||||
audioInput: string;
|
||||
audioInput: string | undefined;
|
||||
audioInputs: MediaDeviceInfo[];
|
||||
setAudioInput: (deviceId: string) => void;
|
||||
videoInput: string;
|
||||
videoInput: string | undefined;
|
||||
videoInputs: MediaDeviceInfo[];
|
||||
setVideoInput: (deviceId: string) => void;
|
||||
audioOutput: string;
|
||||
audioOutput: string | undefined;
|
||||
audioOutputs: MediaDeviceInfo[];
|
||||
setAudioOutput: (deviceId: string) => void;
|
||||
/**
|
||||
* A hook which requests for devices to be named. This requires media
|
||||
* permissions.
|
||||
*/
|
||||
useDeviceNames: () => void;
|
||||
}
|
||||
|
||||
const MediaHandlerContext =
|
||||
@@ -63,6 +54,7 @@ interface MediaPreferences {
|
||||
videoInput?: string;
|
||||
audioOutput?: string;
|
||||
}
|
||||
|
||||
function getMediaPreferences(): MediaPreferences {
|
||||
const mediaPreferences = localStorage.getItem("matrix-media-preferences");
|
||||
|
||||
@@ -70,10 +62,10 @@ function getMediaPreferences(): MediaPreferences {
|
||||
try {
|
||||
return JSON.parse(mediaPreferences);
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
return undefined;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,11 +80,13 @@ function updateMediaPreferences(newPreferences: MediaPreferences): void {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
interface Props {
|
||||
client: MatrixClient;
|
||||
children: ReactNode;
|
||||
}
|
||||
export function MediaHandlerProvider({ client, children }: Props): JSX.Element {
|
||||
|
||||
export function MediaHandlerProvider({ children }: Props): JSX.Element {
|
||||
const { client } = useClient();
|
||||
const [
|
||||
{
|
||||
audioInput,
|
||||
@@ -103,118 +97,109 @@ export function MediaHandlerProvider({ client, children }: Props): JSX.Element {
|
||||
audioOutputs,
|
||||
},
|
||||
setState,
|
||||
] = useState(() => {
|
||||
const mediaPreferences = getMediaPreferences();
|
||||
const mediaHandler = client.getMediaHandler();
|
||||
] = useState(() => ({
|
||||
audioInput: undefined as string | undefined,
|
||||
videoInput: undefined as string | undefined,
|
||||
audioOutput: undefined as string | undefined,
|
||||
audioInputs: [] as MediaDeviceInfo[],
|
||||
videoInputs: [] as MediaDeviceInfo[],
|
||||
audioOutputs: [] as MediaDeviceInfo[],
|
||||
}));
|
||||
|
||||
mediaHandler.restoreMediaSettings(
|
||||
mediaPreferences?.audioInput,
|
||||
mediaPreferences?.videoInput
|
||||
);
|
||||
// A ref counting the number of components currently mounted that want
|
||||
// to know device names
|
||||
const numComponentsWantingNames = useRef(0);
|
||||
|
||||
return {
|
||||
// @ts-ignore, ignore that audioInput is a private members of mediaHandler
|
||||
audioInput: mediaHandler.audioInput,
|
||||
// @ts-ignore, ignore that videoInput is a private members of mediaHandler
|
||||
videoInput: mediaHandler.videoInput,
|
||||
audioOutput: undefined,
|
||||
audioInputs: [],
|
||||
videoInputs: [],
|
||||
audioOutputs: [],
|
||||
};
|
||||
});
|
||||
const updateDevices = useCallback(
|
||||
async (client: MatrixClient, initial: boolean) => {
|
||||
// Only request device names if components actually want them, because it
|
||||
// could trigger an extra permission pop-up
|
||||
const devices = await (numComponentsWantingNames.current > 0
|
||||
? getNamedDevices()
|
||||
: navigator.mediaDevices.enumerateDevices());
|
||||
const mediaPreferences = getMediaPreferences();
|
||||
|
||||
const audioInputs = devices.filter((d) => d.kind === "audioinput");
|
||||
const videoInputs = devices.filter((d) => d.kind === "videoinput");
|
||||
const audioOutputs = devices.filter((d) => d.kind === "audiooutput");
|
||||
|
||||
const audioInput = (
|
||||
mediaPreferences.audioInput === undefined
|
||||
? audioInputs.at(0)
|
||||
: audioInputs.find(
|
||||
(d) => d.deviceId === mediaPreferences.audioInput
|
||||
) ?? audioInputs.at(0)
|
||||
)?.deviceId;
|
||||
const videoInput = (
|
||||
mediaPreferences.videoInput === undefined
|
||||
? videoInputs.at(0)
|
||||
: videoInputs.find(
|
||||
(d) => d.deviceId === mediaPreferences.videoInput
|
||||
) ?? videoInputs.at(0)
|
||||
)?.deviceId;
|
||||
const audioOutput =
|
||||
mediaPreferences.audioOutput === undefined
|
||||
? undefined
|
||||
: audioOutputs.find(
|
||||
(d) => d.deviceId === mediaPreferences.audioOutput
|
||||
)?.deviceId;
|
||||
|
||||
updateMediaPreferences({ audioInput, videoInput, audioOutput });
|
||||
setState({
|
||||
audioInput,
|
||||
videoInput,
|
||||
audioOutput,
|
||||
audioInputs,
|
||||
videoInputs,
|
||||
audioOutputs,
|
||||
});
|
||||
|
||||
if (
|
||||
initial ||
|
||||
audioInput !== mediaPreferences.audioInput ||
|
||||
videoInput !== mediaPreferences.videoInput
|
||||
) {
|
||||
client.getMediaHandler().setMediaInputs(audioInput, videoInput);
|
||||
}
|
||||
},
|
||||
[setState]
|
||||
);
|
||||
|
||||
const useDeviceNames = useCallback(() => {
|
||||
// This is a little weird from React's perspective as it looks like a
|
||||
// dynamic hook, but it works
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
useEffect(() => {
|
||||
if (client) {
|
||||
numComponentsWantingNames.current++;
|
||||
if (numComponentsWantingNames.current === 1)
|
||||
updateDevices(client, false);
|
||||
return () => void numComponentsWantingNames.current--;
|
||||
}
|
||||
}, []);
|
||||
}, [client, updateDevices]);
|
||||
|
||||
useEffect(() => {
|
||||
const mediaHandler = client.getMediaHandler();
|
||||
if (client) {
|
||||
updateDevices(client, true);
|
||||
const onDeviceChange = () => updateDevices(client, false);
|
||||
navigator.mediaDevices.addEventListener("devicechange", onDeviceChange);
|
||||
|
||||
function updateDevices(): void {
|
||||
navigator.mediaDevices.enumerateDevices().then((devices) => {
|
||||
const mediaPreferences = getMediaPreferences();
|
||||
|
||||
const audioInputs = devices.filter(
|
||||
(device) => device.kind === "audioinput"
|
||||
return () => {
|
||||
navigator.mediaDevices.removeEventListener(
|
||||
"devicechange",
|
||||
onDeviceChange
|
||||
);
|
||||
const audioConnected = audioInputs.some(
|
||||
// @ts-ignore
|
||||
(device) => device.deviceId === mediaHandler.audioInput
|
||||
);
|
||||
// @ts-ignore
|
||||
let audioInput = mediaHandler.audioInput;
|
||||
|
||||
if (!audioConnected && audioInputs.length > 0) {
|
||||
audioInput = audioInputs[0].deviceId;
|
||||
}
|
||||
|
||||
const videoInputs = devices.filter(
|
||||
(device) => device.kind === "videoinput"
|
||||
);
|
||||
const videoConnected = videoInputs.some(
|
||||
// @ts-ignore
|
||||
(device) => device.deviceId === mediaHandler.videoInput
|
||||
);
|
||||
|
||||
// @ts-ignore
|
||||
let videoInput = mediaHandler.videoInput;
|
||||
|
||||
if (!videoConnected && videoInputs.length > 0) {
|
||||
videoInput = videoInputs[0].deviceId;
|
||||
}
|
||||
|
||||
const audioOutputs = devices.filter(
|
||||
(device) => device.kind === "audiooutput"
|
||||
);
|
||||
let audioOutput = undefined;
|
||||
|
||||
if (
|
||||
mediaPreferences &&
|
||||
audioOutputs.some(
|
||||
(device) => device.deviceId === mediaPreferences.audioOutput
|
||||
)
|
||||
) {
|
||||
audioOutput = mediaPreferences.audioOutput;
|
||||
}
|
||||
|
||||
if (
|
||||
// @ts-ignore
|
||||
(mediaHandler.videoInput && mediaHandler.videoInput !== videoInput) ||
|
||||
// @ts-ignore
|
||||
mediaHandler.audioInput !== audioInput
|
||||
) {
|
||||
mediaHandler.setMediaInputs(audioInput, videoInput);
|
||||
}
|
||||
|
||||
updateMediaPreferences({ audioInput, videoInput, audioOutput });
|
||||
|
||||
setState({
|
||||
audioInput,
|
||||
videoInput,
|
||||
audioOutput,
|
||||
audioInputs,
|
||||
videoInputs,
|
||||
audioOutputs,
|
||||
});
|
||||
});
|
||||
client.getMediaHandler().stopAllStreams();
|
||||
};
|
||||
}
|
||||
updateDevices();
|
||||
|
||||
mediaHandler.on(MediaHandlerEvent.LocalStreamsChanged, updateDevices);
|
||||
navigator.mediaDevices.addEventListener("devicechange", updateDevices);
|
||||
|
||||
return () => {
|
||||
mediaHandler.removeListener(
|
||||
MediaHandlerEvent.LocalStreamsChanged,
|
||||
updateDevices
|
||||
);
|
||||
navigator.mediaDevices.removeEventListener("devicechange", updateDevices);
|
||||
mediaHandler.stopAllStreams();
|
||||
};
|
||||
}, [client]);
|
||||
}, [client, updateDevices]);
|
||||
|
||||
const setAudioInput: (deviceId: string) => void = useCallback(
|
||||
(deviceId: string) => {
|
||||
updateMediaPreferences({ audioInput: deviceId });
|
||||
setState((prevState) => ({ ...prevState, audioInput: deviceId }));
|
||||
client.getMediaHandler().setAudioInput(deviceId);
|
||||
client?.getMediaHandler().setAudioInput(deviceId);
|
||||
},
|
||||
[client]
|
||||
);
|
||||
@@ -245,6 +230,7 @@ export function MediaHandlerProvider({ client, children }: Props): JSX.Element {
|
||||
audioOutput,
|
||||
audioOutputs,
|
||||
setAudioOutput,
|
||||
useDeviceNames,
|
||||
}),
|
||||
[
|
||||
audioInput,
|
||||
@@ -256,6 +242,7 @@ export function MediaHandlerProvider({ client, children }: Props): JSX.Element {
|
||||
audioOutput,
|
||||
audioOutputs,
|
||||
setAudioOutput,
|
||||
useDeviceNames,
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
@@ -25,12 +25,14 @@ limitations under the License.
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0 auto 24px auto;
|
||||
gap: 16px;
|
||||
overflow: scroll;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.tab {
|
||||
max-width: 190px;
|
||||
min-width: fit-content;
|
||||
height: 32px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 8px;
|
||||
background-color: transparent;
|
||||
display: flex;
|
||||
@@ -38,6 +40,7 @@ limitations under the License.
|
||||
padding: 0 8px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: var(--font-size-body);
|
||||
}
|
||||
|
||||
.tab > * {
|
||||
@@ -78,17 +81,18 @@ limitations under the License.
|
||||
|
||||
@media (min-width: 800px) {
|
||||
.tab {
|
||||
width: 200px;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.tab > * {
|
||||
margin: 0 16px 0 0;
|
||||
margin: 0 12px 0 0;
|
||||
}
|
||||
|
||||
.tabContainer {
|
||||
width: 100%;
|
||||
flex-direction: row;
|
||||
padding: 27px 20px;
|
||||
padding: 20px 18px;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -96,6 +100,7 @@ limitations under the License.
|
||||
.tabList {
|
||||
flex-direction: column;
|
||||
margin-bottom: 0;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.tabPanel {
|
||||
|
||||
@@ -245,6 +245,7 @@ export const NewVideoGrid: FC<Props> = ({
|
||||
opacity: 0,
|
||||
scale: 0,
|
||||
shadow: 1,
|
||||
shadowSpread: 0,
|
||||
zIndex: 1,
|
||||
x,
|
||||
y,
|
||||
|
||||
@@ -51,6 +51,7 @@ export interface TileSpring {
|
||||
opacity: number;
|
||||
scale: number;
|
||||
shadow: number;
|
||||
shadowSpread: number;
|
||||
zIndex: number;
|
||||
x: number;
|
||||
y: number;
|
||||
@@ -172,8 +173,16 @@ function getOneOnOneLayoutTilePositions(
|
||||
const gridAspectRatio = gridWidth / gridHeight;
|
||||
|
||||
const smallPip = gridAspectRatio < 1 || gridWidth < 700;
|
||||
const pipWidth = smallPip ? 114 : 230;
|
||||
const pipHeight = smallPip ? 163 : 155;
|
||||
const maxPipWidth = smallPip ? 114 : 230;
|
||||
const maxPipHeight = smallPip ? 163 : 155;
|
||||
// Cap the PiP size at 1/3 the remote tile size, preserving aspect ratio
|
||||
const pipScaleFactor = Math.min(
|
||||
1,
|
||||
remotePosition.width / 3 / maxPipWidth,
|
||||
remotePosition.height / 3 / maxPipHeight
|
||||
);
|
||||
const pipWidth = maxPipWidth * pipScaleFactor;
|
||||
const pipHeight = maxPipHeight * pipScaleFactor;
|
||||
const pipGap = getPipGap(gridAspectRatio, gridWidth);
|
||||
|
||||
const pipMinX = remotePosition.x + pipGap;
|
||||
@@ -892,6 +901,8 @@ export function VideoGrid({
|
||||
// Whether the tile positions were valid at the time of the previous
|
||||
// animation
|
||||
const tilePositionsWereValid = tilePositionsValid.current;
|
||||
const oneOnOneLayout =
|
||||
tiles.length === 2 && !tiles.some((t) => t.presenter || t.focused);
|
||||
|
||||
return (tileIndex: number) => {
|
||||
const tile = tiles[tileIndex];
|
||||
@@ -911,12 +922,14 @@ export function VideoGrid({
|
||||
opacity: 1,
|
||||
zIndex: 2,
|
||||
shadow: 15,
|
||||
shadowSpread: 0,
|
||||
immediate: (key: string) =>
|
||||
disableAnimations ||
|
||||
key === "zIndex" ||
|
||||
key === "x" ||
|
||||
key === "y" ||
|
||||
key === "shadow",
|
||||
key === "shadow" ||
|
||||
key === "shadowSpread",
|
||||
from: {
|
||||
shadow: 0,
|
||||
scale: 0,
|
||||
@@ -974,10 +987,14 @@ export function VideoGrid({
|
||||
opacity: remove ? 0 : 1,
|
||||
zIndex: tilePosition.zIndex,
|
||||
shadow: 1,
|
||||
shadowSpread: oneOnOneLayout && tile.item.isLocal ? 1 : 0,
|
||||
from,
|
||||
reset,
|
||||
immediate: (key: string) =>
|
||||
disableAnimations || key === "zIndex" || key === "shadow",
|
||||
disableAnimations ||
|
||||
key === "zIndex" ||
|
||||
key === "shadow" ||
|
||||
key === "shadowSpread",
|
||||
// If we just stopped dragging a tile, give it time for the
|
||||
// animation to settle before pushing its z-index back down
|
||||
delay: (key: string) => (key === "zIndex" ? 500 : 0),
|
||||
|
||||
@@ -22,6 +22,8 @@ limitations under the License.
|
||||
height: var(--tileHeight);
|
||||
--tileRadius: 8px;
|
||||
border-radius: var(--tileRadius);
|
||||
box-shadow: rgba(0, 0, 0, 0.5) 0px var(--tileShadow)
|
||||
calc(2 * var(--tileShadow)) var(--tileShadowSpread);
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
|
||||
@@ -45,7 +47,7 @@ limitations under the License.
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
.videoTile.speaking::after {
|
||||
.videoTile::after {
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
left: -1px;
|
||||
@@ -54,6 +56,12 @@ limitations under the License.
|
||||
content: "";
|
||||
border-radius: var(--tileRadius);
|
||||
box-shadow: inset 0 0 0 4px var(--accent) !important;
|
||||
opacity: 0;
|
||||
transition: opacity ease 0.15s;
|
||||
}
|
||||
|
||||
.videoTile.speaking::after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.videoTile.maximised {
|
||||
@@ -83,6 +91,12 @@ limitations under the License.
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.infoBubble > svg {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@@ -126,10 +140,6 @@ limitations under the License.
|
||||
bottom: 16px;
|
||||
}
|
||||
|
||||
.memberName > * {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.memberName > :last-child {
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@ import classNames from "classnames";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import styles from "./VideoTile.module.css";
|
||||
import { ReactComponent as MicIcon } from "../icons/Mic.svg";
|
||||
import { ReactComponent as MicMutedIcon } from "../icons/MicMuted.svg";
|
||||
import { ReactComponent as VideoMutedIcon } from "../icons/VideoMuted.svg";
|
||||
import { AudioButton, FullscreenButton } from "../button/Button";
|
||||
import { ConnectionState } from "../room/useGroupCall";
|
||||
|
||||
@@ -47,6 +47,7 @@ interface Props {
|
||||
opacity?: SpringValue<number>;
|
||||
scale?: SpringValue<number>;
|
||||
shadow?: SpringValue<number>;
|
||||
shadowSpread?: SpringValue<number>;
|
||||
zIndex?: SpringValue<number>;
|
||||
x?: SpringValue<number>;
|
||||
y?: SpringValue<number>;
|
||||
@@ -79,6 +80,7 @@ export const VideoTile = forwardRef<HTMLElement, Props>(
|
||||
opacity,
|
||||
scale,
|
||||
shadow,
|
||||
shadowSpread,
|
||||
zIndex,
|
||||
x,
|
||||
y,
|
||||
@@ -141,9 +143,6 @@ export const VideoTile = forwardRef<HTMLElement, Props>(
|
||||
style={{
|
||||
opacity,
|
||||
scale,
|
||||
boxShadow: shadow?.to(
|
||||
(s) => `rgba(0, 0, 0, 0.5) 0px ${s}px ${2 * s}px 0px`
|
||||
),
|
||||
zIndex,
|
||||
x,
|
||||
y,
|
||||
@@ -152,8 +151,11 @@ export const VideoTile = forwardRef<HTMLElement, Props>(
|
||||
// but React's types say no
|
||||
"--tileWidth": width?.to((w) => `${w}px`),
|
||||
"--tileHeight": height?.to((h) => `${h}px`),
|
||||
"--tileShadow": shadow?.to((s) => `${s}px`),
|
||||
"--tileShadowSpread": shadowSpread?.to((s) => `${s}px`),
|
||||
}}
|
||||
ref={ref as ForwardedRef<HTMLDivElement>}
|
||||
data-testid="videoTile"
|
||||
{...rest}
|
||||
>
|
||||
{toolbarButtons.length > 0 && !maximised && (
|
||||
@@ -177,13 +179,19 @@ export const VideoTile = forwardRef<HTMLElement, Props>(
|
||||
Mute state is currently sent over to-device messages, which
|
||||
aren't quite real-time, so this is an important kludge to make
|
||||
sure no one appears muted when they've clearly begun talking. */
|
||||
audioMuted && !videoMuted && !speaking && <MicMutedIcon />
|
||||
speaking || !audioMuted ? <MicIcon /> : <MicMutedIcon />
|
||||
}
|
||||
{videoMuted && <VideoMutedIcon />}
|
||||
<span title={caption}>{caption}</span>
|
||||
<span data-testid="videoTile_caption" title={caption}>
|
||||
{caption}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
<video ref={mediaRef} playsInline disablePictureInPicture />
|
||||
<video
|
||||
data-testid="videoTile_video"
|
||||
ref={mediaRef}
|
||||
playsInline
|
||||
disablePictureInPicture
|
||||
/>
|
||||
</animated.div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ interface Props {
|
||||
opacity?: SpringValue<number>;
|
||||
scale?: SpringValue<number>;
|
||||
shadow?: SpringValue<number>;
|
||||
shadowSpread?: SpringValue<number>;
|
||||
zIndex?: SpringValue<number>;
|
||||
x?: SpringValue<number>;
|
||||
y?: SpringValue<number>;
|
||||
|
||||
@@ -101,7 +101,14 @@ export const widget: WidgetHelpers | null = (() => {
|
||||
// We need to do this now rather than later because it has capabilities to
|
||||
// request, and is responsible for starting the transport (should it be?)
|
||||
|
||||
const { roomId, userId, deviceId, baseUrl, e2eEnabled } = getUrlParams();
|
||||
const {
|
||||
roomId,
|
||||
userId,
|
||||
deviceId,
|
||||
baseUrl,
|
||||
e2eEnabled,
|
||||
allowIceFallback,
|
||||
} = getUrlParams();
|
||||
if (!roomId) throw new Error("Room ID must be supplied");
|
||||
if (!userId) throw new Error("User ID must be supplied");
|
||||
if (!deviceId) throw new Error("Device ID must be supplied");
|
||||
@@ -148,6 +155,7 @@ export const widget: WidgetHelpers | null = (() => {
|
||||
deviceId,
|
||||
timelineSupport: true,
|
||||
useE2eForGroupCall: e2eEnabled,
|
||||
fallbackICEServerAllowed: allowIceFallback,
|
||||
}
|
||||
);
|
||||
const clientPromise = client.startClient().then(() => client);
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
import { GroupCallStatsReport } from "matrix-js-sdk/src/webrtc/groupCall";
|
||||
import {
|
||||
AudioConcealment,
|
||||
ByteSentStatsReport,
|
||||
ConnectionStatsReport,
|
||||
} from "matrix-js-sdk/src/webrtc/stats/statsReport";
|
||||
import { ObjectFlattener } from "../../src/otel/ObjectFlattener";
|
||||
|
||||
/*
|
||||
@@ -16,8 +22,15 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
describe("ObjectFlattener", () => {
|
||||
const statsReport = {
|
||||
const noConcealment: AudioConcealment = {
|
||||
concealedAudio: 0,
|
||||
totalAudioDuration: 0,
|
||||
};
|
||||
|
||||
const statsReport: GroupCallStatsReport<ConnectionStatsReport> = {
|
||||
report: {
|
||||
callId: "callId",
|
||||
opponentMemberId: "opponentMemberId",
|
||||
bandwidth: { upload: 426, download: 0 },
|
||||
bitrate: {
|
||||
upload: 426,
|
||||
@@ -92,29 +105,39 @@ describe("ObjectFlattener", () => {
|
||||
rtt: null,
|
||||
},
|
||||
],
|
||||
audioConcealment: new Map([
|
||||
["REMOTE_AUDIO_TRACK_ID", noConcealment],
|
||||
["REMOTE_VIDEO_TRACK_ID", noConcealment],
|
||||
]),
|
||||
totalAudioConcealment: noConcealment,
|
||||
},
|
||||
};
|
||||
|
||||
describe("on flattenObjectRecursive", () => {
|
||||
it("should flatter an Map object", () => {
|
||||
const flatObject = {};
|
||||
ObjectFlattener.flattenObjectRecursive(
|
||||
statsReport.report.resolution,
|
||||
flatObject,
|
||||
"matrix.stats.conn.resolution.",
|
||||
"matrix.call.stats.connection.resolution.",
|
||||
0
|
||||
);
|
||||
expect(flatObject).toEqual({
|
||||
"matrix.stats.conn.resolution.local.LOCAL_AUDIO_TRACK_ID.height": -1,
|
||||
"matrix.stats.conn.resolution.local.LOCAL_AUDIO_TRACK_ID.width": -1,
|
||||
"matrix.call.stats.connection.resolution.local.LOCAL_AUDIO_TRACK_ID.height":
|
||||
-1,
|
||||
"matrix.call.stats.connection.resolution.local.LOCAL_AUDIO_TRACK_ID.width":
|
||||
-1,
|
||||
|
||||
"matrix.stats.conn.resolution.local.LOCAL_VIDEO_TRACK_ID.height": 460,
|
||||
"matrix.stats.conn.resolution.local.LOCAL_VIDEO_TRACK_ID.width": 780,
|
||||
"matrix.call.stats.connection.resolution.local.LOCAL_VIDEO_TRACK_ID.height": 460,
|
||||
"matrix.call.stats.connection.resolution.local.LOCAL_VIDEO_TRACK_ID.width": 780,
|
||||
|
||||
"matrix.stats.conn.resolution.remote.REMOTE_AUDIO_TRACK_ID.height": -1,
|
||||
"matrix.stats.conn.resolution.remote.REMOTE_AUDIO_TRACK_ID.width": -1,
|
||||
"matrix.call.stats.connection.resolution.remote.REMOTE_AUDIO_TRACK_ID.height":
|
||||
-1,
|
||||
"matrix.call.stats.connection.resolution.remote.REMOTE_AUDIO_TRACK_ID.width":
|
||||
-1,
|
||||
|
||||
"matrix.stats.conn.resolution.remote.REMOTE_VIDEO_TRACK_ID.height": 960,
|
||||
"matrix.stats.conn.resolution.remote.REMOTE_VIDEO_TRACK_ID.width": 1080,
|
||||
"matrix.call.stats.connection.resolution.remote.REMOTE_VIDEO_TRACK_ID.height": 960,
|
||||
"matrix.call.stats.connection.resolution.remote.REMOTE_VIDEO_TRACK_ID.width": 1080,
|
||||
});
|
||||
});
|
||||
it("should flatter an Array object", () => {
|
||||
@@ -122,100 +145,128 @@ describe("ObjectFlattener", () => {
|
||||
ObjectFlattener.flattenObjectRecursive(
|
||||
statsReport.report.transport,
|
||||
flatObject,
|
||||
"matrix.stats.conn.transport.",
|
||||
"matrix.call.stats.connection.transport.",
|
||||
0
|
||||
);
|
||||
expect(flatObject).toEqual({
|
||||
"matrix.stats.conn.transport.0.ip": "ff11::5fa:abcd:999c:c5c5:50000",
|
||||
"matrix.stats.conn.transport.0.type": "udp",
|
||||
"matrix.stats.conn.transport.0.localIp":
|
||||
"matrix.call.stats.connection.transport.0.ip":
|
||||
"ff11::5fa:abcd:999c:c5c5:50000",
|
||||
"matrix.call.stats.connection.transport.0.type": "udp",
|
||||
"matrix.call.stats.connection.transport.0.localIp":
|
||||
"2aaa:9999:2aaa:999:8888:2aaa:2aaa:7777:50000",
|
||||
"matrix.stats.conn.transport.0.isFocus": true,
|
||||
"matrix.stats.conn.transport.0.localCandidateType": "host",
|
||||
"matrix.stats.conn.transport.0.remoteCandidateType": "host",
|
||||
"matrix.stats.conn.transport.0.networkType": "ethernet",
|
||||
"matrix.stats.conn.transport.0.rtt": "NaN",
|
||||
"matrix.stats.conn.transport.1.ip": "10.10.10.2:22222",
|
||||
"matrix.stats.conn.transport.1.type": "tcp",
|
||||
"matrix.stats.conn.transport.1.localIp": "10.10.10.100:33333",
|
||||
"matrix.stats.conn.transport.1.isFocus": true,
|
||||
"matrix.stats.conn.transport.1.localCandidateType": "srfx",
|
||||
"matrix.stats.conn.transport.1.remoteCandidateType": "srfx",
|
||||
"matrix.stats.conn.transport.1.networkType": "ethernet",
|
||||
"matrix.stats.conn.transport.1.rtt": "null",
|
||||
"matrix.call.stats.connection.transport.0.isFocus": true,
|
||||
"matrix.call.stats.connection.transport.0.localCandidateType": "host",
|
||||
"matrix.call.stats.connection.transport.0.remoteCandidateType": "host",
|
||||
"matrix.call.stats.connection.transport.0.networkType": "ethernet",
|
||||
"matrix.call.stats.connection.transport.0.rtt": "NaN",
|
||||
"matrix.call.stats.connection.transport.1.ip": "10.10.10.2:22222",
|
||||
"matrix.call.stats.connection.transport.1.type": "tcp",
|
||||
"matrix.call.stats.connection.transport.1.localIp":
|
||||
"10.10.10.100:33333",
|
||||
"matrix.call.stats.connection.transport.1.isFocus": true,
|
||||
"matrix.call.stats.connection.transport.1.localCandidateType": "srfx",
|
||||
"matrix.call.stats.connection.transport.1.remoteCandidateType": "srfx",
|
||||
"matrix.call.stats.connection.transport.1.networkType": "ethernet",
|
||||
"matrix.call.stats.connection.transport.1.rtt": "null",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("on flattenConnectionStatsReportObject", () => {
|
||||
describe("on flattenReportObject Connection Stats", () => {
|
||||
it("should flatten a Report to otel Attributes Object", () => {
|
||||
expect(
|
||||
ObjectFlattener.flattenConnectionStatsReportObject(statsReport)
|
||||
ObjectFlattener.flattenReportObject(
|
||||
"matrix.call.stats.connection",
|
||||
statsReport.report
|
||||
)
|
||||
).toEqual({
|
||||
"matrix.stats.conn.bandwidth.download": 0,
|
||||
"matrix.stats.conn.bandwidth.upload": 426,
|
||||
"matrix.stats.conn.bitrate.audio.download": 0,
|
||||
"matrix.stats.conn.bitrate.audio.upload": 124,
|
||||
"matrix.stats.conn.bitrate.download": 0,
|
||||
"matrix.stats.conn.bitrate.upload": 426,
|
||||
"matrix.stats.conn.bitrate.video.download": 0,
|
||||
"matrix.stats.conn.bitrate.video.upload": 302,
|
||||
"matrix.stats.conn.codec.local.LOCAL_AUDIO_TRACK_ID": "opus",
|
||||
"matrix.stats.conn.codec.local.LOCAL_VIDEO_TRACK_ID": "v8",
|
||||
"matrix.stats.conn.codec.remote.REMOTE_AUDIO_TRACK_ID": "opus",
|
||||
"matrix.stats.conn.codec.remote.REMOTE_VIDEO_TRACK_ID": "v9",
|
||||
"matrix.stats.conn.framerate.local.LOCAL_AUDIO_TRACK_ID": 0,
|
||||
"matrix.stats.conn.framerate.local.LOCAL_VIDEO_TRACK_ID": 30,
|
||||
"matrix.stats.conn.framerate.remote.REMOTE_AUDIO_TRACK_ID": 0,
|
||||
"matrix.stats.conn.framerate.remote.REMOTE_VIDEO_TRACK_ID": 60,
|
||||
"matrix.stats.conn.jitter.REMOTE_AUDIO_TRACK_ID": 2,
|
||||
"matrix.stats.conn.jitter.REMOTE_VIDEO_TRACK_ID": 50,
|
||||
"matrix.stats.conn.packetLoss.download": 0,
|
||||
"matrix.stats.conn.packetLoss.total": 0,
|
||||
"matrix.stats.conn.packetLoss.upload": 0,
|
||||
"matrix.stats.conn.resolution.local.LOCAL_AUDIO_TRACK_ID.height": -1,
|
||||
"matrix.stats.conn.resolution.local.LOCAL_AUDIO_TRACK_ID.width": -1,
|
||||
"matrix.stats.conn.resolution.local.LOCAL_VIDEO_TRACK_ID.height": 460,
|
||||
"matrix.stats.conn.resolution.local.LOCAL_VIDEO_TRACK_ID.width": 780,
|
||||
"matrix.stats.conn.resolution.remote.REMOTE_AUDIO_TRACK_ID.height": -1,
|
||||
"matrix.stats.conn.resolution.remote.REMOTE_AUDIO_TRACK_ID.width": -1,
|
||||
"matrix.stats.conn.resolution.remote.REMOTE_VIDEO_TRACK_ID.height": 960,
|
||||
"matrix.stats.conn.resolution.remote.REMOTE_VIDEO_TRACK_ID.width": 1080,
|
||||
"matrix.stats.conn.transport.0.ip": "ff11::5fa:abcd:999c:c5c5:50000",
|
||||
"matrix.stats.conn.transport.0.type": "udp",
|
||||
"matrix.stats.conn.transport.0.localIp":
|
||||
"matrix.call.stats.connection.callId": "callId",
|
||||
"matrix.call.stats.connection.opponentMemberId": "opponentMemberId",
|
||||
"matrix.call.stats.connection.bandwidth.download": 0,
|
||||
"matrix.call.stats.connection.bandwidth.upload": 426,
|
||||
"matrix.call.stats.connection.bitrate.audio.download": 0,
|
||||
"matrix.call.stats.connection.bitrate.audio.upload": 124,
|
||||
"matrix.call.stats.connection.bitrate.download": 0,
|
||||
"matrix.call.stats.connection.bitrate.upload": 426,
|
||||
"matrix.call.stats.connection.bitrate.video.download": 0,
|
||||
"matrix.call.stats.connection.bitrate.video.upload": 302,
|
||||
"matrix.call.stats.connection.codec.local.LOCAL_AUDIO_TRACK_ID": "opus",
|
||||
"matrix.call.stats.connection.codec.local.LOCAL_VIDEO_TRACK_ID": "v8",
|
||||
"matrix.call.stats.connection.codec.remote.REMOTE_AUDIO_TRACK_ID":
|
||||
"opus",
|
||||
"matrix.call.stats.connection.codec.remote.REMOTE_VIDEO_TRACK_ID": "v9",
|
||||
"matrix.call.stats.connection.framerate.local.LOCAL_AUDIO_TRACK_ID": 0,
|
||||
"matrix.call.stats.connection.framerate.local.LOCAL_VIDEO_TRACK_ID": 30,
|
||||
"matrix.call.stats.connection.framerate.remote.REMOTE_AUDIO_TRACK_ID": 0,
|
||||
"matrix.call.stats.connection.framerate.remote.REMOTE_VIDEO_TRACK_ID": 60,
|
||||
"matrix.call.stats.connection.jitter.REMOTE_AUDIO_TRACK_ID": 2,
|
||||
"matrix.call.stats.connection.jitter.REMOTE_VIDEO_TRACK_ID": 50,
|
||||
"matrix.call.stats.connection.packetLoss.download": 0,
|
||||
"matrix.call.stats.connection.packetLoss.total": 0,
|
||||
"matrix.call.stats.connection.packetLoss.upload": 0,
|
||||
"matrix.call.stats.connection.resolution.local.LOCAL_AUDIO_TRACK_ID.height":
|
||||
-1,
|
||||
"matrix.call.stats.connection.resolution.local.LOCAL_AUDIO_TRACK_ID.width":
|
||||
-1,
|
||||
"matrix.call.stats.connection.resolution.local.LOCAL_VIDEO_TRACK_ID.height": 460,
|
||||
"matrix.call.stats.connection.resolution.local.LOCAL_VIDEO_TRACK_ID.width": 780,
|
||||
"matrix.call.stats.connection.resolution.remote.REMOTE_AUDIO_TRACK_ID.height":
|
||||
-1,
|
||||
"matrix.call.stats.connection.resolution.remote.REMOTE_AUDIO_TRACK_ID.width":
|
||||
-1,
|
||||
"matrix.call.stats.connection.resolution.remote.REMOTE_VIDEO_TRACK_ID.height": 960,
|
||||
"matrix.call.stats.connection.resolution.remote.REMOTE_VIDEO_TRACK_ID.width": 1080,
|
||||
"matrix.call.stats.connection.transport.0.ip":
|
||||
"ff11::5fa:abcd:999c:c5c5:50000",
|
||||
"matrix.call.stats.connection.transport.0.type": "udp",
|
||||
"matrix.call.stats.connection.transport.0.localIp":
|
||||
"2aaa:9999:2aaa:999:8888:2aaa:2aaa:7777:50000",
|
||||
"matrix.stats.conn.transport.0.isFocus": true,
|
||||
"matrix.stats.conn.transport.0.localCandidateType": "host",
|
||||
"matrix.stats.conn.transport.0.remoteCandidateType": "host",
|
||||
"matrix.stats.conn.transport.0.networkType": "ethernet",
|
||||
"matrix.stats.conn.transport.0.rtt": "NaN",
|
||||
"matrix.stats.conn.transport.1.ip": "10.10.10.2:22222",
|
||||
"matrix.stats.conn.transport.1.type": "tcp",
|
||||
"matrix.stats.conn.transport.1.localIp": "10.10.10.100:33333",
|
||||
"matrix.stats.conn.transport.1.isFocus": true,
|
||||
"matrix.stats.conn.transport.1.localCandidateType": "srfx",
|
||||
"matrix.stats.conn.transport.1.remoteCandidateType": "srfx",
|
||||
"matrix.stats.conn.transport.1.networkType": "ethernet",
|
||||
"matrix.stats.conn.transport.1.rtt": "null",
|
||||
"matrix.call.stats.connection.transport.0.isFocus": true,
|
||||
"matrix.call.stats.connection.transport.0.localCandidateType": "host",
|
||||
"matrix.call.stats.connection.transport.0.remoteCandidateType": "host",
|
||||
"matrix.call.stats.connection.transport.0.networkType": "ethernet",
|
||||
"matrix.call.stats.connection.transport.0.rtt": "NaN",
|
||||
"matrix.call.stats.connection.transport.1.ip": "10.10.10.2:22222",
|
||||
"matrix.call.stats.connection.transport.1.type": "tcp",
|
||||
"matrix.call.stats.connection.transport.1.localIp":
|
||||
"10.10.10.100:33333",
|
||||
"matrix.call.stats.connection.transport.1.isFocus": true,
|
||||
"matrix.call.stats.connection.transport.1.localCandidateType": "srfx",
|
||||
"matrix.call.stats.connection.transport.1.remoteCandidateType": "srfx",
|
||||
"matrix.call.stats.connection.transport.1.networkType": "ethernet",
|
||||
"matrix.call.stats.connection.transport.1.rtt": "null",
|
||||
"matrix.call.stats.connection.audioConcealment.REMOTE_AUDIO_TRACK_ID.concealedAudio": 0,
|
||||
"matrix.call.stats.connection.audioConcealment.REMOTE_AUDIO_TRACK_ID.totalAudioDuration": 0,
|
||||
"matrix.call.stats.connection.audioConcealment.REMOTE_VIDEO_TRACK_ID.concealedAudio": 0,
|
||||
"matrix.call.stats.connection.audioConcealment.REMOTE_VIDEO_TRACK_ID.totalAudioDuration": 0,
|
||||
"matrix.call.stats.connection.totalAudioConcealment.concealedAudio": 0,
|
||||
"matrix.call.stats.connection.totalAudioConcealment.totalAudioDuration": 0,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("on flattenByteSendStatsReportObject", () => {
|
||||
const byteSent = {
|
||||
report: new Map([
|
||||
["4aa92608-04c6-428e-8312-93e17602a959", 132093],
|
||||
["a08e4237-ee30-4015-a932-b676aec894b1", 913448],
|
||||
]),
|
||||
};
|
||||
const byteSentStatsReport = new Map<
|
||||
string,
|
||||
number
|
||||
>() as ByteSentStatsReport;
|
||||
byteSentStatsReport.callId = "callId";
|
||||
byteSentStatsReport.opponentMemberId = "opponentMemberId";
|
||||
byteSentStatsReport.set("4aa92608-04c6-428e-8312-93e17602a959", 132093);
|
||||
byteSentStatsReport.set("a08e4237-ee30-4015-a932-b676aec894b1", 913448);
|
||||
|
||||
it("should flatten a Report to otel Attributes Object", () => {
|
||||
expect(
|
||||
ObjectFlattener.flattenByteSentStatsReportObject(byteSent)
|
||||
ObjectFlattener.flattenReportObject(
|
||||
"matrix.call.stats.bytesSend",
|
||||
byteSentStatsReport
|
||||
)
|
||||
).toEqual({
|
||||
"matrix.stats.bytesSent.4aa92608-04c6-428e-8312-93e17602a959": 132093,
|
||||
"matrix.stats.bytesSent.a08e4237-ee30-4015-a932-b676aec894b1": 913448,
|
||||
"matrix.call.stats.bytesSend.4aa92608-04c6-428e-8312-93e17602a959": 132093,
|
||||
"matrix.call.stats.bytesSend.a08e4237-ee30-4015-a932-b676aec894b1": 913448,
|
||||
});
|
||||
expect(byteSentStatsReport.callId).toEqual("callId");
|
||||
expect(byteSentStatsReport.opponentMemberId).toEqual("opponentMemberId");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
108
yarn.lock
108
yarn.lock
@@ -1209,13 +1209,20 @@
|
||||
core-js-pure "^3.20.2"
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7":
|
||||
"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.18.3", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7":
|
||||
version "7.18.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.6.tgz#6a1ef59f838debd670421f8c7f2cbb8da9751580"
|
||||
integrity sha512-t9wi7/AW6XtKahAe20Yw0/mMljKq0B1r2fPdvaAdV/KPDZewFXdaaa6K7lxmZBZ8FBNpCiAT6iHPmd6QO9bKfQ==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.12.5":
|
||||
version "7.21.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.5.tgz#8492dddda9644ae3bda3b45eabe87382caee7200"
|
||||
integrity sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.11"
|
||||
|
||||
"@babel/runtime@^7.13.9", "@babel/runtime@^7.9.2":
|
||||
version "7.19.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.4.tgz#a42f814502ee467d55b38dd1c256f53a7b885c78"
|
||||
@@ -1821,10 +1828,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.3.1.tgz#b50a781709c81e10701004214340f25475a171a0"
|
||||
integrity sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw==
|
||||
|
||||
"@matrix-org/matrix-sdk-crypto-js@^0.1.0-alpha.7":
|
||||
version "0.1.0-alpha.7"
|
||||
resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-js/-/matrix-sdk-crypto-js-0.1.0-alpha.7.tgz#136375b84fd8a7e698f70fc969f668e541a61313"
|
||||
integrity sha512-sQEG9cSfNji5NYBf5h7j5IxYVO0dwtAKoetaVyR+LhIXz/Su7zyEE3EwlAWAeJOFdAV/vZ5LTNyh39xADuNlTg==
|
||||
"@matrix-org/matrix-sdk-crypto-js@^0.1.0-alpha.9":
|
||||
version "0.1.0-alpha.9"
|
||||
resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-js/-/matrix-sdk-crypto-js-0.1.0-alpha.9.tgz#00bc266781502641a661858a5a521dd4d95275fc"
|
||||
integrity sha512-g5cjpFwA9h0CbEGoAqNVI2QcyDsbI8FHoLo9+OXWHIezEKITsSv78mc5ilIwN+2YpmVlH0KNeQWTHw4vi0BMnw==
|
||||
|
||||
"@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz":
|
||||
version "3.2.14"
|
||||
@@ -2264,51 +2271,51 @@
|
||||
"@react-aria/utils" "^3.13.1"
|
||||
clsx "^1.1.1"
|
||||
|
||||
"@react-spring/animated@~9.4.5":
|
||||
version "9.4.5"
|
||||
resolved "https://registry.yarnpkg.com/@react-spring/animated/-/animated-9.4.5.tgz#dd9921c716a4f4a3ed29491e0c0c9f8ca0eb1a54"
|
||||
integrity sha512-KWqrtvJSMx6Fj9nMJkhTwM9r6LIriExDRV6YHZV9HKQsaolUFppgkOXpC+rsL1JEtEvKv6EkLLmSqHTnuYjiIA==
|
||||
"@react-spring/animated@~9.7.2":
|
||||
version "9.7.2"
|
||||
resolved "https://registry.yarnpkg.com/@react-spring/animated/-/animated-9.7.2.tgz#0119db8075e91d693ec45c42575541e01b104a70"
|
||||
integrity sha512-ipvleJ99ipqlnHkz5qhSsgf/ny5aW0ZG8Q+/2Oj9cI7LCc7COdnrSO6V/v8MAX3JOoQNzfz6dye2s5Pt5jGaIA==
|
||||
dependencies:
|
||||
"@react-spring/shared" "~9.4.5"
|
||||
"@react-spring/types" "~9.4.5"
|
||||
"@react-spring/shared" "~9.7.2"
|
||||
"@react-spring/types" "~9.7.2"
|
||||
|
||||
"@react-spring/core@~9.4.5":
|
||||
version "9.4.5"
|
||||
resolved "https://registry.yarnpkg.com/@react-spring/core/-/core-9.4.5.tgz#4616e1adc18dd10f5731f100ebdbe9518b89ba3c"
|
||||
integrity sha512-83u3FzfQmGMJFwZLAJSwF24/ZJctwUkWtyPD7KYtNagrFeQKUH1I05ZuhmCmqW+2w1KDW1SFWQ43RawqfXKiiQ==
|
||||
"@react-spring/core@~9.7.2":
|
||||
version "9.7.2"
|
||||
resolved "https://registry.yarnpkg.com/@react-spring/core/-/core-9.7.2.tgz#804ebadee45a6adff00886454d6f1c5d97ee219d"
|
||||
integrity sha512-fF512edZT/gKVCA90ZRxfw1DmELeVwiL4OC2J6bMUlNr707C0h4QRoec6DjzG27uLX2MvS1CEatf9KRjwZR9/w==
|
||||
dependencies:
|
||||
"@react-spring/animated" "~9.4.5"
|
||||
"@react-spring/rafz" "~9.4.5"
|
||||
"@react-spring/shared" "~9.4.5"
|
||||
"@react-spring/types" "~9.4.5"
|
||||
"@react-spring/animated" "~9.7.2"
|
||||
"@react-spring/rafz" "~9.7.2"
|
||||
"@react-spring/shared" "~9.7.2"
|
||||
"@react-spring/types" "~9.7.2"
|
||||
|
||||
"@react-spring/rafz@~9.4.5":
|
||||
version "9.4.5"
|
||||
resolved "https://registry.yarnpkg.com/@react-spring/rafz/-/rafz-9.4.5.tgz#84f809f287f2a66bbfbc66195db340482f886bd7"
|
||||
integrity sha512-swGsutMwvnoyTRxvqhfJBtGM8Ipx6ks0RkIpNX9F/U7XmyPvBMGd3GgX/mqxZUpdlsuI1zr/jiYw+GXZxAlLcQ==
|
||||
"@react-spring/rafz@~9.7.2":
|
||||
version "9.7.2"
|
||||
resolved "https://registry.yarnpkg.com/@react-spring/rafz/-/rafz-9.7.2.tgz#77e7088c215e05cf893851cd87ceb40d89f2a7d7"
|
||||
integrity sha512-kDWMYDQto3+flkrX3vy6DU/l9pxQ4TVW91DglQEc11iDc7shF4+WVDRJvOVLX+xoMP7zyag1dMvlIgvQ+dvA/A==
|
||||
|
||||
"@react-spring/shared@~9.4.5":
|
||||
version "9.4.5"
|
||||
resolved "https://registry.yarnpkg.com/@react-spring/shared/-/shared-9.4.5.tgz#4c3ad817bca547984fb1539204d752a412a6d829"
|
||||
integrity sha512-JhMh3nFKsqyag0KM5IIM8BQANGscTdd0mMv3BXsUiMZrcjQTskyfnv5qxEeGWbJGGar52qr5kHuBHtCjQOzniA==
|
||||
"@react-spring/shared@~9.7.2":
|
||||
version "9.7.2"
|
||||
resolved "https://registry.yarnpkg.com/@react-spring/shared/-/shared-9.7.2.tgz#b8485617bdcc9f6348b245922051fb534e07c566"
|
||||
integrity sha512-6U9qkno+9DxlH5nSltnPs+kU6tYKf0bPLURX2te13aGel8YqgcpFYp5Av8DcN2x3sukinAsmzHUS/FRsdZMMBA==
|
||||
dependencies:
|
||||
"@react-spring/rafz" "~9.4.5"
|
||||
"@react-spring/types" "~9.4.5"
|
||||
"@react-spring/rafz" "~9.7.2"
|
||||
"@react-spring/types" "~9.7.2"
|
||||
|
||||
"@react-spring/types@~9.4.5":
|
||||
version "9.4.5"
|
||||
resolved "https://registry.yarnpkg.com/@react-spring/types/-/types-9.4.5.tgz#9c71e5ff866b5484a7ef3db822bf6c10e77bdd8c"
|
||||
integrity sha512-mpRIamoHwql0ogxEUh9yr4TP0xU5CWyZxVQeccGkHHF8kPMErtDXJlxyo0lj+telRF35XNihtPTWoflqtyARmg==
|
||||
"@react-spring/types@~9.7.2":
|
||||
version "9.7.2"
|
||||
resolved "https://registry.yarnpkg.com/@react-spring/types/-/types-9.7.2.tgz#e04dd72755d88b0e3163ba143ecd8ba78b68a5b0"
|
||||
integrity sha512-GEflx2Ex/TKVMHq5g5MxQDNNPNhqg+4Db9m7+vGTm8ttZiyga7YQUF24shgRNebKIjahqCuei16SZga8h1pe4g==
|
||||
|
||||
"@react-spring/web@^9.4.4":
|
||||
version "9.4.5"
|
||||
resolved "https://registry.yarnpkg.com/@react-spring/web/-/web-9.4.5.tgz#b92f05b87cdc0963a59ee149e677dcaff09f680e"
|
||||
integrity sha512-NGAkOtKmOzDEctL7MzRlQGv24sRce++0xAY7KlcxmeVkR7LRSGkoXHaIfm9ObzxPMcPHQYQhf3+X9jepIFNHQA==
|
||||
version "9.7.2"
|
||||
resolved "https://registry.yarnpkg.com/@react-spring/web/-/web-9.7.2.tgz#76e53dd24033764c3062f9927f88b0f3194688d4"
|
||||
integrity sha512-7qNc7/5KShu2D05x7o2Ols2nUE7mCKfKLaY2Ix70xPMfTle1sZisoQMBFgV9w/fSLZlHZHV9P0uWJqEXQnbV4Q==
|
||||
dependencies:
|
||||
"@react-spring/animated" "~9.4.5"
|
||||
"@react-spring/core" "~9.4.5"
|
||||
"@react-spring/shared" "~9.4.5"
|
||||
"@react-spring/types" "~9.4.5"
|
||||
"@react-spring/animated" "~9.7.2"
|
||||
"@react-spring/core" "~9.7.2"
|
||||
"@react-spring/shared" "~9.7.2"
|
||||
"@react-spring/types" "~9.7.2"
|
||||
|
||||
"@react-stately/collections@^3.3.4", "@react-stately/collections@^3.4.1":
|
||||
version "3.4.1"
|
||||
@@ -10550,12 +10557,12 @@ matrix-events-sdk@0.0.1:
|
||||
resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd"
|
||||
integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==
|
||||
|
||||
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#261bc81554580b442769a65ceed2b154178fbe1c":
|
||||
version "25.0.0"
|
||||
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/261bc81554580b442769a65ceed2b154178fbe1c"
|
||||
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#3cfad3cdeb7b19b8e0e7015784efd803cb9542f1":
|
||||
version "26.0.0"
|
||||
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/3cfad3cdeb7b19b8e0e7015784efd803cb9542f1"
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.5"
|
||||
"@matrix-org/matrix-sdk-crypto-js" "^0.1.0-alpha.7"
|
||||
"@matrix-org/matrix-sdk-crypto-js" "^0.1.0-alpha.9"
|
||||
another-json "^0.2.0"
|
||||
bs58 "^5.0.0"
|
||||
content-type "^1.0.4"
|
||||
@@ -10568,9 +10575,9 @@ matrix-events-sdk@0.0.1:
|
||||
uuid "9"
|
||||
|
||||
matrix-widget-api@^1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-1.3.1.tgz#e38f404c76bb15c113909505c1c1a5b4d781c2f5"
|
||||
integrity sha512-+rN6vGvnXm+fn0uq9r2KWSL/aPtehD6ObC50jYmUcEfgo8CUpf9eUurmjbRlwZkWq3XHXFuKQBUCI9UzqWg37Q==
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-1.4.0.tgz#e426ec16a013897f3a4a9c2bff423f54ab0ba745"
|
||||
integrity sha512-dw0dRylGQzDUoiaY/g5xx1tBbS7aoov31PRtFMAvG58/4uerYllV9Gfou7w+I1aglwB6hihTREzKltVjARWV6A==
|
||||
dependencies:
|
||||
"@types/events" "^3.0.0"
|
||||
events "^3.2.0"
|
||||
@@ -12702,7 +12709,12 @@ regenerate@^1.4.2:
|
||||
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a"
|
||||
integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==
|
||||
|
||||
regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7:
|
||||
regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.4:
|
||||
version "0.13.11"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
|
||||
integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
|
||||
|
||||
regenerator-runtime@^0.13.7:
|
||||
version "0.13.9"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
|
||||
integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==
|
||||
|
||||
Reference in New Issue
Block a user