Compare commits
26 Commits
hs/identit
...
robin/call
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
569d525c6e | ||
|
|
7799cb2ec5 | ||
|
|
e47d7aaaff | ||
|
|
8857c07acb | ||
|
|
28ed506fe1 | ||
|
|
76b3be6263 | ||
|
|
6c768b8b32 | ||
|
|
809ada17a4 | ||
|
|
c7762a80f1 | ||
|
|
261923832d | ||
|
|
3daa1bf06a | ||
|
|
e5c8d7dbf0 | ||
|
|
441119ca3a | ||
|
|
acb3e781a4 | ||
|
|
3fb1f6ef4d | ||
|
|
cbfbfad959 | ||
|
|
7546bbc1f0 | ||
|
|
6a10c86d7a | ||
|
|
6f9e3bfe3e | ||
|
|
61d55462df | ||
|
|
d391c69e53 | ||
|
|
a6afff9759 | ||
|
|
073d8e0b86 | ||
|
|
ecf5d720b0 | ||
|
|
b7fea97bb6 | ||
|
|
90801eb38b |
6
.github/workflows/docker.yaml
vendored
@@ -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
|
||||
|
||||
|
||||
2
.github/workflows/tests.yml
vendored
@@ -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
@@ -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).
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM mcr.microsoft.com/playwright:v1.49.1-noble
|
||||
FROM mcr.microsoft.com/playwright:v1.50.1-noble
|
||||
|
||||
WORKDIR /work
|
||||
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
Before Width: | Height: | Size: 224 KiB After Width: | Height: | Size: 227 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 983 KiB After Width: | Height: | Size: 985 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 239 KiB After Width: | Height: | Size: 247 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 20 KiB |
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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%;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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%
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
23
res/css/views/settings/_SetIdServer.pcss
Normal 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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,3 +31,10 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx_EncryptionCard_buttons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--cpd-space-4x);
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@@ -16,11 +16,4 @@
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_ResetIdentityPanel_footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--cpd-space-4x);
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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 */
|
||||
/* ******************** */
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}
|
||||
</>
|
||||
|
||||
@@ -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),
|
||||
})
|
||||
|
||||
@@ -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]);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
@@ -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");
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -70,7 +70,6 @@ const RoomContext = createContext<
|
||||
threadId: undefined,
|
||||
liveTimeline: undefined,
|
||||
narrow: false,
|
||||
activeCall: null,
|
||||
msc3946ProcessDynamicPredecessor: false,
|
||||
canAskToJoin: false,
|
||||
promptAskToJoin: false,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||