Merge branch 'develop' into travis/render-join-rules

This commit is contained in:
Travis Ralston 2019-02-08 09:24:07 -07:00
commit 8cdfdd2e24
17 changed files with 153 additions and 271 deletions

View file

@ -190,10 +190,13 @@ const LeftPanel = React.createClass({
const tagPanelEnabled = SettingsStore.getValue("TagPanel.enableTagPanel");
let tagPanelContainer;
const isCustomTagsEnabled = SettingsStore.isFeatureEnabled("feature_custom_tags");
if (tagPanelEnabled) {
tagPanelContainer = (<div className="mx_LeftPanel_tagPanelContainer">
<TagPanel />
<CustomRoomTagPanel />
{ isCustomTagsEnabled ? <CustomRoomTagPanel /> : undefined }
<TagPanelButtons />
</div>);
}

View file

@ -19,6 +19,8 @@ import sdk from '../../../../index';
import MatrixClientPeg from '../../../../MatrixClientPeg';
import Modal from '../../../../Modal';
import { MatrixClient } from 'matrix-js-sdk';
import { _t } from '../../../../languageHandler';
const RESTORE_TYPE_PASSPHRASE = 0;
@ -88,7 +90,7 @@ export default React.createClass({
});
try {
const recoverInfo = await MatrixClientPeg.get().restoreKeyBackupWithPassword(
this.state.passPhrase, undefined, undefined, this.state.backupInfo.version,
this.state.passPhrase, undefined, undefined, this.state.backupInfo,
);
this.setState({
loading: false,
@ -107,11 +109,11 @@ export default React.createClass({
this.setState({
loading: true,
restoreError: null,
restoreType: RESTORE_TYPE_PASSPHRASE,
restoreType: RESTORE_TYPE_RECOVERYKEY,
});
try {
const recoverInfo = await MatrixClientPeg.get().restoreKeyBackupWithRecoveryKey(
this.state.recoveryKey, undefined, undefined, this.state.backupInfo.version,
this.state.recoveryKey, undefined, undefined, this.state.backupInfo,
);
this.setState({
loading: false,
@ -185,32 +187,31 @@ export default React.createClass({
title = _t("Error");
content = _t("Unable to load backup status");
} else if (this.state.restoreError) {
title = _t("Error");
content = _t("Unable to restore backup");
if (this.state.restoreError.errcode === MatrixClient.RESTORE_BACKUP_ERROR_BAD_KEY) {
if (this.state.restoreType === RESTORE_TYPE_RECOVERYKEY) {
title = _t("Recovery Key Mismatch");
content = <div>
<p>{_t(
"Backup could not be decrypted with this key: " +
"please verify that you entered the correct recovery key.",
)}</p>
</div>;
} else {
title = _t("Incorrect Recovery Passphrase");
content = <div>
<p>{_t(
"Backup could not be decrypted with this passphrase: " +
"please verify that you entered the correct recovery passphrase.",
)}</p>
</div>;
}
} else {
title = _t("Error");
content = _t("Unable to restore backup");
}
} else if (this.state.backupInfo === null) {
title = _t("Error");
content = _t("No backup found!");
} else if (
this.state.recoverInfo &&
this.state.recoverInfo.imported === 0 &&
this.state.recoverInfo.total > 0
) {
title = _t("Error Restoring Backup");
if (this.state.restoreType === RESTORE_TYPE_RECOVERYKEY) {
content = <div>
<p>{_t(
"Backup could not be decrypted with this key: " +
"please verify that you entered the correct recovery key.",
)}</p>
</div>;
} else {
content = <div>
<p>{_t(
"Backup could not be decrypted with this passphrase: " +
"please verify that you entered the correct recovery passphrase.",
)}</p>
</div>;
}
} else if (this.state.recoverInfo) {
title = _t("Backup Restored");
let failedToDecrypt;

View file

@ -1,103 +0,0 @@
/*
Copyright 2019 New Vector Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import PropTypes from "prop-types";
import classnames from 'classnames';
import sdk from '../../../index';
class HexVerifyPair extends React.Component {
static propTypes = {
text: PropTypes.string.isRequired,
index: PropTypes.number,
verified: PropTypes.bool,
onChange: PropTypes.func.isRequired,
}
_onClick = () => {
this.setState({verified: !this.props.verified});
this.props.onChange(this.props.index, !this.props.verified);
}
render() {
const classNames = {
mx_HexVerify_pair: true,
mx_HexVerify_pair_verified: this.props.verified,
};
const AccessibleButton = sdk.getComponent('views.elements.AccessibleButton');
return <AccessibleButton className={classnames(classNames)}
onClick={this._onClick}
>
{this.props.text}
</AccessibleButton>;
}
}
/*
* Helps a user verify a hexadecimal code matches one displayed
* elsewhere (eg. on a different device)
*/
export default class HexVerify extends React.Component {
static propTypes = {
text: PropTypes.string.isRequired,
onVerifiedStateChange: PropTypes.func,
}
static defaultProps = {
onVerifiedStateChange: function() {},
}
constructor(props) {
super(props);
this.state = {
pairsVerified: [],
};
for (let i = 0; i < props.text.length; i += 2) {
this.state.pairsVerified.push(false);
}
}
_onPairChange = (index, newVal) => {
const oldVerified = this.state.pairsVerified.reduce((acc, val) => {
return acc && val;
}, true);
const newPairsVerified = this.state.pairsVerified.slice(0);
newPairsVerified[index] = newVal;
const newVerified = newPairsVerified.reduce((acc, val) => {
return acc && val;
}, true);
this.setState({pairsVerified: newPairsVerified});
if (oldVerified !== newVerified) {
this.props.onVerifiedStateChange(newVerified);
}
}
render() {
const pairs = [];
for (let i = 0; i < this.props.text.length / 2; ++i) {
pairs.push(<HexVerifyPair key={i} index={i}
text={this.props.text.substr(i * 2, 2)}
verified={this.state.pairsVerified[i]}
onChange={this._onPairChange}
/>);
}
return <div className="mx_HexVerify">
{pairs}
</div>;
}
}

View file

@ -38,6 +38,12 @@ export default class ToggleSwitch extends React.Component {
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.checked !== this.state.checked) {
this.setState({checked: nextProps.checked});
}
}
_onClick = (e) => {
e.stopPropagation();
e.preventDefault();

View file

@ -172,11 +172,14 @@ module.exports = React.createClass({
this._delayedRefreshRoomList();
});
this._customTagStoreToken = CustomRoomTagStore.addListener(() => {
this.setState({
customTags: CustomRoomTagStore.getTags(),
if (SettingsStore.isFeatureEnabled("feature_custom_tags")) {
this._customTagStoreToken = CustomRoomTagStore.addListener(() => {
this.setState({
customTags: CustomRoomTagStore.getTags(),
});
});
});
}
this.refreshRoomList();
@ -728,7 +731,8 @@ module.exports = React.createClass({
];
const tagSubLists = Object.keys(this.state.lists)
.filter((tagName) => {
return this.state.customTags[tagName] && !tagName.match(STANDARD_TAGS_REGEX);
return (!this.state.customTags || this.state.customTags[tagName]) &&
!tagName.match(STANDARD_TAGS_REGEX);
}).map((tagName) => {
return {
list: this.state.lists[tagName],

View file

@ -250,19 +250,26 @@ export default class KeyBackupPanel extends React.PureComponent {
backupSigStatuses = _t("Backup is not signed by any of your devices");
}
let trustedLocally;
if (this.state.backupSigStatus.trusted_locally) {
trustedLocally = _t("This backup is trusted because it has been restored on this device");
}
return <div>
{_t("Backup version: ")}{this.state.backupInfo.version}<br />
{_t("Algorithm: ")}{this.state.backupInfo.algorithm}<br />
{clientBackupStatus}<br />
<div>{_t("Backup version: ")}{this.state.backupInfo.version}</div>
<div>{_t("Algorithm: ")}{this.state.backupInfo.algorithm}</div>
<div>{clientBackupStatus}</div>
{uploadStatus}
<div>{backupSigStatuses}</div><br />
<br />
<AccessibleButton kind="primary" onClick={this._restoreBackup}>
{ _t("Restore backup") }
</AccessibleButton>&nbsp;&nbsp;&nbsp;
<AccessibleButton kind="danger" onClick={this._deleteBackup}>
{ _t("Delete backup") }
</AccessibleButton>
<div>{backupSigStatuses}</div>
<div>{trustedLocally}</div>
<p>
<AccessibleButton kind="primary" onClick={this._restoreBackup}>
{ _t("Restore backup") }
</AccessibleButton>&nbsp;&nbsp;&nbsp;
<AccessibleButton kind="danger" onClick={this._deleteBackup}>
{ _t("Delete backup") }
</AccessibleButton>
</p>
</div>;
} else {
return <div>

View file

@ -18,9 +18,7 @@ import React from 'react';
import {_t} from "../../../../languageHandler";
import PropTypes from "prop-types";
import SettingsStore, {SettingLevel} from "../../../../settings/SettingsStore";
import MatrixClientPeg from "../../../../MatrixClientPeg";
import LabelledToggleSwitch from "../../elements/LabelledToggleSwitch";
const Modal = require("../../../../Modal");
const sdk = require("../../../../index");
export class LabsSettingToggle extends React.Component {
@ -28,38 +26,7 @@ export class LabsSettingToggle extends React.Component {
featureId: PropTypes.string.isRequired,
};
async _onLazyLoadChanging(enabling) {
// don't prevent turning LL off when not supported
if (enabling) {
const supported = await MatrixClientPeg.get().doesServerSupportLazyLoading();
if (!supported) {
await new Promise((resolve) => {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createDialog(QuestionDialog, {
title: _t("Lazy loading members not supported"),
description:
<div>
{ _t("Lazy loading is not supported by your " +
"current homeserver.") }
</div>,
button: _t("OK"),
onFinished: resolve,
});
});
return false;
}
}
return true;
}
_onChange = async (checked) => {
if (this.props.featureId === "feature_lazyloading") {
const confirmed = await this._onLazyLoadChanging(checked);
if (!confirmed) {
return;
}
}
await SettingsStore.setFeatureEnabled(this.props.featureId, checked);
this.forceUpdate();
};

View file

@ -27,8 +27,27 @@ export default class SecurityRoomSettingsTab extends React.Component {
roomId: PropTypes.string.isRequired,
};
constructor() {
super();
this.state = {
joinRule: "invite",
guestAccess: "can_join",
history: "shared",
encrypted: false,
};
}
componentWillMount(): void {
MatrixClientPeg.get().on("RoomState.events", this._onStateEvent);
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
const state = room.currentState;
const joinRule = state.getStateEvents("m.room.join_rules", "").getContent()['join_rule'];
const guestAccess = state.getStateEvents("m.room.guest_access", "").getContent()['guest_access'];
const history = state.getStateEvents("m.room.history_visibility", "").getContent()['history_visibility'];
const encrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.roomId);
this.setState({joinRule, guestAccess, history, encrypted});
}
componentWillUnmount(): void {
@ -46,19 +65,37 @@ export default class SecurityRoomSettingsTab extends React.Component {
};
_onEncryptionChange = (e) => {
const beforeEncrypted = this.state.encrypted;
this.setState({encrypted: true});
MatrixClientPeg.get().sendStateEvent(
this.props.roomId, "m.room.encryption",
{ algorithm: "m.megolm.v1.aes-sha2" },
);
).catch((e) => {
console.error(e);
this.setState({encrypted: beforeEncrypted});
});
};
_fixGuestAccess = (e) => {
e.preventDefault();
e.stopPropagation();
const joinRule = "invite";
const guestAccess = "can_join";
const beforeJoinRule = this.state.joinRule;
const beforeGuestAccess = this.state.guestAccess;
this.setState({joinRule, guestAccess});
const client = MatrixClientPeg.get();
client.sendStateEvent(this.props.roomId, "m.room.join_rules", {join_rule: "invite"}, "");
client.sendStateEvent(this.props.roomId, "m.room.guest_access", {guest_access: "can_join"}, "");
client.sendStateEvent(this.props.roomId, "m.room.join_rules", {join_rule: joinRule}, "").catch((e) => {
console.error(e);
this.setState({joinRule: beforeJoinRule});
});
client.sendStateEvent(this.props.roomId, "m.room.guest_access", {guest_access: guestAccess}, "").catch((e) => {
console.error(e);
this.setState({guestAccess: beforeGuestAccess});
});
};
_onRoomAccessRadioToggle = (ev) => {
@ -92,24 +129,39 @@ export default class SecurityRoomSettingsTab extends React.Component {
break;
}
const beforeJoinRule = this.state.joinRule;
const beforeGuestAccess = this.state.guestAccess;
this.setState({joinRule, guestAccess});
const client = MatrixClientPeg.get();
client.sendStateEvent(this.props.roomId, "m.room.join_rules", {join_rule: joinRule}, "");
client.sendStateEvent(this.props.roomId, "m.room.guest_access", {guest_access: guestAccess}, "");
client.sendStateEvent(this.props.roomId, "m.room.join_rules", {join_rule: joinRule}, "").catch((e) => {
console.error(e);
this.setState({joinRule: beforeJoinRule});
});
client.sendStateEvent(this.props.roomId, "m.room.guest_access", {guest_access: guestAccess}, "").catch((e) => {
console.error(e);
this.setState({guestAccess: beforeGuestAccess});
});
};
_onHistoryRadioToggle = (ev) => {
const beforeHistory = this.state.history;
this.setState({history: ev.target.value});
MatrixClientPeg.get().sendStateEvent(this.props.roomId, "m.room.history_visibility", {
history_visibility: ev.target.value,
}, "");
}, "").catch((e) => {
console.error(e);
this.setState({history: beforeHistory});
});
};
_renderRoomAccess() {
const client = MatrixClientPeg.get();
const room = client.getRoom(this.props.roomId);
const joinRule = room.currentState.getStateEvents("m.room.join_rules", "").getContent()['join_rule'];
const guestAccess = room.currentState.getStateEvents("m.room.guest_access", "").getContent()['guest_access'];
const joinRule = this.state.joinRule;
const guestAccess = this.state.guestAccess;
const aliasEvents = room.currentState.getStateEvents("m.room.aliases") || [];
const hasAliases = aliasEvents.includes((ev) => (ev.getContent().aliases || []).length);
const hasAliases = !!aliasEvents.find((ev) => (ev.getContent().aliases || []).length > 0);
const canChangeAccess = room.currentState.mayClientSendStateEvent("m.room.join_rules", client)
&& room.currentState.mayClientSendStateEvent("m.room.guest_access", client);
@ -170,9 +222,8 @@ export default class SecurityRoomSettingsTab extends React.Component {
_renderHistory() {
const client = MatrixClientPeg.get();
const room = client.getRoom(this.props.roomId);
const state = room.currentState;
const history = state.getStateEvents("m.room.history_visibility", "").getContent()['history_visibility'];
const history = this.state.history;
const state = client.getRoom(this.props.roomId).currentState;
const canChangeHistory = state.mayClientSendStateEvent('m.room.history_visibility', client);
return (
@ -218,7 +269,7 @@ export default class SecurityRoomSettingsTab extends React.Component {
const client = MatrixClientPeg.get();
const room = client.getRoom(this.props.roomId);
const isEncrypted = client.isRoomEncrypted(this.props.roomId);
const isEncrypted = this.state.encrypted;
const hasEncryptionPermission = room.currentState.mayClientSendStateEvent("m.room.encryption", client);
const canEnableEncryption = !isEncrypted && hasEncryptionPermission;

View file

@ -28,19 +28,11 @@ export default class VerificationShowSas extends React.Component {
constructor() {
super();
this.state = {
sasVerified: false,
};
}
_onVerifiedStateChange = (newVal) => {
this.setState({sasVerified: newVal});
}
render() {
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
const HexVerify = sdk.getComponent('views.elements.HexVerify');
return <div>
return <div className="mx_VerificationShowSas">
<p>{_t(
"Verify this user by confirming the following number appears on their screen.",
)}</p>
@ -48,15 +40,11 @@ export default class VerificationShowSas extends React.Component {
"For maximum security, we recommend you do this in person or use another " +
"trusted means of communication.",
)}</p>
<HexVerify text={this.props.sas}
onVerifiedStateChange={this._onVerifiedStateChange}
/>
<p>{_t(
"To continue, click on each pair to confirm it's correct.",
)}</p>
<div className="mx_VerificationShowSas_sas">
{this.props.sas}
</div>
<DialogButtons onPrimaryButtonClick={this.props.onDone}
primaryButton={_t("Continue")}
primaryDisabled={!this.state.sasVerified}
hasCancel={true}
onCancel={this.props.onCancel}
/>