Merge branch 'develop' into luke/room-list-flux

This commit is contained in:
Luke Barnard 2018-02-13 10:18:24 +00:00
commit db0ea9eb0e
51 changed files with 304 additions and 119 deletions

View file

@ -618,18 +618,26 @@ export default React.createClass({
},
_startRegistration: function(params) {
this.setStateForNewView({
const newState = {
view: VIEWS.REGISTER,
// these params may be undefined, but if they are,
// unset them from our state: we don't want to
// resume a previous registration session if the
// user just clicked 'register'
register_client_secret: params.client_secret,
register_session_id: params.session_id,
register_hs_url: params.hs_url,
register_is_url: params.is_url,
register_id_sid: params.sid,
});
};
// Only honour params if they are all present, otherwise we reset
// HS and IS URLs when switching to registration.
if (params.client_secret &&
params.session_id &&
params.hs_url &&
params.is_url &&
params.sid
) {
newState.register_client_secret = params.client_secret;
newState.register_session_id = params.session_id;
newState.register_hs_url = params.hs_url;
newState.register_is_url = params.is_url;
newState.register_id_sid = params.sid;
}
this.setStateForNewView(newState);
this.notifyNewScreen('register');
},
@ -1501,6 +1509,17 @@ export default React.createClass({
}
},
onServerConfigChange(config) {
const newState = {};
if (config.hsUrl) {
newState.register_hs_url = config.hsUrl;
}
if (config.isUrl) {
newState.register_is_url = config.isUrl;
}
this.setState(newState);
},
_makeRegistrationUrl: function(params) {
if (this.props.startingFragmentQueryParams.referrer) {
params.referrer = this.props.startingFragmentQueryParams.referrer;
@ -1589,6 +1608,7 @@ export default React.createClass({
onLoginClick={this.onLoginClick}
onRegisterClick={this.onRegisterClick}
onCancelClick={MatrixClientPeg.get() ? this.onReturnToAppClick : null}
onServerConfigChange={this.onServerConfigChange}
/>
);
}
@ -1623,6 +1643,7 @@ export default React.createClass({
onForgotPasswordClick={this.onForgotPasswordClick}
enableGuest={this.props.enableGuest}
onCancelClick={MatrixClientPeg.get() ? this.onReturnToAppClick : null}
onServerConfigChange={this.onServerConfigChange}
/>
);
}

View file

@ -264,12 +264,19 @@ module.exports = React.createClass({
isPeeking: true, // this will change to false if peeking fails
});
MatrixClientPeg.get().peekInRoom(roomId).then((room) => {
if (this.unmounted) {
return;
}
this.setState({
room: room,
peekLoading: false,
});
this._onRoomLoaded(room);
}, (err) => {
if (this.unmounted) {
return;
}
// Stop peeking if anything went wrong
this.setState({
isPeeking: false,
@ -286,7 +293,7 @@ module.exports = React.createClass({
} else {
throw err;
}
}).done();
});
}
} else if (room) {
// Stop peeking because we have joined this room previously

View file

@ -43,6 +43,7 @@ const TagPanel = React.createClass({
componentWillMount: function() {
this.unmounted = false;
this.context.matrixClient.on("Group.myMembership", this._onGroupMyMembership);
this.context.matrixClient.on("sync", this.onClientSync);
this._tagOrderStoreToken = TagOrderStore.addListener(() => {
if (this.unmounted) {
@ -60,6 +61,7 @@ const TagPanel = React.createClass({
componentWillUnmount() {
this.unmounted = true;
this.context.matrixClient.removeListener("Group.myMembership", this._onGroupMyMembership);
this.context.matrixClient.removeListener("sync", this.onClientSync);
if (this._filterStoreToken) {
this._filterStoreToken.remove();
}
@ -70,6 +72,16 @@ const TagPanel = React.createClass({
dis.dispatch(GroupActions.fetchJoinedGroups(this.context.matrixClient));
},
onClientSync(syncState, prevState) {
// Consider the client reconnected if there is no error with syncing.
// This means the state could be RECONNECTING, SYNCING or PREPARED.
const reconnected = syncState !== "ERROR" && prevState !== syncState;
if (reconnected) {
// Load joined groups
dis.dispatch(GroupActions.fetchJoinedGroups(this.context.matrixClient));
}
},
onClick(e) {
// Ignore clicks on children
if (e.target !== e.currentTarget) return;
@ -82,8 +94,7 @@ const TagPanel = React.createClass({
},
render() {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const TintableSvg = sdk.getComponent('elements.TintableSvg');
const GroupsButton = sdk.getComponent('elements.GroupsButton');
const DNDTagTile = sdk.getComponent('elements.DNDTagTile');
const tags = this.state.orderedTags.map((tag, index) => {
@ -114,9 +125,9 @@ const TagPanel = React.createClass({
</div>
) }
</Droppable>
<AccessibleButton className="mx_TagPanel_createGroupButton" onClick={this.onCreateGroupClick}>
<TintableSvg src="img/icons-create-room.svg" width="25" height="25" />
</AccessibleButton>
<div className="mx_TagPanel_createGroupButton">
<GroupsButton tooltip={true} />
</div>
</div>;
},
});

View file

@ -58,6 +58,7 @@ module.exports = React.createClass({
// login shouldn't care how password recovery is done.
onForgotPasswordClick: PropTypes.func,
onCancelClick: PropTypes.func,
onServerConfigChange: PropTypes.func.isRequired,
},
getInitialState: function() {
@ -218,6 +219,8 @@ module.exports = React.createClass({
if (config.isUrl !== undefined) {
newState.enteredIdentityServerUrl = config.isUrl;
}
this.props.onServerConfigChange(config);
this.setState(newState, function() {
self._initLoginLogic(config.hsUrl || null, config.isUrl);
});

View file

@ -61,6 +61,7 @@ module.exports = React.createClass({
// registration shouldn't know or care how login is done.
onLoginClick: PropTypes.func.isRequired,
onCancelClick: PropTypes.func,
onServerConfigChange: PropTypes.func.isRequired,
},
getInitialState: function() {
@ -131,6 +132,7 @@ module.exports = React.createClass({
if (config.isUrl !== undefined) {
newState.isUrl = config.isUrl;
}
this.props.onServerConfigChange(config);
this.setState(newState, function() {
this._replaceClient();
});

View file

@ -16,6 +16,7 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import { MatrixClient } from 'matrix-js-sdk';
import AvatarLogic from '../../../Avatar';
import sdk from '../../../index';
import AccessibleButton from '../elements/AccessibleButton';
@ -36,6 +37,10 @@ module.exports = React.createClass({
defaultToInitialLetter: PropTypes.bool, // true to add default url
},
contextTypes: {
matrixClient: PropTypes.instanceOf(MatrixClient),
},
getDefaultProps: function() {
return {
width: 40,
@ -49,6 +54,16 @@ module.exports = React.createClass({
return this._getState(this.props);
},
componentWillMount() {
this.unmounted = false;
this.context.matrixClient.on('sync', this.onClientSync);
},
componentWillUnmount() {
this.unmounted = true;
this.context.matrixClient.removeListener('sync', this.onClientSync);
},
componentWillReceiveProps: function(nextProps) {
// work out if we need to call setState (if the image URLs array has changed)
const newState = this._getState(nextProps);
@ -67,6 +82,23 @@ module.exports = React.createClass({
}
},
onClientSync(syncState, prevState) {
if (this.unmounted) return;
// Consider the client reconnected if there is no error with syncing.
// This means the state could be RECONNECTING, SYNCING or PREPARED.
const reconnected = syncState !== "ERROR" && prevState !== syncState;
if (reconnected &&
// Did we fall back?
this.state.urlsIndex > 0
) {
// Start from the highest priority URL again
this.setState({
urlsIndex: 0,
});
}
},
_getState: function(props) {
// work out the full set of urls to try to load. This is formed like so:
// imageUrls: [ props.url, props.urls, default image ]

View file

@ -17,9 +17,12 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import { MatrixClient } from 'matrix-js-sdk';
import { KeyCode } from '../../../Keyboard';
import AccessibleButton from '../elements/AccessibleButton';
import sdk from '../../../index';
import MatrixClientPeg from '../../../MatrixClientPeg';
/**
* Basic container for modal dialogs.
@ -37,6 +40,9 @@ export default React.createClass({
// callback to call when Enter is pressed
onEnterPressed: PropTypes.func,
// called when a key is pressed
onKeyDown: PropTypes.func,
// CSS class to apply to dialog div
className: PropTypes.string,
@ -48,7 +54,24 @@ export default React.createClass({
children: PropTypes.node,
},
childContextTypes: {
matrixClient: PropTypes.instanceOf(MatrixClient),
},
getChildContext: function() {
return {
matrixClient: this._matrixClient,
};
},
componentWillMount() {
this._matrixClient = MatrixClientPeg.get();
},
_onKeyDown: function(e) {
if (this.props.onKeyDown) {
this.props.onKeyDown(e);
}
if (e.keyCode === KeyCode.ESCAPE) {
e.stopPropagation();
e.preventDefault();

View file

@ -17,7 +17,7 @@ import React from 'react';
import sdk from '../../../index';
import dis from '../../../dispatcher';
import classNames from 'classnames';
import { Room, RoomMember } from 'matrix-js-sdk';
import { Room, RoomMember, MatrixClient } from 'matrix-js-sdk';
import PropTypes from 'prop-types';
import MatrixClientPeg from '../../../MatrixClientPeg';
import { MATRIXTO_URL_PATTERN } from '../../../linkify-matrix';
@ -61,6 +61,17 @@ const Pill = React.createClass({
shouldShowPillAvatar: PropTypes.bool,
},
childContextTypes: {
matrixClient: PropTypes.instanceOf(MatrixClient),
},
getChildContext() {
return {
matrixClient: this._matrixClient,
};
},
getInitialState() {
return {
// ID/alias of the room/user
@ -135,6 +146,7 @@ const Pill = React.createClass({
componentWillMount() {
this._unmounted = false;
this._matrixClient = MatrixClientPeg.get();
this.componentWillReceiveProps(this.props);
},

View file

@ -18,8 +18,9 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import { MatrixClient } from 'matrix-js-sdk';
import MFileBody from './MFileBody';
import MatrixClientPeg from '../../../MatrixClientPeg';
import ImageUtils from '../../../ImageUtils';
import Modal from '../../../Modal';
import sdk from '../../../index';
@ -40,15 +41,37 @@ module.exports = React.createClass({
onWidgetLoad: PropTypes.func.isRequired,
},
contextTypes: {
matrixClient: PropTypes.instanceOf(MatrixClient),
},
getInitialState: function() {
return {
decryptedUrl: null,
decryptedThumbnailUrl: null,
decryptedBlob: null,
error: null,
imgError: false,
};
},
componentWillMount() {
this.unmounted = false;
this.context.matrixClient.on('sync', this.onClientSync);
},
onClientSync(syncState, prevState) {
if (this.unmounted) return;
// Consider the client reconnected if there is no error with syncing.
// This means the state could be RECONNECTING, SYNCING or PREPARED.
const reconnected = syncState !== "ERROR" && prevState !== syncState;
if (reconnected && this.state.imgError) {
// Load the image again
this.setState({
imgError: false,
});
}
},
onClick: function onClick(ev) {
if (ev.button == 0 && !ev.metaKey) {
@ -97,12 +120,18 @@ module.exports = React.createClass({
imgElement.src = this._getThumbUrl();
},
onImageError: function() {
this.setState({
imgError: true,
});
},
_getContentUrl: function() {
const content = this.props.mxEvent.getContent();
if (content.file !== undefined) {
return this.state.decryptedUrl;
} else {
return MatrixClientPeg.get().mxcUrlToHttp(content.url);
return this.context.matrixClient.mxcUrlToHttp(content.url);
}
},
@ -115,7 +144,7 @@ module.exports = React.createClass({
}
return this.state.decryptedUrl;
} else {
return MatrixClientPeg.get().mxcUrlToHttp(content.url, 800, 600);
return this.context.matrixClient.mxcUrlToHttp(content.url, 800, 600);
}
},
@ -156,7 +185,9 @@ module.exports = React.createClass({
},
componentWillUnmount: function() {
this.unmounted = true;
dis.unregister(this.dispatcherRef);
this.context.matrixClient.removeListener('sync', this.onClientSync);
},
onAction: function(payload) {
@ -217,6 +248,14 @@ module.exports = React.createClass({
);
}
if (this.state.imgError) {
return (
<span className="mx_MImageBody">
{ _t("This image cannot be displayed.") }
</span>
);
}
const contentUrl = this._getContentUrl();
let thumbUrl;
if (this._isGif() && SettingsStore.getValue("autoplayGifsAndVideos")) {
@ -231,6 +270,7 @@ module.exports = React.createClass({
<a href={contentUrl} onClick={this.onClick}>
<img className="mx_MImageBody_thumbnail" src={thumbUrl} ref="image"
alt={content.body}
onError={this.onImageError}
onLoad={this.props.onWidgetLoad}
onMouseEnter={this.onImageEnter}
onMouseLeave={this.onImageLeave} />

View file

@ -723,6 +723,7 @@ export default class MessageComposerInput extends React.Component {
const cmd = SlashCommands.processInput(this.props.room.roomId, commandText);
if (cmd) {
if (!cmd.error) {
this.historyManager.save(contentState, this.state.isRichtextEnabled ? 'html' : 'markdown');
this.setState({
editorState: this.createEditorState(),
});

View file

@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2018 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.
@ -18,6 +19,7 @@ import PropTypes from 'prop-types';
import MatrixClientPeg from '../../../MatrixClientPeg';
import dis from '../../../dispatcher';
import { _t } from '../../../languageHandler';
import sdk from '../../../index';
module.exports = React.createClass({
displayName: 'IncomingCallBox',
@ -26,14 +28,16 @@ module.exports = React.createClass({
incomingCall: PropTypes.object,
},
onAnswerClick: function() {
onAnswerClick: function(e) {
e.stopPropagation();
dis.dispatch({
action: 'answer',
room_id: this.props.incomingCall.roomId,
});
},
onRejectClick: function() {
onRejectClick: function(e) {
e.stopPropagation();
dis.dispatch({
action: 'hangup',
room_id: this.props.incomingCall.roomId,
@ -59,6 +63,7 @@ module.exports = React.createClass({
}
}
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
return (
<div className="mx_IncomingCallBox" id="incomingCallBox">
<img className="mx_IncomingCallBox_chevron" src="img/chevron-left.png" width="9" height="16" />
@ -67,14 +72,14 @@ module.exports = React.createClass({
</div>
<div className="mx_IncomingCallBox_buttons">
<div className="mx_IncomingCallBox_buttons_cell">
<div className="mx_IncomingCallBox_buttons_decline" onClick={this.onRejectClick}>
<AccessibleButton className="mx_IncomingCallBox_buttons_decline" onClick={this.onRejectClick}>
{ _t("Decline") }
</div>
</AccessibleButton>
</div>
<div className="mx_IncomingCallBox_buttons_cell">
<div className="mx_IncomingCallBox_buttons_accept" onClick={this.onAnswerClick}>
<AccessibleButton className="mx_IncomingCallBox_buttons_accept" onClick={this.onAnswerClick}>
{ _t("Accept") }
</div>
</AccessibleButton>
</div>
</div>
</div>