];
}
@@ -279,23 +309,35 @@ export default class Dropdown extends React.Component {
let menu;
if (this.state.expanded) {
if (this.props.searchEnabled) {
- currentValue =
+ );
}
if (!currentValue) {
const selectedChild = this.props.getShortOption ?
this.props.getShortOption(this.props.value) :
this.childrenByKey[this.props.value];
- currentValue =
+ currentValue =
{ selectedChild }
;
}
@@ -311,9 +353,18 @@ export default class Dropdown extends React.Component {
// Note the menu sits inside the AccessibleButton div so it's anchored
// to the input, but overflows below it. The root contains both.
return
-
+
{ currentValue }
-
+
{ menu }
;
@@ -321,6 +372,7 @@ export default class Dropdown extends React.Component {
}
Dropdown.propTypes = {
+ id: PropTypes.string.isRequired,
// The width that the dropdown should be. If specified,
// the dropped-down part of the menu will be set to this
// width.
@@ -340,4 +392,6 @@ Dropdown.propTypes = {
value: PropTypes.string,
// negative for consistency with HTML
disabled: PropTypes.bool,
+ // ARIA label
+ label: PropTypes.string.isRequired,
};
diff --git a/src/components/views/elements/Field.js b/src/components/views/elements/Field.js
index 0a737d963a..cee33205d7 100644
--- a/src/components/views/elements/Field.js
+++ b/src/components/views/elements/Field.js
@@ -66,10 +66,14 @@ export default class Field extends React.PureComponent {
this.state = {
valid: undefined,
feedback: undefined,
+ focused: false,
};
}
onFocus = (ev) => {
+ this.setState({
+ focused: true,
+ });
this.validate({
focused: true,
});
@@ -88,6 +92,9 @@ export default class Field extends React.PureComponent {
};
onBlur = (ev) => {
+ this.setState({
+ focused: false,
+ });
this.validate({
focused: false,
});
@@ -112,7 +119,9 @@ export default class Field extends React.PureComponent {
allowEmpty,
});
- if (feedback) {
+ // this method is async and so we may have been blurred since the method was called
+ // if we have then hide the feedback as withValidation does
+ if (this.state.focused && feedback) {
this.setState({
valid,
feedback,
diff --git a/src/components/views/elements/ImageView.js b/src/components/views/elements/ImageView.js
index b2f6d0abbb..2b27c27e78 100644
--- a/src/components/views/elements/ImageView.js
+++ b/src/components/views/elements/ImageView.js
@@ -28,6 +28,7 @@ const AccessibleButton = require('../../../components/views/elements/AccessibleB
const Modal = require('../../../Modal');
const sdk = require('../../../index');
import { _t } from '../../../languageHandler';
+import {Key} from "../../../Keyboard";
export default class ImageView extends React.Component {
static propTypes = {
@@ -62,7 +63,7 @@ export default class ImageView extends React.Component {
}
onKeyDown = (ev) => {
- if (ev.keyCode === 27) { // escape
+ if (ev.key === Key.ESCAPE) {
ev.stopPropagation();
ev.preventDefault();
this.props.onFinished();
diff --git a/src/components/views/elements/LanguageDropdown.js b/src/components/views/elements/LanguageDropdown.js
index 451c97d958..ebe26cfad8 100644
--- a/src/components/views/elements/LanguageDropdown.js
+++ b/src/components/views/elements/LanguageDropdown.js
@@ -21,6 +21,7 @@ import PropTypes from 'prop-types';
import sdk from '../../../index';
import * as languageHandler from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore";
+import { _t } from "../../../languageHandler";
function languageMatchesSearchQuery(query, language) {
if (language.label.toUpperCase().indexOf(query.toUpperCase()) == 0) return true;
@@ -105,9 +106,14 @@ export default class LanguageDropdown extends React.Component {
value = this.props.value || language;
}
- return
{ options }
;
diff --git a/src/components/views/elements/ToggleSwitch.js b/src/components/views/elements/ToggleSwitch.js
index 5de249f214..b067840792 100644
--- a/src/components/views/elements/ToggleSwitch.js
+++ b/src/components/views/elements/ToggleSwitch.js
@@ -19,46 +19,32 @@ import React from "react";
import PropTypes from "prop-types";
import classNames from "classnames";
-import {KeyCode} from "../../../Keyboard";
+import sdk from "../../../index";
-// Controlled Toggle Switch element
+// Controlled Toggle Switch element, written with Accessibility in mind
const ToggleSwitch = ({checked, disabled=false, onChange, ...props}) => {
const _onClick = (e) => {
- e.stopPropagation();
- e.preventDefault();
if (disabled) return;
-
onChange(!checked);
};
- const _onKeyDown = (e) => {
- e.stopPropagation();
- e.preventDefault();
- if (disabled) return;
-
- if (e.keyCode === KeyCode.ENTER || e.keyCode === KeyCode.SPACE) {
- onChange(!checked);
- }
- };
-
const classes = classNames({
"mx_ToggleSwitch": true,
"mx_ToggleSwitch_on": checked,
"mx_ToggleSwitch_enabled": !disabled,
});
+ const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
return (
-
+
);
};
diff --git a/src/components/views/messages/MKeyVerificationRequest.js b/src/components/views/messages/MKeyVerificationRequest.js
index b2a1724fc6..4faa1b20aa 100644
--- a/src/components/views/messages/MKeyVerificationRequest.js
+++ b/src/components/views/messages/MKeyVerificationRequest.js
@@ -52,7 +52,7 @@ export default class MKeyVerificationRequest extends React.Component {
const verifier = MatrixClientPeg.get().acceptVerificationDM(this.props.mxEvent, verificationMethods.SAS);
Modal.createTrackedDialog('Incoming Verification', '', IncomingSasDialog, {
verifier,
- });
+ }, null, /* priority = */ false, /* static = */ true);
};
_onRejectClicked = () => {
diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js
index af8b4616f8..d1d7aa0371 100644
--- a/src/components/views/right_panel/UserInfo.js
+++ b/src/components/views/right_panel/UserInfo.js
@@ -58,9 +58,20 @@ const _disambiguateDevices = (devices) => {
}
};
-const _getE2EStatus = (devices) => {
- const hasUnverifiedDevice = devices.some((device) => device.isUnverified());
- return hasUnverifiedDevice ? "warning" : "verified";
+const _getE2EStatus = (cli, userId, devices) => {
+ if (!SettingsStore.isFeatureEnabled("feature_cross_signing")) {
+ const hasUnverifiedDevice = devices.some((device) => device.isUnverified());
+ return hasUnverifiedDevice ? "warning" : "verified";
+ }
+ const userVerified = cli.checkUserTrust(userId).isCrossSigningVerified();
+ const allDevicesVerified = devices.every(device => {
+ const { deviceId } = device;
+ return cli.checkDeviceTrust(userId, deviceId).isCrossSigningVerified();
+ });
+ if (allDevicesVerified) {
+ return userVerified ? "verified" : "normal";
+ }
+ return "warning";
};
async function unverifyUser(matrixClient, userId) {
@@ -98,14 +109,14 @@ function openDMForUser(matrixClient, userId) {
}
function useIsEncrypted(cli, room) {
- const [isEncrypted, setIsEncrypted] = useState(cli.isRoomEncrypted(room.roomId));
+ const [isEncrypted, setIsEncrypted] = useState(room ? cli.isRoomEncrypted(room.roomId) : undefined);
const update = useCallback((event) => {
if (event.getType() === "m.room.encryption") {
setIsEncrypted(cli.isRoomEncrypted(room.roomId));
}
}, [cli, room]);
- useEventEmitter(room.currentState, "RoomState.events", update);
+ useEventEmitter(room ? room.currentState : undefined, "RoomState.events", update);
return isEncrypted;
}
@@ -114,7 +125,7 @@ function verifyDevice(userId, device) {
Modal.createTrackedDialog('Device Verify Dialog', '', DeviceVerifyDialog, {
userId: userId,
device: device,
- });
+ }, null, /* priority = */ false, /* static = */ true);
}
function DeviceItem({userId, device}) {
@@ -1264,7 +1275,8 @@ const UserInfo = withLegacyMatrixClient(({matrixClient: cli, user, groupId, room
let e2eIcon;
if (isRoomEncrypted && devices) {
- e2eIcon =
;
+ const e2eStatus = _getE2EStatus(cli, user.userId, devices);
+ e2eIcon =
;
}
return (
diff --git a/src/components/views/room_settings/RoomProfileSettings.js b/src/components/views/room_settings/RoomProfileSettings.js
index 76d2e5be84..2093a31a28 100644
--- a/src/components/views/room_settings/RoomProfileSettings.js
+++ b/src/components/views/room_settings/RoomProfileSettings.js
@@ -19,8 +19,7 @@ import PropTypes from 'prop-types';
import {_t} from "../../../languageHandler";
import MatrixClientPeg from "../../../MatrixClientPeg";
import Field from "../elements/Field";
-import AccessibleButton from "../elements/AccessibleButton";
-import classNames from 'classnames';
+import sdk from "../../../index";
// TODO: Merge with ProfileSettings?
export default class RoomProfileSettings extends React.Component {
@@ -62,13 +61,20 @@ export default class RoomProfileSettings extends React.Component {
this._avatarUpload = createRef();
}
- _uploadAvatar = (e) => {
- e.stopPropagation();
- e.preventDefault();
-
+ _uploadAvatar = () => {
this._avatarUpload.current.click();
};
+ _removeAvatar = () => {
+ // clear file upload field so same file can be selected
+ this._avatarUpload.current.value = "";
+ this.setState({
+ avatarUrl: undefined,
+ avatarFile: undefined,
+ enableProfileSave: true,
+ });
+ };
+
_saveProfile = async (e) => {
e.stopPropagation();
e.preventDefault();
@@ -139,45 +145,8 @@ export default class RoomProfileSettings extends React.Component {
};
render() {
- // TODO: Why is rendering a box with an overlay so complicated? Can the DOM be reduced?
-
- let showOverlayAnyways = true;
- let avatarElement =
;
- if (this.state.avatarUrl) {
- showOverlayAnyways = false;
- avatarElement =

;
- }
-
- const avatarOverlayClasses = classNames({
- "mx_ProfileSettings_avatarOverlay": true,
- "mx_ProfileSettings_avatarOverlay_show": showOverlayAnyways,
- });
- let avatarHoverElement = (
-
-
{_t("Upload room avatar")}
-
-
- );
- if (!this.state.canSetAvatar) {
- if (!showOverlayAnyways) {
- avatarHoverElement = null;
- } else {
- const disabledOverlayClasses = classNames({
- "mx_ProfileSettings_avatarOverlay": true,
- "mx_ProfileSettings_avatarOverlay_show": true,
- "mx_ProfileSettings_avatarOverlay_disabled": true,
- });
- avatarHoverElement = (
-
- {_t("No room avatar")}
-
- );
- }
- }
-
+ const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
+ const AvatarSetting = sdk.getComponent('settings.AvatarSetting');
return (
- {avatarElement}
- {avatarHoverElement}
-