Merge branch 'develop' into rob/apps

This commit is contained in:
Robert Swain 2017-06-13 15:50:43 +02:00
commit d67e7289e8
30 changed files with 1299 additions and 512 deletions

View file

@ -35,7 +35,7 @@ import * as Rooms from '../../Rooms';
import linkifyMatrix from "../../linkify-matrix";
import * as Lifecycle from '../../Lifecycle';
// LifecycleStore is not used but does listen to and dispatch actions
import LifecycleStore from '../../stores/LifecycleStore';
require('../../stores/LifecycleStore');
import RoomViewStore from '../../stores/RoomViewStore';
import PageTypes from '../../PageTypes';
@ -128,11 +128,6 @@ module.exports = React.createClass({
hasNewVersion: false,
newVersionReleaseNotes: null,
// The username to default to when upgrading an account from a guest
upgradeUsername: null,
// The access token we had for our guest account, used when upgrading to a normal account
guestAccessToken: null,
// Parameters used in the registration dance with the IS
register_client_secret: null,
register_session_id: null,
@ -191,7 +186,7 @@ module.exports = React.createClass({
componentWillMount: function() {
SdkConfig.put(this.props.config);
RoomViewStore.addListener(this._onRoomViewStoreUpdated);
this._roomViewStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdated);
this._onRoomViewStoreUpdated();
if (!UserSettingsStore.getLocalSetting('analyticsOptOut', false)) Analytics.enable();
@ -300,6 +295,7 @@ module.exports = React.createClass({
UDEHandler.stopListening();
window.removeEventListener("focus", this.onFocus);
window.removeEventListener('resize', this.handleResize);
this._roomViewStoreToken.remove();
},
componentDidUpdate: function() {
@ -315,8 +311,6 @@ module.exports = React.createClass({
viewUserId: null,
loggedIn: false,
ready: false,
upgradeUsername: null,
guestAccessToken: null,
};
Object.assign(newState, state);
this.setState(newState);
@ -351,25 +345,6 @@ module.exports = React.createClass({
screen: 'post_registration',
});
break;
case 'start_upgrade_registration':
// also stash our credentials, then if we restore the session,
// we can just do it the same way whether we started upgrade
// registration or explicitly logged out
this.setStateForNewScreen({
guestCreds: MatrixClientPeg.getCredentials(),
screen: "register",
upgradeUsername: MatrixClientPeg.get().getUserIdLocalpart(),
guestAccessToken: MatrixClientPeg.get().getAccessToken(),
});
// stop the client: if we are syncing whilst the registration
// is completed in another browser, we'll be 401ed for using
// a guest access token for a non-guest account.
// It will be restarted in onReturnToGuestClick
Lifecycle.stopMatrixClient();
this.notifyNewScreen('register');
break;
case 'start_password_recovery':
this.setStateForNewScreen({
screen: 'forgot_password',
@ -745,8 +720,8 @@ module.exports = React.createClass({
title: _t('Create Room'),
description: _t('Room name (optional)'),
button: _t('Create Room'),
onFinished: (should_create, name) => {
if (should_create) {
onFinished: (shouldCreate, name) => {
if (shouldCreate) {
const createOpts = {};
if (name) createOpts.name = name;
createRoom({createOpts}).done();
@ -1401,8 +1376,6 @@ module.exports = React.createClass({
idSid={this.state.register_id_sid}
email={this.props.startingFragmentQueryParams.email}
referrer={this.props.startingFragmentQueryParams.referrer}
username={this.state.upgradeUsername}
guestAccessToken={this.state.guestAccessToken}
defaultHsUrl={this.getDefaultHsUrl()}
defaultIsUrl={this.getDefaultIsUrl()}
brand={this.props.config.brand}

View file

@ -110,6 +110,13 @@ const ANALYTICS_SETTINGS_LABELS = [
},
];
const WEBRTC_SETTINGS_LABELS = [
{
id: 'webRtcForceTURN',
label: 'Disable Peer-to-Peer for 1:1 calls',
},
];
// Warning: Each "label" string below must be added to i18n/strings/en_EN.json,
// since they will be translated when rendered.
const CRYPTO_SETTINGS_LABELS = [
@ -310,11 +317,6 @@ module.exports = React.createClass({
},
onAvatarPickerClick: function(ev) {
if (MatrixClientPeg.get().isGuest()) {
dis.dispatch({action: 'view_set_mxid'});
return;
}
if (this.refs.file_label) {
this.refs.file_label.click();
}
@ -389,17 +391,14 @@ module.exports = React.createClass({
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: _t("Success"),
description: _t("Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them") + ".",
description: _t(
"Your password was successfully changed. You will not receive " +
"push notifications on other devices until you log back in to them",
) + ".",
});
dis.dispatch({action: 'password_changed'});
},
onUpgradeClicked: function() {
dis.dispatch({
action: "start_upgrade_registration",
});
},
onEnableNotificationsChange: function(event) {
UserSettingsStore.setEnableNotifications(event.target.checked);
},
@ -427,7 +426,10 @@ module.exports = React.createClass({
this._addThreepid.addEmailAddress(emailAddress, true).done(() => {
Modal.createDialog(QuestionDialog, {
title: _t("Verification Pending"),
description: _t("Please check your email and click on the link it contains. Once this is done, click continue."),
description: _t(
"Please check your email and click on the link it contains. Once this " +
"is done, click continue.",
),
button: _t('Continue'),
onFinished: this.onEmailDialogFinished,
});
@ -447,7 +449,7 @@ module.exports = React.createClass({
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createDialog(QuestionDialog, {
title: _t("Remove Contact Information?"),
description: _t("Remove %(threePid)s?", { threePid : threepid.address }),
description: _t("Remove %(threePid)s?", { threePid: threepid.address }),
button: _t('Remove'),
onFinished: (submit) => {
if (submit) {
@ -489,8 +491,8 @@ module.exports = React.createClass({
this.setState({email_add_pending: false});
if (err.errcode == 'M_THREEPID_AUTH_FAILED') {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
let message = _t("Unable to verify email address.") + " " +
_t("Please check your email and click on the link it contains. Once this is done, click continue.");
const message = _t("Unable to verify email address.") + " " +
_t("Please check your email and click on the link it contains. Once this is done, click continue.");
Modal.createDialog(QuestionDialog, {
title: _t("Verification Pending"),
description: message,
@ -608,7 +610,7 @@ module.exports = React.createClass({
}
},
_renderLanguageSetting: function () {
_renderLanguageSetting: function() {
const LanguageDropdown = sdk.getComponent('views.elements.LanguageDropdown');
return <div>
<label htmlFor="languageSelector">{_t('Interface Language')}</label>
@ -639,7 +641,7 @@ module.exports = React.createClass({
<input id="urlPreviewsDisabled"
type="checkbox"
defaultChecked={ UserSettingsStore.getUrlPreviewsDisabled() }
onChange={ (e) => UserSettingsStore.setUrlPreviewsDisabled(e.target.checked) }
onChange={ this._onPreviewsDisabledChanged }
/>
<label htmlFor="urlPreviewsDisabled">
{ _t("Disable inline URL previews by default") }
@ -647,17 +649,24 @@ module.exports = React.createClass({
</div>;
},
_onPreviewsDisabledChanged: function(e) {
UserSettingsStore.setUrlPreviewsDisabled(e.target.checked);
},
_renderSyncedSetting: function(setting) {
// TODO: this ought to be a separate component so that we don't need
// to rebind the onChange each time we render
const onChange = (e) => {
UserSettingsStore.setSyncedSetting(setting.id, e.target.checked);
if (setting.fn) setting.fn(e.target.checked);
};
return <div className="mx_UserSettings_toggle" key={ setting.id }>
<input id={ setting.id }
type="checkbox"
defaultChecked={ this._syncedSettings[setting.id] }
onChange={
(e) => {
UserSettingsStore.setSyncedSetting(setting.id, e.target.checked);
if (setting.fn) setting.fn(e.target.checked);
}
}
onChange={ onChange }
/>
<label htmlFor={ setting.id }>
{ _t(setting.label) }
@ -666,22 +675,24 @@ module.exports = React.createClass({
},
_renderThemeSelector: function(setting) {
// TODO: this ought to be a separate component so that we don't need
// to rebind the onChange each time we render
const onChange = (e) => {
if (e.target.checked) {
UserSettingsStore.setSyncedSetting(setting.id, setting.value);
}
dis.dispatch({
action: 'set_theme',
value: setting.value,
});
};
return <div className="mx_UserSettings_toggle" key={ setting.id + "_" + setting.value }>
<input id={ setting.id + "_" + setting.value }
type="radio"
name={ setting.id }
value={ setting.value }
defaultChecked={ this._syncedSettings[setting.id] === setting.value }
onChange={ (e) => {
if (e.target.checked) {
UserSettingsStore.setSyncedSetting(setting.id, setting.value);
}
dis.dispatch({
action: 'set_theme',
value: setting.value,
});
}
}
onChange={ onChange }
/>
<label htmlFor={ setting.id + "_" + setting.value }>
{ setting.label }
@ -720,8 +731,10 @@ module.exports = React.createClass({
<h3>{ _t("Cryptography") }</h3>
<div className="mx_UserSettings_section mx_UserSettings_cryptoSection">
<ul>
<li><label>{_t("Device ID:")}</label> <span><code>{deviceId}</code></span></li>
<li><label>{_t("Device key:")}</label> <span><code><b>{identityKey}</b></code></span></li>
<li><label>{_t("Device ID:")}</label>
<span><code>{deviceId}</code></span></li>
<li><label>{_t("Device key:")}</label>
<span><code><b>{identityKey}</b></code></span></li>
</ul>
{ importExportButtons }
</div>
@ -733,16 +746,18 @@ module.exports = React.createClass({
},
_renderLocalSetting: function(setting) {
// TODO: this ought to be a separate component so that we don't need
// to rebind the onChange each time we render
const onChange = (e) => {
UserSettingsStore.setLocalSetting(setting.id, e.target.checked);
if (setting.fn) setting.fn(e.target.checked);
};
return <div className="mx_UserSettings_toggle" key={ setting.id }>
<input id={ setting.id }
type="checkbox"
defaultChecked={ this._localSettings[setting.id] }
onChange={
(e) => {
UserSettingsStore.setLocalSetting(setting.id, e.target.checked);
if (setting.fn) setting.fn(e.target.checked);
}
}
onChange={ onChange }
/>
<label htmlFor={ setting.id }>
{ _t(setting.label) }
@ -794,26 +809,27 @@ module.exports = React.createClass({
if (this.props.enableLabs === false) return null;
UserSettingsStore.doTranslations();
const features = UserSettingsStore.LABS_FEATURES.map((feature) => (
<div key={feature.id} className="mx_UserSettings_toggle">
<input
type="checkbox"
id={feature.id}
name={feature.id}
defaultChecked={ UserSettingsStore.isFeatureEnabled(feature.id) }
onChange={(e) => {
if (MatrixClientPeg.get().isGuest()) {
e.target.checked = false;
dis.dispatch({action: 'view_set_mxid'});
return;
}
const features = UserSettingsStore.LABS_FEATURES.map((feature) => {
// TODO: this ought to be a separate component so that we don't need
// to rebind the onChange each time we render
const onChange = (e) => {
UserSettingsStore.setFeatureEnabled(feature.id, e.target.checked);
this.forceUpdate();
};
UserSettingsStore.setFeatureEnabled(feature.id, e.target.checked);
this.forceUpdate();
}}/>
<label htmlFor={feature.id}>{feature.name}</label>
</div>
));
return (
<div key={feature.id} className="mx_UserSettings_toggle">
<input
type="checkbox"
id={feature.id}
name={feature.id}
defaultChecked={ UserSettingsStore.isFeatureEnabled(feature.id) }
onChange={ onChange }
/>
<label htmlFor={feature.id}>{feature.name}</label>
</div>
);
});
return (
<div>
<h3>{ _t("Labs") }</h3>
@ -826,9 +842,6 @@ module.exports = React.createClass({
},
_renderDeactivateAccount: function() {
// We can't deactivate a guest account.
if (MatrixClientPeg.get().isGuest()) return null;
return <div>
<h3>{ _t("Deactivate Account") }</h3>
<div className="mx_UserSettings_section">
@ -865,9 +878,10 @@ module.exports = React.createClass({
if (!this.state.rejectingInvites) {
// bind() the invited rooms so any new invites that may come in as this button is clicked
// don't inadvertently get rejected as well.
const onClick = this._onRejectAllInvitesClicked.bind(this, invitedRooms);
reject = (
<AccessibleButton className="mx_UserSettings_button danger"
onClick={this._onRejectAllInvitesClicked.bind(this, invitedRooms)}>
onClick={onClick}>
{_t("Reject all %(invitedRooms)s invites", {invitedRooms: invitedRooms.length})}
</AccessibleButton>
);
@ -885,8 +899,6 @@ module.exports = React.createClass({
const settings = this.state.electron_settings;
if (!settings) return;
const {ipcRenderer} = require('electron');
return <div>
<h3>{ _t('Desktop specific') }</h3>
<div className="mx_UserSettings_section">
@ -894,9 +906,7 @@ module.exports = React.createClass({
<input type="checkbox"
name="auto-launch"
defaultChecked={settings['auto-launch']}
onChange={(e) => {
ipcRenderer.send('settings_set', 'auto-launch', e.target.checked);
}}
onChange={this._onAutoLaunchChanged}
/>
<label htmlFor="auto-launch">{_t('Start automatically after system login')}</label>
</div>
@ -904,6 +914,11 @@ module.exports = React.createClass({
</div>;
},
_onAutoLaunchChanged: function(e) {
const {ipcRenderer} = require('electron');
ipcRenderer.send('settings_set', 'auto-launch', e.target.checked);
},
_mapWebRtcDevicesToSpans: function(devices) {
return devices.map((device) => <span key={device.deviceId}>{device.label}</span>);
},
@ -937,16 +952,13 @@ module.exports = React.createClass({
}
},
_renderWebRtcSettings: function() {
_renderWebRtcDeviceSettings: function() {
if (this.state.mediaDevices === false) {
return <div>
<h3>{_t('VoIP')}</h3>
<div className="mx_UserSettings_section">
<p className="mx_UserSettings_link" onClick={this._requestMediaPermissions}>
{_t('Missing Media Permissions, click here to request.')}
</p>
</div>
</div>;
return (
<p className="mx_UserSettings_link" onClick={this._requestMediaPermissions}>
{_t('Missing Media Permissions, click here to request.')}
</p>
);
} else if (!this.state.mediaDevices) return;
const Dropdown = sdk.getComponent('elements.Dropdown');
@ -1000,10 +1012,17 @@ module.exports = React.createClass({
}
return <div>
<h3>{_t('VoIP')}</h3>
<div className="mx_UserSettings_section">
{microphoneDropdown}
{webcamDropdown}
</div>;
},
_renderWebRtcSettings: function() {
return <div>
<h3>{_t('VoIP')}</h3>
<div className="mx_UserSettings_section">
{ WEBRTC_SETTINGS_LABELS.map(this._renderLocalSetting) }
{ this._renderWebRtcDeviceSettings() }
</div>
</div>;
},
@ -1060,6 +1079,9 @@ module.exports = React.createClass({
const threepidsSection = this.state.threepids.map((val, pidIndex) => {
const id = "3pid-" + val.address;
// TODO; make a separate component to avoid having to rebind onClick
// each time we render
const onRemoveClick = (e) => this.onRemoveThreepidClicked(val);
return (
<div className="mx_UserSettings_profileTableRow" key={pidIndex}>
<div className="mx_UserSettings_profileLabelCell">
@ -1071,7 +1093,8 @@ module.exports = React.createClass({
/>
</div>
<div className="mx_UserSettings_threepidButton mx_filterFlipColor">
<img src="img/cancel-small.svg" width="14" height="14" alt={ _t("Remove") } onClick={this.onRemoveThreepidClicked.bind(this, val)} />
<img src="img/cancel-small.svg" width="14" height="14" alt={ _t("Remove") }
onClick={onRemoveClick} />
</div>
</div>
);
@ -1079,7 +1102,7 @@ module.exports = React.createClass({
let addEmailSection;
if (this.state.email_add_pending) {
addEmailSection = <Loader key="_email_add_spinner" />;
} else if (!MatrixClientPeg.get().isGuest()) {
} else {
addEmailSection = (
<div className="mx_UserSettings_profileTableRow" key="_newEmail">
<div className="mx_UserSettings_profileLabelCell">
@ -1107,16 +1130,7 @@ module.exports = React.createClass({
threepidsSection.push(addEmailSection);
threepidsSection.push(addMsisdnSection);
let accountJsx;
if (MatrixClientPeg.get().isGuest()) {
accountJsx = (
<div className="mx_UserSettings_button" onClick={this.onUpgradeClicked}>
{ _t("Create an account") }
</div>
);
} else {
accountJsx = (
const accountJsx = (
<ChangePassword
className="mx_UserSettings_accountTable"
rowClassName="mx_UserSettings_profileTableRow"
@ -1125,10 +1139,10 @@ module.exports = React.createClass({
buttonClassName="mx_UserSettings_button mx_UserSettings_changePasswordButton"
onError={this.onPasswordChangeError}
onFinished={this.onPasswordChanged} />
);
}
);
let notificationArea;
if (!MatrixClientPeg.get().isGuest() && this.state.threepids !== undefined) {
if (this.state.threepids !== undefined) {
notificationArea = (<div>
<h3>{ _t("Notifications") }</h3>
@ -1222,7 +1236,12 @@ module.exports = React.createClass({
{ _t("Logged in as:") } {this._me}
</div>
<div className="mx_UserSettings_advanced">
{_t('Access Token:')} <span className="mx_UserSettings_advanced_spoiler" onClick={this._showSpoiler} data-spoiler={ MatrixClientPeg.get().getAccessToken() }>&lt;{ _t("click to reveal") }&gt;</span>
{_t('Access Token:')}
<span className="mx_UserSettings_advanced_spoiler"
onClick={this._showSpoiler}
data-spoiler={ MatrixClientPeg.get().getAccessToken() }>
&lt;{ _t("click to reveal") }&gt;
</span>
</div>
<div className="mx_UserSettings_advanced">
{ _t("Homeserver is") } { MatrixClientPeg.get().getHomeserverUrl() }

View file

@ -45,8 +45,6 @@ module.exports = React.createClass({
brand: React.PropTypes.string,
email: React.PropTypes.string,
referrer: React.PropTypes.string,
username: React.PropTypes.string,
guestAccessToken: React.PropTypes.string,
teamServerConfig: React.PropTypes.shape({
// Email address to request new teams
supportEmail: React.PropTypes.string.isRequired,
@ -295,17 +293,6 @@ module.exports = React.createClass({
},
_makeRegisterRequest: function(auth) {
let guestAccessToken = this.props.guestAccessToken;
if (
this.state.formVals.username !== this.props.username ||
this.state.hsUrl != this.props.defaultHsUrl
) {
// don't try to upgrade if we changed our username
// or are registering on a different HS
guestAccessToken = null;
}
// Only send the bind params if we're sending username / pw params
// (Since we need to send no params at all to use the ones saved in the
// session).
@ -320,7 +307,7 @@ module.exports = React.createClass({
undefined, // session id: included in the auth dict already
auth,
bindThreepids,
guestAccessToken,
null,
);
},
@ -357,10 +344,6 @@ module.exports = React.createClass({
} else if (this.state.busy || this.state.teamServerBusy) {
registerBody = <Spinner />;
} else {
let guestUsername = this.props.username;
if (this.state.hsUrl != this.props.defaultHsUrl) {
guestUsername = null;
}
let errorSection;
if (this.state.errorText) {
errorSection = <div className="mx_Login_error">{this.state.errorText}</div>;
@ -374,7 +357,6 @@ module.exports = React.createClass({
defaultPhoneNumber={this.state.formVals.phoneNumber}
defaultPassword={this.state.formVals.password}
teamsConfig={this.state.teamsConfig}
guestUsername={guestUsername}
minPasswordLength={MIN_PASSWORD_LENGTH}
onError={this.onFormValidationFailed}
onRegisterClick={this.onFormSubmit}