Merge branch 'livekit' into firefox-audio-output
This commit is contained in:
@@ -37,6 +37,12 @@ module.exports = {
|
|||||||
"@typescript-eslint/promise-function-async": "error",
|
"@typescript-eslint/promise-function-async": "error",
|
||||||
"@typescript-eslint/require-await": "error",
|
"@typescript-eslint/require-await": "error",
|
||||||
"@typescript-eslint/await-thenable": "error",
|
"@typescript-eslint/await-thenable": "error",
|
||||||
|
// To help ensure that we get proper vite/rollup lazy loading (e.g. for matrix-js-sdk):
|
||||||
|
"@typescript-eslint/consistent-type-imports": [
|
||||||
|
"error",
|
||||||
|
{ fixStyle: "inline-type-imports" },
|
||||||
|
],
|
||||||
|
// To encourage good usage of RxJS:
|
||||||
"rxjs/no-exposed-subjects": "error",
|
"rxjs/no-exposed-subjects": "error",
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
|
|||||||
4
.github/workflows/docker.yaml
vendored
4
.github/workflows/docker.yaml
vendored
@@ -42,7 +42,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Extract metadata (tags, labels) for Docker
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1
|
uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5.6.1
|
||||||
with:
|
with:
|
||||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
tags: ${{ inputs.docker_tags}}
|
tags: ${{ inputs.docker_tags}}
|
||||||
@@ -51,7 +51,7 @@ jobs:
|
|||||||
uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1
|
uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1
|
||||||
|
|
||||||
- name: Build and push Docker image
|
- name: Build and push Docker image
|
||||||
uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0
|
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
|
|||||||
2
.github/workflows/element-call.yaml
vendored
2
.github/workflows/element-call.yaml
vendored
@@ -28,7 +28,7 @@ jobs:
|
|||||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
|
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
|
||||||
with:
|
with:
|
||||||
cache: "yarn"
|
cache: "yarn"
|
||||||
node-version: "lts/*"
|
node-version-file: ".node-version"
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: "yarn install"
|
run: "yarn install"
|
||||||
- name: Build
|
- name: Build
|
||||||
|
|||||||
2
.github/workflows/lint.yaml
vendored
2
.github/workflows/lint.yaml
vendored
@@ -12,7 +12,7 @@ jobs:
|
|||||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
|
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
|
||||||
with:
|
with:
|
||||||
cache: "yarn"
|
cache: "yarn"
|
||||||
node-version: "lts/*"
|
node-version-file: ".node-version"
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: "yarn install"
|
run: "yarn install"
|
||||||
- name: Prettier
|
- name: Prettier
|
||||||
|
|||||||
2
.github/workflows/test.yaml
vendored
2
.github/workflows/test.yaml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
|
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
|
||||||
with:
|
with:
|
||||||
cache: "yarn"
|
cache: "yarn"
|
||||||
node-version: "lts/*"
|
node-version-file: ".node-version"
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: "yarn install"
|
run: "yarn install"
|
||||||
- name: Vitest
|
- name: Vitest
|
||||||
|
|||||||
2
.github/workflows/translations-download.yaml
vendored
2
.github/workflows/translations-download.yaml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
|
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
|
||||||
with:
|
with:
|
||||||
cache: "yarn"
|
cache: "yarn"
|
||||||
node-version: "lts/*"
|
node-version-file: ".node-version"
|
||||||
|
|
||||||
- name: Install Deps
|
- name: Install Deps
|
||||||
run: "yarn install --frozen-lockfile"
|
run: "yarn install --frozen-lockfile"
|
||||||
|
|||||||
1
.node-version
Normal file
1
.node-version
Normal file
@@ -0,0 +1 @@
|
|||||||
|
22
|
||||||
11
README.md
11
README.md
@@ -92,6 +92,9 @@ work:
|
|||||||
experimental_features:
|
experimental_features:
|
||||||
# MSC3266: Room summary API. Used for knocking over federation
|
# MSC3266: Room summary API. Used for knocking over federation
|
||||||
msc3266_enabled: true
|
msc3266_enabled: true
|
||||||
|
# MSC4222 needed for syncv2 state_after. This allow clients to
|
||||||
|
# correctly track the state of the room.
|
||||||
|
msc4222_enabled: true
|
||||||
|
|
||||||
# The maximum allowed duration by which sent events can be delayed, as
|
# The maximum allowed duration by which sent events can be delayed, as
|
||||||
# per MSC4140.
|
# per MSC4140.
|
||||||
@@ -109,6 +112,10 @@ summary contains the room join rules. We need that to decide if the user gets
|
|||||||
prompted with the option to knock ("Request to join call"), a cannot join error or the
|
prompted with the option to knock ("Request to join call"), a cannot join error or the
|
||||||
join view.
|
join view.
|
||||||
|
|
||||||
|
MSC4222 allow clients to opt-in to a change of the sync v2 API that allows them
|
||||||
|
to correctly track the state of the room. This is required by Element Call to
|
||||||
|
track room state reliably.
|
||||||
|
|
||||||
Element Call requires a Livekit SFU alongside a [Livekit JWT
|
Element Call requires a Livekit SFU alongside a [Livekit JWT
|
||||||
service](https://github.com/element-hq/lk-jwt-service) to work. The url to the
|
service](https://github.com/element-hq/lk-jwt-service) to work. The url to the
|
||||||
Livekit JWT service can either be configured in the config of Element Call
|
Livekit JWT service can either be configured in the config of Element Call
|
||||||
@@ -213,7 +220,7 @@ 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. 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
|
1. Run `yarn i18n` to extract the new key and update the translation files. This
|
||||||
will add a skeleton entry to the `locales/en-GB/app.json` file:
|
will add a skeleton entry to the `locales/en/app.json` file:
|
||||||
```jsonc
|
```jsonc
|
||||||
{
|
{
|
||||||
...
|
...
|
||||||
@@ -221,7 +228,7 @@ To add a new translation key you can do these steps:
|
|||||||
...
|
...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
1. Update the skeleton entry in the `locales/en-GB/app.json` file with
|
1. Update the skeleton entry in the `locales/en/app.json` file with
|
||||||
the English translation:
|
the English translation:
|
||||||
|
|
||||||
```jsonc
|
```jsonc
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ trusted_key_servers:
|
|||||||
experimental_features:
|
experimental_features:
|
||||||
# MSC3266: Room summary API. Used for knocking over federation
|
# MSC3266: Room summary API. Used for knocking over federation
|
||||||
msc3266_enabled: true
|
msc3266_enabled: true
|
||||||
|
# MSC4222 needed for syncv2 state_after. This allow clients to
|
||||||
|
# correctly track the state of the room.
|
||||||
|
msc4222_enabled: true
|
||||||
|
|
||||||
# The maximum allowed duration by which sent events can be delayed, as
|
# The maximum allowed duration by which sent events can be delayed, as
|
||||||
# per MSC4140. Must be a positive value if set. Defaults to no
|
# per MSC4140. Must be a positive value if set. Defaults to no
|
||||||
|
|||||||
@@ -17,5 +17,9 @@
|
|||||||
},
|
},
|
||||||
"rageshake": {
|
"rageshake": {
|
||||||
"submit_url": "https://element.io/bugreports/submit"
|
"submit_url": "https://element.io/bugreports/submit"
|
||||||
|
},
|
||||||
|
"sentry": {
|
||||||
|
"environment": "netlify-pr-preview",
|
||||||
|
"DSN": "https://b1e328d49be3402ba96101338989fb35@sentry.tools.element.io/41"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
<Location "/">
|
<Location "/">
|
||||||
# disable cache entriely by default (apart from Etag which is accurate enough)
|
# disable cache entriely by default (apart from Etag which is accurate enough)
|
||||||
Header add Cache-Control "private no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0"
|
Header add Cache-Control "private, no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0"
|
||||||
CacheDisable on
|
CacheDisable on
|
||||||
ExpiresActive off
|
ExpiresActive off
|
||||||
|
|
||||||
|
|||||||
@@ -25,230 +25,38 @@ There are two formats for Element Call urls.
|
|||||||
```
|
```
|
||||||
|
|
||||||
With this format the livekit alias that will be used is the `<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.
|
All people connecting to this URL will end up in the same unencrypted room.
|
||||||
This does not scale, is super unsecure
|
This does not scale, is super unsecure
|
||||||
(ppl could end up in the same room by accident) and it also is not really
|
(people could end up in the same room by accident) and it also is not really
|
||||||
possible to support encryption.
|
possible to support encryption.
|
||||||
The url parameters are spit into two categories: **general** and **widget related**.
|
|
||||||
|
|
||||||
## Widget related params
|
## Parameters
|
||||||
|
|
||||||
**widgetId**
|
| Name | Values | Required for widget | Required for SPA | Description |
|
||||||
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
|
| `allowIceFallback` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Allows use of fallback STUN servers for ICE if the user's homeserver doesn’t provide any. |
|
||||||
postMessage communication via the `parentUrl`.
|
| `analyticsID` | Posthog analytics ID | No | No | Available only with user's consent for sharing telemetry in Element Web. |
|
||||||
|
| `appPrompt` | `true` or `false` | No, defaults to `true` | No, defaults to `true` | Prompts the user to launch the native mobile app upon entering a room, applicable only on Android and iOS, and must be enabled in config. |
|
||||||
```ts
|
| `baseUrl` | | Yes | Not applicable | The base URL of the homeserver to use for media lookups. |
|
||||||
widgetId: string | null;
|
| `confineToRoom` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Keeps the user confined to the current call/room. |
|
||||||
```
|
| `deviceId` | Matrix device ID | Yes | Not applicable | The Matrix device ID for the widget host. |
|
||||||
|
| `displayName` | | No | No | Display name used for auto-registration. |
|
||||||
**parentUrl**
|
| `enableE2EE` (deprecated) | `true` or `false` | No, defaults to `true` | No, defaults to `true` | Legacy flag to enable end-to-end encryption, not used in the `livekit` branch. |
|
||||||
The url used to send widget action postMessages. This should be the domain of
|
| `fontScale` | A decimal number such as `0.9` | No, defaults to `1.0` | No, defaults to `1.0` | Factor by which to scale the interface's font size. |
|
||||||
the client or the webview the widget is hosted in. (in case the widget is not
|
| `fonts` | | No | No | Defines the font(s) used by the interface. Multiple font parameters can be specified: `?font=font-one&font=font-two...`. |
|
||||||
in an Iframe but in a dedicated webview we send the postMessages same webview
|
| `hideHeader` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Hides the room header when in a call. |
|
||||||
the widget lives in. Filtering is done in the widget so it ignores the messages
|
| `hideScreensharing` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Hides the screen-sharing button. |
|
||||||
it receives from itself)
|
| `homeserver` | | Not applicable | No | Homeserver for registering a new (guest) user, configures non-default guest user server when creating a spa link. |
|
||||||
|
| `lang` | [BCP 47](https://www.rfc-editor.org/info/bcp47) code | No | No | The language the app should use. |
|
||||||
```ts
|
| `parentUrl` | | Yes | Not applicable | 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) |
|
||||||
parentUrl: string | null;
|
| `password` | | No | No | E2EE password when using a shared secret. (For individual sender keys in embedded mode this is not required.) |
|
||||||
```
|
| `perParticipantE2EE` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Enables per participant encryption with Keys exchanged over encrypted matrix room messages. |
|
||||||
|
| `preload` | `true` or `false` | No, defaults to `false` | Not applicable | Pauses app before joining a call until an `io.element.join` widget action is seen, allowing preloading. |
|
||||||
**userId**
|
| `returnToLobby` | `true` or `false` | No, defaults to `false` | Not applicable | Displays the lobby in widget mode after leaving a call; shows a blank page if set to `false`. Useful for video rooms. |
|
||||||
The user's ID (only used in matryoshka mode).
|
| `roomId` | [Matrix Room ID](https://spec.matrix.org/v1.12/appendices/#room-ids) | Yes | No | Anything about what room we're pointed to should be from useRoomIdentifier which parses the path and resolves alias with respect to the default server name, however roomId is an exception as we need the room ID in embedded widget mode, and not 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()`. |
|
||||||
|
| `showControls` | `true` or `false` | No, defaults to `true` | No, defaults to `true` | Displays controls like mute, screen-share, invite, and hangup buttons during a call. |
|
||||||
```ts
|
| `skipLobby` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Skips the lobby to join a call directly, can be combined with preload in widget. When `true` the audio and video inputs will be muted by default. (This means there currently is no way to start without muted video if one wants to skip the lobby. Also not in widget mode.) |
|
||||||
userId: string | null;
|
| `theme` | One of: `light`, `dark`, `light-high-contrast`, `dark-high-contrast` | No, defaults to `dark` | No, defaults to `dark` | UI theme to use. |
|
||||||
```
|
| `userId` | [Matrix User Identifier](https://spec.matrix.org/v1.12/appendices/#user-identifiers) | Yes | Not applicable | The Matrix user ID. |
|
||||||
|
| `viaServers` | Comma separated list of [Matrix Server Names](https://spec.matrix.org/v1.12/appendices/#server-name) | Not applicable | No | Homeserver for joining a room, non-empty value required for rooms not on the user’s default homeserver. |
|
||||||
**deviceId**
|
| `widgetId` | [MSC2774](https://github.com/matrix-org/matrix-spec-proposals/pull/2774) format widget ID | Yes | Not applicable | 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`. |
|
||||||
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;
|
|
||||||
```
|
|
||||||
|
|
||||||
### General url parameters
|
|
||||||
|
|
||||||
**roomId**
|
|
||||||
Anything about what room we're pointed to should be from useRoomIdentifier which
|
|
||||||
parses the path and resolves alias with respect to the default server name, however
|
|
||||||
roomId is an exception as we need the room ID in embedded (matroyska) mode, and not
|
|
||||||
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.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
confineToRoom: boolean; (default: false)
|
|
||||||
```
|
|
||||||
|
|
||||||
**appPrompt**
|
|
||||||
Whether upon entering a room, the user should be prompted to launch the
|
|
||||||
native mobile app. (Affects only Android and iOS.)
|
|
||||||
|
|
||||||
The app prompt must also be enabled in the config for this to take effect.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
appPrompt: boolean; (default: true)
|
|
||||||
```
|
|
||||||
|
|
||||||
**preload**
|
|
||||||
Whether the app should pause before joining the call until it sees an
|
|
||||||
io.element.join widget action, allowing it to be preloaded.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
preload: boolean; (default: false)
|
|
||||||
```
|
|
||||||
|
|
||||||
**hideHeader**
|
|
||||||
Whether to hide the room header when in a call.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
hideHeader: boolean; (default: false)
|
|
||||||
```
|
|
||||||
|
|
||||||
**showControls**
|
|
||||||
Whether to show the buttons to mute, screen-share, invite, hangup are shown
|
|
||||||
when in a call.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
showControls: boolean; (default: true)
|
|
||||||
```
|
|
||||||
|
|
||||||
**hideScreensharing**
|
|
||||||
Whether to hide the screen-sharing button.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
hideScreensharing: boolean; (default: false)
|
|
||||||
```
|
|
||||||
|
|
||||||
**enableE2EE** (Deprecated)
|
|
||||||
Whether to use end-to-end encryption. This is a legacy flag for the full mesh branch.
|
|
||||||
It is not used on the livekit branch and has no impact there!
|
|
||||||
|
|
||||||
```ts
|
|
||||||
enableE2EE: boolean; (default: true)
|
|
||||||
```
|
|
||||||
|
|
||||||
**perParticipantE2EE**
|
|
||||||
Whether to use per participant encryption.
|
|
||||||
Keys will be exchanged over encrypted matrix room messages.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
perParticipantE2EE: boolean; (default: false)
|
|
||||||
```
|
|
||||||
|
|
||||||
**password**
|
|
||||||
E2EE password when using a shared secret.
|
|
||||||
(For individual sender keys in embedded mode this is not required.)
|
|
||||||
|
|
||||||
```ts
|
|
||||||
password: string | null;
|
|
||||||
```
|
|
||||||
|
|
||||||
**displayName**
|
|
||||||
The display name to use for auto-registration.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
displayName: string | null;
|
|
||||||
```
|
|
||||||
|
|
||||||
**lang**
|
|
||||||
The BCP 47 code of the language the app should use.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
lang: string | null;
|
|
||||||
```
|
|
||||||
|
|
||||||
**fonts**
|
|
||||||
The font/fonts which the interface should use.
|
|
||||||
There can be multiple font url parameters: `?font=font-one&font=font-two...`
|
|
||||||
|
|
||||||
```ts
|
|
||||||
font: string;
|
|
||||||
font: string;
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
**fontScale**
|
|
||||||
The factor by which to scale the interface's font size.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
fontScale: number | null;
|
|
||||||
```
|
|
||||||
|
|
||||||
**analyticsID**
|
|
||||||
The Posthog analytics ID. It is only available if the user has given consent for
|
|
||||||
sharing telemetry in element web.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
analyticsID: string | null;
|
|
||||||
```
|
|
||||||
|
|
||||||
**allowIceFallback**
|
|
||||||
Whether the app is allowed to use fallback STUN servers for ICE in case the
|
|
||||||
user's homeserver doesn't provide any.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
allowIceFallback: boolean; (default: false)
|
|
||||||
```
|
|
||||||
|
|
||||||
**skipLobby**
|
|
||||||
Setting this flag skips the lobby and brings you in the call directly.
|
|
||||||
In the widget this can be combined with preload to pass the device settings
|
|
||||||
with the join widget action.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
skipLobby: boolean; (default: false)
|
|
||||||
```
|
|
||||||
|
|
||||||
**returnToLobby**
|
|
||||||
Setting this flag makes element call show the lobby in widget mode after leaving
|
|
||||||
a call.
|
|
||||||
This is useful for video rooms.
|
|
||||||
If set to false, the widget will show a blank page after leaving the call.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
returnToLobby: boolean; (default: false)
|
|
||||||
```
|
|
||||||
|
|
||||||
**theme**
|
|
||||||
The theme to use for element call.
|
|
||||||
can be "light", "dark", "light-high-contrast" or "dark-high-contrast".
|
|
||||||
If not set element call will use the dark theme.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
theme: string | null;
|
|
||||||
```
|
|
||||||
|
|
||||||
**viaServers**
|
|
||||||
This defines the homeserver that is going to be used when joining a room.
|
|
||||||
It has to be set to a non default value for links to rooms
|
|
||||||
that are not on the default homeserver,
|
|
||||||
that is in use for the current user.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
viaServers: string; (default: undefined)
|
|
||||||
```
|
|
||||||
|
|
||||||
**homeserver**
|
|
||||||
This defines the homeserver that is going to be used when registering
|
|
||||||
a new (guest) user.
|
|
||||||
This can be user to configure a non default guest user server when
|
|
||||||
creating a spa link.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
homeserver: string; (default: undefined)
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export default {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
locales: ["en-GB"],
|
locales: ["en"],
|
||||||
output: "locales/$LOCALE/$NAMESPACE.json",
|
output: "locales/$LOCALE/$NAMESPACE.json",
|
||||||
input: ["src/**/*.{ts,tsx}"],
|
input: ["src/**/*.{ts,tsx}"],
|
||||||
sort: true,
|
sort: true,
|
||||||
|
|||||||
@@ -7,13 +7,13 @@
|
|||||||
"features": ["plural_postfix_us", "filter_untranslated"],
|
"features": ["plural_postfix_us", "filter_untranslated"],
|
||||||
"files": [
|
"files": [
|
||||||
{
|
{
|
||||||
"pattern": "locales/en-GB/*.json",
|
"pattern": "locales/en/*.json",
|
||||||
"lang": "inherited"
|
"lang": "inherited"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"group": "existing",
|
"group": "existing",
|
||||||
"pattern": "locales/*/*.json",
|
"pattern": "locales/*/*.json",
|
||||||
"excludes": ["locales/en-GB/*.json"],
|
"excludes": ["locales/en/*.json"],
|
||||||
"lang": "${autodetectLang}"
|
"lang": "${autodetectLang}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -25,9 +25,6 @@
|
|||||||
"output": "locales/${langLsrDash}/${file}"
|
"output": "locales/${langLsrDash}/${file}"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"includeSourceLang": "${includeSourceLang|false}",
|
"includeSourceLang": "${includeSourceLang|false}"
|
||||||
"langAliases": {
|
|
||||||
"en": "en_GB"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,8 +68,13 @@
|
|||||||
"username": "Benutzername",
|
"username": "Benutzername",
|
||||||
"video": "Video"
|
"video": "Video"
|
||||||
},
|
},
|
||||||
"crypto_version": "Krypto-Version:{{version}}",
|
"developer_mode": {
|
||||||
"device_id": "Geräte-ID: {{id}}",
|
"crypto_version": "Krypto-Version: {{version}}",
|
||||||
|
"device_id": "Geräte-ID: {{id}}",
|
||||||
|
"duplicate_tiles_label": "Anzahl zusätzlicher Kachelkopien pro Teilnehmer",
|
||||||
|
"hostname": "Hostname: {{hostname}}",
|
||||||
|
"matrix_id": "Matrix-ID: {{id}}"
|
||||||
|
},
|
||||||
"disconnected_banner": "Die Verbindung zum Server wurde getrennt.",
|
"disconnected_banner": "Die Verbindung zum Server wurde getrennt.",
|
||||||
"full_screen_view_description": "<0>Übermittelte Problemberichte helfen uns, Fehler zu beheben.</0>",
|
"full_screen_view_description": "<0>Übermittelte Problemberichte helfen uns, Fehler zu beheben.</0>",
|
||||||
"full_screen_view_h1": "<0>Hoppla, etwas ist schiefgelaufen.</0>",
|
"full_screen_view_h1": "<0>Hoppla, etwas ist schiefgelaufen.</0>",
|
||||||
@@ -111,7 +116,6 @@
|
|||||||
"login_auth_links_prompt": "Noch nicht registriert?",
|
"login_auth_links_prompt": "Noch nicht registriert?",
|
||||||
"login_subheading": "Weiter zu Element",
|
"login_subheading": "Weiter zu Element",
|
||||||
"login_title": "Anmelden",
|
"login_title": "Anmelden",
|
||||||
"matrix_id": "Matrix-ID: {{id}}",
|
|
||||||
"microphone_off": "Mikrofon aus",
|
"microphone_off": "Mikrofon aus",
|
||||||
"microphone_on": "Mikrofon an",
|
"microphone_on": "Mikrofon an",
|
||||||
"mute_microphone_button_label": "Mikrofon stumm schalten",
|
"mute_microphone_button_label": "Mikrofon stumm schalten",
|
||||||
@@ -143,13 +147,11 @@
|
|||||||
"screenshare_button_label": "Bildschirm teilen",
|
"screenshare_button_label": "Bildschirm teilen",
|
||||||
"settings": {
|
"settings": {
|
||||||
"audio_tab": {
|
"audio_tab": {
|
||||||
"effect_volume_description": "Lautstärke anpassen, mit der Reaktionen und Handmeldungen abgespielt werden",
|
|
||||||
"effect_volume_label": "Lautstärke der Soundeffekte"
|
"effect_volume_label": "Lautstärke der Soundeffekte"
|
||||||
},
|
},
|
||||||
"developer_settings_label": "Entwicklereinstellungen",
|
"developer_settings_label": "Entwicklereinstellungen",
|
||||||
"developer_settings_label_description": "Zeige die Entwicklereinstellungen im Einstellungsfenster.",
|
"developer_settings_label_description": "Zeige die Entwicklereinstellungen im Einstellungsfenster.",
|
||||||
"developer_tab_title": "Entwickler",
|
"developer_tab_title": "Entwickler",
|
||||||
"duplicate_tiles_label": "Anzahl zusätzlicher Kachelkopien pro Teilnehmer",
|
|
||||||
"feedback_tab_body": "Falls du auf Probleme stößt oder einfach nur eine Rückmeldung geben möchtest, sende uns bitte eine kurze Beschreibung.",
|
"feedback_tab_body": "Falls du auf Probleme stößt oder einfach nur eine Rückmeldung geben möchtest, sende uns bitte eine kurze Beschreibung.",
|
||||||
"feedback_tab_description_label": "Deine Rückmeldung",
|
"feedback_tab_description_label": "Deine Rückmeldung",
|
||||||
"feedback_tab_h4": "Rückmeldung geben",
|
"feedback_tab_h4": "Rückmeldung geben",
|
||||||
@@ -159,16 +161,16 @@
|
|||||||
"more_tab_title": "Mehr",
|
"more_tab_title": "Mehr",
|
||||||
"opt_in_description": "<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.",
|
"opt_in_description": "<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.",
|
||||||
"preferences_tab": {
|
"preferences_tab": {
|
||||||
|
"introduction": "Hier können zusätzliche Optionen für individuelle Anforderungen eingestellt werden",
|
||||||
"reactions_play_sound_description": "Einen Soundeffekt abspielen, wenn jemand eine Reaktion sendet",
|
"reactions_play_sound_description": "Einen Soundeffekt abspielen, wenn jemand eine Reaktion sendet",
|
||||||
"reactions_play_sound_label": "Reaktionstöne abspielen",
|
"reactions_play_sound_label": "Reaktionstöne abspielen",
|
||||||
"reactions_show_description": "Zeige eine Animation, wenn jemand eine Reaktion sendet.",
|
"reactions_show_description": "Zeige eine Animation, wenn jemand eine Reaktion sendet.",
|
||||||
"reactions_show_label": "Reaktionen anzeigen",
|
"reactions_show_label": "Reaktionen anzeigen",
|
||||||
"reactions_title": "Reaktionen"
|
"reactions_title": "Reaktionen",
|
||||||
|
"show_hand_raised_timer_description": "Einen Timer zur Handmeldung anzeigen",
|
||||||
|
"show_hand_raised_timer_label": "Dauer der Handmeldung anzeigen"
|
||||||
},
|
},
|
||||||
"preferences_tab_body": "Hier können zusätzliche Optionen für individuelle Anforderungen eingestellt werden",
|
|
||||||
"preferences_tab_h4": "Einstellungen",
|
"preferences_tab_h4": "Einstellungen",
|
||||||
"preferences_tab_show_hand_raised_timer_description": "Einen Timer zur Handmeldung anzeigen",
|
|
||||||
"preferences_tab_show_hand_raised_timer_label": "Dauer der Handmeldung anzeigen",
|
|
||||||
"speaker_device_selection_label": "Lautsprecher"
|
"speaker_device_selection_label": "Lautsprecher"
|
||||||
},
|
},
|
||||||
"star_rating_input_label_one": "{{count}} Stern",
|
"star_rating_input_label_one": "{{count}} Stern",
|
||||||
@@ -191,6 +193,7 @@
|
|||||||
"expand": "Erweitern",
|
"expand": "Erweitern",
|
||||||
"mute_for_me": "Für mich stumm schalten",
|
"mute_for_me": "Für mich stumm schalten",
|
||||||
"muted_for_me": "Für mich stumm geschaltet",
|
"muted_for_me": "Für mich stumm geschaltet",
|
||||||
"volume": "Lautstärke"
|
"volume": "Lautstärke",
|
||||||
|
"waiting_for_media": "Warten auf Medien..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,8 +66,15 @@
|
|||||||
"username": "Username",
|
"username": "Username",
|
||||||
"video": "Video"
|
"video": "Video"
|
||||||
},
|
},
|
||||||
"crypto_version": "Crypto version: {{version}}",
|
"developer_mode": {
|
||||||
"device_id": "Device ID: {{id}}",
|
"crypto_version": "Crypto version: {{version}}",
|
||||||
|
"debug_tile_layout_label": "Debug tile layout",
|
||||||
|
"device_id": "Device ID: {{id}}",
|
||||||
|
"duplicate_tiles_label": "Number of additional tile copies per participant",
|
||||||
|
"hostname": "Hostname: {{hostname}}",
|
||||||
|
"matrix_id": "Matrix ID: {{id}}",
|
||||||
|
"show_non_member_tiles": "Show tiles for non-member media"
|
||||||
|
},
|
||||||
"disconnected_banner": "Connectivity to the server has been lost.",
|
"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>",
|
"full_screen_view_description": "<0>Submitting debug logs will help us track down the problem.</0>",
|
||||||
"full_screen_view_h1": "<0>Oops, something's gone wrong.</0>",
|
"full_screen_view_h1": "<0>Oops, something's gone wrong.</0>",
|
||||||
@@ -109,7 +116,6 @@
|
|||||||
"login_auth_links_prompt": "Not registered yet?",
|
"login_auth_links_prompt": "Not registered yet?",
|
||||||
"login_subheading": "To continue to Element",
|
"login_subheading": "To continue to Element",
|
||||||
"login_title": "Login",
|
"login_title": "Login",
|
||||||
"matrix_id": "Matrix ID: {{id}}",
|
|
||||||
"microphone_off": "Microphone off",
|
"microphone_off": "Microphone off",
|
||||||
"microphone_on": "Microphone on",
|
"microphone_on": "Microphone on",
|
||||||
"mute_microphone_button_label": "Mute microphone",
|
"mute_microphone_button_label": "Mute microphone",
|
||||||
@@ -141,11 +147,9 @@
|
|||||||
"screenshare_button_label": "Share screen",
|
"screenshare_button_label": "Share screen",
|
||||||
"settings": {
|
"settings": {
|
||||||
"audio_tab": {
|
"audio_tab": {
|
||||||
"effect_volume_description": "Adjust the volume at which reactions and hand raised effects play",
|
"effect_volume_description": "Adjust the volume at which reactions and hand raised effects play.",
|
||||||
"effect_volume_label": "Sound effect volume"
|
"effect_volume_label": "Sound effect volume"
|
||||||
},
|
},
|
||||||
"developer_settings_label": "Developer Settings",
|
|
||||||
"developer_settings_label_description": "Expose developer settings in the settings window.",
|
|
||||||
"developer_tab_title": "Developer",
|
"developer_tab_title": "Developer",
|
||||||
"devices": {
|
"devices": {
|
||||||
"camera": "Camera",
|
"camera": "Camera",
|
||||||
@@ -156,28 +160,26 @@
|
|||||||
"speaker": "Speaker",
|
"speaker": "Speaker",
|
||||||
"speaker_numbered": "Speaker {{n}}"
|
"speaker_numbered": "Speaker {{n}}"
|
||||||
},
|
},
|
||||||
"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_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_description_label": "Your feedback",
|
||||||
"feedback_tab_h4": "Submit feedback",
|
"feedback_tab_h4": "Submit feedback",
|
||||||
"feedback_tab_send_logs_label": "Include debug logs",
|
"feedback_tab_send_logs_label": "Include debug logs",
|
||||||
"feedback_tab_thank_you": "Thanks, we received your feedback!",
|
"feedback_tab_thank_you": "Thanks, we received your feedback!",
|
||||||
"feedback_tab_title": "Feedback",
|
"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.",
|
"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.",
|
||||||
"preferences_tab": {
|
"preferences_tab": {
|
||||||
|
"developer_mode_label": "Developer mode",
|
||||||
|
"developer_mode_label_description": "Enable developer mode and show developer settings tab.",
|
||||||
|
"introduction": "Here you can configure extra options for an improved experience.",
|
||||||
"reactions_play_sound_description": "Play a sound effect when anyone sends a reaction into a call.",
|
"reactions_play_sound_description": "Play a sound effect when anyone sends a reaction into a call.",
|
||||||
"reactions_play_sound_label": "Play reaction sounds",
|
"reactions_play_sound_label": "Play reaction sounds",
|
||||||
"reactions_show_description": "Show an animation when anyone sends a reaction.",
|
"reactions_show_description": "Show an animation when anyone sends a reaction.",
|
||||||
"reactions_show_label": "Show reactions",
|
"reactions_show_label": "Show reactions",
|
||||||
"reactions_title": "Reactions"
|
"show_hand_raised_timer_description": "Show a timer when a participant raises their hand",
|
||||||
},
|
"show_hand_raised_timer_label": "Show hand raise duration"
|
||||||
"preferences_tab_body": "Here you can configure extra options for an improved experience",
|
}
|
||||||
"preferences_tab_h4": "Preferences",
|
|
||||||
"preferences_tab_show_hand_raised_timer_description": "Show a timer when a participant raises their hand",
|
|
||||||
"preferences_tab_show_hand_raised_timer_label": "Show hand raise duration"
|
|
||||||
},
|
},
|
||||||
"star_rating_input_label_one": "{{count}} stars",
|
"star_rating_input_label_one": "{{count}} star",
|
||||||
"star_rating_input_label_other": "{{count}} stars",
|
"star_rating_input_label_other": "{{count}} stars",
|
||||||
"start_new_call": "Start new call",
|
"start_new_call": "Start new call",
|
||||||
"start_video_button_label": "Start video",
|
"start_video_button_label": "Start video",
|
||||||
@@ -197,6 +199,7 @@
|
|||||||
"expand": "Expand",
|
"expand": "Expand",
|
||||||
"mute_for_me": "Mute for me",
|
"mute_for_me": "Mute for me",
|
||||||
"muted_for_me": "Muted for me",
|
"muted_for_me": "Muted for me",
|
||||||
"volume": "Volume"
|
"volume": "Volume",
|
||||||
|
"waiting_for_media": "Waiting for media..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
195
locales/ro/app.json
Normal file
195
locales/ro/app.json
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
{
|
||||||
|
"a11y": {
|
||||||
|
"user_menu": "Meniul utilizatorului"
|
||||||
|
},
|
||||||
|
"action": {
|
||||||
|
"close": "Închide",
|
||||||
|
"copy_link": "Copiază linkul",
|
||||||
|
"edit": "Editare",
|
||||||
|
"go": "Du-te",
|
||||||
|
"invite": "Invită",
|
||||||
|
"lower_hand": "Mâna inferioară",
|
||||||
|
"no": "No",
|
||||||
|
"pick_reaction": "Alegeți reacția",
|
||||||
|
"raise_hand": "Ridicați mâna",
|
||||||
|
"register": "Inregistrare",
|
||||||
|
"remove": "elimina",
|
||||||
|
"show_less": "Arată mai puțin",
|
||||||
|
"show_more": "Arată mai mult",
|
||||||
|
"sign_in": "Autentificare",
|
||||||
|
"sign_out": "Sign out",
|
||||||
|
"submit": "Trimiteți",
|
||||||
|
"upload_file": "Încărcați fișierul"
|
||||||
|
},
|
||||||
|
"analytics_notice": "Prin participarea la această versiune beta, sunteți de acord cu colectarea de date anonime, pe care le folosim pentru a îmbunătăți produsul. Puteți găsi mai multe informații despre datele pe care le urmărim în Politica noastră de <2> confidențialitate </2> și Politica noastră <6> privind cookie-urile</6>.",
|
||||||
|
"app_selection_modal": {
|
||||||
|
"continue_in_browser": "Continuați în browser",
|
||||||
|
"open_in_app": "Deschideți în aplicație",
|
||||||
|
"text": "Sunteți gata să vă alăturați?",
|
||||||
|
"title": "Selectați aplicația"
|
||||||
|
},
|
||||||
|
"application_opened_another_tab": "Această aplicație a fost deschisă într-o altă filă.",
|
||||||
|
"browser_media_e2ee_unsupported": "Browserul dvs. web nu acceptă criptarea media end-to-end. Browserele acceptate sunt Chrome, Safari, Firefox > = 117",
|
||||||
|
"browser_media_e2ee_unsupported_heading": "Browser incompatibil",
|
||||||
|
"call_ended_view": {
|
||||||
|
"body": "Ai fost deconectat de la apel",
|
||||||
|
"create_account_button": "Creează cont",
|
||||||
|
"create_account_prompt": "<0>De ce să nu terminați prin configurarea unei parole pentru a vă păstra contul? </0><1>Veți putea să vă păstrați numele și să setați un avatar pentru a fi utilizat la apelurile viitoare </1>",
|
||||||
|
"feedback_done": "<0>Vă mulțumim pentru feedback! </0>",
|
||||||
|
"feedback_prompt": "<0>Ne-ar plăcea să auzim feedback-ul dvs., astfel încât să vă putem îmbunătăți experiența. </0>",
|
||||||
|
"headline": "{{displayName}}, apelul tău s-a încheiat.",
|
||||||
|
"not_now_button": "Nu acum, reveniți la ecranul de pornire",
|
||||||
|
"reconnect_button": "Reconecta",
|
||||||
|
"survey_prompt": "Cum a mers?"
|
||||||
|
},
|
||||||
|
"call_name": "Numele apelului",
|
||||||
|
"common": {
|
||||||
|
"analytics": "Analiză",
|
||||||
|
"audio": "Audio",
|
||||||
|
"avatar": "avatar",
|
||||||
|
"back": "Înapoi",
|
||||||
|
"camera": "Aparat foto",
|
||||||
|
"display_name": "Nume afișat",
|
||||||
|
"encrypted": "Criptat",
|
||||||
|
"error": "Eroare",
|
||||||
|
"home": "Acasa",
|
||||||
|
"loading": "Se încarcă...",
|
||||||
|
"microphone": "Microfon",
|
||||||
|
"next": "Urmator\n",
|
||||||
|
"options": "Opțiuni",
|
||||||
|
"password": "Parolă",
|
||||||
|
"preferences": "preferinte",
|
||||||
|
"profile": "Profil",
|
||||||
|
"reaction": "Reacție",
|
||||||
|
"reactions": "Reacții",
|
||||||
|
"settings": "Settings",
|
||||||
|
"something_went_wrong": "Ceva nu a mers bine",
|
||||||
|
"unencrypted": "Nu este criptat",
|
||||||
|
"username": "Nume utilizator",
|
||||||
|
"video": "Videoclip"
|
||||||
|
},
|
||||||
|
"developer_mode": {
|
||||||
|
"crypto_version": "Versiunea Crypto: {{version}}",
|
||||||
|
"device_id": "ID-ul dispozitivului: {{id}}",
|
||||||
|
"duplicate_tiles_label": "Numărul de exemplare suplimentare de cartonașe per participant",
|
||||||
|
"hostname": "Numele gazdei: {{hostname}}",
|
||||||
|
"matrix_id": "ID-ul matricei: {{id}}"
|
||||||
|
},
|
||||||
|
"disconnected_banner": "Conectivitatea la server a fost pierdută.",
|
||||||
|
"full_screen_view_description": "<0>Trimiterea jurnalelor de depanare ne va ajuta să urmărim problema. </0>",
|
||||||
|
"full_screen_view_h1": "<0>Hopa, ceva nu a mers bine. </0>",
|
||||||
|
"group_call_loader": {
|
||||||
|
"banned_body": "Ai fost interzis să ieși din cameră.",
|
||||||
|
"banned_heading": "Interzis",
|
||||||
|
"call_ended_body": "Ați fost eliminat din apel.",
|
||||||
|
"call_ended_heading": "Apel încheiat",
|
||||||
|
"failed_heading": "Nu s-a putut alătura",
|
||||||
|
"failed_text": "Apelul nu a fost găsit sau nu este accesibil.",
|
||||||
|
"knock_reject_body": "Cererea dvs. de a vă alătura a fost respinsă.",
|
||||||
|
"knock_reject_heading": "Acces refuzat",
|
||||||
|
"reason": "Motivul"
|
||||||
|
},
|
||||||
|
"hangup_button_label": "Încheiați apelul",
|
||||||
|
"header_label": "Element Call Home",
|
||||||
|
"header_participants_label": "Participanți",
|
||||||
|
"invite_modal": {
|
||||||
|
"link_copied_toast": "Link copiat în clipboard",
|
||||||
|
"title": "Invitați la acest apel"
|
||||||
|
},
|
||||||
|
"join_existing_call_modal": {
|
||||||
|
"join_button": "Da, alăturați-vă apelului",
|
||||||
|
"text": "Acest apel există deja, doriți să vă alăturați?",
|
||||||
|
"title": "Alăturați-vă apelului existent?"
|
||||||
|
},
|
||||||
|
"layout_grid_label": "GRILĂ",
|
||||||
|
"layout_spotlight_label": "Spotlight",
|
||||||
|
"lobby": {
|
||||||
|
"ask_to_join": "Solicitare de participare la apel",
|
||||||
|
"join_as_guest": "Alăturați-vă ca invitat",
|
||||||
|
"join_button": "Alăturați-vă apelului",
|
||||||
|
"leave_button": "Înapoi la cele mai recente",
|
||||||
|
"waiting_for_invite": "Solicitare trimisă! În așteptarea permisiunii de a participa..."
|
||||||
|
},
|
||||||
|
"log_in": "Autentificare",
|
||||||
|
"logging_in": "Autentificare...",
|
||||||
|
"login_auth_links": "<0>Creați un cont </0> sau <2> accesați ca invitat </2>",
|
||||||
|
"login_auth_links_prompt": "Nu sunteți încă înregistrat?",
|
||||||
|
"login_subheading": "Pentru a continua la Element",
|
||||||
|
"login_title": "Logare",
|
||||||
|
"microphone_off": "Microfon oprit",
|
||||||
|
"microphone_on": "Microfon pornit",
|
||||||
|
"mute_microphone_button_label": "Dezactivați microfonul",
|
||||||
|
"qr_code": "COD QR",
|
||||||
|
"rageshake_button_error_caption": "Încearcă din nou trimiterea jurnalelor",
|
||||||
|
"rageshake_request_modal": {
|
||||||
|
"body": "Un alt utilizator al acestui apel are o problemă. Pentru a diagnostica mai bine aceste probleme, am dori să colectăm un jurnal de depanare.",
|
||||||
|
"title": "Solicitare jurnal de depanare"
|
||||||
|
},
|
||||||
|
"rageshake_send_logs": "Trimiteți jurnale de depanare",
|
||||||
|
"rageshake_sending": "Trimiterea...",
|
||||||
|
"rageshake_sending_logs": "Trimiterea jurnalelor de depanare...",
|
||||||
|
"rageshake_sent": "Multumesc!",
|
||||||
|
"recaptcha_caption": "Acest site este protejat de reCAPTCHA și se aplică Politica de <2> confidențialitate Google </2> și <6> Termenii și condițiile. </6> <9></9>Făcând clic pe „Înregistrare”, sunteți de acord cu Acordul nostru de licențiere pentru utilizatorul <12> final (EULA) </12>",
|
||||||
|
"recaptcha_dismissed": "Recaptcha a fost respins",
|
||||||
|
"recaptcha_not_loaded": "Recaptcha nu a fost încărcat",
|
||||||
|
"register": {
|
||||||
|
"passwords_must_match": "Parolele trebuie să se potrivească",
|
||||||
|
"registering": "Înregistrare..."
|
||||||
|
},
|
||||||
|
"register_auth_links": "<0>Ai deja un cont? </0><1><0>Conectați-vă </0> sau <2> accesați ca invitat </2> </1>",
|
||||||
|
"register_confirm_password_label": "Confirmă Parola",
|
||||||
|
"register_heading": "Creează-ți contul",
|
||||||
|
"return_home_button": "Reveniți la ecranul de pornire",
|
||||||
|
"room_auth_view_continue_button": "Continuă",
|
||||||
|
"room_auth_view_eula_caption": "Făcând clic pe „Continuați”, sunteți de acord cu Acordul nostru de licențiere pentru utilizatorul <2> final (EULA) </2>",
|
||||||
|
"screenshare_button_label": "Partajare ecran",
|
||||||
|
"settings": {
|
||||||
|
"audio_tab": {
|
||||||
|
"effect_volume_description": "Reglați volumul la care reacționează reacțiile și efectele ridicate de mână",
|
||||||
|
"effect_volume_label": "Volumul efectului sonor"
|
||||||
|
},
|
||||||
|
"developer_settings_label": "Setări pentru dezvoltatori",
|
||||||
|
"developer_settings_label_description": "Expuneți setările dezvoltatorului în fereastra de setări.",
|
||||||
|
"developer_tab_title": "dezvoltator",
|
||||||
|
"feedback_tab_body": "Dacă întâmpinați probleme sau pur și simplu doriți să oferiți feedback, vă rugăm să ne trimiteți o scurtă descriere mai jos.",
|
||||||
|
"feedback_tab_description_label": "Feedback-ul tău",
|
||||||
|
"feedback_tab_h4": "Trimiteți Feedback",
|
||||||
|
"feedback_tab_send_logs_label": "Includeți jurnale de depanare",
|
||||||
|
"feedback_tab_thank_you": "Vă mulțumim, am primit feedback-ul dvs.!",
|
||||||
|
"feedback_tab_title": "Feedback",
|
||||||
|
"more_tab_title": "Mai mult",
|
||||||
|
"opt_in_description": "<0></0><1></1>Puteți retrage consimțământul debifând această casetă. Dacă sunteți în prezent la un apel, această setare va intra în vigoare la sfârșitul apelului.",
|
||||||
|
"preferences_tab": {
|
||||||
|
"introduction": "Aici puteți configura opțiuni suplimentare pentru o experiență îmbunătățită",
|
||||||
|
"reactions_play_sound_description": "Redați un efect sonor atunci când cineva trimite o reacție la un apel.",
|
||||||
|
"reactions_play_sound_label": "Redați sunete de reacție",
|
||||||
|
"reactions_show_description": "Afișați o animație atunci când cineva trimite o reacție.",
|
||||||
|
"reactions_show_label": "Afișați reacțiile",
|
||||||
|
"reactions_title": "Reacții",
|
||||||
|
"show_hand_raised_timer_description": "Afișați un cronometru atunci când un participant ridică mâna",
|
||||||
|
"show_hand_raised_timer_label": "Afișați durata ridicării mâinii"
|
||||||
|
},
|
||||||
|
"preferences_tab_h4": "preferinte",
|
||||||
|
"speaker_device_selection_label": "vorbitor"
|
||||||
|
},
|
||||||
|
"start_new_call": "Începe un nou apel",
|
||||||
|
"start_video_button_label": "Începeți videoclipul",
|
||||||
|
"stop_screenshare_button_label": "Partajarea ecranului",
|
||||||
|
"stop_video_button_label": "Opriți videoclipul",
|
||||||
|
"submitting": "Trimiterea...",
|
||||||
|
"switch_camera": "Comutați camera",
|
||||||
|
"unauthenticated_view_body": "Nu sunteți încă înregistrat? <2>Creați un cont </2>",
|
||||||
|
"unauthenticated_view_eula_caption": "Făcând clic pe „Go”, sunteți de acord cu Acordul nostru de licențiere pentru utilizatorul <2> final (EULA) </2>",
|
||||||
|
"unauthenticated_view_login_button": "Conectați-vă la contul dvs.",
|
||||||
|
"unmute_microphone_button_label": "Anulează microfonul",
|
||||||
|
"version": "{{productName}}Versiune: {{version}}",
|
||||||
|
"video_tile": {
|
||||||
|
"always_show": "Arată întotdeauna",
|
||||||
|
"change_fit_contain": "Se potrivește cadrului",
|
||||||
|
"collapse": "colaps",
|
||||||
|
"expand": "Extindeți",
|
||||||
|
"mute_for_me": "Mute pentru mine",
|
||||||
|
"muted_for_me": "Dezactivat pentru mine",
|
||||||
|
"volume": "VOLUM"
|
||||||
|
}
|
||||||
|
}
|
||||||
13
package.json
13
package.json
@@ -27,13 +27,13 @@
|
|||||||
"@codecov/vite-plugin": "^1.3.0",
|
"@codecov/vite-plugin": "^1.3.0",
|
||||||
"@fontsource/inconsolata": "^5.1.0",
|
"@fontsource/inconsolata": "^5.1.0",
|
||||||
"@fontsource/inter": "^5.1.0",
|
"@fontsource/inter": "^5.1.0",
|
||||||
"@formatjs/intl-durationformat": "^0.6.1",
|
"@formatjs/intl-durationformat": "^0.7.0",
|
||||||
"@formatjs/intl-segmenter": "^11.7.3",
|
"@formatjs/intl-segmenter": "^11.7.3",
|
||||||
"@livekit/components-core": "^0.11.0",
|
"@livekit/components-core": "^0.11.0",
|
||||||
"@livekit/components-react": "^2.0.0",
|
"@livekit/components-react": "^2.0.0",
|
||||||
"@opentelemetry/api": "^1.4.0",
|
"@opentelemetry/api": "^1.4.0",
|
||||||
"@opentelemetry/core": "^1.25.1",
|
"@opentelemetry/core": "^1.25.1",
|
||||||
"@opentelemetry/exporter-trace-otlp-http": "^0.54.0",
|
"@opentelemetry/exporter-trace-otlp-http": "^0.56.0",
|
||||||
"@opentelemetry/resources": "^1.25.1",
|
"@opentelemetry/resources": "^1.25.1",
|
||||||
"@opentelemetry/sdk-trace-base": "^1.25.1",
|
"@opentelemetry/sdk-trace-base": "^1.25.1",
|
||||||
"@opentelemetry/sdk-trace-web": "^1.9.1",
|
"@opentelemetry/sdk-trace-web": "^1.9.1",
|
||||||
@@ -45,6 +45,7 @@
|
|||||||
"@sentry/react": "^8.0.0",
|
"@sentry/react": "^8.0.0",
|
||||||
"@sentry/vite-plugin": "^2.0.0",
|
"@sentry/vite-plugin": "^2.0.0",
|
||||||
"@testing-library/dom": "^10.1.0",
|
"@testing-library/dom": "^10.1.0",
|
||||||
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
"@testing-library/react": "^16.0.0",
|
"@testing-library/react": "^16.0.0",
|
||||||
"@testing-library/react-hooks": "^8.0.1",
|
"@testing-library/react-hooks": "^8.0.1",
|
||||||
"@testing-library/user-event": "^14.5.1",
|
"@testing-library/user-event": "^14.5.1",
|
||||||
@@ -52,7 +53,7 @@
|
|||||||
"@types/grecaptcha": "^3.0.9",
|
"@types/grecaptcha": "^3.0.9",
|
||||||
"@types/jsdom": "^21.1.7",
|
"@types/jsdom": "^21.1.7",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/node": "^20.0.0",
|
"@types/node": "^22.0.0",
|
||||||
"@types/pako": "^2.0.3",
|
"@types/pako": "^2.0.3",
|
||||||
"@types/qrcode": "^1.5.5",
|
"@types/qrcode": "^1.5.5",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
@@ -62,7 +63,7 @@
|
|||||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||||
"@typescript-eslint/parser": "^8.0.0",
|
"@typescript-eslint/parser": "^8.0.0",
|
||||||
"@use-gesture/react": "^10.2.11",
|
"@use-gesture/react": "^10.2.11",
|
||||||
"@vector-im/compound-design-tokens": "^1.9.1",
|
"@vector-im/compound-design-tokens": "^2.0.0",
|
||||||
"@vector-im/compound-web": "^7.2.0",
|
"@vector-im/compound-web": "^7.2.0",
|
||||||
"@vitejs/plugin-basic-ssl": "^1.0.1",
|
"@vitejs/plugin-basic-ssl": "^1.0.1",
|
||||||
"@vitejs/plugin-react": "^4.0.1",
|
"@vitejs/plugin-react": "^4.0.1",
|
||||||
@@ -90,7 +91,7 @@
|
|||||||
"livekit-client": "^2.5.7",
|
"livekit-client": "^2.5.7",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"loglevel": "^1.9.1",
|
"loglevel": "^1.9.1",
|
||||||
"matrix-js-sdk": "matrix-org/matrix-js-sdk#2210255d6ffc909c574fb8ef16f92140b2fb7797",
|
"matrix-js-sdk": "matrix-org/matrix-js-sdk#develop",
|
||||||
"matrix-widget-api": "^1.10.0",
|
"matrix-widget-api": "^1.10.0",
|
||||||
"normalize.css": "^8.0.1",
|
"normalize.css": "^8.0.1",
|
||||||
"observable-hooks": "^4.2.3",
|
"observable-hooks": "^4.2.3",
|
||||||
@@ -112,7 +113,7 @@
|
|||||||
"typescript-eslint-language-service": "^5.0.5",
|
"typescript-eslint-language-service": "^5.0.5",
|
||||||
"unique-names-generator": "^4.6.0",
|
"unique-names-generator": "^4.6.0",
|
||||||
"vaul": "^1.0.0",
|
"vaul": "^1.0.0",
|
||||||
"vite": "^5.0.0",
|
"vite": "^6.0.0",
|
||||||
"vite-plugin-compression2": "^1.3.1",
|
"vite-plugin-compression2": "^1.3.1",
|
||||||
"vite-plugin-html-template": "^1.1.0",
|
"vite-plugin-html-template": "^1.1.0",
|
||||||
"vite-plugin-svgr": "^4.0.0",
|
"vite-plugin-svgr": "^4.0.0",
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
{
|
|
||||||
"applinks": {
|
|
||||||
"details": [
|
|
||||||
{
|
|
||||||
"appIDs": [
|
|
||||||
"7J4U792NQT.io.element.elementx",
|
|
||||||
"7J4U792NQT.io.element.elementx.nightly",
|
|
||||||
"7J4U792NQT.io.element.elementx.pr"
|
|
||||||
],
|
|
||||||
"components": [
|
|
||||||
{
|
|
||||||
"?": {
|
|
||||||
"no_universal_links": "?*"
|
|
||||||
},
|
|
||||||
"exclude": true,
|
|
||||||
"comment": "Opt out of universal links"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"/": "/*",
|
|
||||||
"comment": "Matches any URL"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"relation": ["delegate_permission/common.handle_all_urls"],
|
|
||||||
"target": {
|
|
||||||
"namespace": "android_app",
|
|
||||||
"package_name": "io.element.android.x.debug",
|
|
||||||
"sha256_cert_fingerprints": [
|
|
||||||
"B0:B0:51:DC:56:5C:81:2F:E1:7F:6F:3E:94:5B:4D:79:04:71:23:AB:0D:A6:12:86:76:9E:B2:94:91:97:13:0E"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"relation": ["delegate_permission/common.handle_all_urls"],
|
|
||||||
"target": {
|
|
||||||
"namespace": "android_app",
|
|
||||||
"package_name": "io.element.android.x.nightly",
|
|
||||||
"sha256_cert_fingerprints": [
|
|
||||||
"CA:D3:85:16:84:3A:05:CC:EB:00:AB:7B:D3:80:0F:01:BA:8F:E0:4B:38:86:F3:97:D8:F7:9A:1B:C4:54:E4:0F"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"relation": ["delegate_permission/common.handle_all_urls"],
|
|
||||||
"target": {
|
|
||||||
"namespace": "android_app",
|
|
||||||
"package_name": "io.element.android.x",
|
|
||||||
"sha256_cert_fingerprints": [
|
|
||||||
"C6:DB:9B:9C:8C:BD:D6:5D:16:E8:EC:8C:8B:91:C8:31:B9:EF:C9:5C:BF:98:AE:41:F6:A9:D8:35:15:1A:7E:16"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
2
src/@types/global.d.ts
vendored
2
src/@types/global.d.ts
vendored
@@ -7,7 +7,7 @@ Please see LICENSE in the repository root for full details.
|
|||||||
|
|
||||||
import "matrix-js-sdk/src/@types/global";
|
import "matrix-js-sdk/src/@types/global";
|
||||||
import type { DurationFormat as PolyfillDurationFormat } from "@formatjs/intl-durationformat";
|
import type { DurationFormat as PolyfillDurationFormat } from "@formatjs/intl-durationformat";
|
||||||
import { Controls } from "../controls";
|
import { type Controls } from "../controls";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Document {
|
interface Document {
|
||||||
|
|||||||
2
src/@types/i18next.d.ts
vendored
2
src/@types/i18next.d.ts
vendored
@@ -7,7 +7,7 @@ Please see LICENSE in the repository root for full details.
|
|||||||
|
|
||||||
import "i18next";
|
import "i18next";
|
||||||
// import all namespaces (for the default language, only)
|
// import all namespaces (for the default language, only)
|
||||||
import app from "../../locales/en-GB/app.json";
|
import type app from "../../locales/en/app.json";
|
||||||
|
|
||||||
declare module "i18next" {
|
declare module "i18next" {
|
||||||
interface CustomTypeOptions {
|
interface CustomTypeOptions {
|
||||||
|
|||||||
4
src/@types/matrix-js-sdk.d.ts
vendored
4
src/@types/matrix-js-sdk.d.ts
vendored
@@ -6,8 +6,8 @@ Please see LICENSE in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ElementCallReactionEventType,
|
type ElementCallReactionEventType,
|
||||||
ECallReactionEventContent,
|
type ECallReactionEventContent,
|
||||||
} from "../reactions";
|
} from "../reactions";
|
||||||
|
|
||||||
// Extend Matrix JS SDK types via Typescript declaration merging to support unspecced event fields and types
|
// Extend Matrix JS SDK types via Typescript declaration merging to support unspecced event fields and types
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FC, Suspense, useEffect, useState } from "react";
|
import { type FC, Suspense, useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
BrowserRouter as Router,
|
BrowserRouter as Router,
|
||||||
Switch,
|
Switch,
|
||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
useLocation,
|
useLocation,
|
||||||
} from "react-router-dom";
|
} from "react-router-dom";
|
||||||
import * as Sentry from "@sentry/react";
|
import * as Sentry from "@sentry/react";
|
||||||
import { History } from "history";
|
import { type History } from "history";
|
||||||
import { TooltipProvider } from "@vector-im/compound-web";
|
import { TooltipProvider } from "@vector-im/compound-web";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
|
|||||||
156
src/Avatar.test.tsx
Normal file
156
src/Avatar.test.tsx
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2024 New Vector Ltd.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { afterEach, expect, test, vi } from "vitest";
|
||||||
|
import { render, screen } from "@testing-library/react";
|
||||||
|
import { type MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
|
import { type FC, type PropsWithChildren } from "react";
|
||||||
|
|
||||||
|
import { ClientContextProvider } from "./ClientContext";
|
||||||
|
import { Avatar } from "./Avatar";
|
||||||
|
import { mockMatrixRoomMember, mockRtcMembership } from "./utils/test";
|
||||||
|
|
||||||
|
const TestComponent: FC<
|
||||||
|
PropsWithChildren<{ client: MatrixClient; supportsThumbnails?: boolean }>
|
||||||
|
> = ({ client, children, supportsThumbnails }) => {
|
||||||
|
return (
|
||||||
|
<ClientContextProvider
|
||||||
|
value={{
|
||||||
|
state: "valid",
|
||||||
|
disconnected: false,
|
||||||
|
supportedFeatures: {
|
||||||
|
reactions: true,
|
||||||
|
thumbnails: supportsThumbnails ?? true,
|
||||||
|
},
|
||||||
|
setClient: vi.fn(),
|
||||||
|
authenticated: {
|
||||||
|
client,
|
||||||
|
isPasswordlessUser: true,
|
||||||
|
changePassword: vi.fn(),
|
||||||
|
logout: vi.fn(),
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ClientContextProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.unstubAllGlobals();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should just render a placeholder when the user has no avatar", () => {
|
||||||
|
const client = vi.mocked<MatrixClient>({
|
||||||
|
getAccessToken: () => "my-access-token",
|
||||||
|
mxcUrlToHttp: () => vi.fn(),
|
||||||
|
} as unknown as MatrixClient);
|
||||||
|
|
||||||
|
vi.spyOn(client, "mxcUrlToHttp");
|
||||||
|
const member = mockMatrixRoomMember(
|
||||||
|
mockRtcMembership("@alice:example.org", "AAAA"),
|
||||||
|
{
|
||||||
|
getMxcAvatarUrl: () => undefined,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const displayName = "Alice";
|
||||||
|
render(
|
||||||
|
<TestComponent client={client}>
|
||||||
|
<Avatar
|
||||||
|
id={member.userId}
|
||||||
|
name={displayName}
|
||||||
|
size={96}
|
||||||
|
src={member.getMxcAvatarUrl()}
|
||||||
|
/>
|
||||||
|
</TestComponent>,
|
||||||
|
);
|
||||||
|
const element = screen.getByRole("img", { name: "@alice:example.org" });
|
||||||
|
expect(element.tagName).toEqual("SPAN");
|
||||||
|
expect(client.mxcUrlToHttp).toBeCalledTimes(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should just render a placeholder when thumbnails are not supported", () => {
|
||||||
|
const client = vi.mocked<MatrixClient>({
|
||||||
|
getAccessToken: () => "my-access-token",
|
||||||
|
mxcUrlToHttp: () => vi.fn(),
|
||||||
|
} as unknown as MatrixClient);
|
||||||
|
|
||||||
|
vi.spyOn(client, "mxcUrlToHttp");
|
||||||
|
const member = mockMatrixRoomMember(
|
||||||
|
mockRtcMembership("@alice:example.org", "AAAA"),
|
||||||
|
{
|
||||||
|
getMxcAvatarUrl: () => "mxc://example.org/alice-avatar",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const displayName = "Alice";
|
||||||
|
render(
|
||||||
|
<TestComponent client={client} supportsThumbnails={false}>
|
||||||
|
<Avatar
|
||||||
|
id={member.userId}
|
||||||
|
name={displayName}
|
||||||
|
size={96}
|
||||||
|
src={member.getMxcAvatarUrl()}
|
||||||
|
/>
|
||||||
|
</TestComponent>,
|
||||||
|
);
|
||||||
|
const element = screen.getByRole("img", { name: "@alice:example.org" });
|
||||||
|
expect(element.tagName).toEqual("SPAN");
|
||||||
|
expect(client.mxcUrlToHttp).toBeCalledTimes(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should attempt to fetch authenticated media", async () => {
|
||||||
|
const expectedAuthUrl = "http://example.org/media/alice-avatar";
|
||||||
|
const expectedObjectURL = "my-object-url";
|
||||||
|
const accessToken = "my-access-token";
|
||||||
|
const theBlob = new Blob([]);
|
||||||
|
|
||||||
|
// vitest doesn't have a implementation of create/revokeObjectURL, so we need
|
||||||
|
// to delete the property. It's a bit odd, but it works.
|
||||||
|
Reflect.deleteProperty(global.window.URL, "createObjectURL");
|
||||||
|
globalThis.URL.createObjectURL = vi.fn().mockReturnValue(expectedObjectURL);
|
||||||
|
Reflect.deleteProperty(global.window.URL, "revokeObjectURL");
|
||||||
|
globalThis.URL.revokeObjectURL = vi.fn();
|
||||||
|
|
||||||
|
const fetchFn = vi.fn().mockResolvedValue({
|
||||||
|
blob: async () => Promise.resolve(theBlob),
|
||||||
|
});
|
||||||
|
vi.stubGlobal("fetch", fetchFn);
|
||||||
|
|
||||||
|
const client = vi.mocked<MatrixClient>({
|
||||||
|
getAccessToken: () => accessToken,
|
||||||
|
mxcUrlToHttp: () => vi.fn(),
|
||||||
|
} as unknown as MatrixClient);
|
||||||
|
|
||||||
|
vi.spyOn(client, "mxcUrlToHttp").mockReturnValue(expectedAuthUrl);
|
||||||
|
const member = mockMatrixRoomMember(
|
||||||
|
mockRtcMembership("@alice:example.org", "AAAA"),
|
||||||
|
{
|
||||||
|
getMxcAvatarUrl: () => "mxc://example.org/alice-avatar",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const displayName = "Alice";
|
||||||
|
render(
|
||||||
|
<TestComponent client={client}>
|
||||||
|
<Avatar
|
||||||
|
id={member.userId}
|
||||||
|
name={displayName}
|
||||||
|
size={96}
|
||||||
|
src={member.getMxcAvatarUrl()}
|
||||||
|
/>
|
||||||
|
</TestComponent>,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fetch is asynchronous, so wait for this to resolve.
|
||||||
|
await vi.waitUntil(() =>
|
||||||
|
document.querySelector(`img[src='${expectedObjectURL}']`),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(client.mxcUrlToHttp).toBeCalledTimes(1);
|
||||||
|
expect(globalThis.fetch).toBeCalledWith(expectedAuthUrl, {
|
||||||
|
headers: { Authorization: `Bearer ${accessToken}` },
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -5,11 +5,17 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useMemo, FC } from "react";
|
import {
|
||||||
|
useMemo,
|
||||||
|
type FC,
|
||||||
|
type CSSProperties,
|
||||||
|
useState,
|
||||||
|
useEffect,
|
||||||
|
} from "react";
|
||||||
import { Avatar as CompoundAvatar } from "@vector-im/compound-web";
|
import { Avatar as CompoundAvatar } from "@vector-im/compound-web";
|
||||||
|
import { type MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
|
|
||||||
import { getAvatarUrl } from "./utils/matrix";
|
import { useClientState } from "./ClientContext";
|
||||||
import { useClient } from "./ClientContext";
|
|
||||||
|
|
||||||
export enum Size {
|
export enum Size {
|
||||||
XS = "xs",
|
XS = "xs",
|
||||||
@@ -33,6 +39,29 @@ interface Props {
|
|||||||
className?: string;
|
className?: string;
|
||||||
src?: string;
|
src?: string;
|
||||||
size?: Size | number;
|
size?: Size | number;
|
||||||
|
style?: CSSProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAvatarUrl(
|
||||||
|
client: MatrixClient,
|
||||||
|
mxcUrl: string | null,
|
||||||
|
avatarSize = 96,
|
||||||
|
): string | null {
|
||||||
|
const width = Math.floor(avatarSize * window.devicePixelRatio);
|
||||||
|
const height = Math.floor(avatarSize * window.devicePixelRatio);
|
||||||
|
// scale is more suitable for larger sizes
|
||||||
|
const resizeMethod = avatarSize <= 96 ? "crop" : "scale";
|
||||||
|
return mxcUrl
|
||||||
|
? client.mxcUrlToHttp(
|
||||||
|
mxcUrl,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
resizeMethod,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Avatar: FC<Props> = ({
|
export const Avatar: FC<Props> = ({
|
||||||
@@ -41,8 +70,10 @@ export const Avatar: FC<Props> = ({
|
|||||||
name,
|
name,
|
||||||
src,
|
src,
|
||||||
size = Size.MD,
|
size = Size.MD,
|
||||||
|
style,
|
||||||
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
const { client } = useClient();
|
const clientState = useClientState();
|
||||||
|
|
||||||
const sizePx = useMemo(
|
const sizePx = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -52,10 +83,50 @@ export const Avatar: FC<Props> = ({
|
|||||||
[size],
|
[size],
|
||||||
);
|
);
|
||||||
|
|
||||||
const resolvedSrc = useMemo(() => {
|
const [avatarUrl, setAvatarUrl] = useState<string | undefined>(undefined);
|
||||||
if (!client || !src || !sizePx) return undefined;
|
|
||||||
return src.startsWith("mxc://") ? getAvatarUrl(client, src, sizePx) : src;
|
useEffect(() => {
|
||||||
}, [client, src, sizePx]);
|
if (clientState?.state !== "valid") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { authenticated, supportedFeatures } = clientState;
|
||||||
|
const client = authenticated?.client;
|
||||||
|
|
||||||
|
if (!client || !src || !sizePx || !supportedFeatures.thumbnails) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = client.getAccessToken();
|
||||||
|
if (!token) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const resolveSrc = getAvatarUrl(client, src, sizePx);
|
||||||
|
if (!resolveSrc) {
|
||||||
|
setAvatarUrl(undefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let objectUrl: string | undefined;
|
||||||
|
fetch(resolveSrc, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(async (req) => req.blob())
|
||||||
|
.then((blob) => {
|
||||||
|
objectUrl = URL.createObjectURL(blob);
|
||||||
|
setAvatarUrl(objectUrl);
|
||||||
|
})
|
||||||
|
.catch((ex) => {
|
||||||
|
setAvatarUrl(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (): void => {
|
||||||
|
if (objectUrl) {
|
||||||
|
URL.revokeObjectURL(objectUrl);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [clientState, src, sizePx]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CompoundAvatar
|
<CompoundAvatar
|
||||||
@@ -63,7 +134,9 @@ export const Avatar: FC<Props> = ({
|
|||||||
id={id}
|
id={id}
|
||||||
name={name}
|
name={name}
|
||||||
size={`${sizePx}px`}
|
size={`${sizePx}px`}
|
||||||
src={resolvedSrc}
|
src={avatarUrl}
|
||||||
|
style={style}
|
||||||
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FC,
|
type FC,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useState,
|
useState,
|
||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
import { useHistory } from "react-router-dom";
|
import { useHistory } from "react-router-dom";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { ISyncStateData, SyncState } from "matrix-js-sdk/src/sync";
|
import { type ISyncStateData, type SyncState } from "matrix-js-sdk/src/sync";
|
||||||
import { ClientEvent, type MatrixClient } from "matrix-js-sdk/src/client";
|
import { ClientEvent, type MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
|
|
||||||
import type { WidgetApi } from "matrix-widget-api";
|
import type { WidgetApi } from "matrix-widget-api";
|
||||||
@@ -48,6 +48,7 @@ export type ValidClientState = {
|
|||||||
disconnected: boolean;
|
disconnected: boolean;
|
||||||
supportedFeatures: {
|
supportedFeatures: {
|
||||||
reactions: boolean;
|
reactions: boolean;
|
||||||
|
thumbnails: boolean;
|
||||||
};
|
};
|
||||||
setClient: (params?: SetClientParams) => void;
|
setClient: (params?: SetClientParams) => void;
|
||||||
};
|
};
|
||||||
@@ -71,6 +72,8 @@ export type SetClientParams = {
|
|||||||
|
|
||||||
const ClientContext = createContext<ClientState | undefined>(undefined);
|
const ClientContext = createContext<ClientState | undefined>(undefined);
|
||||||
|
|
||||||
|
export const ClientContextProvider = ClientContext.Provider;
|
||||||
|
|
||||||
export const useClientState = (): ClientState | undefined =>
|
export const useClientState = (): ClientState | undefined =>
|
||||||
useContext(ClientContext);
|
useContext(ClientContext);
|
||||||
|
|
||||||
@@ -253,6 +256,7 @@ export const ClientProvider: FC<Props> = ({ children }) => {
|
|||||||
|
|
||||||
const [isDisconnected, setIsDisconnected] = useState(false);
|
const [isDisconnected, setIsDisconnected] = useState(false);
|
||||||
const [supportsReactions, setSupportsReactions] = useState(false);
|
const [supportsReactions, setSupportsReactions] = useState(false);
|
||||||
|
const [supportsThumbnails, setSupportsThumbnails] = useState(false);
|
||||||
|
|
||||||
const state: ClientState | undefined = useMemo(() => {
|
const state: ClientState | undefined = useMemo(() => {
|
||||||
if (alreadyOpenedErr) {
|
if (alreadyOpenedErr) {
|
||||||
@@ -278,6 +282,7 @@ export const ClientProvider: FC<Props> = ({ children }) => {
|
|||||||
disconnected: isDisconnected,
|
disconnected: isDisconnected,
|
||||||
supportedFeatures: {
|
supportedFeatures: {
|
||||||
reactions: supportsReactions,
|
reactions: supportsReactions,
|
||||||
|
thumbnails: supportsThumbnails,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
@@ -288,6 +293,7 @@ export const ClientProvider: FC<Props> = ({ children }) => {
|
|||||||
setClient,
|
setClient,
|
||||||
isDisconnected,
|
isDisconnected,
|
||||||
supportsReactions,
|
supportsReactions,
|
||||||
|
supportsThumbnails,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const onSync = useCallback(
|
const onSync = useCallback(
|
||||||
@@ -313,6 +319,8 @@ export const ClientProvider: FC<Props> = ({ children }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (initClientState.widgetApi) {
|
if (initClientState.widgetApi) {
|
||||||
|
// There is currently no widget API for authenticated media thumbnails.
|
||||||
|
setSupportsThumbnails(false);
|
||||||
const reactSend = initClientState.widgetApi.hasCapability(
|
const reactSend = initClientState.widgetApi.hasCapability(
|
||||||
"org.matrix.msc2762.send.event:m.reaction",
|
"org.matrix.msc2762.send.event:m.reaction",
|
||||||
);
|
);
|
||||||
@@ -334,6 +342,7 @@ export const ClientProvider: FC<Props> = ({ children }) => {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setSupportsReactions(true);
|
setSupportsReactions(true);
|
||||||
|
setSupportsThumbnails(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (): void => {
|
return (): void => {
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ Please see LICENSE in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { FC, HTMLAttributes, ReactNode } from "react";
|
import { type FC, type HTMLAttributes, type ReactNode } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import styles from "./DisconnectedBanner.module.css";
|
import styles from "./DisconnectedBanner.module.css";
|
||||||
import { ValidClientState, useClientState } from "./ClientContext";
|
import { type ValidClientState, useClientState } from "./ClientContext";
|
||||||
|
|
||||||
interface Props extends HTMLAttributes<HTMLElement> {
|
interface Props extends HTMLAttributes<HTMLElement> {
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FC, ReactNode, useCallback, useEffect } from "react";
|
import { type FC, type ReactNode, useCallback, useEffect } from "react";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
|
|||||||
@@ -6,7 +6,12 @@ Please see LICENSE in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { FC, HTMLAttributes, ReactNode, forwardRef } from "react";
|
import {
|
||||||
|
type FC,
|
||||||
|
type HTMLAttributes,
|
||||||
|
type ReactNode,
|
||||||
|
forwardRef,
|
||||||
|
} from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Heading, Text } from "@vector-im/compound-web";
|
import { Heading, Text } from "@vector-im/compound-web";
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Please see LICENSE in the repository root for full details.
|
|||||||
|
|
||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
import { render } from "@testing-library/react";
|
import { render } from "@testing-library/react";
|
||||||
import { ReactNode, useState } from "react";
|
import { type ReactNode, useState } from "react";
|
||||||
import { afterEach } from "node:test";
|
import { afterEach } from "node:test";
|
||||||
import userEvent from "@testing-library/user-event";
|
import userEvent from "@testing-library/user-event";
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FC, ReactNode, useCallback } from "react";
|
import { type FC, type ReactNode, useCallback } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
Root as DialogRoot,
|
Root as DialogRoot,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FC, useEffect, useState } from "react";
|
import { type FC, useEffect, useState } from "react";
|
||||||
import { toDataURL } from "qrcode";
|
import { toDataURL } from "qrcode";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FC, useCallback } from "react";
|
import { type FC, useCallback } from "react";
|
||||||
import { Root, Track, Range, Thumb } from "@radix-ui/react-slider";
|
import { Root, Track, Range, Thumb } from "@radix-ui/react-slider";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { Tooltip } from "@vector-im/compound-web";
|
import { Tooltip } from "@vector-im/compound-web";
|
||||||
@@ -16,6 +16,9 @@ interface Props {
|
|||||||
className?: string;
|
className?: string;
|
||||||
label: string;
|
label: string;
|
||||||
value: number;
|
value: number;
|
||||||
|
/**
|
||||||
|
* Event handler called when the value changes during an interaction.
|
||||||
|
*/
|
||||||
onValueChange: (value: number) => void;
|
onValueChange: (value: number) => void;
|
||||||
/**
|
/**
|
||||||
* Event handler called when the value changes at the end of an interaction.
|
* Event handler called when the value changes at the end of an interaction.
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ Please see LICENSE in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ComponentType,
|
type ComponentType,
|
||||||
FC,
|
type FC,
|
||||||
SVGAttributes,
|
type SVGAttributes,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
} from "react";
|
} from "react";
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export abstract class TranslatedError extends Error {
|
|||||||
messageKey: ParseKeys<DefaultNamespace, TOptions>,
|
messageKey: ParseKeys<DefaultNamespace, TOptions>,
|
||||||
translationFn: TFunction<DefaultNamespace>,
|
translationFn: TFunction<DefaultNamespace>,
|
||||||
) {
|
) {
|
||||||
super(translationFn(messageKey, { lng: "en-GB" } as TOptions));
|
super(translationFn(messageKey, { lng: "en" } as TOptions));
|
||||||
this.translatedMessage = translationFn(messageKey);
|
this.translatedMessage = translationFn(messageKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Please see LICENSE in the repository root for full details.
|
|||||||
|
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
import { getRoomIdentifierFromUrl } from "../src/UrlParams";
|
import { getRoomIdentifierFromUrl, getUrlParams } from "../src/UrlParams";
|
||||||
|
|
||||||
const ROOM_NAME = "roomNameHere";
|
const ROOM_NAME = "roomNameHere";
|
||||||
const ROOM_ID = "!d45f138fsd";
|
const ROOM_ID = "!d45f138fsd";
|
||||||
@@ -86,4 +86,113 @@ describe("UrlParams", () => {
|
|||||||
.roomAlias,
|
.roomAlias,
|
||||||
).toBeFalsy();
|
).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("preload", () => {
|
||||||
|
it("defaults to false", () => {
|
||||||
|
expect(getUrlParams().preload).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ignored in SPA mode", () => {
|
||||||
|
expect(getUrlParams("?preload=true").preload).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("respected in widget mode", () => {
|
||||||
|
expect(
|
||||||
|
getUrlParams(
|
||||||
|
"?preload=true&widgetId=12345&parentUrl=https%3A%2F%2Flocalhost%2Ffoo",
|
||||||
|
).preload,
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("returnToLobby", () => {
|
||||||
|
it("is true in SPA mode", () => {
|
||||||
|
expect(getUrlParams("?returnToLobby=false").returnToLobby).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("defaults to false in widget mode", () => {
|
||||||
|
expect(
|
||||||
|
getUrlParams("?widgetId=12345&parentUrl=https%3A%2F%2Flocalhost%2Ffoo")
|
||||||
|
.returnToLobby,
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("respected in widget mode", () => {
|
||||||
|
expect(
|
||||||
|
getUrlParams(
|
||||||
|
"?returnToLobby=true&widgetId=12345&parentUrl=https%3A%2F%2Flocalhost%2Ffoo",
|
||||||
|
).returnToLobby,
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("userId", () => {
|
||||||
|
it("is ignored in SPA mode", () => {
|
||||||
|
expect(getUrlParams("?userId=asd").userId).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is parsed in widget mode", () => {
|
||||||
|
expect(
|
||||||
|
getUrlParams(
|
||||||
|
"?userId=asd&widgetId=12345&parentUrl=https%3A%2F%2Flocalhost%2Ffoo",
|
||||||
|
).userId,
|
||||||
|
).toBe("asd");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("deviceId", () => {
|
||||||
|
it("is ignored in SPA mode", () => {
|
||||||
|
expect(getUrlParams("?deviceId=asd").deviceId).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is parsed in widget mode", () => {
|
||||||
|
expect(
|
||||||
|
getUrlParams(
|
||||||
|
"?deviceId=asd&widgetId=12345&parentUrl=https%3A%2F%2Flocalhost%2Ffoo",
|
||||||
|
).deviceId,
|
||||||
|
).toBe("asd");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("baseUrl", () => {
|
||||||
|
it("is ignored in SPA mode", () => {
|
||||||
|
expect(getUrlParams("?baseUrl=asd").baseUrl).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is parsed in widget mode", () => {
|
||||||
|
expect(
|
||||||
|
getUrlParams(
|
||||||
|
"?baseUrl=asd&widgetId=12345&parentUrl=https%3A%2F%2Flocalhost%2Ffoo",
|
||||||
|
).baseUrl,
|
||||||
|
).toBe("asd");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("viaServers", () => {
|
||||||
|
it("is ignored in widget mode", () => {
|
||||||
|
expect(
|
||||||
|
getUrlParams(
|
||||||
|
"?viaServers=asd&widgetId=12345&parentUrl=https%3A%2F%2Flocalhost%2Ffoo",
|
||||||
|
).viaServers,
|
||||||
|
).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is parsed in SPA mode", () => {
|
||||||
|
expect(getUrlParams("?viaServers=asd").viaServers).toBe("asd");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("homeserver", () => {
|
||||||
|
it("is ignored in widget mode", () => {
|
||||||
|
expect(
|
||||||
|
getUrlParams(
|
||||||
|
"?homeserver=asd&widgetId=12345&parentUrl=https%3A%2F%2Flocalhost%2Ffoo",
|
||||||
|
).homeserver,
|
||||||
|
).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is parsed in SPA mode", () => {
|
||||||
|
expect(getUrlParams("?homeserver=asd").homeserver).toBe("asd");
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { useLocation } from "react-router-dom";
|
|||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import { Config } from "./config/Config";
|
import { Config } from "./config/Config";
|
||||||
import { EncryptionSystem } from "./e2ee/sharedKeyManagement";
|
import { type EncryptionSystem } from "./e2ee/sharedKeyManagement";
|
||||||
import { E2eeType } from "./e2ee/e2eeType";
|
import { E2eeType } from "./e2ee/e2eeType";
|
||||||
|
|
||||||
interface RoomIdentifier {
|
interface RoomIdentifier {
|
||||||
@@ -211,9 +211,13 @@ export const getUrlParams = (
|
|||||||
|
|
||||||
const fontScale = parseFloat(parser.getParam("fontScale") ?? "");
|
const fontScale = parseFloat(parser.getParam("fontScale") ?? "");
|
||||||
|
|
||||||
|
const widgetId = parser.getParam("widgetId");
|
||||||
|
const parentUrl = parser.getParam("parentUrl");
|
||||||
|
const isWidget = !!widgetId && !!parentUrl;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
widgetId: parser.getParam("widgetId"),
|
widgetId,
|
||||||
parentUrl: parser.getParam("parentUrl"),
|
parentUrl,
|
||||||
|
|
||||||
// NB. we don't validate roomId here as we do in getRoomIdentifierFromUrl:
|
// NB. we don't validate roomId here as we do in getRoomIdentifierFromUrl:
|
||||||
// what would we do if it were invalid? If the widget API says that's what
|
// what would we do if it were invalid? If the widget API says that's what
|
||||||
@@ -224,15 +228,15 @@ export const getUrlParams = (
|
|||||||
confineToRoom:
|
confineToRoom:
|
||||||
parser.getFlagParam("confineToRoom") || parser.getFlagParam("embed"),
|
parser.getFlagParam("confineToRoom") || parser.getFlagParam("embed"),
|
||||||
appPrompt: parser.getFlagParam("appPrompt", true),
|
appPrompt: parser.getFlagParam("appPrompt", true),
|
||||||
preload: parser.getFlagParam("preload"),
|
preload: isWidget ? parser.getFlagParam("preload") : false,
|
||||||
hideHeader: parser.getFlagParam("hideHeader"),
|
hideHeader: parser.getFlagParam("hideHeader"),
|
||||||
showControls: parser.getFlagParam("showControls", true),
|
showControls: parser.getFlagParam("showControls", true),
|
||||||
hideScreensharing: parser.getFlagParam("hideScreensharing"),
|
hideScreensharing: parser.getFlagParam("hideScreensharing"),
|
||||||
e2eEnabled: parser.getFlagParam("enableE2EE", true),
|
e2eEnabled: parser.getFlagParam("enableE2EE", true),
|
||||||
userId: parser.getParam("userId"),
|
userId: isWidget ? parser.getParam("userId") : null,
|
||||||
displayName: parser.getParam("displayName"),
|
displayName: parser.getParam("displayName"),
|
||||||
deviceId: parser.getParam("deviceId"),
|
deviceId: isWidget ? parser.getParam("deviceId") : null,
|
||||||
baseUrl: parser.getParam("baseUrl"),
|
baseUrl: isWidget ? parser.getParam("baseUrl") : null,
|
||||||
lang: parser.getParam("lang"),
|
lang: parser.getParam("lang"),
|
||||||
fonts: parser.getAllParams("font"),
|
fonts: parser.getAllParams("font"),
|
||||||
fontScale: Number.isNaN(fontScale) ? null : fontScale,
|
fontScale: Number.isNaN(fontScale) ? null : fontScale,
|
||||||
@@ -240,10 +244,10 @@ export const getUrlParams = (
|
|||||||
allowIceFallback: parser.getFlagParam("allowIceFallback"),
|
allowIceFallback: parser.getFlagParam("allowIceFallback"),
|
||||||
perParticipantE2EE: parser.getFlagParam("perParticipantE2EE"),
|
perParticipantE2EE: parser.getFlagParam("perParticipantE2EE"),
|
||||||
skipLobby: parser.getFlagParam("skipLobby"),
|
skipLobby: parser.getFlagParam("skipLobby"),
|
||||||
returnToLobby: parser.getFlagParam("returnToLobby"),
|
returnToLobby: isWidget ? parser.getFlagParam("returnToLobby") : true,
|
||||||
theme: parser.getParam("theme"),
|
theme: parser.getParam("theme"),
|
||||||
viaServers: parser.getParam("viaServers"),
|
viaServers: !isWidget ? parser.getParam("viaServers") : null,
|
||||||
homeserver: parser.getParam("homeserver"),
|
homeserver: !isWidget ? parser.getParam("homeserver") : null,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FC, useMemo, useState } from "react";
|
import { type FC, useMemo, useState } from "react";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Menu, MenuItem } from "@vector-im/compound-web";
|
import { Menu, MenuItem } from "@vector-im/compound-web";
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FC, useCallback, useState } from "react";
|
import { type FC, useCallback, useState } from "react";
|
||||||
import { useHistory, useLocation } from "react-router-dom";
|
import { useHistory, useLocation } from "react-router-dom";
|
||||||
|
|
||||||
import { useClientLegacy } from "./ClientContext";
|
import { useClientLegacy } from "./ClientContext";
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FC } from "react";
|
import { type FC } from "react";
|
||||||
import { Trans } from "react-i18next";
|
import { Trans } from "react-i18next";
|
||||||
|
|
||||||
import { ExternalLink } from "../button/Link";
|
import { ExternalLink } from "../button/Link";
|
||||||
|
|||||||
@@ -5,9 +5,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import posthog, { CaptureOptions, PostHog, Properties } from "posthog-js";
|
import posthog, {
|
||||||
|
type CaptureOptions,
|
||||||
|
type PostHog,
|
||||||
|
type Properties,
|
||||||
|
} from "posthog-js";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
import { type MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||||
import { Buffer } from "buffer";
|
import { Buffer } from "buffer";
|
||||||
|
|
||||||
import { widget } from "../widget";
|
import { widget } from "../widget";
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DisconnectReason } from "livekit-client";
|
import { type DisconnectReason } from "livekit-client";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc";
|
import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
IPosthogEvent,
|
type IPosthogEvent,
|
||||||
PosthogAnalytics,
|
PosthogAnalytics,
|
||||||
RegistrationType,
|
RegistrationType,
|
||||||
} from "./PosthogAnalytics";
|
} from "./PosthogAnalytics";
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ Please see LICENSE in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
SpanProcessor,
|
type SpanProcessor,
|
||||||
ReadableSpan,
|
type ReadableSpan,
|
||||||
Span,
|
type Span,
|
||||||
} from "@opentelemetry/sdk-trace-base";
|
} from "@opentelemetry/sdk-trace-base";
|
||||||
import { hrTimeToMilliseconds } from "@opentelemetry/core";
|
import { hrTimeToMilliseconds } from "@opentelemetry/core";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { AttributeValue, Attributes } from "@opentelemetry/api";
|
import { type AttributeValue, type Attributes } from "@opentelemetry/api";
|
||||||
import { hrTimeToMicroseconds } from "@opentelemetry/core";
|
import { hrTimeToMicroseconds } from "@opentelemetry/core";
|
||||||
import {
|
import {
|
||||||
SpanProcessor,
|
type SpanProcessor,
|
||||||
ReadableSpan,
|
type ReadableSpan,
|
||||||
Span,
|
type Span,
|
||||||
} from "@opentelemetry/sdk-trace-base";
|
} from "@opentelemetry/sdk-trace-base";
|
||||||
|
|
||||||
const dumpAttributes = (
|
const dumpAttributes = (
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FC, FormEvent, useCallback, useRef, useState } from "react";
|
import { type FC, type FormEvent, useCallback, useRef, useState } from "react";
|
||||||
import { useHistory, useLocation } from "react-router-dom";
|
import { useHistory, useLocation } from "react-router-dom";
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
import { Button } from "@vector-im/compound-web";
|
import { Button } from "@vector-im/compound-web";
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ Please see LICENSE in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ChangeEvent,
|
type ChangeEvent,
|
||||||
FC,
|
type FC,
|
||||||
FormEvent,
|
type FormEvent,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useRef,
|
useRef,
|
||||||
|
|||||||
@@ -9,12 +9,12 @@ import { useCallback } from "react";
|
|||||||
import { InteractiveAuth } from "matrix-js-sdk/src/interactive-auth";
|
import { InteractiveAuth } from "matrix-js-sdk/src/interactive-auth";
|
||||||
import {
|
import {
|
||||||
createClient,
|
createClient,
|
||||||
LoginResponse,
|
type LoginResponse,
|
||||||
MatrixClient,
|
type MatrixClient,
|
||||||
} from "matrix-js-sdk/src/matrix";
|
} from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import { initClient } from "../utils/matrix";
|
import { initClient } from "../utils/matrix";
|
||||||
import { Session } from "../ClientContext";
|
import { type Session } from "../ClientContext";
|
||||||
/**
|
/**
|
||||||
* This provides the login method to login using user credentials.
|
* This provides the login method to login using user credentials.
|
||||||
* @param oldClient If there is an already authenticated client it should be passed to this hook
|
* @param oldClient If there is an already authenticated client it should be passed to this hook
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ import { useState, useEffect, useCallback, useRef } from "react";
|
|||||||
import { InteractiveAuth } from "matrix-js-sdk/src/interactive-auth";
|
import { InteractiveAuth } from "matrix-js-sdk/src/interactive-auth";
|
||||||
import {
|
import {
|
||||||
createClient,
|
createClient,
|
||||||
MatrixClient,
|
type MatrixClient,
|
||||||
RegisterResponse,
|
type RegisterResponse,
|
||||||
} from "matrix-js-sdk/src/matrix";
|
} from "matrix-js-sdk/src/matrix";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import { initClient } from "../utils/matrix";
|
import { initClient } from "../utils/matrix";
|
||||||
import { Session } from "../ClientContext";
|
import { type Session } from "../ClientContext";
|
||||||
import { Config } from "../config/Config";
|
import { Config } from "../config/Config";
|
||||||
import { widget } from "../widget";
|
import { widget } from "../widget";
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Copyright 2022-2024 New Vector Ltd.
|
|||||||
SPDX-License-Identifier: AGPL-3.0-only
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
import { ComponentPropsWithoutRef, FC } from "react";
|
import { type ComponentPropsWithoutRef, type FC } from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Button as CpdButton, Tooltip } from "@vector-im/compound-web";
|
import { Button as CpdButton, Tooltip } from "@vector-im/compound-web";
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ComponentPropsWithoutRef, FC } from "react";
|
import { type ComponentPropsWithoutRef, type FC } from "react";
|
||||||
import { Button } from "@vector-im/compound-web";
|
import { Button } from "@vector-im/compound-web";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { UserAddIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
import { UserAddIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||||
|
|||||||
@@ -6,15 +6,15 @@ Please see LICENSE in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ComponentPropsWithoutRef,
|
type ComponentPropsWithoutRef,
|
||||||
forwardRef,
|
forwardRef,
|
||||||
MouseEvent,
|
type MouseEvent,
|
||||||
useCallback,
|
useCallback,
|
||||||
useMemo,
|
useMemo,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { Link as CpdLink } from "@vector-im/compound-web";
|
import { Link as CpdLink } from "@vector-im/compound-web";
|
||||||
import { useHistory } from "react-router-dom";
|
import { useHistory } from "react-router-dom";
|
||||||
import { createPath, LocationDescriptor, Path } from "history";
|
import { createPath, type LocationDescriptor, type Path } from "history";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
import { useLatest } from "../useLatest";
|
import { useLatest } from "../useLatest";
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ComponentPropsWithoutRef, forwardRef } from "react";
|
import { type ComponentPropsWithoutRef, forwardRef } from "react";
|
||||||
import { Button } from "@vector-im/compound-web";
|
import { Button } from "@vector-im/compound-web";
|
||||||
import { LocationDescriptor } from "history";
|
import { type LocationDescriptor } from "history";
|
||||||
|
|
||||||
import { useLink } from "./Link";
|
import { useLink } from "./Link";
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { render } from "@testing-library/react";
|
|||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
import { TooltipProvider } from "@vector-im/compound-web";
|
import { TooltipProvider } from "@vector-im/compound-web";
|
||||||
import { userEvent } from "@testing-library/user-event";
|
import { userEvent } from "@testing-library/user-event";
|
||||||
import { ReactNode } from "react";
|
import { type ReactNode } from "react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
MockRoom,
|
MockRoom,
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ import {
|
|||||||
ReactionSolidIcon,
|
ReactionSolidIcon,
|
||||||
} from "@vector-im/compound-design-tokens/assets/web/icons";
|
} from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||||
import {
|
import {
|
||||||
ComponentPropsWithoutRef,
|
type ComponentPropsWithoutRef,
|
||||||
FC,
|
type FC,
|
||||||
ReactNode,
|
type ReactNode,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
@@ -27,7 +27,11 @@ import classNames from "classnames";
|
|||||||
|
|
||||||
import { useReactions } from "../useReactions";
|
import { useReactions } from "../useReactions";
|
||||||
import styles from "./ReactionToggleButton.module.css";
|
import styles from "./ReactionToggleButton.module.css";
|
||||||
import { ReactionOption, ReactionSet, ReactionsRowSize } from "../reactions";
|
import {
|
||||||
|
type ReactionOption,
|
||||||
|
ReactionSet,
|
||||||
|
ReactionsRowSize,
|
||||||
|
} from "../reactions";
|
||||||
import { Modal } from "../Modal";
|
import { Modal } from "../Modal";
|
||||||
|
|
||||||
interface InnerButtonProps extends ComponentPropsWithoutRef<"button"> {
|
interface InnerButtonProps extends ComponentPropsWithoutRef<"button"> {
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import { merge } from "lodash-es";
|
|||||||
import { getUrlParams } from "../UrlParams";
|
import { getUrlParams } from "../UrlParams";
|
||||||
import {
|
import {
|
||||||
DEFAULT_CONFIG,
|
DEFAULT_CONFIG,
|
||||||
ConfigOptions,
|
type ConfigOptions,
|
||||||
ResolvedConfigOptions,
|
type ResolvedConfigOptions,
|
||||||
} from "./ConfigOptions";
|
} from "./ConfigOptions";
|
||||||
|
|
||||||
export class Config {
|
export class Config {
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export interface ConfigOptions {
|
|||||||
// a livekit service url in the client well-known.
|
// a livekit service url in the client well-known.
|
||||||
// The well known needs to be formatted like so:
|
// The well known needs to be formatted like so:
|
||||||
// {"type":"livekit", "livekit_service_url":"https://livekit.example.com"}
|
// {"type":"livekit", "livekit_service_url":"https://livekit.example.com"}
|
||||||
// and stored under the key: "livekit_focus"
|
// and stored under the key: "org.matrix.msc4143.rtc_foci"
|
||||||
livekit_service_url: string;
|
livekit_service_url: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Please see LICENSE in the repository root for full details.
|
|||||||
|
|
||||||
import { describe, expect, test, vi } from "vitest";
|
import { describe, expect, test, vi } from "vitest";
|
||||||
import {
|
import {
|
||||||
MatrixRTCSession,
|
type MatrixRTCSession,
|
||||||
MatrixRTCSessionEvent,
|
MatrixRTCSessionEvent,
|
||||||
} from "matrix-js-sdk/src/matrixrtc";
|
} from "matrix-js-sdk/src/matrixrtc";
|
||||||
import { KeyProviderEvent } from "livekit-client";
|
import { KeyProviderEvent } from "livekit-client";
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ Please see LICENSE in the repository root for full details.
|
|||||||
import { BaseKeyProvider, createKeyMaterialFromBuffer } from "livekit-client";
|
import { BaseKeyProvider, createKeyMaterialFromBuffer } from "livekit-client";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import {
|
import {
|
||||||
MatrixRTCSession,
|
type MatrixRTCSession,
|
||||||
MatrixRTCSessionEvent,
|
MatrixRTCSessionEvent,
|
||||||
} from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
} from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ Please see LICENSE in the repository root for full details.
|
|||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
|
|
||||||
import { setLocalStorageItem, useLocalStorage } from "../useLocalStorage";
|
import { setLocalStorageItem, useLocalStorage } from "../useLocalStorage";
|
||||||
import { UrlParams, getUrlParams, useUrlParams } from "../UrlParams";
|
import { type UrlParams, getUrlParams, useUrlParams } from "../UrlParams";
|
||||||
import { E2eeType } from "./e2eeType";
|
import { E2eeType } from "./e2eeType";
|
||||||
import { useClient } from "../ClientContext";
|
import { useClient } from "../ClientContext";
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { FormEventHandler, forwardRef, ReactNode } from "react";
|
import { type FormEventHandler, forwardRef, type ReactNode } from "react";
|
||||||
|
|
||||||
import styles from "./Form.module.css";
|
import styles from "./Form.module.css";
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { BehaviorSubject, Observable } from "rxjs";
|
import { type BehaviorSubject, type Observable } from "rxjs";
|
||||||
import { ComponentType } from "react";
|
import { type ComponentType } from "react";
|
||||||
|
|
||||||
import { LayoutProps } from "./Grid";
|
import { type LayoutProps } from "./Grid";
|
||||||
import { TileViewModel } from "../state/TileViewModel";
|
import { type TileViewModel } from "../state/TileViewModel";
|
||||||
|
|
||||||
export interface Bounds {
|
export interface Bounds {
|
||||||
width: number;
|
width: number;
|
||||||
|
|||||||
@@ -6,25 +6,24 @@ Please see LICENSE in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
SpringRef,
|
type SpringRef,
|
||||||
TransitionFn,
|
type TransitionFn,
|
||||||
animated,
|
type animated,
|
||||||
useTransition,
|
useTransition,
|
||||||
} from "@react-spring/web";
|
} from "@react-spring/web";
|
||||||
import { EventTypes, Handler, useScroll } from "@use-gesture/react";
|
import { type EventTypes, type Handler, useScroll } from "@use-gesture/react";
|
||||||
import {
|
import {
|
||||||
CSSProperties,
|
type CSSProperties,
|
||||||
ComponentProps,
|
type ComponentProps,
|
||||||
ComponentType,
|
type ComponentType,
|
||||||
Dispatch,
|
type Dispatch,
|
||||||
FC,
|
type FC,
|
||||||
LegacyRef,
|
type LegacyRef,
|
||||||
ReactNode,
|
type ReactNode,
|
||||||
SetStateAction,
|
type SetStateAction,
|
||||||
createContext,
|
createContext,
|
||||||
forwardRef,
|
forwardRef,
|
||||||
memo,
|
memo,
|
||||||
useCallback,
|
|
||||||
useContext,
|
useContext,
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
@@ -54,7 +53,6 @@ interface Tile<Model> {
|
|||||||
id: string;
|
id: string;
|
||||||
model: Model;
|
model: Model;
|
||||||
onDrag: DragCallback | undefined;
|
onDrag: DragCallback | undefined;
|
||||||
setVisible: (visible: boolean) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type PlacedTile<Model> = Tile<Model> & Rect;
|
type PlacedTile<Model> = Tile<Model> & Rect;
|
||||||
@@ -88,7 +86,6 @@ interface SlotProps<Model> extends Omit<ComponentProps<"div">, "onDrag"> {
|
|||||||
id: string;
|
id: string;
|
||||||
model: Model;
|
model: Model;
|
||||||
onDrag?: DragCallback;
|
onDrag?: DragCallback;
|
||||||
onVisibilityChange?: (visible: boolean) => void;
|
|
||||||
style?: CSSProperties;
|
style?: CSSProperties;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
@@ -115,24 +112,47 @@ function offset(element: HTMLElement, relativeTo: Element): Offset {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type VisibleTilesCallback = (visibleTiles: number) => void;
|
||||||
|
|
||||||
interface LayoutContext {
|
interface LayoutContext {
|
||||||
setGeneration: Dispatch<SetStateAction<number | null>>;
|
setGeneration: Dispatch<SetStateAction<number | null>>;
|
||||||
|
setVisibleTilesCallback: Dispatch<
|
||||||
|
SetStateAction<VisibleTilesCallback | null>
|
||||||
|
>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const LayoutContext = createContext<LayoutContext | null>(null);
|
const LayoutContext = createContext<LayoutContext | null>(null);
|
||||||
|
|
||||||
|
function useLayoutContext(): LayoutContext {
|
||||||
|
const context = useContext(LayoutContext);
|
||||||
|
if (context === null)
|
||||||
|
throw new Error("useUpdateLayout called outside a Grid layout context");
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enables Grid to react to layout changes. You must call this in your Layout
|
* Enables Grid to react to layout changes. You must call this in your Layout
|
||||||
* component or else Grid will not be reactive.
|
* component or else Grid will not be reactive.
|
||||||
*/
|
*/
|
||||||
export function useUpdateLayout(): void {
|
export function useUpdateLayout(): void {
|
||||||
const context = useContext(LayoutContext);
|
const { setGeneration } = useLayoutContext();
|
||||||
if (context === null)
|
|
||||||
throw new Error("useUpdateLayout called outside a Grid layout context");
|
|
||||||
|
|
||||||
// On every render, tell Grid that the layout may have changed
|
// On every render, tell Grid that the layout may have changed
|
||||||
useEffect(() =>
|
useEffect(() => setGeneration((prev) => (prev === null ? 0 : prev + 1)));
|
||||||
context.setGeneration((prev) => (prev === null ? 0 : prev + 1)),
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asks Grid to call a callback whenever the number of visible tiles may have
|
||||||
|
* changed.
|
||||||
|
*/
|
||||||
|
export function useVisibleTiles(callback: VisibleTilesCallback): void {
|
||||||
|
const { setVisibleTilesCallback } = useLayoutContext();
|
||||||
|
useEffect(
|
||||||
|
() => setVisibleTilesCallback(() => callback),
|
||||||
|
[callback, setVisibleTilesCallback],
|
||||||
|
);
|
||||||
|
useEffect(
|
||||||
|
() => (): void => setVisibleTilesCallback(null),
|
||||||
|
[setVisibleTilesCallback],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,39 +265,20 @@ export function Grid<
|
|||||||
const windowHeight = useObservableEagerState(windowHeightObservable);
|
const windowHeight = useObservableEagerState(windowHeightObservable);
|
||||||
const [layoutRoot, setLayoutRoot] = useState<HTMLElement | null>(null);
|
const [layoutRoot, setLayoutRoot] = useState<HTMLElement | null>(null);
|
||||||
const [generation, setGeneration] = useState<number | null>(null);
|
const [generation, setGeneration] = useState<number | null>(null);
|
||||||
|
const [visibleTilesCallback, setVisibleTilesCallback] =
|
||||||
|
useState<VisibleTilesCallback | null>(null);
|
||||||
const tiles = useInitial(() => new Map<string, Tile<TileModel>>());
|
const tiles = useInitial(() => new Map<string, Tile<TileModel>>());
|
||||||
const prefersReducedMotion = usePrefersReducedMotion();
|
const prefersReducedMotion = usePrefersReducedMotion();
|
||||||
|
|
||||||
const Slot: FC<SlotProps<TileModel>> = useMemo(
|
const Slot: FC<SlotProps<TileModel>> = useMemo(
|
||||||
() =>
|
() =>
|
||||||
function Slot({
|
function Slot({ id, model, onDrag, style, className, ...props }) {
|
||||||
id,
|
|
||||||
model,
|
|
||||||
onDrag,
|
|
||||||
onVisibilityChange,
|
|
||||||
style,
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}) {
|
|
||||||
const ref = useRef<HTMLDivElement | null>(null);
|
const ref = useRef<HTMLDivElement | null>(null);
|
||||||
const prevVisible = useRef<boolean | null>(null);
|
|
||||||
const setVisible = useCallback(
|
|
||||||
(visible: boolean) => {
|
|
||||||
if (
|
|
||||||
onVisibilityChange !== undefined &&
|
|
||||||
visible !== prevVisible.current
|
|
||||||
) {
|
|
||||||
onVisibilityChange(visible);
|
|
||||||
prevVisible.current = visible;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[onVisibilityChange],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
tiles.set(id, { id, model, onDrag, setVisible });
|
tiles.set(id, { id, model, onDrag });
|
||||||
return (): void => void tiles.delete(id);
|
return (): void => void tiles.delete(id);
|
||||||
}, [id, model, onDrag, setVisible]);
|
}, [id, model, onDrag]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -307,7 +308,10 @@ export function Grid<
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const context: LayoutContext = useMemo(() => ({ setGeneration }), []);
|
const context: LayoutContext = useMemo(
|
||||||
|
() => ({ setGeneration, setVisibleTilesCallback }),
|
||||||
|
[setVisibleTilesCallback],
|
||||||
|
);
|
||||||
|
|
||||||
// Combine the tile definitions and slots together to create placed tiles
|
// Combine the tile definitions and slots together to create placed tiles
|
||||||
const placedTiles = useMemo(() => {
|
const placedTiles = useMemo(() => {
|
||||||
@@ -342,9 +346,11 @@ export function Grid<
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
for (const tile of placedTiles)
|
visibleTilesCallback?.(
|
||||||
tile.setVisible(tile.y + tile.height <= visibleHeight);
|
placedTiles.filter((tile) => tile.y + tile.height <= visibleHeight)
|
||||||
}, [placedTiles, visibleHeight]);
|
.length,
|
||||||
|
);
|
||||||
|
}, [placedTiles, visibleTilesCallback, visibleHeight]);
|
||||||
|
|
||||||
// Drag state is stored in a ref rather than component state, because we use
|
// Drag state is stored in a ref rather than component state, because we use
|
||||||
// react-spring's imperative API during gestures to improve responsiveness
|
// react-spring's imperative API during gestures to improve responsiveness
|
||||||
|
|||||||
@@ -5,15 +5,15 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CSSProperties, forwardRef, useCallback, useMemo } from "react";
|
import { type CSSProperties, forwardRef, useCallback, useMemo } from "react";
|
||||||
import { distinctUntilChanged } from "rxjs";
|
import { distinctUntilChanged } from "rxjs";
|
||||||
import { useObservableEagerState } from "observable-hooks";
|
import { useObservableEagerState } from "observable-hooks";
|
||||||
|
|
||||||
import { GridLayout as GridLayoutModel } from "../state/CallViewModel";
|
import { type GridLayout as GridLayoutModel } from "../state/CallViewModel";
|
||||||
import styles from "./GridLayout.module.css";
|
import styles from "./GridLayout.module.css";
|
||||||
import { useInitial } from "../useInitial";
|
import { useInitial } from "../useInitial";
|
||||||
import { CallLayout, arrangeTiles } from "./CallLayout";
|
import { type CallLayout, arrangeTiles } from "./CallLayout";
|
||||||
import { DragCallback, useUpdateLayout } from "./Grid";
|
import { type DragCallback, useUpdateLayout, useVisibleTiles } from "./Grid";
|
||||||
|
|
||||||
interface GridCSSProperties extends CSSProperties {
|
interface GridCSSProperties extends CSSProperties {
|
||||||
"--gap": string;
|
"--gap": string;
|
||||||
@@ -73,6 +73,7 @@ export const makeGridLayout: CallLayout<GridLayoutModel> = ({
|
|||||||
// The scrolling part of the layout is where all the grid tiles live
|
// The scrolling part of the layout is where all the grid tiles live
|
||||||
scrolling: forwardRef(function GridLayout({ model, Slot }, ref) {
|
scrolling: forwardRef(function GridLayout({ model, Slot }, ref) {
|
||||||
useUpdateLayout();
|
useUpdateLayout();
|
||||||
|
useVisibleTiles(model.setVisibleTiles);
|
||||||
const { width, height: minHeight } = useObservableEagerState(minBounds);
|
const { width, height: minHeight } = useObservableEagerState(minBounds);
|
||||||
const { gap, tileWidth, tileHeight } = useMemo(
|
const { gap, tileWidth, tileHeight } = useMemo(
|
||||||
() => arrangeTiles(width, minHeight, model.grid.length),
|
() => arrangeTiles(width, minHeight, model.grid.length),
|
||||||
@@ -93,13 +94,7 @@ export const makeGridLayout: CallLayout<GridLayoutModel> = ({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{model.grid.map((m) => (
|
{model.grid.map((m) => (
|
||||||
<Slot
|
<Slot key={m.id} className={styles.slot} id={m.id} model={m} />
|
||||||
key={m.id}
|
|
||||||
className={styles.slot}
|
|
||||||
id={m.id}
|
|
||||||
model={m}
|
|
||||||
onVisibilityChange={m.setVisible}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ import { forwardRef, useCallback, useMemo } from "react";
|
|||||||
import { useObservableEagerState } from "observable-hooks";
|
import { useObservableEagerState } from "observable-hooks";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
import { OneOnOneLayout as OneOnOneLayoutModel } from "../state/CallViewModel";
|
import { type OneOnOneLayout as OneOnOneLayoutModel } from "../state/CallViewModel";
|
||||||
import { CallLayout, arrangeTiles } from "./CallLayout";
|
import { type CallLayout, arrangeTiles } from "./CallLayout";
|
||||||
import styles from "./OneOnOneLayout.module.css";
|
import styles from "./OneOnOneLayout.module.css";
|
||||||
import { DragCallback, useUpdateLayout } from "./Grid";
|
import { type DragCallback, useUpdateLayout } from "./Grid";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An implementation of the "one-on-one" layout, in which the remote participant
|
* An implementation of the "one-on-one" layout, in which the remote participant
|
||||||
@@ -52,7 +52,6 @@ export const makeOneOnOneLayout: CallLayout<OneOnOneLayoutModel> = ({
|
|||||||
<Slot
|
<Slot
|
||||||
id={model.remote.id}
|
id={model.remote.id}
|
||||||
model={model.remote}
|
model={model.remote}
|
||||||
onVisibilityChange={model.remote.setVisible}
|
|
||||||
className={styles.container}
|
className={styles.container}
|
||||||
style={{ width: tileWidth, height: tileHeight }}
|
style={{ width: tileWidth, height: tileHeight }}
|
||||||
>
|
>
|
||||||
@@ -61,7 +60,6 @@ export const makeOneOnOneLayout: CallLayout<OneOnOneLayoutModel> = ({
|
|||||||
id={model.local.id}
|
id={model.local.id}
|
||||||
model={model.local}
|
model={model.local}
|
||||||
onDrag={onDragLocalTile}
|
onDrag={onDragLocalTile}
|
||||||
onVisibilityChange={model.local.setVisible}
|
|
||||||
data-block-alignment={pipAlignmentValue.block}
|
data-block-alignment={pipAlignmentValue.block}
|
||||||
data-inline-alignment={pipAlignmentValue.inline}
|
data-inline-alignment={pipAlignmentValue.inline}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ Please see LICENSE in the repository root for full details.
|
|||||||
import { forwardRef, useCallback } from "react";
|
import { forwardRef, useCallback } from "react";
|
||||||
import { useObservableEagerState } from "observable-hooks";
|
import { useObservableEagerState } from "observable-hooks";
|
||||||
|
|
||||||
import { SpotlightExpandedLayout as SpotlightExpandedLayoutModel } from "../state/CallViewModel";
|
import { type SpotlightExpandedLayout as SpotlightExpandedLayoutModel } from "../state/CallViewModel";
|
||||||
import { CallLayout } from "./CallLayout";
|
import { type CallLayout } from "./CallLayout";
|
||||||
import { DragCallback, useUpdateLayout } from "./Grid";
|
import { type DragCallback, useUpdateLayout } from "./Grid";
|
||||||
import styles from "./SpotlightExpandedLayout.module.css";
|
import styles from "./SpotlightExpandedLayout.module.css";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,7 +63,6 @@ export const makeSpotlightExpandedLayout: CallLayout<
|
|||||||
id={model.pip.id}
|
id={model.pip.id}
|
||||||
model={model.pip}
|
model={model.pip}
|
||||||
onDrag={onDragPip}
|
onDrag={onDragPip}
|
||||||
onVisibilityChange={model.pip.setVisible}
|
|
||||||
data-block-alignment={pipAlignmentValue.block}
|
data-block-alignment={pipAlignmentValue.block}
|
||||||
data-inline-alignment={pipAlignmentValue.inline}
|
data-inline-alignment={pipAlignmentValue.inline}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ import { forwardRef } from "react";
|
|||||||
import { useObservableEagerState } from "observable-hooks";
|
import { useObservableEagerState } from "observable-hooks";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
import { CallLayout } from "./CallLayout";
|
import { type CallLayout } from "./CallLayout";
|
||||||
import { SpotlightLandscapeLayout as SpotlightLandscapeLayoutModel } from "../state/CallViewModel";
|
import { type SpotlightLandscapeLayout as SpotlightLandscapeLayoutModel } from "../state/CallViewModel";
|
||||||
import styles from "./SpotlightLandscapeLayout.module.css";
|
import styles from "./SpotlightLandscapeLayout.module.css";
|
||||||
import { useUpdateLayout } from "./Grid";
|
import { useUpdateLayout, useVisibleTiles } from "./Grid";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An implementation of the "spotlight landscape" layout, in which the spotlight
|
* An implementation of the "spotlight landscape" layout, in which the spotlight
|
||||||
@@ -50,6 +50,7 @@ export const makeSpotlightLandscapeLayout: CallLayout<
|
|||||||
ref,
|
ref,
|
||||||
) {
|
) {
|
||||||
useUpdateLayout();
|
useUpdateLayout();
|
||||||
|
useVisibleTiles(model.setVisibleTiles);
|
||||||
useObservableEagerState(minBounds);
|
useObservableEagerState(minBounds);
|
||||||
const withIndicators =
|
const withIndicators =
|
||||||
useObservableEagerState(model.spotlight.media).length > 1;
|
useObservableEagerState(model.spotlight.media).length > 1;
|
||||||
@@ -63,13 +64,7 @@ export const makeSpotlightLandscapeLayout: CallLayout<
|
|||||||
/>
|
/>
|
||||||
<div className={styles.grid}>
|
<div className={styles.grid}>
|
||||||
{model.grid.map((m) => (
|
{model.grid.map((m) => (
|
||||||
<Slot
|
<Slot key={m.id} className={styles.slot} id={m.id} model={m} />
|
||||||
key={m.id}
|
|
||||||
className={styles.slot}
|
|
||||||
id={m.id}
|
|
||||||
model={m}
|
|
||||||
onVisibilityChange={m.setVisible}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,14 +5,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CSSProperties, forwardRef } from "react";
|
import { type CSSProperties, forwardRef } from "react";
|
||||||
import { useObservableEagerState } from "observable-hooks";
|
import { useObservableEagerState } from "observable-hooks";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
import { CallLayout, arrangeTiles } from "./CallLayout";
|
import { type CallLayout, arrangeTiles } from "./CallLayout";
|
||||||
import { SpotlightPortraitLayout as SpotlightPortraitLayoutModel } from "../state/CallViewModel";
|
import { type SpotlightPortraitLayout as SpotlightPortraitLayoutModel } from "../state/CallViewModel";
|
||||||
import styles from "./SpotlightPortraitLayout.module.css";
|
import styles from "./SpotlightPortraitLayout.module.css";
|
||||||
import { useUpdateLayout } from "./Grid";
|
import { useUpdateLayout, useVisibleTiles } from "./Grid";
|
||||||
|
|
||||||
interface GridCSSProperties extends CSSProperties {
|
interface GridCSSProperties extends CSSProperties {
|
||||||
"--grid-gap": string;
|
"--grid-gap": string;
|
||||||
@@ -54,6 +54,7 @@ export const makeSpotlightPortraitLayout: CallLayout<
|
|||||||
ref,
|
ref,
|
||||||
) {
|
) {
|
||||||
useUpdateLayout();
|
useUpdateLayout();
|
||||||
|
useVisibleTiles(model.setVisibleTiles);
|
||||||
const { width } = useObservableEagerState(minBounds);
|
const { width } = useObservableEagerState(minBounds);
|
||||||
const { gap, tileWidth, tileHeight } = arrangeTiles(
|
const { gap, tileWidth, tileHeight } = arrangeTiles(
|
||||||
width,
|
width,
|
||||||
@@ -84,13 +85,7 @@ export const makeSpotlightPortraitLayout: CallLayout<
|
|||||||
/>
|
/>
|
||||||
<div className={styles.grid}>
|
<div className={styles.grid}>
|
||||||
{model.grid.map((m) => (
|
{model.grid.map((m) => (
|
||||||
<Slot
|
<Slot key={m.id} className={styles.slot} id={m.id} model={m} />
|
||||||
key={m.id}
|
|
||||||
className={styles.slot}
|
|
||||||
id={m.id}
|
|
||||||
model={m}
|
|
||||||
onVisibilityChange={m.setVisible}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ComponentType, memo, RefObject, useRef } from "react";
|
import { type ComponentType, memo, type RefObject, useRef } from "react";
|
||||||
import { EventTypes, Handler, useDrag } from "@use-gesture/react";
|
import { type EventTypes, type Handler, useDrag } from "@use-gesture/react";
|
||||||
import { SpringValue } from "@react-spring/web";
|
import { type SpringValue } from "@react-spring/web";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
import { TileProps } from "./Grid";
|
import { type TileProps } from "./Grid";
|
||||||
import styles from "./TileWrapper.module.css";
|
import styles from "./TileWrapper.module.css";
|
||||||
|
|
||||||
interface Props<M, R extends HTMLElement> {
|
interface Props<M, R extends HTMLElement> {
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { render, RenderResult } from "@testing-library/react";
|
import { render, type RenderResult } from "@testing-library/react";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
import { type MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||||
import { MemoryRouter } from "react-router-dom";
|
import { MemoryRouter } from "react-router-dom";
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
import { CallList } from "../../src/home/CallList";
|
import { CallList } from "../../src/home/CallList";
|
||||||
import { GroupCallRoom } from "../../src/home/useGroupCallRooms";
|
import { type GroupCallRoom } from "../../src/home/useGroupCallRooms";
|
||||||
|
|
||||||
describe("CallList", () => {
|
describe("CallList", () => {
|
||||||
const renderComponent = (rooms: GroupCallRoom[]): RenderResult => {
|
const renderComponent = (rooms: GroupCallRoom[]): RenderResult => {
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ Please see LICENSE in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { type MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
import { type RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { type Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { FC, useCallback, MouseEvent, useState } from "react";
|
import { type FC, useCallback, type MouseEvent, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { IconButton, Text } from "@vector-im/compound-web";
|
import { IconButton, Text } from "@vector-im/compound-web";
|
||||||
import { CloseIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
import { CloseIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||||
@@ -18,7 +18,7 @@ import classNames from "classnames";
|
|||||||
import { Avatar, Size } from "../Avatar";
|
import { Avatar, Size } from "../Avatar";
|
||||||
import styles from "./CallList.module.css";
|
import styles from "./CallList.module.css";
|
||||||
import { getRelativeRoomUrl } from "../utils/matrix";
|
import { getRelativeRoomUrl } from "../utils/matrix";
|
||||||
import { GroupCallRoom } from "./useGroupCallRooms";
|
import { type GroupCallRoom } from "./useGroupCallRooms";
|
||||||
import { useRoomEncryptionSystem } from "../e2ee/sharedKeyManagement";
|
import { useRoomEncryptionSystem } from "../e2ee/sharedKeyManagement";
|
||||||
|
|
||||||
interface CallListProps {
|
interface CallListProps {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { FC } from "react";
|
import { type FC } from "react";
|
||||||
|
|
||||||
import { useClientState } from "../ClientContext";
|
import { useClientState } from "../ClientContext";
|
||||||
import { ErrorView, LoadingView } from "../FullScreenView";
|
import { ErrorView, LoadingView } from "../FullScreenView";
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { FC, MouseEvent } from "react";
|
import { type FC, type MouseEvent } from "react";
|
||||||
import { Button } from "@vector-im/compound-web";
|
import { Button } from "@vector-im/compound-web";
|
||||||
|
|
||||||
import { Modal } from "../Modal";
|
import { Modal } from "../Modal";
|
||||||
|
|||||||
@@ -5,9 +5,15 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useCallback, FormEvent, FormEventHandler, FC } from "react";
|
import {
|
||||||
|
useState,
|
||||||
|
useCallback,
|
||||||
|
type FormEvent,
|
||||||
|
type FormEventHandler,
|
||||||
|
type FC,
|
||||||
|
} from "react";
|
||||||
import { useHistory } from "react-router-dom";
|
import { useHistory } from "react-router-dom";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { type MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Heading, Text } from "@vector-im/compound-web";
|
import { Heading, Text } from "@vector-im/compound-web";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FC, useCallback, useState, FormEventHandler } from "react";
|
import { type FC, useCallback, useState, type FormEventHandler } from "react";
|
||||||
import { useHistory } from "react-router-dom";
|
import { useHistory } from "react-router-dom";
|
||||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { type MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import { Room, RoomEvent } from "matrix-js-sdk/src/models/room";
|
import { type Room, RoomEvent } from "matrix-js-sdk/src/models/room";
|
||||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
import { type RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { EventTimeline, EventType, JoinRule } from "matrix-js-sdk/src/matrix";
|
import { EventTimeline, EventType, JoinRule } from "matrix-js-sdk/src/matrix";
|
||||||
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
||||||
import { MatrixRTCSessionManagerEvents } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSessionManager";
|
import { MatrixRTCSessionManagerEvents } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSessionManager";
|
||||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,11 @@ layer(compound);
|
|||||||
--background-gradient: url("graphics/backgroundGradient.svg");
|
--background-gradient: url("graphics/backgroundGradient.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:root,
|
||||||
|
[class*="cpd-theme-"] {
|
||||||
|
--video-tile-background: var(--cpd-color-bg-subtle-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
.cpd-theme-dark {
|
.cpd-theme-dark {
|
||||||
--cpd-color-border-accent: var(--cpd-color-green-1100);
|
--cpd-color-border-accent: var(--cpd-color-green-1100);
|
||||||
--stopgap-color-on-solid-accent: var(--cpd-color-text-primary);
|
--stopgap-color-on-solid-accent: var(--cpd-color-text-primary);
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import { platform } from "./Platform";
|
|||||||
|
|
||||||
// This generates a map of locale names to their URL (based on import.meta.url), which looks like this:
|
// This generates a map of locale names to their URL (based on import.meta.url), which looks like this:
|
||||||
// {
|
// {
|
||||||
// "../locales/en-GB/app.json": "/whatever/assets/root/locales/en-aabbcc.json",
|
// "../locales/en/app.json": "/whatever/assets/root/locales/en-aabbcc.json",
|
||||||
// ...
|
// ...
|
||||||
// }
|
// }
|
||||||
const locales = import.meta.glob<string>("../locales/*/*.json", {
|
const locales = import.meta.glob<string>("../locales/*/*.json", {
|
||||||
@@ -41,7 +41,7 @@ const getLocaleUrl = (
|
|||||||
const supportedLngs = [
|
const supportedLngs = [
|
||||||
...new Set(
|
...new Set(
|
||||||
Object.keys(locales).map((url) => {
|
Object.keys(locales).map((url) => {
|
||||||
// The URLs are of the form ../locales/en-GB/app.json
|
// The URLs are of the form ../locales/en/app.json
|
||||||
// This extracts the language code from the URL
|
// This extracts the language code from the URL
|
||||||
const lang = url.match(/\/([^/]+)\/[^/]+\.json$/)?.[1];
|
const lang = url.match(/\/([^/]+)\/[^/]+\.json$/)?.[1];
|
||||||
if (!lang) {
|
if (!lang) {
|
||||||
@@ -133,7 +133,7 @@ export class Initializer {
|
|||||||
.use(languageDetector)
|
.use(languageDetector)
|
||||||
.use(initReactI18next)
|
.use(initReactI18next)
|
||||||
.init({
|
.init({
|
||||||
fallbackLng: "en-GB",
|
fallbackLng: "en",
|
||||||
defaultNS: "app",
|
defaultNS: "app",
|
||||||
keySeparator: ".",
|
keySeparator: ".",
|
||||||
nsSeparator: false,
|
nsSeparator: false,
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ Please see LICENSE in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AllHTMLAttributes,
|
type AllHTMLAttributes,
|
||||||
useEffect,
|
useEffect,
|
||||||
useCallback,
|
useCallback,
|
||||||
useState,
|
useState,
|
||||||
ChangeEvent,
|
type ChangeEvent,
|
||||||
useRef,
|
useRef,
|
||||||
FC,
|
type FC,
|
||||||
} from "react";
|
} from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ Please see LICENSE in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ChangeEvent,
|
type ChangeEvent,
|
||||||
FC,
|
type FC,
|
||||||
ForwardedRef,
|
type ForwardedRef,
|
||||||
forwardRef,
|
forwardRef,
|
||||||
ReactNode,
|
type ReactNode,
|
||||||
useId,
|
useId,
|
||||||
} from "react";
|
} from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
@@ -73,6 +73,7 @@ interface InputFieldProps {
|
|||||||
defaultValue?: string;
|
defaultValue?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
defaultChecked?: boolean;
|
defaultChecked?: boolean;
|
||||||
|
min?: number;
|
||||||
onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
|
onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,6 +92,7 @@ export const InputField = forwardRef<
|
|||||||
suffix,
|
suffix,
|
||||||
description,
|
description,
|
||||||
disabled,
|
disabled,
|
||||||
|
min,
|
||||||
...rest
|
...rest
|
||||||
},
|
},
|
||||||
ref,
|
ref,
|
||||||
@@ -127,6 +129,7 @@ export const InputField = forwardRef<
|
|||||||
checked={checked}
|
checked={checked}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
aria-describedby={descriptionId}
|
aria-describedby={descriptionId}
|
||||||
|
min={min}
|
||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FC,
|
type FC,
|
||||||
createContext,
|
createContext,
|
||||||
useCallback,
|
useCallback,
|
||||||
useContext,
|
useContext,
|
||||||
@@ -25,7 +25,7 @@ import {
|
|||||||
audioInput as audioInputSetting,
|
audioInput as audioInputSetting,
|
||||||
audioOutput as audioOutputSetting,
|
audioOutput as audioOutputSetting,
|
||||||
videoInput as videoInputSetting,
|
videoInput as videoInputSetting,
|
||||||
Setting,
|
type Setting,
|
||||||
} from "../settings/settings";
|
} from "../settings/settings";
|
||||||
|
|
||||||
export type DeviceLabel =
|
export type DeviceLabel =
|
||||||
@@ -143,13 +143,13 @@ function useMediaDevice(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const deviceStub: MediaDevice = {
|
export const deviceStub: MediaDevice = {
|
||||||
available: new Map(),
|
available: new Map(),
|
||||||
selectedId: undefined,
|
selectedId: undefined,
|
||||||
selectedGroupId: undefined,
|
selectedGroupId: undefined,
|
||||||
select: () => {},
|
select: () => {},
|
||||||
};
|
};
|
||||||
const devicesStub: MediaDevices = {
|
export const devicesStub: MediaDevices = {
|
||||||
audioInput: deviceStub,
|
audioInput: deviceStub,
|
||||||
audioOutput: deviceStub,
|
audioOutput: deviceStub,
|
||||||
videoInput: deviceStub,
|
videoInput: deviceStub,
|
||||||
@@ -157,7 +157,7 @@ const devicesStub: MediaDevices = {
|
|||||||
stopUsingDeviceNames: () => {},
|
stopUsingDeviceNames: () => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
const MediaDevicesContext = createContext<MediaDevices>(devicesStub);
|
export const MediaDevicesContext = createContext<MediaDevices>(devicesStub);
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: JSX.Element;
|
children: JSX.Element;
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IOpenIDToken, MatrixClient } from "matrix-js-sdk/src/matrix";
|
import { type IOpenIDToken, type MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { LivekitFocus } from "matrix-js-sdk/src/matrixrtc/LivekitFocus";
|
import { type LivekitFocus } from "matrix-js-sdk/src/matrixrtc/LivekitFocus";
|
||||||
|
|
||||||
import { useActiveLivekitFocus } from "../room/useActiveFocus";
|
import { useActiveLivekitFocus } from "../room/useActiveFocus";
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ Please see LICENSE in the repository root for full details.
|
|||||||
import {
|
import {
|
||||||
AudioPresets,
|
AudioPresets,
|
||||||
DefaultReconnectPolicy,
|
DefaultReconnectPolicy,
|
||||||
RoomOptions,
|
type RoomOptions,
|
||||||
ScreenSharePresets,
|
ScreenSharePresets,
|
||||||
TrackPublishDefaults,
|
type TrackPublishDefaults,
|
||||||
VideoPreset,
|
type VideoPreset,
|
||||||
VideoPresets,
|
VideoPresets,
|
||||||
} from "livekit-client";
|
} from "livekit-client";
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ Please see LICENSE in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AudioCaptureOptions,
|
type AudioCaptureOptions,
|
||||||
ConnectionState,
|
ConnectionState,
|
||||||
LocalTrack,
|
type LocalTrack,
|
||||||
Room,
|
type Room,
|
||||||
RoomEvent,
|
RoomEvent,
|
||||||
Track,
|
Track,
|
||||||
} from "livekit-client";
|
} from "livekit-client";
|
||||||
@@ -17,7 +17,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
|||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import * as Sentry from "@sentry/react";
|
import * as Sentry from "@sentry/react";
|
||||||
|
|
||||||
import { SFUConfig, sfuConfigEquals } from "./openIDSFU";
|
import { type SFUConfig, sfuConfigEquals } from "./openIDSFU";
|
||||||
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
|
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
|||||||
@@ -7,32 +7,32 @@ Please see LICENSE in the repository root for full details.
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
ConnectionState,
|
ConnectionState,
|
||||||
E2EEOptions,
|
type E2EEManagerOptions,
|
||||||
ExternalE2EEKeyProvider,
|
ExternalE2EEKeyProvider,
|
||||||
Room,
|
Room,
|
||||||
RoomOptions,
|
type RoomOptions,
|
||||||
Track,
|
Track,
|
||||||
} from "livekit-client";
|
} from "livekit-client";
|
||||||
import { useEffect, useMemo, useRef } from "react";
|
import { useEffect, useMemo, useRef } from "react";
|
||||||
import E2EEWorker from "livekit-client/e2ee-worker?worker";
|
import E2EEWorker from "livekit-client/e2ee-worker?worker";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
||||||
|
|
||||||
import { defaultLiveKitOptions } from "./options";
|
import { defaultLiveKitOptions } from "./options";
|
||||||
import { SFUConfig } from "./openIDSFU";
|
import { type SFUConfig } from "./openIDSFU";
|
||||||
import { MuteStates } from "../room/MuteStates";
|
import { type MuteStates } from "../room/MuteStates";
|
||||||
import {
|
import {
|
||||||
MediaDevice,
|
type MediaDevice,
|
||||||
MediaDevices,
|
type MediaDevices,
|
||||||
useMediaDevices,
|
useMediaDevices,
|
||||||
} from "./MediaDevicesContext";
|
} from "./MediaDevicesContext";
|
||||||
import {
|
import {
|
||||||
ECConnectionState,
|
type ECConnectionState,
|
||||||
useECConnectionState,
|
useECConnectionState,
|
||||||
} from "./useECConnectionState";
|
} from "./useECConnectionState";
|
||||||
import { MatrixKeyProvider } from "../e2ee/matrixKeyProvider";
|
import { MatrixKeyProvider } from "../e2ee/matrixKeyProvider";
|
||||||
import { E2eeType } from "../e2ee/e2eeType";
|
import { E2eeType } from "../e2ee/e2eeType";
|
||||||
import { EncryptionSystem } from "../e2ee/sharedKeyManagement";
|
import { type EncryptionSystem } from "../e2ee/sharedKeyManagement";
|
||||||
|
|
||||||
interface UseLivekitResult {
|
interface UseLivekitResult {
|
||||||
livekitRoom?: Room;
|
livekitRoom?: Room;
|
||||||
@@ -45,7 +45,7 @@ export function useLiveKit(
|
|||||||
sfuConfig: SFUConfig | undefined,
|
sfuConfig: SFUConfig | undefined,
|
||||||
e2eeSystem: EncryptionSystem,
|
e2eeSystem: EncryptionSystem,
|
||||||
): UseLivekitResult {
|
): UseLivekitResult {
|
||||||
const e2eeOptions = useMemo((): E2EEOptions | undefined => {
|
const e2eeOptions = useMemo((): E2EEManagerOptions | undefined => {
|
||||||
if (e2eeSystem.kind === E2eeType.NONE) return undefined;
|
if (e2eeSystem.kind === E2eeType.NONE) return undefined;
|
||||||
|
|
||||||
if (e2eeSystem.kind === E2eeType.PER_PARTICIPANT) {
|
if (e2eeSystem.kind === E2eeType.PER_PARTICIPANT) {
|
||||||
|
|||||||
@@ -5,17 +5,17 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Span } from "@opentelemetry/api";
|
import { type Span } from "@opentelemetry/api";
|
||||||
import { MatrixCall } from "matrix-js-sdk/src/matrix";
|
import { type MatrixCall } from "matrix-js-sdk/src/matrix";
|
||||||
import { CallEvent } from "matrix-js-sdk/src/webrtc/call";
|
import { CallEvent } from "matrix-js-sdk/src/webrtc/call";
|
||||||
import {
|
import {
|
||||||
TransceiverStats,
|
type TransceiverStats,
|
||||||
CallFeedStats,
|
type CallFeedStats,
|
||||||
} from "matrix-js-sdk/src/webrtc/stats/statsReport";
|
} from "matrix-js-sdk/src/webrtc/stats/statsReport";
|
||||||
|
|
||||||
import { ObjectFlattener } from "./ObjectFlattener";
|
import { ObjectFlattener } from "./ObjectFlattener";
|
||||||
import { ElementCallOpenTelemetry } from "./otel";
|
import { ElementCallOpenTelemetry } from "./otel";
|
||||||
import { OTelCallAbstractMediaStreamSpan } from "./OTelCallAbstractMediaStreamSpan";
|
import { type OTelCallAbstractMediaStreamSpan } from "./OTelCallAbstractMediaStreamSpan";
|
||||||
import { OTelCallTransceiverMediaStreamSpan } from "./OTelCallTransceiverMediaStreamSpan";
|
import { OTelCallTransceiverMediaStreamSpan } from "./OTelCallTransceiverMediaStreamSpan";
|
||||||
import { OTelCallFeedMediaStreamSpan } from "./OTelCallFeedMediaStreamSpan";
|
import { OTelCallFeedMediaStreamSpan } from "./OTelCallFeedMediaStreamSpan";
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import opentelemetry, { Span } from "@opentelemetry/api";
|
import opentelemetry, { type Span } from "@opentelemetry/api";
|
||||||
import { TrackStats } from "matrix-js-sdk/src/webrtc/stats/statsReport";
|
import { type TrackStats } from "matrix-js-sdk/src/webrtc/stats/statsReport";
|
||||||
|
|
||||||
import { ElementCallOpenTelemetry } from "./otel";
|
import { type ElementCallOpenTelemetry } from "./otel";
|
||||||
import { OTelCallMediaStreamTrackSpan } from "./OTelCallMediaStreamTrackSpan";
|
import { OTelCallMediaStreamTrackSpan } from "./OTelCallMediaStreamTrackSpan";
|
||||||
|
|
||||||
type TrackId = string;
|
type TrackId = string;
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Span } from "@opentelemetry/api";
|
import { type Span } from "@opentelemetry/api";
|
||||||
import {
|
import {
|
||||||
CallFeedStats,
|
type CallFeedStats,
|
||||||
TrackStats,
|
type TrackStats,
|
||||||
} from "matrix-js-sdk/src/webrtc/stats/statsReport";
|
} from "matrix-js-sdk/src/webrtc/stats/statsReport";
|
||||||
|
|
||||||
import { ElementCallOpenTelemetry } from "./otel";
|
import { type ElementCallOpenTelemetry } from "./otel";
|
||||||
import { OTelCallAbstractMediaStreamSpan } from "./OTelCallAbstractMediaStreamSpan";
|
import { OTelCallAbstractMediaStreamSpan } from "./OTelCallAbstractMediaStreamSpan";
|
||||||
|
|
||||||
export class OTelCallFeedMediaStreamSpan extends OTelCallAbstractMediaStreamSpan {
|
export class OTelCallFeedMediaStreamSpan extends OTelCallAbstractMediaStreamSpan {
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { TrackStats } from "matrix-js-sdk/src/webrtc/stats/statsReport";
|
import { type TrackStats } from "matrix-js-sdk/src/webrtc/stats/statsReport";
|
||||||
import opentelemetry, { Span } from "@opentelemetry/api";
|
import opentelemetry, { type Span } from "@opentelemetry/api";
|
||||||
|
|
||||||
import { ElementCallOpenTelemetry } from "./otel";
|
import { type ElementCallOpenTelemetry } from "./otel";
|
||||||
|
|
||||||
export class OTelCallMediaStreamTrackSpan {
|
export class OTelCallMediaStreamTrackSpan {
|
||||||
private readonly span: Span;
|
private readonly span: Span;
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Span } from "@opentelemetry/api";
|
import { type Span } from "@opentelemetry/api";
|
||||||
import {
|
import {
|
||||||
TrackStats,
|
type TrackStats,
|
||||||
TransceiverStats,
|
type TransceiverStats,
|
||||||
} from "matrix-js-sdk/src/webrtc/stats/statsReport";
|
} from "matrix-js-sdk/src/webrtc/stats/statsReport";
|
||||||
|
|
||||||
import { ElementCallOpenTelemetry } from "./otel";
|
import { type ElementCallOpenTelemetry } from "./otel";
|
||||||
import { OTelCallAbstractMediaStreamSpan } from "./OTelCallAbstractMediaStreamSpan";
|
import { OTelCallAbstractMediaStreamSpan } from "./OTelCallAbstractMediaStreamSpan";
|
||||||
|
|
||||||
export class OTelCallTransceiverMediaStreamSpan extends OTelCallAbstractMediaStreamSpan {
|
export class OTelCallTransceiverMediaStreamSpan extends OTelCallAbstractMediaStreamSpan {
|
||||||
|
|||||||
@@ -5,31 +5,35 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import opentelemetry, { Span, Attributes, Context } from "@opentelemetry/api";
|
import opentelemetry, {
|
||||||
|
type Span,
|
||||||
|
type Attributes,
|
||||||
|
type Context,
|
||||||
|
} from "@opentelemetry/api";
|
||||||
import {
|
import {
|
||||||
GroupCall,
|
type GroupCall,
|
||||||
MatrixClient,
|
type MatrixClient,
|
||||||
MatrixEvent,
|
type MatrixEvent,
|
||||||
RoomMember,
|
type RoomMember,
|
||||||
} from "matrix-js-sdk/src/matrix";
|
} from "matrix-js-sdk/src/matrix";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import {
|
import {
|
||||||
CallError,
|
type CallError,
|
||||||
CallState,
|
type CallState,
|
||||||
MatrixCall,
|
type MatrixCall,
|
||||||
VoipEvent,
|
type VoipEvent,
|
||||||
} from "matrix-js-sdk/src/webrtc/call";
|
} from "matrix-js-sdk/src/webrtc/call";
|
||||||
import {
|
import {
|
||||||
CallsByUserAndDevice,
|
type CallsByUserAndDevice,
|
||||||
GroupCallError,
|
type GroupCallError,
|
||||||
GroupCallEvent,
|
GroupCallEvent,
|
||||||
GroupCallStatsReport,
|
type GroupCallStatsReport,
|
||||||
} from "matrix-js-sdk/src/webrtc/groupCall";
|
} from "matrix-js-sdk/src/webrtc/groupCall";
|
||||||
import {
|
import {
|
||||||
ConnectionStatsReport,
|
type ConnectionStatsReport,
|
||||||
ByteSentStatsReport,
|
type ByteSentStatsReport,
|
||||||
SummaryStatsReport,
|
type SummaryStatsReport,
|
||||||
CallFeedReport,
|
type CallFeedReport,
|
||||||
} from "matrix-js-sdk/src/webrtc/stats/statsReport";
|
} from "matrix-js-sdk/src/webrtc/stats/statsReport";
|
||||||
|
|
||||||
import { ElementCallOpenTelemetry } from "./otel";
|
import { ElementCallOpenTelemetry } from "./otel";
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { GroupCallStatsReport } from "matrix-js-sdk/src/webrtc/groupCall";
|
import { type GroupCallStatsReport } from "matrix-js-sdk/src/webrtc/groupCall";
|
||||||
import {
|
import {
|
||||||
AudioConcealment,
|
type AudioConcealment,
|
||||||
ByteSentStatsReport,
|
type ByteSentStatsReport,
|
||||||
ConnectionStatsReport,
|
type ConnectionStatsReport,
|
||||||
} from "matrix-js-sdk/src/webrtc/stats/statsReport";
|
} from "matrix-js-sdk/src/webrtc/stats/statsReport";
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ Copyright 2023, 2024 New Vector Ltd.
|
|||||||
SPDX-License-Identifier: AGPL-3.0-only
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
import { Attributes } from "@opentelemetry/api";
|
import { type Attributes } from "@opentelemetry/api";
|
||||||
import { VoipEvent } from "matrix-js-sdk/src/webrtc/call";
|
import { type VoipEvent } from "matrix-js-sdk/src/webrtc/call";
|
||||||
import { GroupCallStatsReport } from "matrix-js-sdk/src/webrtc/groupCall";
|
import { type GroupCallStatsReport } from "matrix-js-sdk/src/webrtc/groupCall";
|
||||||
import {
|
import {
|
||||||
ByteSentStatsReport,
|
type ByteSentStatsReport,
|
||||||
ConnectionStatsReport,
|
type ConnectionStatsReport,
|
||||||
SummaryStatsReport,
|
type SummaryStatsReport,
|
||||||
} from "matrix-js-sdk/src/webrtc/stats/statsReport";
|
} from "matrix-js-sdk/src/webrtc/stats/statsReport";
|
||||||
|
|
||||||
export class ObjectFlattener {
|
export class ObjectFlattener {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ Please see LICENSE in the repository root for full details.
|
|||||||
import { SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
|
import { SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
|
||||||
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
||||||
import { WebTracerProvider } from "@opentelemetry/sdk-trace-web";
|
import { WebTracerProvider } from "@opentelemetry/sdk-trace-web";
|
||||||
import opentelemetry, { Tracer } from "@opentelemetry/api";
|
import opentelemetry, { type Tracer } from "@opentelemetry/api";
|
||||||
import { Resource } from "@opentelemetry/resources";
|
import { Resource } from "@opentelemetry/resources";
|
||||||
import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";
|
import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { type MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
import { type MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
import { User, UserEvent } from "matrix-js-sdk/src/models/user";
|
import { type User, UserEvent } from "matrix-js-sdk/src/models/user";
|
||||||
import { FileType } from "matrix-js-sdk/src/http-api";
|
import { type FileType } from "matrix-js-sdk/src/http-api";
|
||||||
import { useState, useCallback, useEffect } from "react";
|
import { useState, useCallback, useEffect } from "react";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ Please see LICENSE in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
MouseEventHandler,
|
type MouseEventHandler,
|
||||||
ReactNode,
|
type ReactNode,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useState,
|
useState,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.reactionIndicatorWidget {
|
.reactionIndicatorWidget {
|
||||||
display: flex;
|
display: flex;
|
||||||
/* background-color: var(--cpd-color-bg-subtle-primary); */
|
background-color: #00000030;
|
||||||
border-radius: var(--cpd-radius-pill-effect);
|
border-radius: var(--cpd-radius-pill-effect);
|
||||||
box-shadow: 0 0 var(--cpd-space-2x) #00000040;
|
box-shadow: 0 0 var(--cpd-space-2x) #00000040;
|
||||||
background: "ffffff40";
|
background: "ffffff40";
|
||||||
@@ -14,12 +14,15 @@
|
|||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
margin-bottom: auto;
|
margin-bottom: auto;
|
||||||
width: 3em;
|
width: 3em;
|
||||||
|
padding-right: var(--cpd-space-2x);
|
||||||
|
margin-left: calc(var(--cpd-space-2x) * -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.reactionIndicatorWidgetLarge > p {
|
.reactionIndicatorWidgetLarge > p {
|
||||||
padding: var(--cpd-space-2x);
|
padding: var(--cpd-space-2x);
|
||||||
padding-right: var(--cpd-space-4x);
|
padding-right: var(--cpd-space-4x);
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reactionLarge {
|
.reactionLarge {
|
||||||
@@ -30,14 +33,12 @@
|
|||||||
|
|
||||||
.reaction {
|
.reaction {
|
||||||
margin: var(--cpd-space-1x);
|
margin: var(--cpd-space-1x);
|
||||||
color: var(--cpd-color-icon-secondary);
|
color: white;
|
||||||
/* background-color: var(--cpd-color-icon-secondary); */
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: var(--cpd-radius-pill-effect);
|
border-radius: var(--cpd-radius-pill-effect);
|
||||||
user-select: none;
|
user-select: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
/* box-shadow: var(--small-drop-shadow); */
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
max-inline-size: 100%;
|
max-inline-size: 100%;
|
||||||
max-width: fit-content;
|
max-width: fit-content;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { PropsWithChildren, ReactNode } from "react";
|
import { type PropsWithChildren, type ReactNode } from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { RelationType } from "matrix-js-sdk/src/types";
|
import { type RelationType } from "matrix-js-sdk/src/types";
|
||||||
|
|
||||||
import catSoundOgg from "../sound/reactions/cat.ogg?url";
|
import catSoundOgg from "../sound/reactions/cat.ogg?url";
|
||||||
import catSoundMp3 from "../sound/reactions/cat.mp3?url";
|
import catSoundMp3 from "../sound/reactions/cat.mp3?url";
|
||||||
|
|||||||
@@ -5,7 +5,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FC, MouseEvent, useCallback, useMemo, useState } from "react";
|
import {
|
||||||
|
type FC,
|
||||||
|
type MouseEvent,
|
||||||
|
useCallback,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Button, Text } from "@vector-im/compound-web";
|
import { Button, Text } from "@vector-im/compound-web";
|
||||||
import { PopOutIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
import { PopOutIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user