Compare commits

..

26 Commits

Author SHA1 Message Date
Robin
569d525c6e Wait for a close action before closing a call
This creates a distinction between the user hanging up and the widget being ready to close, which is useful for allowing Element Call to show error screens when disconnected from the call, for example.
2025-02-20 13:54:10 +07:00
Robin
7799cb2ec5 Avoid destroying calls until they are hidden from the UI
We often want calls to exist even when no more participants are left in the MatrixRTC session. So, we should avoid destroying calls as long as they're being presented in the UI; this means that the user has an intent to either join the call or continue looking at an error screen, and we shouldn't interrupt that interaction.

The RoomViewStore is now what takes care of creating and destroying calls, rather than the CallView. In general it seems kinda impossible to safely create and destroy model objects from React lifecycle hooks, so moving this responsibility to a store seemed appropriate and resolves existing issues with calls in React strict mode.
2025-02-20 13:54:10 +07:00
Michael Telatynski
e47d7aaaff Add Windows 64-bit arm link and remove 32-bit link on compatibility page (#29312)
* Add Windows 64-bit arm link and remove 32-bit link on compatibility page

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update snapshots

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-02-19 16:17:56 +00:00
Florian Duros
8857c07acb Move view_create_chat & view_create_room to actions.ts (#29319)
* refactor: replace `view_create_chat` by `Action.CreateChat`

* refactor: replace `view_create_room` by `Action.CreateRoom`
2025-02-19 15:11:43 +00:00
Florian Duros
28ed506fe1 refactor: rename RoomListHeader as LegacyRoomListHeader (#29308) 2025-02-19 14:06:01 +00:00
R Midhun Suresh
76b3be6263 Add some basic documentation for MVVM (#29316) 2025-02-19 13:13:28 +00:00
ElementRobot
6c768b8b32 [create-pull-request] automated change (#29313)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-02-19 10:39:36 +00:00
ElementRobot
809ada17a4 [create-pull-request] automated change (#29314)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-02-19 06:19:54 +00:00
renovate[bot]
c7762a80f1 Update dependency @sentry/browser to v9 (#29303)
* Update dependency @sentry/browser to v9

* Remove redundant option

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-02-18 21:11:36 +00:00
renovate[bot]
261923832d Update dependency caniuse-lite to v1.0.30001699 (#29297)
* Update dependency caniuse-lite to v1.0.30001699

* Update tests

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-02-18 20:29:47 +00:00
renovate[bot]
3daa1bf06a Update all non-major dependencies (#29298)
* Update all non-major dependencies

* Prettier

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-02-18 20:20:06 +00:00
renovate[bot]
e5c8d7dbf0 Update playwright to v1.50.1 (#29183)
* Update playwright to v1.50.1

* Update snapshots

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-02-18 18:43:40 +00:00
ElementRobot
441119ca3a [create-pull-request] automated change (#29286)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-02-18 17:49:15 +00:00
renovate[bot]
acb3e781a4 Update typescript-eslint monorepo to v8.24.0 (#29302)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-18 16:59:10 +00:00
renovate[bot]
3fb1f6ef4d Update testcontainers-node monorepo to v10.18.0 (#29301)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-18 16:55:48 +00:00
renovate[bot]
cbfbfad959 Update docker (#29291)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-18 15:27:32 +00:00
renovate[bot]
7546bbc1f0 Update dependency @sentry/browser to v8.55.0 (#29299)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-18 15:07:10 +00:00
renovate[bot]
6a10c86d7a Update dependency @stylistic/eslint-plugin to v3.1.0 (#29300)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-18 15:07:06 +00:00
renovate[bot]
6f9e3bfe3e Update dependency @types/node to v18.19.76 (#29296)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-18 15:06:06 +00:00
David Baker
61d55462df Honour the backup disable flag from Element X (#29290)
* Honour the backup disable flag from Element X

This unfortunately named and unspecced flag is set by Element X
to denote that the user has chosen to disable key storage and it
should not automatically try to enable it again. This changes Element
web to not prompt to enable recovery if this flag is set.

* Remove unnecessary conditional

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-02-18 15:06:00 +00:00
renovate[bot]
d391c69e53 Update dependency @formatjs/intl-segmenter to v11.7.9 (#29295)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-18 15:02:23 +00:00
renovate[bot]
a6afff9759 Update babel monorepo (#29294)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-18 15:02:22 +00:00
renovate[bot]
073d8e0b86 Update sigstore/cosign-installer digest to c56c2d3 (#29293)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-18 14:53:35 +00:00
renovate[bot]
ecf5d720b0 Update guibranco/github-status-action-v2 digest to 7ca807c (#29292)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-18 14:52:13 +00:00
David Baker
b7fea97bb6 Factor out duplicated CSS for buttons in encryption settings (#29269)
* Factor out duplicated CSS for buttons in encryption settings

By adding a component to hold the common CSS

* Update snapshot

* Update snapshot

* More snapshots

* Split EncryptionCardButtons out to separate component

* Update imports
2025-02-18 11:01:48 +00:00
Florian Duros
90801eb38b fix(member list): remove hardcoded font size in member list tile (#29285) 2025-02-17 18:06:14 +00:00
133 changed files with 1149 additions and 1114 deletions

View File

@@ -22,13 +22,13 @@ jobs:
fetch-depth: 0 # needed for docker-package to be able to calculate the version
- name: Install Cosign
uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3
uses: sigstore/cosign-installer@c56c2d3e59e4281cc41dea2217323ba5694b171e # v3
- name: Set up QEMU
uses: docker/setup-qemu-action@53851d14592bedcffcf25ea515637cff71ef929a # v3
uses: docker/setup-qemu-action@4574d27a4764455b42196d70a065bc6853246a25 # v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3
uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3
with:
install: true

View File

@@ -104,7 +104,7 @@ jobs:
- name: Skip SonarCloud in merge queue
if: github.event_name == 'merge_group' || inputs.disable_coverage == 'true'
uses: guibranco/github-status-action-v2@119b3320db3f04d89e91df840844b92d57ce3468
uses: guibranco/github-status-action-v2@7ca807c2ba3401be532d29a876b93262108099fb
with:
authToken: ${{ secrets.GITHUB_TOKEN }}
state: success

67
docs/MVVM.md Normal file
View File

@@ -0,0 +1,67 @@
# MVVM
General description of the pattern can be found [here](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel). But the gist of it is that you divide your code into three sections:
1. Model: This is where the business logic and data resides.
2. View Model: This code exists to provide the logic necessary for the UI. It directly uses the Model code.
3. View: This is the UI code itself and depends on the view model.
If you do MVVM right, your view should be dumb i.e it gets data from the view model and merely displays it.
### Practical guidelines for MVVM in element-web
#### Model
This is anywhere your data or business logic comes from. If your view model is accessing something simple exposed from `matrix-js-sdk`, then the sdk is your model. If you're using something more high level in element-web to get your data/logic (eg: `MemberListStore`), then that becomes your model.
#### View Model
1. View model is always a custom react hook named like `useFooViewModel()`.
2. The return type of your view model (known as view state) must be defined as a typescript interface:
```ts
inteface FooViewState {
somethingUseful: string;
somethingElse: BarType;
update: () => Promise<void>
...
}
```
3. Any react state that your UI needs must be in the view model.
#### View
1. Views are simple react components (eg: `FooView`).
2. Views usually start by calling the view model hook, eg:
```tsx
const FooView: React.FC<IProps> = (props: IProps) => {
const vm = useFooViewModel();
....
return(
<div>
{vm.somethingUseful}
</div>
);
}
```
3. Views are also allowed to accept the view model as a prop, eg:
```tsx
const FooView: React.FC<IProps> = ({ vm }: IProps) => {
....
return(
<div>
{vm.somethingUseful}
</div>
);
}
```
4. Multiple views can share the same view model if necessary.
### Benefits
1. MVVM forces a separation of concern i.e we will no longer have large react components that have a lot of state and rendering code mixed together. This improves code readability and makes it easier to introduce changes.
2. Introduces the possibility of code reuse. You can reuse an old view model with a new view or vice versa.
3. Adding to the point above, in future you could import element-web view models to your project and supply your own views thus creating something similar to the [hydrogen sdk](https://github.com/element-hq/hydrogen-web/blob/master/doc/SDK.md).
### Example
We started experimenting with MVVM in the redesigned memberlist, you can see the code [here](https://github.com/vector-im/element-web/blob/develop/src/components/views/rooms/MemberList/MemberListView.tsx).

View File

@@ -163,14 +163,14 @@ These two options describe the various availability for the application. When th
such as trying to get the user to use an Android app or the desktop app for encrypted search, the config options will be looked
at to see if the link should be to somewhere else.
Starting with `desktop_builds`, the following subproperties are available:
Starting with `desktop_builds`, the following sub-properties are available:
1. `available`: Required. When `true`, the desktop app can be downloaded from somewhere.
2. `logo`: Required. A URL to a logo (SVG), intended to be shown at 24x24 pixels.
3. `url`: Required. The download URL for the app. This is used as a hyperlink.
4. `url_macos`: Optional. Direct link to download macOS desktop app.
5. `url_win32`: Optional. Direct link to download Windows 32-bit desktop app.
6. `url_win64`: Optional. Direct link to download Windows 64-bit desktop app.
5. `url_win64`: Optional. Direct link to download Windows x86 64-bit desktop app.
6. `url_win64arm`: Optional. Direct link to download Windows ARM 64-bit desktop app.
7. `url_linux`: Optional. Direct link to download Linux desktop app.
When `desktop_builds` is not specified at all, the app will assume desktop downloads are available from https://element.io

View File

@@ -74,7 +74,7 @@
"@types/react-dom": "18.3.5",
"oidc-client-ts": "3.1.0",
"jwt-decode": "4.0.0",
"caniuse-lite": "1.0.30001697",
"caniuse-lite": "1.0.30001699",
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0",
"wrap-ansi": "npm:wrap-ansi@^7.0.0"
},
@@ -88,7 +88,7 @@
"@matrix-org/emojibase-bindings": "^1.3.4",
"@matrix-org/react-sdk-module-api": "^2.4.0",
"@matrix-org/spec": "^1.7.0",
"@sentry/browser": "^8.0.0",
"@sentry/browser": "^9.0.0",
"@types/png-chunks-extract": "^1.0.2",
"@types/react-virtualized": "^9.21.30",
"@vector-im/compound-design-tokens": "^3.0.0",
@@ -274,7 +274,7 @@
"postcss-preset-env": "^10.0.0",
"postcss-scss": "^4.0.4",
"postcss-simple-vars": "^7.0.1",
"prettier": "3.4.2",
"prettier": "3.5.1",
"process": "^0.11.10",
"raw-loader": "^4.0.2",
"rimraf": "^6.0.0",

View File

@@ -1,4 +1,4 @@
FROM mcr.microsoft.com/playwright:v1.49.1-noble
FROM mcr.microsoft.com/playwright:v1.50.1-noble
WORKDIR /work

View File

@@ -6,22 +6,21 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { expect, test as base } from "../../element-web-test";
import { type CredentialsWithDisplayName, expect, test as base } from "../../element-web-test";
import { selectHomeserver } from "../utils";
import { emailHomeserver } from "../../plugins/homeserver/synapse/emailHomeserver.ts";
import { isDendrite } from "../../plugins/homeserver/dendrite";
import { type Credentials } from "../../plugins/homeserver";
const email = "user@nowhere.dummy";
const test = base.extend<{ credentials: Pick<Credentials, "username" | "password"> }>({
const test = base.extend({
// eslint-disable-next-line no-empty-pattern
credentials: async ({}, use, testInfo) => {
await use({
username: `user_${testInfo.testId}`,
// this has to be password-like enough to please zxcvbn. Needless to say it's just from pwgen.
password: "oETo7MPf0o",
});
} as CredentialsWithDisplayName);
},
});

View File

@@ -32,7 +32,7 @@ test.describe("Security user settings tab", () => {
});
test.describe("AnalyticsLearnMoreDialog", () => {
test("should be rendered properly", { tag: "@screenshot" }, async ({ app, page, user }) => {
test("should be rendered properly", { tag: "@screenshot" }, async ({ app, page }) => {
const tab = await app.settings.openUserSettings("Security");
await tab.getByRole("button", { name: "Learn more" }).click();
await expect(page.locator(".mx_AnalyticsLearnMoreDialog_wrapper .mx_Dialog")).toMatchScreenshot(
@@ -41,57 +41,16 @@ test.describe("Security user settings tab", () => {
});
});
test("should be able to set an ID server", async ({ app, context, user, page }) => {
test("should contain section to set ID server", async ({ app }) => {
const tab = await app.settings.openUserSettings("Security");
await context.route("https://identity.example.org/_matrix/identity/v2", async (route) => {
await route.fulfill({
status: 200,
json: {},
});
});
await context.route("https://identity.example.org/_matrix/identity/v2/account/register", async (route) => {
await route.fulfill({
status: 200,
json: {
token: "AToken",
},
});
});
await context.route("https://identity.example.org/_matrix/identity/v2/account", async (route) => {
await route.fulfill({
status: 200,
json: {
user_id: user.userId,
},
});
});
await context.route("https://identity.example.org/_matrix/identity/v2/terms", async (route) => {
await route.fulfill({
status: 200,
json: {
policies: {},
},
});
});
const setIdServer = tab.locator(".mx_IdentityServerPicker");
const setIdServer = tab.locator(".mx_SetIdServer");
await setIdServer.scrollIntoViewIfNeeded();
const textElement = setIdServer.getByRole("textbox", { name: "Enter a new identity server" });
await textElement.click();
await textElement.fill("https://identity.example.org");
await setIdServer.getByRole("button", { name: "Change" }).click();
await expect(setIdServer.getByText("Checking server")).toBeVisible();
// Accept terms
await page.getByTestId("dialog-primary-button").click();
// Check identity has changed.
await expect(setIdServer.getByText("Your identity server has been changed")).toBeVisible();
// Ensure section title is updated.
await expect(tab.getByText(`Identity server (identity.example.org)`, { exact: true })).toBeVisible();
// Assert that an input area for identity server exists
await expect(setIdServer.getByRole("textbox", { name: "Enter a new identity server" })).toBeVisible();
});
test("should enable show integrations as enabled", async ({ app, page, user }) => {
test("should enable show integrations as enabled", async ({ app, page }) => {
const tab = await app.settings.openUserSettings("Security");
const setIntegrationManager = tab.locator(".mx_SetIntegrationManager");

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 KiB

After

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 983 KiB

After

Width:  |  Height:  |  Size: 985 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 239 KiB

After

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -25,7 +25,7 @@ import { type HomeserverContainer, type StartedHomeserverContainer } from "./Hom
import { type StartedMatrixAuthenticationServiceContainer } from "./mas.ts";
import { Api, ClientServerApi, type Verb } from "../plugins/utils/api.ts";
const TAG = "develop@sha256:32ee365ad97dde86033e8a33e143048167271299e4c727413f3cdff48c65f8d9";
const TAG = "develop@sha256:fa3090607a5e07a4ff245247aa3b598c6bbcff9231fd89a558de97c37adbd744";
const DEFAULT_CONFIG = {
server_name: "localhost",

View File

@@ -589,21 +589,18 @@ legend {
* in the app look the same by being AccessibleButtons, or possibly by having explict button classes.
* We should go through and have one consistent set of styles for buttons throughout the app.
* For now, I am duplicating the selectors here for mx_Dialog and mx_DialogButtons.
*
* Elements that should not be styled like a dialog button are mentioned in a :not() pseudo-class.
* For the widest browser support, we use multiple :not pseudo-classes instead of :not(.a, .b).
*/
.mx_Dialog
button:not(
.mx_EncryptionUserSettingsTab button,
.mx_UserProfileSettings button,
.mx_ShareDialog button,
.mx_UnpinAllDialog button,
.mx_ThemeChoicePanel_CustomTheme button,
.mx_Dialog_nonDialogButton,
.mx_AccessibleButton,
.mx_IdentityServerPicker button,
[class|="maplibregl"]
button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not(
.mx_UserProfileSettings button
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not(
.mx_EncryptionUserSettingsTab button
),
.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton, .mx_AccessibleButton),
.mx_Dialog input[type="submit"],
.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton),
.mx_Dialog_buttons input[type="submit"] {
@mixin mx_DialogButton;
margin-left: 0px;
@@ -619,46 +616,32 @@ legend {
}
.mx_Dialog
button:not(
.mx_Dialog_nonDialogButton,
[class|="maplibregl"],
.mx_AccessibleButton,
.mx_UserProfileSettings button,
.mx_ThemeChoicePanel_CustomTheme button,
.mx_UnpinAllDialog button,
.mx_ShareDialog button,
button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not(
.mx_UserProfileSettings button
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not(
.mx_EncryptionUserSettingsTab button
):last-child {
margin-right: 0px;
}
.mx_Dialog
button:not(
.mx_Dialog_nonDialogButton,
[class|="maplibregl"],
.mx_AccessibleButton,
.mx_UserProfileSettings button,
.mx_ThemeChoicePanel_CustomTheme button,
.mx_UnpinAllDialog button,
.mx_ShareDialog button,
button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not(
.mx_UserProfileSettings button
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not(
.mx_EncryptionUserSettingsTab button
):focus,
.mx_Dialog input[type="submit"]:focus,
.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton, .mx_AccessibleButton):focus,
.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):focus,
.mx_Dialog_buttons input[type="submit"]:focus {
filter: brightness($focus-brightness);
}
.mx_Dialog button.mx_Dialog_primary:not(.mx_Dialog_nonDialogButton, [class|="maplibregl"]),
.mx_Dialog button.mx_Dialog_primary:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]),
.mx_Dialog input[type="submit"].mx_Dialog_primary,
.mx_Dialog_buttons
button:not(
.mx_Dialog_nonDialogButton,
.mx_AccessibleButton,
.mx_UserProfileSettings button,
.mx_ThemeChoicePanel_CustomTheme button,
.mx_UnpinAllDialog button,
.mx_ShareDialog button,
button.mx_Dialog_primary:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):not(
.mx_UserProfileSettings button
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not(
.mx_EncryptionUserSettingsTab button
),
.mx_Dialog_buttons input[type="submit"].mx_Dialog_primary {
@@ -668,43 +651,32 @@ legend {
min-width: 156px;
}
.mx_Dialog button.danger:not(.mx_Dialog_nonDialogButton, [class|="maplibregl"]),
.mx_Dialog button.danger:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]),
.mx_Dialog input[type="submit"].danger,
.mx_Dialog_buttons
button.danger:not(
.mx_Dialog_nonDialogButton,
.mx_AccessibleButton,
.mx_UserProfileSettings button,
.mx_ThemeChoicePanel_CustomTheme button,
.mx_UnpinAllDialog button,
.mx_ShareDialog button,
.mx_EncryptionUserSettingsTab button
),
button.danger:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):not(.mx_UserProfileSettings button):not(
.mx_ThemeChoicePanel_CustomTheme button
):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not(.mx_EncryptionUserSettingsTab button),
.mx_Dialog_buttons input[type="submit"].danger {
background-color: var(--cpd-color-bg-critical-primary);
border: solid 1px var(--cpd-color-bg-critical-primary);
color: var(--cpd-color-text-on-solid-primary);
}
.mx_Dialog button.warning:not(.mx_Dialog_nonDialogButton, [class|="maplibregl"]),
.mx_Dialog button.warning:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]),
.mx_Dialog input[type="submit"].warning {
border: solid 1px var(--cpd-color-border-critical-subtle);
color: var(--cpd-color-text-critical-primary);
}
.mx_Dialog
button:not(
.mx_Dialog_nonDialogButton,
[class|="maplibregl"],
.mx_AccessibleButton,
.mx_UserProfileSettings button,
.mx_ThemeChoicePanel_CustomTheme button,
.mx_UnpinAllDialog button,
.mx_ShareDialog button,
button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not(
.mx_UserProfileSettings button
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not(
.mx_EncryptionUserSettingsTab button
):disabled,
.mx_Dialog input[type="submit"]:disabled,
.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton, .mx_AccessibleButton):disabled,
.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):disabled,
.mx_Dialog_buttons input[type="submit"]:disabled {
background-color: $light-fg-color;
border: solid 1px $light-fg-color;

View File

@@ -288,6 +288,7 @@
@import "./views/rooms/_IRCLayout.pcss";
@import "./views/rooms/_InvitedIconView.pcss";
@import "./views/rooms/_JumpToBottomButton.pcss";
@import "./views/rooms/_LegacyRoomListHeader.pcss";
@import "./views/rooms/_LinkPreviewGroup.pcss";
@import "./views/rooms/_LinkPreviewWidget.pcss";
@import "./views/rooms/_LiveContentSummary.pcss";
@@ -312,7 +313,6 @@
@import "./views/rooms/_RoomInfoLine.pcss";
@import "./views/rooms/_RoomKnocksBar.pcss";
@import "./views/rooms/_RoomList.pcss";
@import "./views/rooms/_RoomListHeader.pcss";
@import "./views/rooms/_RoomPreviewBar.pcss";
@import "./views/rooms/_RoomPreviewCard.pcss";
@import "./views/rooms/_RoomSearchAuxPanel.pcss";
@@ -348,6 +348,7 @@
@import "./views/settings/_PowerLevelSelector.pcss";
@import "./views/settings/_RoomProfileSettings.pcss";
@import "./views/settings/_SecureBackupPanel.pcss";
@import "./views/settings/_SetIdServer.pcss";
@import "./views/settings/_SetIntegrationManager.pcss";
@import "./views/settings/_SettingsFieldset.pcss";
@import "./views/settings/_SettingsHeader.pcss";

View File

@@ -10,8 +10,9 @@ Please see LICENSE files in the repository root for full details.
--cpd-separator-inset: calc(50% - (var(--width) / 2));
--cpd-separator-spacing: var(--cpd-space-8x);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji",
"Segoe UI Emoji", "Segoe UI Symbol";
text-align: center;
color: var(--cpd-color-text-primary);
width: 100%;

View File

@@ -113,7 +113,7 @@ Please see LICENSE files in the repository root for full details.
display: flex;
align-items: center;
& + .mx_RoomListHeader {
& + .mx_LegacyRoomListHeader {
margin-top: 12px;
}
@@ -180,7 +180,7 @@ Please see LICENSE files in the repository root for full details.
}
}
.mx_RoomListHeader:first-child {
.mx_LegacyRoomListHeader:first-child {
margin-top: 12px;
}

View File

@@ -365,7 +365,8 @@ Please see LICENSE files in the repository root for full details.
Note the top fade is much smaller because the spaces start close to the top,
so otherwise a large gradient suddenly appears when you scroll down.
*/
mask-image: linear-gradient(to bottom, transparent, black 16px),
mask-image:
linear-gradient(to bottom, transparent, black 16px),
linear-gradient(
to top,
transparent,

View File

@@ -16,7 +16,8 @@ Please see LICENSE files in the repository root for full details.
position: absolute;
z-index: -1;
opacity: 0.6;
background-image: radial-gradient(
background-image:
radial-gradient(
53.85% 66.75% at 87.55% 0%,
hsla(250deg, 76%, 71%, 0.261) 0%,
hsla(250deg, 100%, 88%, 0) 100%

View File

@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
.mx_RoomListHeader {
.mx_LegacyRoomListHeader {
display: flex;
align-items: center;
.mx_RoomListHeader_contextLessTitle,
.mx_RoomListHeader_contextMenuButton {
.mx_LegacyRoomListHeader_contextLessTitle,
.mx_LegacyRoomListHeader_contextMenuButton {
font: var(--cpd-font-heading-sm-semibold);
font-weight: var(--cpd-font-weight-semibold);
padding: 1px 24px 1px 4px;
@@ -24,7 +24,7 @@ Please see LICENSE files in the repository root for full details.
user-select: none;
}
.mx_RoomListHeader_contextMenuButton {
.mx_LegacyRoomListHeader_contextMenuButton {
border-radius: 6px;
&:hover {
@@ -54,7 +54,7 @@ Please see LICENSE files in the repository root for full details.
}
}
.mx_RoomListHeader_plusButton {
.mx_LegacyRoomListHeader_plusButton {
width: 32px;
height: 32px;
border-radius: 8px;
@@ -88,21 +88,21 @@ Please see LICENSE files in the repository root for full details.
}
}
.mx_RoomListHeader_iconInvite::before {
.mx_LegacyRoomListHeader_iconInvite::before {
mask-image: url("$(res)/img/element-icons/room/invite.svg");
}
.mx_RoomListHeader_iconStartChat::before {
.mx_LegacyRoomListHeader_iconStartChat::before {
mask-image: url("@vector-im/compound-design-tokens/icons/user-add-solid.svg");
}
.mx_RoomListHeader_iconNewRoom::before {
.mx_LegacyRoomListHeader_iconNewRoom::before {
mask-image: url("$(res)/img/element-icons/roomlist/hash-plus.svg");
}
.mx_RoomListHeader_iconNewVideoRoom::before {
.mx_LegacyRoomListHeader_iconNewVideoRoom::before {
mask-image: url("$(res)/img/element-icons/roomlist/hash-video.svg");
}
.mx_RoomListHeader_iconExplore::before {
.mx_LegacyRoomListHeader_iconExplore::before {
mask-image: url("$(res)/img/element-icons/roomlist/hash-search.svg");
}
.mx_RoomListHeader_iconPlus::before {
.mx_LegacyRoomListHeader_iconPlus::before {
mask-image: url("@vector-im/compound-design-tokens/icons/plus.svg");
}

View File

@@ -27,13 +27,11 @@ Please see LICENSE files in the repository root for full details.
.mx_MemberTileView_name {
font: var(--cpd-font-body-md-medium);
font-size: 15px;
min-width: 0;
}
.mx_MemberTileView_userLabel {
font: var(--cpd-font-body-sm-regular);
font-size: 13px;
color: var(--cpd-color-text-secondary);
margin-left: var(--cpd-space-4x);
}

View File

@@ -0,0 +1,23 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2019-2023 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
.mx_SetIdServer {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: $spacing-8;
.mx_Field {
width: 100%;
margin: 0;
}
}
.mx_SetIdServer_tooltip {
max-width: var(--SettingsTab_tooltip-max-width);
}

View File

@@ -69,11 +69,4 @@
flex-direction: column;
gap: var(--cpd-space-8x);
}
.mx_ChangeRecoveryKey_footer {
display: flex;
flex-direction: column;
gap: var(--cpd-space-4x);
justify-content: center;
}
}

View File

@@ -31,3 +31,10 @@
}
}
}
.mx_EncryptionCard_buttons {
display: flex;
flex-direction: column;
gap: var(--cpd-space-4x);
justify-content: center;
}

View File

@@ -16,11 +16,4 @@
text-align: center;
}
}
.mx_ResetIdentityPanel_footer {
display: flex;
flex-direction: column;
gap: var(--cpd-space-4x);
justify-content: center;
}
}

View File

@@ -309,11 +309,8 @@ body {
/* Splash Page Gradient */
.mx_SplashPage::before {
background-image: radial-gradient(
53.85% 66.75% at 87.55% 0%,
hsla(0deg, 0%, 11%, 0.15) 0%,
hsla(250deg, 100%, 88%, 0) 100%
),
background-image:
radial-gradient(53.85% 66.75% at 87.55% 0%, hsla(0deg, 0%, 11%, 0.15) 0%, hsla(250deg, 100%, 88%, 0) 100%),
radial-gradient(41.93% 41.93% at 0% 0%, hsla(0deg, 0%, 38%, 0.28) 0%, hsla(250deg, 100%, 88%, 0) 100%),
radial-gradient(100% 100% at 0% 0%, hsla(250deg, 100%, 88%, 0.3) 0%, hsla(0deg, 100%, 86%, 0) 100%),
radial-gradient(106.35% 96.26% at 100% 0%, hsla(25deg, 100%, 88%, 0.4) 0%, hsla(167deg, 76%, 82%, 0) 100%) !important;

View File

@@ -10,11 +10,13 @@
/* Noto Color Emoji contains digits, in fixed-width, therefore causing
digits in flowed text to stand out.
TODO: Consider putting all emoji fonts to the end rather than the front. */
$font-family: "Nunito", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Arial", "Helvetica",
sans-serif, "Noto Color Emoji";
$font-family:
"Nunito", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Arial", "Helvetica", sans-serif,
"Noto Color Emoji";
$monospace-font-family: "Inconsolata", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Courier",
monospace, "Noto Color Emoji";
$monospace-font-family:
"Inconsolata", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Courier", monospace,
"Noto Color Emoji";
/* unified palette */
/* try to use these colors when possible */

View File

@@ -10,11 +10,13 @@
/* Noto Color Emoji contains digits, in fixed-width, therefore causing
digits in flowed text to stand out.
TODO: Consider putting all emoji fonts to the end rather than the front. */
$font-family: "Inter", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Arial", "Helvetica",
sans-serif, "Noto Color Emoji";
$font-family:
"Inter", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Arial", "Helvetica", sans-serif,
"Noto Color Emoji";
$monospace-font-family: "Inconsolata", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Courier",
monospace, "Noto Color Emoji";
$monospace-font-family:
"Inconsolata", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Courier", monospace,
"Noto Color Emoji";
/* Colors from Figma Compound https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=559%3A120 */
/* ******************** */

View File

@@ -48,6 +48,11 @@ import { asyncSomeParallel } from "./utils/arrays.ts";
const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000;
// Unfortunately named account data key used by Element X to indicate that the user
// has chosen to disable server side key backups. We need to set and honour this
// to prevent Element X from automatically turning key backup back on.
const BACKUP_DISABLED_ACCOUNT_DATA_KEY = "m.org.matrix.custom.backup_disabled";
const logger = baseLogger.getChild("DeviceListener:");
export default class DeviceListener {
@@ -323,9 +328,11 @@ export default class DeviceListener {
logger.info("Some secrets not cached: showing KEY_STORAGE_OUT_OF_SYNC toast");
showSetupEncryptionToast(SetupKind.KEY_STORAGE_OUT_OF_SYNC);
} else if (defaultKeyId === null) {
// the user just hasn't set up 4S yet: prompt them to do so
logger.info("No default 4S key: showing SET_UP_RECOVERY toast");
showSetupEncryptionToast(SetupKind.SET_UP_RECOVERY);
// the user just hasn't set up 4S yet: prompt them to do so (unless they've explicitly said no to key storage)
const disabledEvent = cli.getAccountData(BACKUP_DISABLED_ACCOUNT_DATA_KEY);
if (!disabledEvent?.getContent().disabled) {
showSetupEncryptionToast(SetupKind.SET_UP_RECOVERY);
}
} else {
// some other condition... yikes! Show the 'set up encryption' toast: this is what we previously did
// in 'other' situations. Possibly we should consider prompting for a full reset in this case?

View File

@@ -71,7 +71,7 @@ export interface IConfigOptions {
url: string; // download url
url_macos?: string;
url_win64?: string;
url_win32?: string;
url_win64arm?: string;
url_linux?: string;
};
mobile_builds: {

View File

@@ -59,7 +59,7 @@ export const DEFAULTS: DeepReadonly<IConfigOptions> = {
url: "https://element.io/download",
url_macos: "https://packages.element.io/desktop/install/macos/Element.dmg",
url_win64: "https://packages.element.io/desktop/install/win32/x64/Element%20Setup.exe",
url_win32: "https://packages.element.io/desktop/install/win32/ia32/Element%20Setup.exe",
url_win64arm: "https://packages.element.io/desktop/install/win32/arm64/Element%20Setup.exe",
url_linux: "https://element.io/download#linux",
},
mobile_builds: {

View File

@@ -80,9 +80,9 @@ const MobileAppLinks: React.FC<{
const DesktopAppLinks: React.FC<{
macOsUrl?: string;
win64Url?: string;
win32Url?: string;
win64ArmUrl?: string;
linuxUrl?: string;
}> = ({ macOsUrl, win64Url, win32Url, linuxUrl }) => {
}> = ({ macOsUrl, win64Url, win64ArmUrl, linuxUrl }) => {
return (
<Flex gap="var(--cpd-space-4x)">
{macOsUrl && (
@@ -92,12 +92,12 @@ const DesktopAppLinks: React.FC<{
)}
{win64Url && (
<Button as="a" href={win64Url} kind="secondary" Icon={MicrosoftIcon}>
{_t("incompatible_browser|windows", { bits: "64" })}
{_t("incompatible_browser|windows_64bit")}
</Button>
)}
{win32Url && (
<Button as="a" href={win32Url} kind="secondary" Icon={MicrosoftIcon}>
{_t("incompatible_browser|windows", { bits: "32" })}
{win64ArmUrl && (
<Button as="a" href={win64ArmUrl} kind="secondary" Icon={MicrosoftIcon}>
{_t("incompatible_browser|windows_arm_64bit")}
</Button>
)}
{linuxUrl && (
@@ -127,7 +127,7 @@ export const UnsupportedBrowserView: React.FC<{
config.desktop_builds?.available &&
(config.desktop_builds?.url_macos ||
config.desktop_builds?.url_win64 ||
config.desktop_builds?.url_win32 ||
config.desktop_builds?.url_win64arm ||
config.desktop_builds?.url_linux);
const hasMobileBuilds = Boolean(
config.mobile_builds?.ios || config.mobile_builds?.android || config.mobile_builds?.fdroid,
@@ -157,7 +157,7 @@ export const UnsupportedBrowserView: React.FC<{
<DesktopAppLinks
macOsUrl={config.desktop_builds?.url_macos}
win64Url={config.desktop_builds?.url_win64}
win32Url={config.desktop_builds?.url_win32}
win64ArmUrl={config.desktop_builds?.url_win64arm}
linuxUrl={config.desktop_builds?.url_linux}
/>
</>

View File

@@ -27,7 +27,7 @@ import EmbeddedPage from "./EmbeddedPage";
const onClickSendDm = (ev: ButtonEvent): void => {
PosthogTrackers.trackInteraction("WebHomeCreateChatButton", ev);
dis.dispatch({ action: "view_create_chat" });
dis.dispatch({ action: Action.CreateChat });
};
const onClickExplore = (ev: ButtonEvent): void => {
@@ -37,7 +37,7 @@ const onClickExplore = (ev: ButtonEvent): void => {
const onClickNewRoom = (ev: ButtonEvent): void => {
PosthogTrackers.trackInteraction("WebHomeCreateRoomButton", ev);
dis.dispatch({ action: "view_create_room" });
dis.dispatch({ action: Action.CreateRoom });
};
interface IProps {

View File

@@ -23,7 +23,7 @@ import { MetaSpace, type SpaceKey, UPDATE_SELECTED_SPACE } from "../../stores/sp
import { getKeyBindingsManager } from "../../KeyBindingsManager";
import UIStore from "../../stores/UIStore";
import { type IState as IRovingTabIndexState } from "../../accessibility/RovingTabIndex";
import RoomListHeader from "../views/rooms/RoomListHeader";
import LegacyRoomListHeader from "../views/rooms/LegacyRoomListHeader";
import { BreadcrumbsStore } from "../../stores/BreadcrumbsStore";
import RoomListStore, { LISTS_UPDATE_EVENT } from "../../stores/room-list/RoomListStore";
import { UPDATE_EVENT } from "../../stores/AsyncStore";
@@ -415,7 +415,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
<div className="mx_LeftPanel_roomListContainer">
{shouldShowComponent(UIComponent.FilterContainer) && this.renderSearchDialExplore()}
{this.renderBreadcrumbs()}
{!this.props.isMinimized && <RoomListHeader onVisibilityChange={this.refreshStickyHeaders} />}
{!this.props.isMinimized && <LegacyRoomListHeader onVisibilityChange={this.refreshStickyHeaders} />}
<nav className="mx_LeftPanel_roomListWrapper" aria-label={_t("common|rooms")}>
<div
className={roomListClasses}

View File

@@ -144,7 +144,7 @@ const AUTH_SCREENS = ["register", "mobile_register", "login", "forgot_password",
// Actions that are redirected through the onboarding process prior to being
// re-dispatched. NOTE: some actions are non-trivial and would require
// re-factoring to be included in this list in future.
const ONBOARDING_FLOW_STARTERS = [Action.ViewUserSettings, "view_create_chat", "view_create_room"];
const ONBOARDING_FLOW_STARTERS = [Action.ViewUserSettings, Action.CreateChat, Action.CreateRoom];
interface IScreen {
screen: string;
@@ -616,7 +616,10 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
}
// Start the onboarding process for certain actions
if (MatrixClientPeg.get()?.isGuest() && ONBOARDING_FLOW_STARTERS.includes(payload.action)) {
if (
MatrixClientPeg.get()?.isGuest() &&
ONBOARDING_FLOW_STARTERS.includes(payload.action as unknown as Action)
) {
// This will cause `payload` to be dispatched later, once a
// sync has reached the "prepared" state. Setting a matrix ID
// will cause a full login and sync and finally the deferred
@@ -785,7 +788,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
this.viewSomethingBehindModal();
break;
}
case "view_create_room":
case Action.CreateRoom:
this.createRoom(payload.public, payload.defaultName, payload.type);
// View the welcome or home page if we need something to look at
@@ -816,7 +819,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
case Action.ViewStartChatOrReuse:
this.chatCreateOrReuse(payload.user_id);
break;
case "view_create_chat":
case Action.CreateChat:
showStartChatInviteDialog(payload.initialText || "");
// View the welcome or home page if we need something to look at
@@ -1758,11 +1761,11 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
}
} else if (screen === "new") {
dis.dispatch({
action: "view_create_room",
action: Action.CreateRoom,
});
} else if (screen === "dm") {
dis.dispatch({
action: "view_create_chat",
action: Action.CreateChat,
});
} else if (screen === "settings") {
dis.fire(Action.ViewUserSettings);

View File

@@ -118,8 +118,6 @@ import { RoomStatusBarUnsentMessages } from "./RoomStatusBarUnsentMessages";
import { LargeLoader } from "./LargeLoader";
import { isVideoRoom } from "../../utils/video-rooms";
import { SDKContext } from "../../contexts/SDKContext";
import { CallStore, CallStoreEvent } from "../../stores/CallStore";
import { type Call } from "../../models/Call";
import { RoomSearchView } from "./RoomSearchView";
import eventSearch, { type SearchInfo, SearchScope } from "../../Searching";
import VoipUserMapper from "../../VoipUserMapper";
@@ -190,7 +188,6 @@ export interface IRoomState {
*/
search?: SearchInfo;
callState?: CallState;
activeCall: Call | null;
canPeek: boolean;
canSelfRedact: boolean;
showApps: boolean;
@@ -401,7 +398,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
membersLoaded: !llMembers,
numUnreadMessages: 0,
callState: undefined,
activeCall: null,
canPeek: false,
canSelfRedact: false,
showApps: false,
@@ -577,7 +573,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
mainSplitContentType: room ? this.getMainSplitContentType(room) : undefined,
initialEventId: undefined, // default to clearing this, will get set later in the method if needed
showRightPanel: roomId ? this.context.rightPanelStore.isOpenForRoom(roomId) : false,
activeCall: roomId ? CallStore.instance.getActiveCall(roomId) : null,
promptAskToJoin: this.context.roomViewStore.promptAskToJoin(),
viewRoomOpts: this.context.roomViewStore.getViewRoomOpts(),
};
@@ -727,23 +722,17 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
});
};
private onConnectedCalls = (): void => {
if (this.state.roomId === undefined) return;
const activeCall = CallStore.instance.getActiveCall(this.state.roomId);
if (activeCall === null) {
// We disconnected from the call, so stop viewing it
defaultDispatcher.dispatch<ViewRoomPayload>(
{
action: Action.ViewRoom,
room_id: this.state.roomId,
view_call: false,
metricsTrigger: undefined,
},
true,
); // Synchronous so that CallView disappears immediately
}
this.setState({ activeCall });
private onCallClose = (): void => {
// Stop viewing the call
defaultDispatcher.dispatch<ViewRoomPayload>(
{
action: Action.ViewRoom,
room_id: this.state.roomId,
view_call: false,
metricsTrigger: undefined,
},
true,
); // Synchronous so that CallView disappears immediately
};
private getRoomId = (): string | undefined => {
@@ -900,8 +889,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
WidgetEchoStore.on(UPDATE_EVENT, this.onWidgetEchoStoreUpdate);
this.context.widgetStore.on(UPDATE_EVENT, this.onWidgetStoreUpdate);
CallStore.instance.on(CallStoreEvent.ConnectedCalls, this.onConnectedCalls);
this.props.resizeNotifier.on("isResizing", this.onIsResizing);
this.settingWatchers = [
@@ -1027,7 +1014,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
);
}
CallStore.instance.off(CallStoreEvent.ConnectedCalls, this.onConnectedCalls);
this.context.legacyCallHandler.off(LegacyCallHandlerEvent.CallState, this.onCallState);
// cancel any pending calls to the throttled updated
@@ -2562,9 +2548,9 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
<CallView
room={this.state.room}
resizing={this.state.resizing}
waitForCall={isVideoRoom(this.state.room)}
skipLobby={this.context.roomViewStore.skipCallLobby() ?? false}
role="main"
onClose={this.onCallClose}
/>
{previewBar}
</>

View File

@@ -954,7 +954,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
className="mx_SpotlightDialog_createRoom"
onClick={() =>
defaultDispatcher.dispatch({
action: "view_create_room",
action: Action.CreateRoom,
public: true,
defaultName: capitalize(trimmedQuery),
})

View File

@@ -126,10 +126,6 @@ const ActiveLoadedCallEvent = forwardRef<any, ActiveLoadedCallEventProps>(({ mxE
return [_t("action|leave"), "danger", disconnect];
case ConnectionState.Disconnecting:
return [_t("action|leave"), "danger", null];
case ConnectionState.Connecting:
case ConnectionState.Lobby:
case ConnectionState.WidgetLoading:
return [_t("action|join"), "primary", null];
}
}, [connectionState, connect, disconnect]);

View File

@@ -147,7 +147,7 @@ const DmAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex, dispatcher = default
e.preventDefault();
e.stopPropagation();
closeMenu();
defaultDispatcher.dispatch({ action: "view_create_chat" });
defaultDispatcher.dispatch({ action: Action.CreateChat });
PosthogTrackers.trackInteraction(
"WebRoomListRoomsSublistPlusMenuCreateChatItem",
e,
@@ -194,7 +194,7 @@ const DmAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex, dispatcher = default
<AccessibleButton
tabIndex={tabIndex}
onClick={(e) => {
dispatcher.dispatch({ action: "view_create_chat" });
dispatcher.dispatch({ action: Action.CreateChat });
PosthogTrackers.trackInteraction("WebRoomListRoomsSublistPlusMenuCreateChatItem", e);
}}
className="mx_RoomSublist_auxButton"
@@ -305,7 +305,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
e.preventDefault();
e.stopPropagation();
closeMenu();
defaultDispatcher.dispatch({ action: "view_create_room" });
defaultDispatcher.dispatch({ action: Action.CreateRoom });
PosthogTrackers.trackInteraction("WebRoomListRoomsSublistPlusMenuCreateRoomItem", e);
}}
/>
@@ -318,7 +318,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
e.stopPropagation();
closeMenu();
defaultDispatcher.dispatch({
action: "view_create_room",
action: Action.CreateRoom,
type: elementCallVideoRoomsEnabled
? RoomType.UnstableCall
: RoomType.ElementVideo,

View File

@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { EventType, RoomType, type Room, RoomEvent, ClientEvent } from "matrix-js-sdk/src/matrix";
import { ClientEvent, EventType, type Room, RoomEvent, RoomType } from "matrix-js-sdk/src/matrix";
import React, { useContext, useEffect, useState } from "react";
import { Tooltip } from "@vector-im/compound-web";
@@ -38,10 +38,10 @@ import {
} from "../../../utils/space";
import {
ChevronFace,
ContextMenuTooltipButton,
useContextMenu,
type MenuProps,
ContextMenuButton,
ContextMenuTooltipButton,
type MenuProps,
useContextMenu,
} from "../../structures/ContextMenu";
import { BetaPill } from "../beta/BetaCard";
import IconizedContextMenu, {
@@ -108,7 +108,7 @@ interface IProps {
onVisibilityChange?(): void;
}
const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
const LegacyRoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
const cli = useContext(MatrixClientContext);
const [mainMenuDisplayed, mainMenuHandle, openMainMenu, closeMainMenu] = useContextMenu<HTMLDivElement>();
const [plusMenuDisplayed, plusMenuHandle, openPlusMenu, closePlusMenu] = useContextMenu<HTMLDivElement>();
@@ -178,7 +178,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
inviteOption = (
<IconizedContextMenuOption
label={_t("action|invite")}
iconClassName="mx_RoomListHeader_iconInvite"
iconClassName="mx_LegacyRoomListHeader_iconInvite"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
@@ -194,7 +194,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
newRoomOptions = (
<>
<IconizedContextMenuOption
iconClassName="mx_RoomListHeader_iconNewRoom"
iconClassName="mx_LegacyRoomListHeader_iconNewRoom"
label={_t("action|new_room")}
onClick={(e) => {
e.preventDefault();
@@ -206,7 +206,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
/>
{videoRoomsEnabled && (
<IconizedContextMenuOption
iconClassName="mx_RoomListHeader_iconNewVideoRoom"
iconClassName="mx_LegacyRoomListHeader_iconNewVideoRoom"
label={_t("action|new_video_room")}
onClick={(e) => {
e.preventDefault();
@@ -236,7 +236,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
{newRoomOptions}
<IconizedContextMenuOption
label={_t("action|explore_rooms")}
iconClassName="mx_RoomListHeader_iconExplore"
iconClassName="mx_LegacyRoomListHeader_iconExplore"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
@@ -251,7 +251,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
/>
<IconizedContextMenuOption
label={_t("action|add_existing_room")}
iconClassName="mx_RoomListHeader_iconPlus"
iconClassName="mx_LegacyRoomListHeader_iconPlus"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
@@ -264,7 +264,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
{canCreateSpaces && (
<IconizedContextMenuOption
label={_t("room_list|add_space_label")}
iconClassName="mx_RoomListHeader_iconPlus"
iconClassName="mx_LegacyRoomListHeader_iconPlus"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
@@ -289,22 +289,22 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
<>
<IconizedContextMenuOption
label={_t("action|start_new_chat")}
iconClassName="mx_RoomListHeader_iconStartChat"
iconClassName="mx_LegacyRoomListHeader_iconStartChat"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
defaultDispatcher.dispatch({ action: "view_create_chat" });
defaultDispatcher.dispatch({ action: Action.CreateChat });
PosthogTrackers.trackInteraction("WebRoomListHeaderPlusMenuCreateChatItem", e);
closePlusMenu();
}}
/>
<IconizedContextMenuOption
label={_t("action|new_room")}
iconClassName="mx_RoomListHeader_iconNewRoom"
iconClassName="mx_LegacyRoomListHeader_iconNewRoom"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
defaultDispatcher.dispatch({ action: "view_create_room" });
defaultDispatcher.dispatch({ action: Action.CreateRoom });
PosthogTrackers.trackInteraction("WebRoomListHeaderPlusMenuCreateRoomItem", e);
closePlusMenu();
}}
@@ -312,12 +312,12 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
{videoRoomsEnabled && (
<IconizedContextMenuOption
label={_t("action|new_video_room")}
iconClassName="mx_RoomListHeader_iconNewVideoRoom"
iconClassName="mx_LegacyRoomListHeader_iconNewVideoRoom"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
defaultDispatcher.dispatch({
action: "view_create_room",
action: Action.CreateRoom,
type: elementCallVideoRoomsEnabled ? RoomType.UnstableCall : RoomType.ElementVideo,
});
closePlusMenu();
@@ -333,7 +333,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
joinRoomOpt = (
<IconizedContextMenuOption
label={_t("room_list|join_public_room_label")}
iconClassName="mx_RoomListHeader_iconExplore"
iconClassName="mx_LegacyRoomListHeader_iconExplore"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
@@ -378,13 +378,13 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
})
.join("\n");
let contextMenuButton: JSX.Element = <div className="mx_RoomListHeader_contextLessTitle">{title}</div>;
let contextMenuButton: JSX.Element = <div className="mx_LegacyRoomListHeader_contextLessTitle">{title}</div>;
if (canShowMainMenu) {
const commonProps = {
ref: mainMenuHandle,
onClick: openMainMenu,
isExpanded: mainMenuDisplayed,
className: "mx_RoomListHeader_contextMenuButton",
className: "mx_LegacyRoomListHeader_contextMenuButton",
children: title,
};
@@ -401,7 +401,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
}
return (
<aside className="mx_RoomListHeader" aria-label={_t("room|context_menu|title")}>
<aside className="mx_LegacyRoomListHeader" aria-label={_t("room|context_menu|title")}>
{contextMenuButton}
{pendingActionSummary ? (
<Tooltip label={pendingActionSummary} isTriggerInteractive={false}>
@@ -413,7 +413,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
ref={plusMenuHandle}
onClick={openPlusMenu}
isExpanded={plusMenuDisplayed}
className="mx_RoomListHeader_plusButton"
className="mx_LegacyRoomListHeader_plusButton"
title={_t("action|add")}
/>
)}
@@ -423,4 +423,4 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
);
};
export default RoomListHeader;
export default LegacyRoomListHeader;

View File

@@ -27,18 +27,6 @@ export const RoomTileCallSummary: FC<Props> = ({ call }) => {
text = _t("common|video");
active = false;
break;
case ConnectionState.WidgetLoading:
text = _t("common|loading");
active = false;
break;
case ConnectionState.Lobby:
text = _t("common|lobby");
active = false;
break;
case ConnectionState.Connecting:
text = _t("room|joining");
active = true;
break;
case ConnectionState.Connected:
case ConnectionState.Disconnecting:
text = _t("common|joined");

View File

@@ -1,5 +1,5 @@
/*
Copyright 2024-2025 New Vector Ltd.
Copyright 2024 New Vector Ltd.
Copyright 2019-2021 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -9,7 +9,6 @@ Please see LICENSE files in the repository root for full details.
import React, { type ReactNode } from "react";
import { logger } from "matrix-js-sdk/src/logger";
import { type IThreepid } from "matrix-js-sdk/src/matrix";
import { EditInPlace, ErrorMessage } from "@vector-im/compound-web";
import { _t } from "../../../languageHandler";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
@@ -23,6 +22,7 @@ import { timeout } from "../../../utils/promise";
import { type ActionPayload } from "../../../dispatcher/payloads";
import InlineSpinner from "../elements/InlineSpinner";
import AccessibleButton from "../elements/AccessibleButton";
import Field from "../elements/Field";
import QuestionDialog from "../dialogs/QuestionDialog";
import SettingsFieldset from "./SettingsFieldset";
import { SettingsSubsectionText } from "./shared/SettingsSubsection";
@@ -86,12 +86,10 @@ export default class SetIdServer extends React.Component<IProps, IState> {
defaultIdServer = abbreviateUrl(getDefaultIdentityServerUrl());
}
const currentClientIdServer = MatrixClientPeg.safeGet().getIdentityServerUrl();
this.state = {
defaultIdServer,
currentClientIdServer,
idServer: currentClientIdServer ?? "",
currentClientIdServer: MatrixClientPeg.safeGet().getIdentityServerUrl(),
idServer: "",
busy: false,
disconnectBusy: false,
checking: false,
@@ -119,7 +117,26 @@ export default class SetIdServer extends React.Component<IProps, IState> {
private onIdentityServerChanged = (ev: React.ChangeEvent<HTMLInputElement>): void => {
const u = ev.target.value;
this.setState({ idServer: u, error: undefined });
this.setState({ idServer: u });
};
private getTooltip = (): JSX.Element | undefined => {
if (this.state.checking) {
return (
<div>
<InlineSpinner />
{_t("identity_server|checking")}
</div>
);
} else if (this.state.error) {
return <strong className="warning">{this.state.error}</strong>;
} else {
return undefined;
}
};
private idServerChangeEnabled = (): boolean => {
return !!this.state.idServer && !this.state.busy;
};
private saveIdServer = (fullUrl: string): void => {
@@ -131,7 +148,7 @@ export default class SetIdServer extends React.Component<IProps, IState> {
busy: false,
error: undefined,
currentClientIdServer: fullUrl,
idServer: fullUrl,
idServer: "",
});
};
@@ -158,7 +175,7 @@ export default class SetIdServer extends React.Component<IProps, IState> {
// Double check that the identity server even has terms of service.
const hasTerms = await doesIdentityServerHaveTerms(MatrixClientPeg.safeGet(), fullUrl);
if (!hasTerms) {
const [confirmed] = await this.showNoTermsWarning();
const [confirmed] = await this.showNoTermsWarning(fullUrl);
save = !!confirmed;
}
@@ -196,7 +213,7 @@ export default class SetIdServer extends React.Component<IProps, IState> {
});
};
private showNoTermsWarning(): Promise<[ok?: boolean]> {
private showNoTermsWarning(fullUrl: string): Promise<[ok?: boolean]> {
const { finished } = Modal.createDialog(QuestionDialog, {
title: _t("terms|identity_server_no_terms_title"),
description: (
@@ -330,9 +347,6 @@ export default class SetIdServer extends React.Component<IProps, IState> {
});
};
private onInputCancel = (): void => this.setState((s) => ({ idServer: s.currentClientIdServer ?? "" }));
private onClearServerErrors = (): void => this.setState({ error: undefined });
public render(): React.ReactNode {
const idServerUrl = this.state.currentClientIdServer;
let sectionTitle;
@@ -342,13 +356,13 @@ export default class SetIdServer extends React.Component<IProps, IState> {
bodyText = _t(
"identity_server|description_connected",
{},
{ server: () => <strong>{abbreviateUrl(idServerUrl)}</strong> },
{ server: (sub) => <strong>{abbreviateUrl(idServerUrl)}</strong> },
);
if (this.props.missingTerms) {
bodyText = _t(
"identity_server|change_server_prompt",
{},
{ server: () => <strong>{abbreviateUrl(idServerUrl)}</strong> },
{ server: (sub) => <strong>{abbreviateUrl(idServerUrl)}</strong> },
);
}
} else {
@@ -379,25 +393,28 @@ export default class SetIdServer extends React.Component<IProps, IState> {
return (
<SettingsFieldset legend={sectionTitle} description={bodyText}>
<EditInPlace
className="mx_IdentityServerPicker"
cancelButtonLabel={_t("action|reset")}
disabled={!!this.state.busy}
label={_t("identity_server|url_field_label")}
onCancel={this.onInputCancel}
onChange={this.onIdentityServerChanged}
onClearServerErrors={this.onClearServerErrors}
onSave={this.checkIdServer}
placeholder={this.state.defaultIdServer}
saveButtonLabel={_t("action|change")}
savedLabel={this.state.error ? undefined : _t("identity_server|changed")}
savingLabel={_t("identity_server|checking")}
serverInvalid={!!this.state.error}
value={this.state.idServer}
>
{this.state.error && <ErrorMessage>{this.state.error}</ErrorMessage>}
</EditInPlace>
{discoSection}
<form className="mx_SetIdServer" onSubmit={this.checkIdServer}>
<Field
label={_t("identity_server|url_field_label")}
type="text"
autoComplete="off"
placeholder={this.state.defaultIdServer}
value={this.state.idServer}
onChange={this.onIdentityServerChanged}
tooltipContent={this.getTooltip()}
tooltipClassName="mx_SetIdServer_tooltip"
disabled={this.state.busy}
forceValidity={this.state.error ? false : undefined}
/>
<AccessibleButton
kind="primary_sm"
onClick={this.checkIdServer}
disabled={!this.idServerChangeEnabled()}
>
{_t("action|change")}
</AccessibleButton>
{discoSection}
</form>
</SettingsFieldset>
);
}

View File

@@ -27,6 +27,7 @@ import { useAsyncMemo } from "../../../../hooks/useAsyncMemo";
import { copyPlaintext } from "../../../../utils/strings";
import { initialiseDehydrationIfEnabled } from "../../../../utils/device/dehydration.ts";
import { withSecretStorageKeyCache } from "../../../../SecurityManager";
import { EncryptionCardButtons } from "./EncryptionCardButtons";
import { logErrorAndShowErrorDialog } from "../../../../utils/ErrorUtils.tsx";
/**
@@ -239,12 +240,12 @@ function InformationPanel({ onContinueClick, onCancelClick }: InformationPanelPr
<Text as="span" weight="medium" className="mx_InformationPanel_description">
{_t("settings|encryption|recovery|set_up_recovery_secondary_description")}
</Text>
<div className="mx_ChangeRecoveryKey_footer">
<EncryptionCardButtons>
<Button onClick={onContinueClick}>{_t("action|continue")}</Button>
<Button kind="tertiary" onClick={onCancelClick}>
{_t("action|cancel")}
</Button>
</div>
</EncryptionCardButtons>
</>
);
}
@@ -286,12 +287,12 @@ function KeyPanel({ recoveryKey, onConfirmClick, onCancelClick }: KeyPanelProps)
<CopyIcon />
</IconButton>
</div>
<div className="mx_ChangeRecoveryKey_footer">
<EncryptionCardButtons>
<Button onClick={onConfirmClick}>{_t("action|continue")}</Button>
<Button kind="tertiary" onClick={onCancelClick}>
{_t("action|cancel")}
</Button>
</div>
</EncryptionCardButtons>
</>
);
}
@@ -349,12 +350,12 @@ function KeyForm({ onCancelClick, onSubmit, recoveryKey, submitButtonLabel }: Ke
<ErrorMessage>{_t("settings|encryption|recovery|enter_key_error")}</ErrorMessage>
)}
</Field>
<div className="mx_ChangeRecoveryKey_footer">
<EncryptionCardButtons>
<Button disabled={!isKeyValid}>{submitButtonLabel}</Button>
<Button kind="tertiary" onClick={onCancelClick}>
{_t("action|cancel")}
</Button>
</div>
</EncryptionCardButtons>
</Root>
);
}

View File

@@ -0,0 +1,16 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
* Please see LICENSE files in the repository root for full details.
*/
import React, { type PropsWithChildren } from "react";
/**
* A component to present action buttons at the bottom of an {@link EncryptionCard}
* (mostly as somewhere for the common CSS to live).
*/
export function EncryptionCardButtons({ children }: PropsWithChildren): JSX.Element {
return <div className="mx_EncryptionCard_buttons">{children}</div>;
}

View File

@@ -15,6 +15,7 @@ import { _t } from "../../../../languageHandler";
import { EncryptionCard } from "./EncryptionCard";
import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext";
import { uiAuthCallback } from "../../../../CreateCrossSigning";
import { EncryptionCardButtons } from "./EncryptionCardButtons";
interface ResetIdentityPanelProps {
/**
@@ -74,7 +75,7 @@ export function ResetIdentityPanel({ onCancelClick, onFinish, variant }: ResetId
</VisualList>
{variant === "compromised" && <span>{_t("settings|encryption|advanced|breadcrumb_warning")}</span>}
</div>
<div className="mx_ResetIdentityPanel_footer">
<EncryptionCardButtons>
<Button
destructive={true}
onClick={async (evt) => {
@@ -89,7 +90,7 @@ export function ResetIdentityPanel({ onCancelClick, onFinish, variant }: ResetId
<Button kind="tertiary" onClick={onCancelClick}>
{_t("action|cancel")}
</Button>
</div>
</EncryptionCardButtons>
</EncryptionCard>
</>
);

View File

@@ -9,12 +9,13 @@ Please see LICENSE files in the repository root for full details.
import React, { type FC, useContext, useEffect, type AriaRole, useCallback } from "react";
import type { Room } from "matrix-js-sdk/src/matrix";
import { type Call, ConnectionState, ElementCall } from "../../../models/Call";
import { useCall } from "../../../hooks/useCall";
import { type Call, CallEvent } from "../../../models/Call";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import AppTile from "../elements/AppTile";
import { CallStore } from "../../../stores/CallStore";
import { SdkContextClass } from "../../../contexts/SDKContext";
import { useTypedEventEmitter } from "../../../hooks/useEventEmitter";
import { useCall } from "../../../hooks/useCall";
interface JoinCallViewProps {
room: Room;
@@ -22,10 +23,12 @@ interface JoinCallViewProps {
call: Call;
skipLobby?: boolean;
role?: AriaRole;
onClose: () => void;
}
const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call, skipLobby, role }) => {
const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call, skipLobby, role, onClose }) => {
const cli = useContext(MatrixClientContext);
useTypedEventEmitter(call, CallEvent.Close, onClose);
useEffect(() => {
// We'll take this opportunity to tidy up our room state
@@ -38,17 +41,6 @@ const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call, skipLobby,
call.widget.data.skipLobby = skipLobby;
}, [call.widget, skipLobby]);
useEffect(() => {
if (call.connectionState === ConnectionState.Disconnected) {
// immediately start the call
// (this will start the lobby view in the widget and connect to all required widget events)
call.start();
}
return (): void => {
// If we are connected the widget is sticky and we do not want to destroy the call.
if (!call.connected) call.destroy();
};
}, [call]);
const disconnectAllOtherCalls: () => Promise<void> = useCallback(async () => {
// The stickyPromise has to resolve before the widget actually becomes sticky.
// We only let the widget become sticky after disconnecting all other active calls.
@@ -57,6 +49,7 @@ const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call, skipLobby,
);
await Promise.all(calls.map(async (call) => await call.disconnect()));
}, []);
return (
<div className="mx_CallView" role={role}>
<AppTile
@@ -76,26 +69,27 @@ const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call, skipLobby,
interface CallViewProps {
room: Room;
resizing: boolean;
/**
* If true, the view will be blank until a call appears. Otherwise, the join
* button will create a call if there isn't already one.
*/
waitForCall: boolean;
skipLobby?: boolean;
role?: AriaRole;
/**
* Callback for when the user closes the call.
*/
onClose: () => void;
}
export const CallView: FC<CallViewProps> = ({ room, resizing, waitForCall, skipLobby, role }) => {
export const CallView: FC<CallViewProps> = ({ room, resizing, skipLobby, role, onClose }) => {
const call = useCall(room.roomId);
useEffect(() => {
if (call === null && !waitForCall) {
ElementCall.create(room, skipLobby);
}
}, [call, room, skipLobby, waitForCall]);
if (call === null) {
return null;
} else {
return <JoinCallView room={room} resizing={resizing} call={call} skipLobby={skipLobby} role={role} />;
}
return (
call && (
<JoinCallView
room={room}
resizing={resizing}
call={call}
skipLobby={skipLobby}
role={role}
onClose={onClose}
/>
)
);
};

View File

@@ -70,7 +70,6 @@ const RoomContext = createContext<
threadId: undefined,
liveTimeline: undefined,
narrow: false,
activeCall: null,
msc3946ProcessDynamicPredecessor: false,
canAskToJoin: false,
promptAskToJoin: false,

View File

@@ -343,7 +343,7 @@ export default async function createRoom(client: MatrixClient, opts: IOpts): Pro
await client.setPowerLevel(roomId, client.getUserId()!, 100);
} else if (opts.roomType === RoomType.UnstableCall) {
// Set up this video room with an Element call
await ElementCall.create(await room);
ElementCall.create(await room);
// Reset our power level back to admin so that the call becomes immutable
await client.setPowerLevel(roomId, client.getUserId()!, 100);

View File

@@ -365,4 +365,14 @@ export enum Action {
* Opens right panel room summary and focuses the search input
*/
FocusMessageSearch = "focus_search",
/**
* Open the direct message dialog
*/
CreateChat = "view_create_chat",
/**
* Open the create room dialog
*/
CreateRoom = "view_create_room",
}

View File

@@ -75,9 +75,5 @@ export const useFull = (call: Call | null): boolean => {
export const useJoinCallButtonDisabledTooltip = (call: Call | null): string | null => {
const isFull = useFull(call);
const state = useConnectionState(call);
if (state === ConnectionState.Connecting) return _t("voip|join_button_tooltip_connecting");
if (isFull) return _t("voip|join_button_tooltip_call_full");
return null;
return isFull ? _t("voip|join_button_tooltip_call_full") : null;
};

Some files were not shown because too many files have changed in this diff Show More