Use EditInPlace control for Identity Server picker to improve a11y (#29280)

* Use EditInPlace for identity server picker.

* Exclude picker from default dialog button styles.

* Remove unused import.

* Update test

* Remove unused css

* Update test

* drop only

* Add a test for setting an ID server.

* Add a unit test for SetIdServer

* fix tests

* Reformat mx_Dialog button :not list to use a more readable selector.

* Reformat other :not sections

* forgot a comma

* We're in 2025 now.

* Update copyright + use class methods.
This commit is contained in:
Will Hunt
2025-02-21 13:18:14 +00:00
committed by GitHub
parent 2e1798edc4
commit 22943ee06a
9 changed files with 309 additions and 129 deletions

View File

@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024-2025 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,6 +9,7 @@ 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";
@@ -22,7 +23,6 @@ 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,10 +86,12 @@ export default class SetIdServer extends React.Component<IProps, IState> {
defaultIdServer = abbreviateUrl(getDefaultIdentityServerUrl());
}
const currentClientIdServer = MatrixClientPeg.safeGet().getIdentityServerUrl();
this.state = {
defaultIdServer,
currentClientIdServer: MatrixClientPeg.safeGet().getIdentityServerUrl(),
idServer: "",
currentClientIdServer,
idServer: currentClientIdServer ?? "",
busy: false,
disconnectBusy: false,
checking: false,
@@ -117,26 +119,7 @@ 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 });
};
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;
this.setState({ idServer: u, error: undefined });
};
private saveIdServer = (fullUrl: string): void => {
@@ -148,7 +131,7 @@ export default class SetIdServer extends React.Component<IProps, IState> {
busy: false,
error: undefined,
currentClientIdServer: fullUrl,
idServer: "",
idServer: fullUrl,
});
};
@@ -175,7 +158,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(fullUrl);
const [confirmed] = await this.showNoTermsWarning();
save = !!confirmed;
}
@@ -213,7 +196,7 @@ export default class SetIdServer extends React.Component<IProps, IState> {
});
};
private showNoTermsWarning(fullUrl: string): Promise<[ok?: boolean]> {
private showNoTermsWarning(): Promise<[ok?: boolean]> {
const { finished } = Modal.createDialog(QuestionDialog, {
title: _t("terms|identity_server_no_terms_title"),
description: (
@@ -347,6 +330,9 @@ 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;
@@ -356,13 +342,13 @@ export default class SetIdServer extends React.Component<IProps, IState> {
bodyText = _t(
"identity_server|description_connected",
{},
{ server: (sub) => <strong>{abbreviateUrl(idServerUrl)}</strong> },
{ server: () => <strong>{abbreviateUrl(idServerUrl)}</strong> },
);
if (this.props.missingTerms) {
bodyText = _t(
"identity_server|change_server_prompt",
{},
{ server: (sub) => <strong>{abbreviateUrl(idServerUrl)}</strong> },
{ server: () => <strong>{abbreviateUrl(idServerUrl)}</strong> },
);
}
} else {
@@ -393,28 +379,25 @@ export default class SetIdServer extends React.Component<IProps, IState> {
return (
<SettingsFieldset legend={sectionTitle} description={bodyText}>
<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>
<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}
</SettingsFieldset>
);
}

View File

@@ -1245,6 +1245,7 @@
"change": "Change identity server",
"change_prompt": "Disconnect from the identity server <current /> and connect to <new /> instead?",
"change_server_prompt": "If you don't want to use <server /> to discover and be discoverable by existing contacts you know, enter another identity server below.",
"changed": "Your identity server has been changed",
"checking": "Checking server",
"description_connected": "You are currently using <server></server> to discover and be discoverable by existing contacts you know. You can change your identity server below.",
"description_disconnected": "You are not currently using an identity server. To discover and be discoverable by existing contacts you know, add one below.",