* Fix for missing client store (caused by: #2587)
* Fix interactive login with authenticated guest user.
Fix clearing storage before logging in a new account.
We need to be consistent about whether we import matrix-js-sdk from `src` or
`lib`, otherwise we get two copies of matrix-js-sdk, and everything explodes.
* Fix issues detected by Knip
Including cleaning up some unused code and dependencies, using a React hook that we unintentionally stopped using, and also adding some previously undeclared dependencies.
* Replace remaining React ARIA components with Compound components
* fix button position
* disable scrollbars to resolve overlapping button
---------
Co-authored-by: Timo <toger5@hotmail.de>
* Install Knip
* Clarify an import that was confusing Knip
* Fix issues detected by Knip
Including cleaning up some unused code and dependencies, using a React hook that we unintentionally stopped using, and also adding some previously undeclared dependencies.
* Run dead code analysis in lint script and CI
---------
Co-authored-by: Timo <toger5@hotmail.de>
* Fix coverage reporting
Codecov hasn't been working recently because Vitest doesn't report coverage by default.
* Suppress some noisy log lines
Closes https://github.com/element-hq/element-call/issues/686
* Store test files alongside source files
This way we benefit from not having to maintain the same directory structure twice, and our linters etc. will actually lint test files by default.
* Stop using Vitest globals
Vitest provides globals primarily to make the transition from Jest more smooth. But importing its functions explicitly is considered a better pattern, and we have so few tests right now that it's trivial to migrate them all.
* Remove Storybook directory
We no longer use Storybook.
* Configure Codecov
Add a coverage gate for all new changes and disable its comments.
* upgrade vitest
---------
Co-authored-by: Timo <toger5@hotmail.de>
* Stop sharing state observables when the view model is destroyed
By default, observables running with shareReplay will continue running forever even if there are no subscribers. We need to stop them when the view model is destroyed to avoid memory leaks and other unintuitive behavior.
* Hydrate the call view model in a less hacky way
This ensures that only a single view model is created per call, unlike the previous solution which would create extra view models in strict mode which it was unable to dispose of. The other way was invalid because React gives us no way to reliably dispose of a resource created in the render phase. This is essentially a memory leak fix.
* Add simple global controls to put the call in picture-in-picture mode
Our web and mobile apps (will) all support putting calls into a picture-in-picture mode. However, it'd be nice to have a way of doing this that's more explicit than a breakpoint, because PiP views could in theory get fairly large. Specifically, on mobile, we want a way to do this that can tell you whether the call is ongoing, and that works even without the widget API (because we support SPA calls in the Element X apps…)
To this end, I've created a simple global "controls" API on the window. Right now it only has methods for controlling the picture-in-picture state, but in theory we can expand it to also control mute states, which is current possible via the widget API only.
* Fix footer appearing in large PiP views
* Add a method for whether you can enter picture-in-picture mode
* Have the controls emit booleans directly
The buttons were scrolling with the view instead of always being visible in a fixed location on the tile, and the indicators were not adopting the correct width.
The code path for when all tiles can fit on screen was failing to realize that it could sometimes get by with fewer columns. This resulted in wasted space for 4 person calls at some window sizes.
We've gotten feedback that it's distracting whenever the same video is shown in two places on screen. This fixes the spotlight case by showing only the avatar of anyone who is already visible in the spotlight. It also makes sense to hide the speaking indicators in spotlight layouts, I think, because this information is redundant to the spotlight tile.
This is because our layouts for flat windows are good at adapting to both small width and small height, while our layouts for narrow windows aren't so good at adapting to a small height.
If you were the only one in the call, you could get a broken-looking view in which the local tile is shown in the spotlight, and it's also shown in the PiP. This is redundant.
Apparently Renovate doesn't really like it when you use a group: preset inside packageRules, instead of at the top level of the config. We do want to apply schedule:weekly only to the "all non-major dependencies" group though, so we need to write the group definition out by hand.
There were a couple of cases where the lack of margins after the new layout changes just looked odd. Specifically, when the header is hidden (as in embedded mode), there would be no margin at the top of the window. Also the floating tile would run directly up against the sides of the window.
Due to an oversight of mine, 2440037639 actually removed the ability to see the one-on-one layout on mobile. This restores mobile one-on-one calls to working order and also avoids showing the spotlight tile unless there are more than a few participants.
If no one had spoken yet, we were still showing the local user in the spotlight. We should instead eagerly switch to showing an arbitrary remote participant in this case.
* Add DeviceMute widget action `io.element.device_mute`.
This allows to send mute requests ("toWidget") and get the current mute state as a response.
And it will update the client about each change of mute states.
* review + better explanation
* review
* add comments
* use `useCallback`
We've concluded that this behavior is actually more distracting than it is helpful, and we want to try out what it's like to just have the importance ordering and visual cues help you find who's speaking.
We're finding that if we reorder participants based on whether their mic is muted, this just creates a lot of distracting layout shifts. People who speak are automatically promoted into the speaker category, so there's little value in additionally caring about mute state.
The Compound design tokens package is now set up to generate React components for every icon, so we no longer need to use our more error-prone method of importing the SVGs.
Ensure that they don't interfere with say, using spacebar to press a button, and also ensure that they won't do surprising things like scroll the page at the same time.
Follow-up to ea2d98179c
This took a couple of iterations to find something that works without creating update loops, but I think that by automatically informing Grid whenever a layout component is re-rendered, we'll have a much easier time ensuring that our layouts are fully reactive.
We no longer allow individual tiles to be put in full screen, because we're seeing what it's like to just stretch the spotlight tile edge-to-edge and keep the margins minimal.
Includes the mobile UX optimizations and the tweaks we've made to cut down on wasted space, but does not yet include the change to embed the spotlight tile within the grid.
Because we were hiding even the local participant during initial connection, there would be no participants, and therefore nothing to put in the spotlight. The designs don't really tell us what the connecting state should look like, so I've taken the liberty of restoring it to its former glory of showing the local participant immediately.
react-rxjs is the library we've been using to connect our React components to view models and consume observables. However, after spending some time with react-rxjs, I feel that it's a very heavy-handed solution. It requires us to sprinkle <Subscribe /> and <RemoveSubscribe /> components all throughout the code, and makes React go through an extra render cycle whenever we mount a component that binds to a view model. What I really want is a lightweight React hook that just gets the current value out of a plain observable, without any extra setup. Luckily the observable-hooks library with its useObservableEagerState hook seems to do just that—and it's more actively maintained, too!
If not set, legacy call membership state events are sent instead.
Even if set, legacy events are sent in rooms with active legacy calls.
---------
Co-authored-by: Timo <16718859+toger5@users.noreply.github.com>
Here I've implemented an MVP for the new unified grid layout, which scales smoothly up to arbitrarily many participants. It doesn't yet have a special 1:1 layout, so in spotlight mode and 1:1s, we will still fall back to the legacy grid systems.
Things that happened along the way:
- The part of VideoTile that is common to both spotlight and grid tiles, I refactored into MediaView
- VideoTile renamed to GridTile
- Added SpotlightTile for the new, glassy spotlight designs
- NewVideoGrid renamed to Grid, and refactored to be even more generic
- I extracted the media name logic into a custom React hook
- Deleted the BigGrid experiment
* Add try inner try block to the room summary fetching and only throw after fetching and a "blind join" fails.
(blind join: call room.join without knowing if the room is public)
Co-authored-by: Robin <robin@robin.town>
---------
Co-authored-by: Robin <robin@robin.town>
It thought that we were just trying to follow the latest commit on these actions, when in reality we want to follow the latest tag and pin its commit hash.
What I've tried to do here is to group most dependency updates together and put them on a weekly schedule. Some of our more sensitive dependencies such as LiveKit and Compound have been put into separate groups, so we still receive frequent updates for them.
* Load focus information from well known and use client config only as a fallback.
Signed-off-by: Timo K <toger5@hotmail.de>
Co-authored-by: Andrew Ferrazzutti <andrewf@element.io>
The message originally focused on the old feature of being able to
create a room with a custom URL. Instead, be more direct & say that the
current URL is for an inaccessible room.
This new message is based on what Element Web says for this scenario.
If you send a knock that is rejected, or your knock is accepted and you
are later removed from the room, do not automatically accept subsequent
invites to that room.
Note that the auto-join behaviour happened only if the page was not
refreshed after sending a knock.
Include:
- all rooms you are a member of
- knock rooms you've knocked on and are waiting for an invite to
- knock rooms you've been invited to in response to a knock
When visiting the page for a knock room you are already invited to, join
it right away instead of offering to knock (which will fail as long as
you remain invited to the room).
* Add joining with knock room creation flow.
Also add `WaitForInviteView` after knocking.
And appropriate error views when knock failed or gets rejected.
Signed-off-by: Timo K <toger5@hotmail.de>
* Refactor encryption information.
We had lots of enums and booleans to describe the encryption situation.
Now we only use the `EncryptionSystem` "enum" which contains the
additional information like sharedKey. (and we don't use the isRoomE2EE
function that is somewhat confusing since it checks `return widget ===
null && !room.getCanonicalAlias();` which is only indirectly related to
e2ee)
Signed-off-by: Timo K <toger5@hotmail.de>
* Update recent list.
- Don't use deprecated `groupCallEventHander` anymore (it used the old
`m.call` state event.)
- make the recent list reactive (getting removed from a call removes the
item from the list)
- support having rooms without shared secret but actual matrix
encryption in the recent list
- change the share link creation button so that we create a link with
pwd for sharedKey rooms and with `perParticipantE2EE=true` for matrix
encrypted rooms.
Signed-off-by: Timo K <toger5@hotmail.de>
* fix types
Signed-off-by: Timo K <toger5@hotmail.de>
* patch js-sdk for linter
Signed-off-by: Timo K <toger5@hotmail.de>
* ignore ts expect error
Signed-off-by: Timo K <toger5@hotmail.de>
* Fix error in widget mode.
We cannot call client.getRoomSummary in widget mode. The code path needs
to throw before reaching this call. (In general we should never call
getRoomSummary if getRoom returns a room)
Signed-off-by: Timo K <toger5@hotmail.de>
* tempDemo
Signed-off-by: Timo K <toger5@hotmail.de>
* remove wait for invite view
Signed-off-by: Timo K <toger5@hotmail.de>
* yarn i18n
Signed-off-by: Timo K <toger5@hotmail.de>
* reset back mute participant count
* add logic to show error view when getting removed
* include reason whenever someone gets removed from a call.
* fix activeRoom not beeing early enough
* fix lints
* add comment about encryption situation
Signed-off-by: Timo K <toger5@hotmail.de>
* Fix lockfile
* Use (unmerged!) RoomSummary type from the js-sdk
Temporarily change the js-sdk dependency to the PR branch that provides
that type
* review
Signed-off-by: Timo K <toger5@hotmail.de>
* review (remove participant count unknown)
Signed-off-by: Timo K <toger5@hotmail.de>
* remove error for unencrypted calls (allow intentional unencrypted calls)
Signed-off-by: Timo K <toger5@hotmail.de>
* update js-sdk
Signed-off-by: Timo K <toger5@hotmail.de>
---------
Signed-off-by: Timo K <toger5@hotmail.de>
Co-authored-by: Andrew Ferrazzutti <andrewf@element.io>
* use pull_request event rather than workflow_run
* use default for enable-pull-request-comment and enable-commit-comment
* we dont need prdetails going forward
I discovered that this hook was calling complete on the returned observable almost immediately when it gets mounted. This caused the call view model to never know when the application was switching focuses. At first I thought this was just because I forgot to move the call to complete to the effect's clean-up function, but even with that changed, React still calls the effect twice in strict mode. So, let's just remove the call entirely.
* dont register in widget mode
Signed-off-by: Timo K <toger5@hotmail.de>
* not call registerPasswordlessUser where its called in a widget.
Signed-off-by: Timo K <toger5@hotmail.de>
---------
Signed-off-by: Timo K <toger5@hotmail.de>
It's part of React Spectrum, which we're trying to avoid updating at this time because we're phasing out usage of the libraries and upgrading them is painful.
Because the author of the vitest PR used the semantic commit naming convention, Renovate now thinks our entire repo uses semantic commits and has renamed all of its PRs.
This is a start at implementing the call layouts from the new designs. I've added data types to model the contents of each possible layout, and begun implementing the business logic to produce these layouts in the call view model.
This hack was added in the early days of Element Call, back when we were doing call signaling using non-state room events, and missing part of a room's history could cause calls to fall apart. Nowadays we use state events for signaling, and all this hack is doing is making sync times unnecessarily long, so we can remove it.
If all went well, you can now find the build output under `dist` as a series of static files. These can be hosted using any web server of your choice.
If all went well, you can now find the build output under `dist` as a series of static files. These can be hosted using any web server that can be configured with custom routes (see below).
You may also wish to add a configuration file (Element Call uses the domain it's hosted on as a Homeserver URL by default,
but you can change this in the config file). This goes in `public/config.json` - you can use the sample as a starting point:
@@ -54,6 +54,38 @@ Therefore, to use a self-hosted homeserver, this is recommended to be a new serv
There are currently two different config files. `.env` holds variables that are used at build time, while `public/config.json` holds variables that are used at runtime. Documentation and default values for `public/config.json` can be found in [ConfigOptions.ts](src/config/ConfigOptions.ts).
If you're using [Synapse](https://github.com/element-hq/synapse/), you'll need to additionally add the following to `homeserver.yaml` or Element Call won't work:
```
experimental_features:
msc3266_enabled: true
```
MSC3266 allows to request a room summary of rooms you are not joined.
The summary contains the room join rules. We need that to decide if the user gets prompted with the option to knock ("ask to join"), a cannot join error or the join view.
Element Call requires a Livekit SFU behind a Livekit jwt service to work. The url to the Livekit jwt service can either be configured in the config of Element Call (fallback/legacy configuration) or be configured by your homeserver via the `.well-known`.
This is the recommended method.
The configuration is a list of Foci configs:
```json
"org.matrix.msc4143.rtc_foci":[
{
"type":"livekit",
"livekit_service_url":"https://someurl.com"
},
{
"type":"livekit",
"livekit_service_url":"https://livekit2.com"
},
{
"type":"another_foci",
"props_for_another_foci":"val"
},
]
```
## Translation
If you'd like to help translate Element Call, head over to [Localazy](https://localazy.com/p/element-call). You're also encouraged to join the [Element Translators](https://matrix.to/#/#translators:element.io) space to discuss and coordinate translation efforts.
@@ -93,11 +125,13 @@ service for development. These use a test 'secret' published in this
repository, so this must be used only for local development and
**_never be exposed to the public Internet._**
To use it, add SFU parameter in your local config `./public/config.json`:
To use it, add a SFU parameter in your local config `./public/config.json`:
(Be aware, that this is only the fallback Livekit SFU. If the homeserver
advertises one in the client well-known, this will not be used.)
To add a new translation key you can do these steps:
1. Add the new key entry to the code where the new key is used: `t("some_new_key")`
1. Run `yarn i18n` to extract the new key and update the translation files. This will add a skeleton entry to the `public/locales/en-GB/app.json` file:
```jsonc
{
...
"some_new_key": "",
...
}
```
1. Update the skeleton entry in the `public/locales/en-GB/app.json` file with the English translation:
```jsonc
{
...
"some_new_key": "Some new key",
...
}
```
## Documentation
Usage and other technical details about the project can be found here:
A few aspects of Element Call's interface can be controlled through a global API on the `window`:
-`controls.canEnterPip(): boolean` Determines whether it's possible to enter picture-in-picture mode.
-`controls.enablePip(): void` Puts the call interface into picture-in-picture mode. Throws if not in a call.
-`controls.disablePip(): void` Takes the call interface out of picture-in-picture mode, restoring it to its natural display mode. Throws if not in a call.
/<room_name_alias>?roomId=!id:domain&password=1234&<other params see below>
```
The url is split into two sections. The `https://element_call.domain/room/#` contains the app and the intend that the link brings you into a specific room (`https://call.element.io/#` would be the homepage). The fragment is used for query parameters to make sure they never get sent to the element_call.domain server. Here we have the actual matrix roomId and the password which are used to connect all participants with e2ee. This allows that `<room_name_alias>` does not need to be unique. Multiple meetings with the label weekly-sync can be created without collisions.
The url is split into two sections. The `https://element_call.domain/room/#`
contains the app and the intend that the link brings you into a specific room
(`https://call.element.io/#` would be the homepage). The fragment is used for
query parameters to make sure they never get sent to the element_call.domain
server. Here we have the actual matrix roomId and the password which are used
to connect all participants with e2ee. This allows that `<room_name_alias>` does
not need to be unique. Multiple meetings with the label weekly-sync can be created
without collisions.
- **deprecated**
```
```text
https://element_call.domain/<room_name>
```
With this format the livekit alias that will be used is the `<room_name>`. All ppl connecting to this url will end up in the same unencrypted room. This does not scale, is super unsecure (ppl could end up in the same room by accident) and it also is not really possible to support encryption.
With this format the livekit alias that will be used is the `<room_name>`.
All ppl connecting to this url will end up in the same unencrypted room.
This does not scale, is super unsecure
(ppl could end up in the same room by accident) and it also is not really
possible to support encryption.
The url parameters are spit into two categories: **general** and **widget related**.
### Widget related params
## Widget related params
**widgetId**
The id used by the widget. The presence of this parameter inplis that elemetn call will not connect to a homeserver directly and instead tries to establish postMessage communication via the `parentUrl`
The id used by the widget. The presence of this parameter implies that element
call will not connect to a homeserver directly and instead tries to establish
postMessage communication via the `parentUrl`.
```
```ts
widgetId: string | null;
```
**parentUrl**
The url used to send widget action postMessages. This should be the domain of the client
or the webview the widget is hosted in. (in case the widget is not in an Iframe but in a
dedicated webview we send the postMessages same webview the widget lives in. Filtering is
done in the widget so it ignores the messages it receives from itself)
The url used to send widget action postMessages. This should be the domain of
the client or the webview the widget is hosted in. (in case the widget is not
in an Iframe but in a dedicated webview we send the postMessages same webview
the widget lives in. Filtering is done in the widget so it ignores the messages
it receives from itself)
```
```ts
parentUrl: string | null;
```
**userId**
The user's ID (only used in matryoshka mode).
```
```ts
userId: string | null;
```
**deviceId**
The device's ID (only used in matryoshka mode).
```
```ts
deviceId: string | null;
```
**baseUrl**
The base URL of the homeserver to use for media lookups in matryoshka mode.
```
```ts
baseUrl: string | null;
```
@@ -64,14 +83,14 @@ roomId is an exception as we need the room ID in embedded (matroyska) mode, and
the room alias (or even the via params because we are not trying to join it). This
is also not validated, where it is in useRoomIdentifier().
```
```ts
roomId: string | null;
```
**confineToRoom**
Whether the app should keep the user confined to the current call/room.
"full_screen_view_description":"<0>Übermittelte Problemberichte helfen uns, Fehler zu beheben.</0>",
"full_screen_view_h1":"<0>Hoppla, etwas ist schiefgelaufen.</0>",
"fullscreen_button_label":"Vollbild",
"group_call_loader_failed_heading":"Anruf nicht gefunden",
"group_call_loader_failed_text":"Anrufe sind nun Ende-zu-Ende-verschlüsselt und müssen auf der Startseite erstellt werden. Damit stellen wir sicher, dass alle denselben Schlüssel verwenden.",
"hangup_button_label":"Anruf beenden",
@@ -80,7 +78,6 @@
"join_button":"Anruf beitreten",
"leave_button":"Zurück zu kürzlichen Anrufen"
},
"local_volume_label":"Lokale Lautstärke",
"log_in":"Anmelden",
"logging_in":"Anmelden…",
"login_auth_links":"<0>Konto erstellen</0> Oder <2>Als Gast betreten</2>",
"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 Policy</2> and our <5>Cookie Policy</5>.",
"app_selection_modal":{
@@ -41,14 +42,15 @@
"analytics":"Analytics",
"audio":"Audio",
"avatar":"Avatar",
"back":"Back",
"camera":"Camera",
"copied":"Copied!",
"display_name":"Display name",
"encrypted":"Encrypted",
"error":"Error",
"home":"Home",
"loading":"Loading…",
"microphone":"Microphone",
"next":"Next",
"options":"Options",
"password":"Password",
"profile":"Profile",
@@ -57,11 +59,22 @@
"username":"Username",
"video":"Video"
},
"crypto_version":"Crypto version: {{version}}",
"device_id":"Device ID: {{id}}",
"disconnected_banner":"Connectivity to the server has been lost.",
"full_screen_view_description":"<0>Submitting debug logs will help us track down the problem.</0>",
"group_call_loader_failed_heading":"Call not found",
"group_call_loader_failed_text":"Calls are now end-to-end encrypted and need to be created from the home page. This helps make sure everyone's using the same encryption key.",
"group_call_loader":{
"banned_body":"You have been banned from the room.",
"banned_heading":"Banned",
"call_ended_body":"You have been removed from the call.",
"call_ended_heading":"Call ended",
"failed_heading":"Failed to join",
"failed_text":"Call not found or is not accessible.",
"knock_reject_body":"The room members declined your request to join.",
"body":"Another user on this call is having an issue. In order to better diagnose these issues we'd like to collect a debug log.",
@@ -114,11 +131,11 @@
"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",
"screenshare_button_label":"Share screen",
"select_input_unset_button":"Select an option",
"settings":{
"developer_settings_label":"Developer Settings",
"developer_settings_label_description":"Expose developer settings in the settings window.",
"developer_tab_title":"Developer",
"duplicate_tiles_label":"Number of additional tile copies per participant",
"feedback_tab_body":"If you are experiencing issues or simply would like to provide some feedback, please send us a short description below.",
"feedback_tab_description_label":"Your feedback",
"feedback_tab_h4":"Submit feedback",
@@ -127,7 +144,6 @@
"feedback_tab_title":"Feedback",
"more_tab_title":"More",
"opt_in_description":"<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.",
"full_screen_view_description":"<0>Kui saadad meile vealogid, siis on lihtsam vea põhjust otsida.</0>",
"full_screen_view_h1":"<0>Ohoo, midagi on nüüd katki.</0>",
"fullscreen_button_label":"Täisekraan",
"group_call_loader_failed_heading":"Kõnet ei leidu",
"group_call_loader_failed_text":"Kõned on nüüd läbivalt krüptitud ning need pead looma kodulehelt. Sellega tagad, et kõik kasutavad samu krüptovõtmeid.",
"hangup_button_label":"Lõpeta kõne",
@@ -75,7 +73,6 @@
"join_button":"Kõnega liitumine",
"leave_button":"Tagasi hiljutiste kõnede juurde"
},
"local_volume_label":"Kohalik helitugevus",
"logging_in":"Sisselogimine …",
"login_auth_links":"<0>Loo konto</0> Või <2>Sisene külalisena</2>",
"disconnected_banner":"La connexion avec le serveur a été perdue.",
"exit_fullscreen_button_label":"Quitter le plein écran",
"full_screen_view_description":"<0>Soumettre les journaux de débogage nous aidera à déterminer le problème.</0>",
"full_screen_view_h1":"<0>Oups, quelque chose s’est mal passé.</0>",
"fullscreen_button_label":"Plein écran",
"group_call_loader_failed_heading":"Appel non trouvé",
"group_call_loader_failed_text":"Les appels sont maintenant chiffrés de bout-en-bout et doivent être créés depuis la page d’accueil. Cela permet d’être sûr que tout le monde utilise la même clé de chiffrement.",
"hangup_button_label":"Terminer l’appel",
@@ -73,7 +71,6 @@
"join_button":"Rejoindre l’appel",
"leave_button":"Revenir à l’historique des appels"
},
"local_volume_label":"Volume local",
"logging_in":"Connexion…",
"login_auth_links":"<0>Créer un compte</0> Or <2>Accès invité</2>",
"login_title":"Connexion",
@@ -131,7 +128,6 @@
"unmute_microphone_button_label":"Allumer le microphone",
"version":"Version: {{version}}",
"video_tile":{
"presenter_label":"{{displayName}} est à l’écran",
"disconnected_banner":"Koneksi ke server telah hilang.",
"exit_fullscreen_button_label":"Keluar dari layar penuh",
"full_screen_view_description":"<0>Mengirim catatan pengawakutuan akan membantu kami melacak masalahnya.</0>",
"full_screen_view_h1":"<0>Aduh, ada yang salah.</0>",
"fullscreen_button_label":"Layar penuh",
"group_call_loader_failed_heading":"Panggilan tidak ditemukan",
"group_call_loader_failed_text":"Panggilan sekarang terenkripsi secara ujung ke ujung dan harus dibuat dari laman beranda. Ini memastikan bahwa semuanya menggunakan kunci enkripsi yang sama.",
"hangup_button_label":"Akhiri panggilan",
@@ -74,7 +72,6 @@
"join_button":"Bergabung ke panggilan",
"leave_button":"Kembali ke terkini"
},
"local_volume_label":"Volume lokal",
"logging_in":"Memasuki…",
"login_auth_links":"<0>Buat akun</0> Atau <2>Akses sebagai tamu</2>",
"disconnected_banner":"La connessione al server è stata persa.",
"exit_fullscreen_button_label":"Esci da schermo intero",
"full_screen_view_description":"<0>L'invio di registri di debug ci aiuterà ad individuare il problema.</0>",
"full_screen_view_h1":"<0>Ops, qualcosa è andato storto.</0>",
"fullscreen_button_label":"Schermo intero",
"group_call_loader_failed_heading":"Chiamata non trovata",
"group_call_loader_failed_text":"Le chiamate ora sono cifrate end-to-end e devono essere create dalla pagina principale. Ciò assicura che chiunque usi la stessa chiave di crittografia.",
"hangup_button_label":"Termina chiamata",
@@ -72,7 +70,6 @@
"join_button":"Entra in chiamata",
"leave_button":"Torna ai recenti"
},
"local_volume_label":"Volume locale",
"logging_in":"Accesso…",
"login_auth_links":"<0>Crea un profilo</0> o <2>Accedi come ospite</2>",
"login_title":"Accedi",
@@ -129,7 +126,6 @@
"unmute_microphone_button_label":"Riaccendi il microfono",
"version":"Versione: {{version}}",
"video_tile":{
"presenter_label":"{{displayName}} sta presentando",
"sfu_participant_local":"Tu"
},
"waiting_for_participants":"In attesa di altri partecipanti…"
"full_screen_view_description":"<0>Wysłanie dzienników debuggowania pomoże nam ustalić przyczynę problemu.</0>",
"full_screen_view_h1":"<0>Ojej, coś poszło nie tak.</0>",
"fullscreen_button_label":"Pełny ekran",
"group_call_loader_failed_heading":"Nie znaleziono połączenia",
"group_call_loader_failed_text":"Połączenia są teraz szyfrowane end-to-end i muszą zostać utworzone ze strony głównej. Pomaga to upewnić się, że każdy korzysta z tego samego klucza szyfrującego.",
"hangup_button_label":"Zakończ połączenie",
@@ -77,7 +75,6 @@
"join_button":"Dołącz do połączenia",
"leave_button":"Wróć do ostatnie"
},
"local_volume_label":"Głośność lokalna",
"logging_in":"Logowanie…",
"login_auth_links":"<0>Utwórz konto</0> lub <2>Dołącz jako gość</2>",
"disconnected_banner":"Spojenie so serverom sa stratilo.",
"exit_fullscreen_button_label":"Ukončiť zobrazenie na celú obrazovku",
"full_screen_view_description":"<0>Odoslanie záznamov ladenia nám pomôže nájsť problém.</0>",
"full_screen_view_h1":"<0>Hups, niečo sa pokazilo.</0>",
"fullscreen_button_label":"Zobrazenie na celú obrazovku",
"group_call_loader_failed_heading":"Hovor nebol nájdený",
"group_call_loader_failed_text":"Hovory sú teraz end-to-end šifrované a je potrebné ich vytvoriť z domovskej stránky. To pomáha zabezpečiť, aby všetci používali rovnaký šifrovací kľúč.",
"hangup_button_label":"Ukončiť hovor",
@@ -75,7 +73,6 @@
"join_button":"Pripojiť sa k hovoru",
"leave_button":"Späť k nedávnym"
},
"local_volume_label":"Lokálna hlasitosť",
"logging_in":"Prihlasovanie…",
"login_auth_links":"<0>Vytvoriť konto</0> Alebo <2>Prihlásiť sa ako hosť</2>",
"disconnected_banner":"Втрачено зв'язок з сервером.",
"exit_fullscreen_button_label":"Вийти з повноекранного режиму",
"full_screen_view_description":"<0>Надсилання журналів налагодження допоможе нам виявити проблему.</0>",
"full_screen_view_h1":"<0>Йой, щось пішло не за планом.</0>",
"fullscreen_button_label":"Повноекранний режим",
"group_call_loader_failed_heading":"Виклик не знайдено",
"group_call_loader_failed_text":"Відтепер виклики захищено наскрізним шифруванням, і їх потрібно створювати з домашньої сторінки. Це допомагає переконатися, що всі користувачі використовують один і той самий ключ шифрування.",
"hangup_button_label":"Завершити виклик",
@@ -77,7 +75,6 @@
"join_button":"Приєднатися до виклику",
"leave_button":"Повернутися до недавніх"
},
"local_volume_label":"Локальна гучність",
"logging_in":"Вхід…",
"login_auth_links":"<0>Створити обліковий запис</0> або <2>Отримати доступ як гість</2>",
// If all the tiles could fit on one page, we want to ensure that they do by
// not leaving fractional rows hanging off the bottom
if(tilesPerPage===tileCount){
rows=Math.ceil(rows);
// We may now be able to fit the tiles into fewer columns
columns=Math.ceil(tileCount/rows);
}
lettileWidth=(width-(columns+1)*gap)/columns;
lettileHeight=(minHeight-(rows-1)*gap)/rows;
// Impose a minimum and maximum aspect ratio on the tiles
consttileAspectRatio=tileWidth/tileHeight;
if(tileAspectRatio>tileMaxAspectRatio)
tileWidth=tileHeight*tileMaxAspectRatio;
elseif(tileAspectRatio<tileMinAspectRatio)
tileHeight=tileWidth/tileMinAspectRatio;
return{tileWidth,tileHeight,gap,columns};
}
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.