diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml
index b3a913bf..3f707a36 100644
--- a/.github/workflows/docker.yaml
+++ b/.github/workflows/docker.yaml
@@ -23,7 +23,7 @@ jobs:
packages: write
steps:
- name: Check it out
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
+ uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
- name: 📥 Download artifact
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
@@ -48,10 +48,10 @@ jobs:
tags: ${{ inputs.docker_tags}}
- name: Set up Docker Buildx
- uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1
+ uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1
- name: Build and push Docker image
- uses: docker/build-push-action@5cd11c3a4ced054e52742c5fd54dca954e0edd85 # v6.7.0
+ uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0
with:
context: .
platforms: linux/amd64,linux/arm64
diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml
index dfb8fc2b..c1fbca28 100644
--- a/.github/workflows/e2e.yml
+++ b/.github/workflows/e2e.yml
@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out test private repo
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
+ uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
repository: element-hq/static-call-participant
ref: refs/heads/main
diff --git a/.github/workflows/element-call.yaml b/.github/workflows/element-call.yaml
index c6485e7f..5a342cb8 100644
--- a/.github/workflows/element-call.yaml
+++ b/.github/workflows/element-call.yaml
@@ -21,9 +21,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
+ uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
- name: Yarn cache
- uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4
+ uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
with:
cache: "yarn"
node-version: "lts/*"
@@ -39,7 +39,7 @@ jobs:
VITE_APP_VERSION: ${{ inputs.vite_app_version }}
NODE_OPTIONS: "--max-old-space-size=4096"
- name: Upload Artifact
- uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4
+ uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
with:
name: build-output
path: dist
diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml
index 5c676a7c..585f0f43 100644
--- a/.github/workflows/lint.yaml
+++ b/.github/workflows/lint.yaml
@@ -7,9 +7,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
+ uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
- name: Yarn cache
- uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4
+ uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
with:
cache: "yarn"
node-version: "lts/*"
diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml
index 63ad7fbf..19d4ffdf 100644
--- a/.github/workflows/publish.yaml
+++ b/.github/workflows/publish.yaml
@@ -51,7 +51,7 @@ jobs:
run: |
tar --numeric-owner --transform "s/dist/element-call-${TARBALL_VERSION}/" -cvzf element-call-${TARBALL_VERSION}.tar.gz dist
- name: Upload
- uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
+ uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
env:
GITHUB_TOKEN: ${{ github.token }}
with:
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index 870dabe1..fde77b63 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -9,9 +9,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
+ uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
- name: Yarn cache
- uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4
+ uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
with:
cache: "yarn"
node-version: "lts/*"
@@ -20,7 +20,7 @@ jobs:
- name: Vitest
run: "yarn run test:coverage"
- name: Upload to codecov
- uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4
+ uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
diff --git a/.github/workflows/translations-download.yaml b/.github/workflows/translations-download.yaml
index 42a1cc69..1caab0b0 100644
--- a/.github/workflows/translations-download.yaml
+++ b/.github/workflows/translations-download.yaml
@@ -13,9 +13,9 @@ jobs:
steps:
- name: Checkout the code
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
+ uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
- - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4
+ - uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
with:
cache: "yarn"
node-version: "lts/*"
@@ -39,7 +39,7 @@ jobs:
- name: Create Pull Request
id: cpr
- uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6.1.0
+ uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
with:
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
branch: actions/localazy-download
diff --git a/.github/workflows/translations-upload.yaml b/.github/workflows/translations-upload.yaml
index 0f759ddb..f52421b8 100644
--- a/.github/workflows/translations-upload.yaml
+++ b/.github/workflows/translations-upload.yaml
@@ -14,7 +14,7 @@ jobs:
steps:
- name: Checkout the code
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
+ uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
- name: Upload
uses: localazy/upload@27e6b5c0fddf4551596b42226b1c24124335d24a # v1
diff --git a/README.md b/README.md
index 4a9adc73..9ff4e156 100644
--- a/README.md
+++ b/README.md
@@ -44,7 +44,7 @@ server {
}
```
-By default, the app expects you to have a Matrix homeserver (such as [Synapse](https://matrix-org.github.io/synapse/latest/setup/installation.html)) installed locally and running on port 8008. If you wish to use a homeserver on a different URL or one that is hosted on a different server, you can add a config file as above, and include the homeserver URL that you'd like to use.
+By default, the app expects you to have a Matrix homeserver (such as [Synapse](https://element-hq.github.io/synapse/latest/setup/installation.html)) installed locally and running on port 8008. If you wish to use a homeserver on a different URL or one that is hosted on a different server, you can add a config file as above, and include the homeserver URL that you'd like to use.
Element Call requires a homeserver with registration enabled without any 3pid or token requirements, if you want it to be used by unregistered users. Furthermore, it is not recommended to use it with an existing homeserver where user accounts have joined normal rooms, as it may not be able to handle those yet and it may behave unreliably.
diff --git a/backend-docker-compose.yml b/backend-docker-compose.yml
index 75b7f720..b0dbe822 100644
--- a/backend-docker-compose.yml
+++ b/backend-docker-compose.yml
@@ -7,12 +7,16 @@ services:
auth-service:
image: ghcr.io/element-hq/lk-jwt-service:latest-ci
hostname: auth-server
- ports:
- - 8881:8080
+ # Use host network in case the configured homeserver runs on localhost
+ network_mode: host
environment:
+ - LK_JWT_PORT=8881
- LIVEKIT_URL=ws://localhost:7880
- LIVEKIT_KEY=devkey
- LIVEKIT_SECRET=secret
+ # If the configured homeserver runs on localhost, it'll probably be using
+ # a self-signed certificate
+ - LIVEKIT_INSECURE_SKIP_VERIFY_TLS=YES_I_KNOW_WHAT_I_AM_DOING
deploy:
restart_policy:
condition: on-failure
@@ -23,11 +27,15 @@ services:
image: livekit/livekit-server:latest
command: --dev --config /etc/livekit.yaml
restart: unless-stopped
- ports:
- - "7880:7880"
- - "7881:7881"
- - "7882:7882"
- - "50100-50200:50100-50200"
+ # The SFU seems to work far more reliably when we let it share the host
+ # network rather than opening specific ports (but why?? we're not missing
+ # any…)
+ network_mode: host
+ # ports:
+ # - "7880:7880/tcp"
+ # - "7881:7881/tcp"
+ # - "7882:7882/tcp"
+ # - "50100-50200:50100-50200/udp"
volumes:
- ./backend/livekit.yaml:/etc/livekit.yaml
networks:
diff --git a/codecov.yaml b/codecov.yaml
index 0f42ad4e..e1289344 100644
--- a/codecov.yaml
+++ b/codecov.yaml
@@ -1,6 +1,10 @@
# Don't post comments on PRs; they're noisy and the same information can be
# gotten through the checks section at the bottom of the PR anyways
comment: false
+github_checks:
+ # Don't mark up the diffs on PRs with warnings about untested lines; we're not
+ # aiming for 100% test coverage and they just get in the way of reviewing
+ annotations: false
coverage:
status:
project:
diff --git a/package.json b/package.json
index 4355effc..8f3e49d5 100644
--- a/package.json
+++ b/package.json
@@ -41,6 +41,7 @@
"@sentry/vite-plugin": "^2.0.0",
"@testing-library/dom": "^10.1.0",
"@testing-library/react": "^16.0.0",
+ "@testing-library/react-hooks": "^8.0.1",
"@testing-library/user-event": "^14.5.1",
"@types/content-type": "^1.1.5",
"@types/grecaptcha": "^3.0.9",
@@ -70,9 +71,9 @@
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-matrix-org": "^1.2.1",
"eslint-plugin-react": "^7.29.4",
- "eslint-plugin-react-hooks": "^4.5.0",
+ "eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-unicorn": "^55.0.0",
- "global-jsdom": "^24.0.0",
+ "global-jsdom": "^25.0.0",
"history": "^4.0.0",
"i18next": "^23.0.0",
"i18next-browser-languagedetector": "^8.0.0",
@@ -80,7 +81,7 @@
"i18next-parser": "^9.0.0",
"jsdom": "^25.0.0",
"knip": "^5.27.2",
- "livekit-client": "^2.0.2",
+ "livekit-client": "^2.5.7",
"lodash": "^4.17.21",
"loglevel": "^1.9.1",
"matrix-js-sdk": "matrix-org/matrix-js-sdk#b0174eccdb0e33f5df5d7b590938daf8ff5c7f7a",
@@ -90,7 +91,7 @@
"pako": "^2.0.4",
"postcss": "^8.4.41",
"postcss-preset-env": "^10.0.0",
- "posthog-js": "^1.29.0",
+ "posthog-js": "1.160.3",
"prettier": "^3.0.0",
"qrcode": "^1.5.4",
"react": "18",
@@ -108,6 +109,10 @@
"vite": "^5.0.0",
"vite-plugin-html-template": "^1.1.0",
"vite-plugin-svgr": "^4.0.0",
- "vitest": "^2.0.0"
+ "vitest": "^2.0.0",
+ "vitest-axe": "^1.0.0-pre.3"
+ },
+ "resolutions": {
+ "strip-ansi": "6.0.1"
}
}
diff --git a/public/locales/de/app.json b/public/locales/de/app.json
index 359b352c..80691ad2 100644
--- a/public/locales/de/app.json
+++ b/public/locales/de/app.json
@@ -15,7 +15,7 @@
"sign_out": "Abmelden",
"submit": "Absenden"
},
- "analytics_notice": "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ärung2> und unseren <5>Cookie-Richtlinien5>.",
+ "analytics_notice": "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ärung2> und unseren <6>Cookie-Richtlinien6>.",
"app_selection_modal": {
"continue_in_browser": "Weiter im Browser",
"open_in_app": "In der App öffnen",
diff --git a/public/locales/el/app.json b/public/locales/el/app.json
index 249ae236..cdc814aa 100644
--- a/public/locales/el/app.json
+++ b/public/locales/el/app.json
@@ -13,7 +13,7 @@
"sign_out": "Αποσύνδεση",
"submit": "Υποβολή"
},
- "analytics_notice": "Συμμετέχοντας σε αυτή τη δοκιμαστική έκδοση, συναινείτε στη συλλογή ανώνυμων δεδομένων, τα οποία χρησιμοποιούμε για τη βελτίωση του προϊόντος. Μπορείτε να βρείτε περισσότερες πληροφορίες σχετικά με το ποια δεδομένα καταγράφουμε στην <2>Πολιτική απορρήτου2> και στην <5>Πολιτική cookies5>.",
+ "analytics_notice": "Συμμετέχοντας σε αυτή τη δοκιμαστική έκδοση, συναινείτε στη συλλογή ανώνυμων δεδομένων, τα οποία χρησιμοποιούμε για τη βελτίωση του προϊόντος. Μπορείτε να βρείτε περισσότερες πληροφορίες σχετικά με το ποια δεδομένα καταγράφουμε στην <2>Πολιτική απορρήτου2> και στην <6>Πολιτική cookies6>.",
"call_ended_view": {
"create_account_button": "Δημιουργία λογαριασμού",
"create_account_prompt": "<0>Γιατί να μην ολοκληρώσετε με τη δημιουργία ενός κωδικού πρόσβασης για τη διατήρηση του λογαριασμού σας;0><1>Θα μπορείτε να διατηρήσετε το όνομά σας και να ορίσετε ένα avatar για χρήση σε μελλοντικές κλήσεις.1>",
diff --git a/public/locales/en-GB/app.json b/public/locales/en-GB/app.json
index 6581274a..0cfa3085 100644
--- a/public/locales/en-GB/app.json
+++ b/public/locales/en-GB/app.json
@@ -16,7 +16,7 @@
"submit": "Submit",
"upload_file": "Upload file"
},
- "analytics_notice": "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 Policy2> and our <5>Cookie Policy5>.",
+ "analytics_notice": "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 Policy2> and our <6>Cookie Policy6>.",
"app_selection_modal": {
"continue_in_browser": "Continue in browser",
"open_in_app": "Open in the app",
@@ -91,6 +91,7 @@
"layout_spotlight_label": "Spotlight",
"lobby": {
"ask_to_join": "Ask to join call",
+ "join_as_guest": "Join as guest",
"join_button": "Join call",
"leave_button": "Back to recents",
"waiting_for_invite": "Request sent"
@@ -128,8 +129,8 @@
"register_confirm_password_label": "Confirm password",
"register_heading": "Create your account",
"return_home_button": "Return to home screen",
- "room_auth_view_eula_caption": "By clicking \"Join call now\", you agree to our <2>End User Licensing Agreement (EULA)2>",
- "room_auth_view_join_button": "Join call now",
+ "room_auth_view_continue_button": "Continue",
+ "room_auth_view_eula_caption": "By clicking \"Continue\", you agree to our <2>End User Licensing Agreement (EULA)2>",
"screenshare_button_label": "Share screen",
"settings": {
"developer_settings_label": "Developer Settings",
@@ -161,8 +162,8 @@
"video_tile": {
"always_show": "Always show",
"change_fit_contain": "Fit to frame",
- "exit_full_screen": "Exit full screen",
- "full_screen": "Full screen",
+ "collapse": "Collapse",
+ "expand": "Expand",
"mute_for_me": "Mute for me",
"volume": "Volume"
}
diff --git a/public/locales/es/app.json b/public/locales/es/app.json
index b9a11989..f58e0998 100644
--- a/public/locales/es/app.json
+++ b/public/locales/es/app.json
@@ -12,7 +12,7 @@
"sign_out": "Cerrar sesión",
"submit": "Enviar"
},
- "analytics_notice": "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 privacidad2> y en nuestra <5>Política sobre Cookies5>.",
+ "analytics_notice": "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 privacidad2> y en nuestra <6>Política sobre Cookies6>.",
"call_ended_view": {
"create_account_button": "Crear cuenta",
"create_account_prompt": "<0>¿Por qué no mantienes tu cuenta estableciendo una contraseña?0><1>Podrás mantener tu nombre y establecer un avatar para usarlo en futuras llamadas1>",
diff --git a/public/locales/et/app.json b/public/locales/et/app.json
index 2ef7a76b..042d06a7 100644
--- a/public/locales/et/app.json
+++ b/public/locales/et/app.json
@@ -15,7 +15,7 @@
"sign_out": "Logi välja",
"submit": "Saada"
},
- "analytics_notice": "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>Privaatsuspoliitikast2> ja meie <5>Küpsiste kasutamise reeglitest5>.",
+ "analytics_notice": "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>Privaatsuspoliitikast2> ja meie <6>Küpsiste kasutamise reeglitest6>.",
"app_selection_modal": {
"continue_in_browser": "Jätka veebibrauseris",
"open_in_app": "Ava rakenduses",
diff --git a/public/locales/fr/app.json b/public/locales/fr/app.json
index b5ddd1e1..406ca5a2 100644
--- a/public/locales/fr/app.json
+++ b/public/locales/fr/app.json
@@ -15,7 +15,7 @@
"sign_out": "Déconnexion",
"submit": "Envoyer"
},
- "analytics_notice": "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ée2> et notre <5>Politique de cookies5>.",
+ "analytics_notice": "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ée2> et notre <6>Politique de cookies6>.",
"app_selection_modal": {
"continue_in_browser": "Continuer dans le navigateur",
"open_in_app": "Ouvrir dans l’application",
diff --git a/public/locales/id/app.json b/public/locales/id/app.json
index ce5cee36..ae4c2ba5 100644
--- a/public/locales/id/app.json
+++ b/public/locales/id/app.json
@@ -15,7 +15,7 @@
"sign_out": "Keluar",
"submit": "Kirim"
},
- "analytics_notice": "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 Privasi2> dan <5>Kebijakan Kuki5> kami.",
+ "analytics_notice": "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 Privasi2> dan <6>Kebijakan Kuki6> kami.",
"app_selection_modal": {
"continue_in_browser": "Lanjutkan dalam peramban",
"open_in_app": "Buka dalam aplikasi",
diff --git a/public/locales/it/app.json b/public/locales/it/app.json
index 7819fd30..6c0f9322 100644
--- a/public/locales/it/app.json
+++ b/public/locales/it/app.json
@@ -14,7 +14,7 @@
"sign_out": "Disconnetti",
"submit": "Invia"
},
- "analytics_notice": "Partecipando a questa beta, acconsenti alla raccolta di dati anonimi che usiamo per migliorare il prodotto. Puoi trovare più informazioni su quali dati monitoriamo nella nostra <2>informativa sulla privacy2> e nell'<5>informativa sui cookie5>.",
+ "analytics_notice": "Partecipando a questa beta, acconsenti alla raccolta di dati anonimi che usiamo per migliorare il prodotto. Puoi trovare più informazioni su quali dati monitoriamo nella nostra <2>informativa sulla privacy2> e nell'<6>informativa sui cookie6>.",
"app_selection_modal": {
"continue_in_browser": "Continua nel browser",
"open_in_app": "Apri nell'app",
diff --git a/public/locales/lv/app.json b/public/locales/lv/app.json
index 5c8ab81a..bde88489 100644
--- a/public/locales/lv/app.json
+++ b/public/locales/lv/app.json
@@ -13,7 +13,7 @@
"sign_out": "Atteikties",
"submit": "Iesniegt"
},
- "analytics_notice": "Piedalīšanās šajā beta apliecina piekrišanu anonīmu datu ievākšanai, ko mēs izmantojam, lai uzlabotu izstrādājumu. Vairāk informācijas par datiem, ko mēs ievācam, var atrast mūsu <2>privātuma nosacījumos2> un <5>sīkdatņu nosacījumos5>.",
+ "analytics_notice": "Piedalīšanās šajā beta apliecina piekrišanu anonīmu datu ievākšanai, ko mēs izmantojam, lai uzlabotu izstrādājumu. Vairāk informācijas par datiem, ko mēs ievācam, var atrast mūsu <2>privātuma nosacījumos2> un <6>sīkdatņu nosacījumos6>.",
"call_ended_view": {
"body": "Tu tiki atvienots no zvana",
"create_account_button": "Izveidot kontu",
diff --git a/public/locales/pl/app.json b/public/locales/pl/app.json
index 47375d60..2882bc24 100644
--- a/public/locales/pl/app.json
+++ b/public/locales/pl/app.json
@@ -15,7 +15,7 @@
"sign_out": "Wyloguj się",
"submit": "Wyślij"
},
- "analytics_notice": "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ści2> i <5>Polityce ciasteczek5>.",
+ "analytics_notice": "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ści2> i <6>Polityce ciasteczek6>.",
"app_selection_modal": {
"continue_in_browser": "Kontynuuj w przeglądarce",
"open_in_app": "Otwórz w aplikacji",
diff --git a/public/locales/ru/app.json b/public/locales/ru/app.json
index 3409acf4..36517c7e 100644
--- a/public/locales/ru/app.json
+++ b/public/locales/ru/app.json
@@ -13,7 +13,7 @@
"sign_out": "Выйти",
"submit": "Отправить"
},
- "analytics_notice": "Участвуя в этой бета-версии, вы соглашаетесь на сбор анонимных данных, которые мы используем для улучшения продукта. Более подробную информацию о том, какие данные мы отслеживаем, вы можете найти в нашей <2> Политике конфиденциальности2> и нашей <5> Политике использования файлов cookie5>.",
+ "analytics_notice": "Участвуя в этой бета-версии, вы соглашаетесь на сбор анонимных данных, которые мы используем для улучшения продукта. Более подробную информацию о том, какие данные мы отслеживаем, вы можете найти в нашей <2> Политике конфиденциальности2> и нашей <6> Политике использования файлов cookie6>.",
"call_ended_view": {
"create_account_button": "Создать аккаунт",
"create_account_prompt": "<0>Почему бы не задать пароль, тем самым сохранив аккаунт?0><1>Так вы можете оставить своё имя и задать аватар для будущих звонков.1>",
diff --git a/public/locales/sk/app.json b/public/locales/sk/app.json
index 7888c256..eabef9e8 100644
--- a/public/locales/sk/app.json
+++ b/public/locales/sk/app.json
@@ -15,7 +15,7 @@
"sign_out": "Odhlásiť sa",
"submit": "Odoslať"
},
- "analytics_notice": "Úč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 údajov2> a <5>Zásadách používania súborov cookie5>.",
+ "analytics_notice": "Úč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 údajov2> a <6>Zásadách používania súborov cookie6>.",
"app_selection_modal": {
"continue_in_browser": "Pokračovať v prehliadači",
"open_in_app": "Otvoriť v aplikácii",
diff --git a/public/locales/uk/app.json b/public/locales/uk/app.json
index dc2587ba..922658ef 100644
--- a/public/locales/uk/app.json
+++ b/public/locales/uk/app.json
@@ -15,7 +15,7 @@
"sign_out": "Вийти",
"submit": "Надіслати"
},
- "analytics_notice": "Користуючись дочасним доступом, ви даєте згоду на збір анонімних даних, які ми використовуємо для вдосконалення продукту. Ви можете знайти більше інформації про те, які дані ми відстежуємо в нашій <2>Політиці Приватності2> і нашій <5>Політиці про куки5>.",
+ "analytics_notice": "Користуючись дочасним доступом, ви даєте згоду на збір анонімних даних, які ми використовуємо для вдосконалення продукту. Ви можете знайти більше інформації про те, які дані ми відстежуємо в нашій <2>Політиці Приватності2> і нашій <6>Політиці про куки6>.",
"app_selection_modal": {
"continue_in_browser": "Продовжити у браузері",
"open_in_app": "Відкрити у застосунку",
diff --git a/public/locales/zh-Hans/app.json b/public/locales/zh-Hans/app.json
index 86a926c8..a7d3d876 100644
--- a/public/locales/zh-Hans/app.json
+++ b/public/locales/zh-Hans/app.json
@@ -13,7 +13,7 @@
"sign_out": "登出",
"submit": "提交"
},
- "analytics_notice": "参与测试即表示您同意我们收集匿名数据,用于改进产品。您可以在我们的<2>隐私政策2>和<5>Cookie政策5>中找到有关我们跟踪哪些数据以及更多信息。",
+ "analytics_notice": "参与测试即表示您同意我们收集匿名数据,用于改进产品。您可以在我们的<2>隐私政策2>和<6>Cookie政策6>中找到有关我们跟踪哪些数据以及更多信息。",
"app_selection_modal": {
"continue_in_browser": "在浏览器中继续",
"open_in_app": "在应用中打开",
diff --git a/public/locales/zh-Hant/app.json b/public/locales/zh-Hant/app.json
index c6484d4a..f667ef1d 100644
--- a/public/locales/zh-Hant/app.json
+++ b/public/locales/zh-Hant/app.json
@@ -15,7 +15,7 @@
"sign_out": "登出",
"submit": "遞交"
},
- "analytics_notice": "參與此測試版即表示您同意蒐集匿名資料,我們使用這些資料來改進產品。您可以在我們的<2>隱私政策2>與我們的 <5>Cookie 政策5> 中找到關於我們追蹤哪些資料的更多資訊。",
+ "analytics_notice": "參與此測試版即表示您同意蒐集匿名資料,我們使用這些資料來改進產品。您可以在我們的<2>隱私政策2>與我們的 <6>Cookie 政策6> 中找到關於我們追蹤哪些資料的更多資訊。",
"app_selection_modal": {
"continue_in_browser": "在瀏覽器中繼續",
"open_in_app": "在應用程式中開啟",
diff --git a/renovate.json b/renovate.json
index 91fc985a..556a876a 100644
--- a/renovate.json
+++ b/renovate.json
@@ -44,5 +44,6 @@
"prHeader": "Please review modals on mobile for visual regressions."
}
],
- "semanticCommits": "disabled"
+ "semanticCommits": "disabled",
+ "ignoreDeps": ["posthog-js"]
}
diff --git a/src/Header.test.tsx b/src/Header.test.tsx
new file mode 100644
index 00000000..681ef991
--- /dev/null
+++ b/src/Header.test.tsx
@@ -0,0 +1,30 @@
+/*
+Copyright 2024 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only
+Please see LICENSE in the repository root for full details.
+*/
+
+import { expect, test } from "vitest";
+import { render, screen } from "@testing-library/react";
+import { axe } from "vitest-axe";
+import { TooltipProvider } from "@vector-im/compound-web";
+
+import { RoomHeaderInfo } from "./Header";
+
+test("RoomHeaderInfo is accessible", async () => {
+ const { container } = render(
+
+
+ ,
+ );
+ expect(await axe(container)).toHaveNoViolations();
+ // Check that the room name acts as a heading
+ screen.getByRole("heading", { name: "Mission Control" });
+});
diff --git a/src/Modal.tsx b/src/Modal.tsx
index 6ba9e239..deef7635 100644
--- a/src/Modal.tsx
+++ b/src/Modal.tsx
@@ -89,6 +89,9 @@ export const Modal: FC = ({
styles.drawer,
{ [styles.tabbed]: tabbed },
)}
+ // Suppress the warning about there being no description; the modal
+ // has an accessible title
+ aria-describedby={undefined}
{...rest}
>
@@ -111,7 +114,9 @@ export const Modal: FC = ({
-
+ {/* Suppress the warning about there being no description; the modal
+ has an accessible title */}
+ void;
+ /**
+ * Event handler called when the value changes at the end of an interaction.
+ * Useful when you only need to capture a final value to update a backend
+ * service, or when you want to remember the last value that the user
+ * "committed" to.
+ */
+ onValueCommit?: (value: number) => void;
min: number;
max: number;
step: number;
@@ -30,6 +37,7 @@ export const Slider: FC = ({
label,
value,
onValueChange: onValueChangeProp,
+ onValueCommit: onValueCommitProp,
min,
max,
step,
@@ -39,12 +47,17 @@ export const Slider: FC = ({
([v]: number[]) => onValueChangeProp(v),
[onValueChangeProp],
);
+ const onValueCommit = useCallback(
+ ([v]: number[]) => onValueCommitProp?.(v),
+ [onValueCommitProp],
+ );
return (
{
test("renders", () => {
const { queryByRole } = render(
@@ -36,7 +29,7 @@ describe("Toast", () => {
});
test("dismisses when Esc is pressed", async () => {
- const user = userEvent.setup({ document: window.document });
+ const user = userEvent.setup();
const onDismiss = vi.fn();
render(
@@ -50,7 +43,7 @@ describe("Toast", () => {
test("dismisses when background is clicked", async () => {
const user = userEvent.setup();
const onDismiss = vi.fn();
- const { getByRole, unmount } = render(
+ const { getByRole } = render(
Hello world!
,
@@ -58,7 +51,6 @@ describe("Toast", () => {
const background = getByRole("dialog").previousSibling! as Element;
await user.click(background);
expect(onDismiss).toHaveBeenCalled();
- unmount();
});
test("dismisses itself after the specified timeout", () => {
diff --git a/src/analytics/AnalyticsNotice.tsx b/src/analytics/AnalyticsNotice.tsx
index 8df2d742..9ba78f0d 100644
--- a/src/analytics/AnalyticsNotice.tsx
+++ b/src/analytics/AnalyticsNotice.tsx
@@ -8,14 +8,20 @@ Please see LICENSE in the repository root for full details.
import { FC } from "react";
import { Trans } from "react-i18next";
-import { Link } from "../typography/Typography";
+import { ExternalLink } from "../button/Link";
export const AnalyticsNotice: FC = () => (
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{" "}
- Privacy Policy and our{" "}
- Cookie Policy.
+
+ Privacy Policy
+ {" "}
+ and our{" "}
+
+ Cookie Policy
+
+ .
);
diff --git a/src/analytics/PosthogEvents.ts b/src/analytics/PosthogEvents.ts
index e2e428b4..492d5781 100644
--- a/src/analytics/PosthogEvents.ts
+++ b/src/analytics/PosthogEvents.ts
@@ -66,6 +66,7 @@ export class CallEndedTracker {
e2eeType: E2eeType,
rtcSession: MatrixRTCSession,
sendInstantly: boolean,
+ rtcSession: MatrixRTCSession,
): void {
PosthogAnalytics.instance.trackEvent(
{
diff --git a/src/auth/LoginPage.module.css b/src/auth/LoginPage.module.css
index d42e1b5a..9bd9f3e5 100644
--- a/src/auth/LoginPage.module.css
+++ b/src/auth/LoginPage.module.css
@@ -64,15 +64,6 @@ Please see LICENSE in the repository root for full details.
flex-direction: column;
justify-content: flex-end;
align-items: center;
-}
-
-.authLinks {
margin-bottom: 100px;
font-size: var(--font-size-body);
}
-
-.authLinks a {
- color: var(--cpd-color-text-action-accent);
- text-decoration: none;
- font-weight: normal;
-}
diff --git a/src/auth/LoginPage.tsx b/src/auth/LoginPage.tsx
index 515b6c99..e4aede09 100644
--- a/src/auth/LoginPage.tsx
+++ b/src/auth/LoginPage.tsx
@@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details.
*/
import { FC, FormEvent, useCallback, useRef, useState } from "react";
-import { useHistory, useLocation, Link } from "react-router-dom";
+import { useHistory, useLocation } from "react-router-dom";
import { Trans, useTranslation } from "react-i18next";
import { Button } from "@vector-im/compound-web";
@@ -18,6 +18,7 @@ import { useInteractiveLogin } from "./useInteractiveLogin";
import { usePageTitle } from "../usePageTitle";
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
import { Config } from "../config/Config";
+import { Link } from "../button/Link";
export const LoginPage: FC = () => {
const { t } = useTranslation();
diff --git a/src/auth/RegisterPage.tsx b/src/auth/RegisterPage.tsx
index 5ee1c9eb..392f8a7a 100644
--- a/src/auth/RegisterPage.tsx
+++ b/src/auth/RegisterPage.tsx
@@ -19,7 +19,7 @@ import { captureException } from "@sentry/react";
import { sleep } from "matrix-js-sdk/src/utils";
import { Trans, useTranslation } from "react-i18next";
import { logger } from "matrix-js-sdk/src/logger";
-import { Button } from "@vector-im/compound-web";
+import { Button, Text } from "@vector-im/compound-web";
import { FieldRow, InputField, ErrorMessage } from "../input/Input";
import { useClientLegacy } from "../ClientContext";
@@ -28,10 +28,10 @@ import styles from "./LoginPage.module.css";
import Logo from "../icons/LogoLarge.svg?react";
import { LoadingView } from "../FullScreenView";
import { useRecaptcha } from "./useRecaptcha";
-import { Caption, Link } from "../typography/Typography";
import { usePageTitle } from "../usePageTitle";
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
import { Config } from "../config/Config";
+import { ExternalLink, Link } from "../button/Link";
export const RegisterPage: FC = () => {
const { t } = useTranslation();
@@ -201,24 +201,24 @@ export const RegisterPage: FC = () => {
data-testid="register_confirm_password"
/>
-
+
This site is protected by ReCAPTCHA and the Google{" "}
-
+
Privacy Policy
- {" "}
+ {" "}
and{" "}
-
+
Terms of Service
- {" "}
+ {" "}
apply.
By clicking "Register", you agree to our{" "}
-
+
End User Licensing Agreement (EULA)
-
+
-
+
{error && (
diff --git a/src/button/Link.module.css b/src/button/Link.module.css
new file mode 100644
index 00000000..6248bc40
--- /dev/null
+++ b/src/button/Link.module.css
@@ -0,0 +1,13 @@
+/*
+Copyright 2024 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only
+Please see LICENSE in the repository root for full details.
+*/
+
+.external {
+ /* By default links will be blue/purple (or whatever the user agent does), but
+ in our designs we generally want external links to be the same color as the
+ surrounding text */
+ color: inherit;
+}
diff --git a/src/button/Link.tsx b/src/button/Link.tsx
index 35c9af98..68c4dd13 100644
--- a/src/button/Link.tsx
+++ b/src/button/Link.tsx
@@ -15,10 +15,16 @@ import {
import { Link as CpdLink } from "@vector-im/compound-web";
import { useHistory } from "react-router-dom";
import { createPath, LocationDescriptor, Path } from "history";
+import classNames from "classnames";
+
+import { useLatest } from "../useLatest";
+import styles from "./Link.module.css";
export function useLink(
to: LocationDescriptor,
+ state?: unknown,
): [Path, (e: MouseEvent) => void] {
+ const latestState = useLatest(state);
const history = useHistory();
const path = useMemo(
() => (typeof to === "string" ? to : createPath(to)),
@@ -27,9 +33,9 @@ export function useLink(
const onClick = useCallback(
(e: MouseEvent) => {
e.preventDefault();
- history.push(to);
+ history.push(to, latestState.current);
},
- [history, to],
+ [history, to, latestState],
);
return [path, onClick];
@@ -38,15 +44,37 @@ export function useLink(
type Props = Omit<
ComponentPropsWithoutRef,
"href" | "onClick"
-> & { to: LocationDescriptor };
+> & { to: LocationDescriptor; state?: unknown };
/**
* A version of Compound's link component that integrates with our router setup.
+ * This is only for app-internal links.
*/
export const Link = forwardRef(function Link(
- { to, ...props },
+ { to, state, ...props },
ref,
) {
- const [path, onClick] = useLink(to);
+ const [path, onClick] = useLink(to, state);
return ;
});
+
+/**
+ * A link to an external web page, made to fit into blocks of text more subtly
+ * than the normal Compound link component.
+ */
+export const ExternalLink = forwardRef<
+ HTMLAnchorElement,
+ ComponentPropsWithoutRef<"a">
+>(function ExternalLink({ className, children, ...props }, ref) {
+ return (
+
+ {children}
+
+ );
+});
diff --git a/src/config/Config.ts b/src/config/Config.ts
index 941ffc82..972c9e0c 100644
--- a/src/config/Config.ts
+++ b/src/config/Config.ts
@@ -5,6 +5,8 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
+import { merge } from "lodash";
+
import { getUrlParams } from "../UrlParams";
import {
DEFAULT_CONFIG,
@@ -15,7 +17,7 @@ import {
export class Config {
private static internalInstance: Config | undefined;
- public static get(): ConfigOptions {
+ public static get(): ResolvedConfigOptions {
if (!this.internalInstance?.config)
throw new Error("Config instance read before config got initialized");
return this.internalInstance.config;
@@ -29,7 +31,7 @@ export class Config {
Config.internalInstance.initPromise = downloadConfig(
"../config.json",
).then((config) => {
- internalInstance.config = { ...DEFAULT_CONFIG, ...config };
+ internalInstance.config = merge({}, DEFAULT_CONFIG, config);
});
}
return Config.internalInstance.initPromise;
diff --git a/src/config/ConfigOptions.ts b/src/config/ConfigOptions.ts
index 2fd264ab..4f1ed02a 100644
--- a/src/config/ConfigOptions.ts
+++ b/src/config/ConfigOptions.ts
@@ -77,6 +77,17 @@ export interface ConfigOptions {
* A link to the end-user license agreement (EULA)
*/
eula: string;
+
+ media_devices?: {
+ /**
+ * Defines whether participants should start with audio enabled by default.
+ */
+ enable_audio?: boolean;
+ /**
+ * Defines whether participants should start with video enabled by default.
+ */
+ enable_video?: boolean;
+ };
}
// Overrides members from ConfigOptions that are always provided by the
@@ -88,6 +99,10 @@ export interface ResolvedConfigOptions extends ConfigOptions {
server_name: string;
};
};
+ media_devices: {
+ enable_audio: boolean;
+ enable_video: boolean;
+ };
}
export const DEFAULT_CONFIG: ResolvedConfigOptions = {
@@ -98,4 +113,8 @@ export const DEFAULT_CONFIG: ResolvedConfigOptions = {
},
},
eula: "https://static.element.io/legal/online-EULA.pdf",
+ media_devices: {
+ enable_audio: true,
+ enable_video: true,
+ },
};
diff --git a/src/e2ee/matrixKeyProvider.test.ts b/src/e2ee/matrixKeyProvider.test.ts
new file mode 100644
index 00000000..e5b4015f
--- /dev/null
+++ b/src/e2ee/matrixKeyProvider.test.ts
@@ -0,0 +1,72 @@
+/*
+Copyright 2024 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only
+Please see LICENSE in the repository root for full details.
+*/
+
+import { describe, expect, test, vi } from "vitest";
+import {
+ MatrixRTCSession,
+ MatrixRTCSessionEvent,
+} from "matrix-js-sdk/src/matrixrtc";
+import { KeyProviderEvent } from "livekit-client";
+
+import { MatrixKeyProvider } from "./matrixKeyProvider";
+
+function mockRTCSession(): MatrixRTCSession {
+ return {
+ on: vi.fn(),
+ off: vi.fn(),
+ reemitEncryptionKeys: vi.fn(),
+ } as unknown as MatrixRTCSession;
+}
+
+describe("matrixKeyProvider", () => {
+ test("initializes", () => {
+ const keyProvider = new MatrixKeyProvider();
+ expect(keyProvider).toBeTruthy();
+ });
+
+ test("listens for key requests and emits existing keys", () => {
+ const keyProvider = new MatrixKeyProvider();
+
+ const session = mockRTCSession();
+
+ keyProvider.setRTCSession(session);
+
+ expect(session.on).toHaveBeenCalledWith(
+ MatrixRTCSessionEvent.EncryptionKeyChanged,
+ expect.any(Function),
+ );
+ expect(session.off).not.toHaveBeenCalled();
+ });
+
+ test("stops listening when session changes", () => {
+ const keyProvider = new MatrixKeyProvider();
+
+ const session1 = mockRTCSession();
+ const session2 = mockRTCSession();
+
+ keyProvider.setRTCSession(session1);
+ expect(session1.off).not.toHaveBeenCalled();
+
+ keyProvider.setRTCSession(session2);
+ expect(session1.off).toHaveBeenCalledWith(
+ MatrixRTCSessionEvent.EncryptionKeyChanged,
+ expect.any(Function),
+ );
+ });
+
+ test("emits existing keys", () => {
+ const keyProvider = new MatrixKeyProvider();
+ const setKeyListener = vi.fn();
+ keyProvider.on(KeyProviderEvent.SetKey, setKeyListener);
+
+ const session = mockRTCSession();
+
+ keyProvider.setRTCSession(session);
+
+ expect(session.reemitEncryptionKeys).toHaveBeenCalled();
+ });
+});
diff --git a/src/e2ee/matrixKeyProvider.ts b/src/e2ee/matrixKeyProvider.ts
index d84c3684..6cbecd19 100644
--- a/src/e2ee/matrixKeyProvider.ts
+++ b/src/e2ee/matrixKeyProvider.ts
@@ -16,7 +16,7 @@ export class MatrixKeyProvider extends BaseKeyProvider {
private rtcSession?: MatrixRTCSession;
public constructor() {
- super({ ratchetWindowSize: 0 });
+ super({ ratchetWindowSize: 0, keyringSize: 256 });
}
public setRTCSession(rtcSession: MatrixRTCSession): void {
@@ -35,15 +35,8 @@ export class MatrixKeyProvider extends BaseKeyProvider {
);
// The new session could be aware of keys of which the old session wasn't,
- // so emit a key changed event.
- for (const [
- participant,
- encryptionKeys,
- ] of this.rtcSession.getEncryptionKeys()) {
- for (const [index, encryptionKey] of encryptionKeys.entries()) {
- this.onEncryptionKeyChanged(encryptionKey, index, participant);
- }
- }
+ // so emit key changed events
+ this.rtcSession.reemitEncryptionKeys();
}
private onEncryptionKeyChanged = (
diff --git a/src/home/CallList.module.css b/src/home/CallList.module.css
index 8e988d03..faa5bf2d 100644
--- a/src/home/CallList.module.css
+++ b/src/home/CallList.module.css
@@ -50,6 +50,12 @@ Please see LICENSE in the repository root for full details.
margin-bottom: 0;
}
+.callName {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
.facePile {
margin-top: 8px;
}
@@ -64,3 +70,8 @@ Please see LICENSE in the repository root for full details.
justify-content: center;
margin-bottom: 24px;
}
+
+.disabled {
+ cursor: not-allowed;
+ opacity: 0.8;
+}
diff --git a/src/home/CallList.tsx b/src/home/CallList.tsx
index aa6db6f3..72b7356a 100644
--- a/src/home/CallList.tsx
+++ b/src/home/CallList.tsx
@@ -9,12 +9,15 @@ import { Link } from "react-router-dom";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { Room } from "matrix-js-sdk/src/models/room";
-import { FC } from "react";
+import { FC, useCallback, MouseEvent, useState } from "react";
+import { useTranslation } from "react-i18next";
+import { IconButton, Text } from "@vector-im/compound-web";
+import { CloseIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
+import classNames from "classnames";
import { Avatar, Size } from "../Avatar";
import styles from "./CallList.module.css";
import { getRelativeRoomUrl } from "../utils/matrix";
-import { Body } from "../typography/Typography";
import { GroupCallRoom } from "./useGroupCallRooms";
import { useRoomEncryptionSystem } from "../e2ee/sharedKeyManagement";
@@ -55,22 +58,53 @@ interface CallTileProps {
client: MatrixClient;
}
-const CallTile: FC = ({ name, avatarUrl, room }) => {
+const CallTile: FC = ({ name, avatarUrl, room, client }) => {
+ const { t } = useTranslation();
const roomEncryptionSystem = useRoomEncryptionSystem(room.roomId);
+ const [isLeaving, setIsLeaving] = useState(false);
+
+ const onRemove = useCallback(
+ (e: MouseEvent) => {
+ e.stopPropagation();
+ e.preventDefault();
+ setIsLeaving(true);
+ client.leave(room.roomId).catch(() => setIsLeaving(false));
+ },
+ [room, client],
+ );
+
+ const body = (
+ <>
+
+