diff --git a/src/RoomNotifs.js b/src/RoomNotifs.js
index 5cc078dc59..91e49fe09b 100644
--- a/src/RoomNotifs.js
+++ b/src/RoomNotifs.js
@@ -34,7 +34,14 @@ export function getRoomNotifsState(roomId) {
}
// for everything else, look at the room rule.
- const roomRule = MatrixClientPeg.get().getRoomPushRule('global', roomId);
+ let roomRule = null;
+ try {
+ roomRule = MatrixClientPeg.get().getRoomPushRule('global', roomId);
+ } catch (err) {
+ // Possible that the client doesn't have pushRules yet. If so, it
+ // hasn't started eiher, so indicate that this room is not notifying.
+ return null;
+ }
// XXX: We have to assume the default is to notify for all messages
// (in particular this will be 'wrong' for one to one rooms because
@@ -130,6 +137,11 @@ function setRoomNotifsStateUnmuted(roomId, newState) {
}
function findOverrideMuteRule(roomId) {
+ if (!MatrixClientPeg.get().pushRules ||
+ !MatrixClientPeg.get().pushRules['global'] ||
+ !MatrixClientPeg.get().pushRules['global'].override) {
+ return null;
+ }
for (const rule of MatrixClientPeg.get().pushRules['global'].override) {
if (isRuleForRoom(roomId, rule)) {
if (isMuteRule(rule) && rule.enabled) {
diff --git a/src/components/structures/TagPanel.js b/src/components/structures/TagPanel.js
index 1cd3f04f9d..49a7a4020a 100644
--- a/src/components/structures/TagPanel.js
+++ b/src/components/structures/TagPanel.js
@@ -44,6 +44,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) {
@@ -61,6 +62,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();
}
@@ -71,6 +73,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;
diff --git a/src/components/views/avatars/BaseAvatar.js b/src/components/views/avatars/BaseAvatar.js
index 47c217eb96..5735a99125 100644
--- a/src/components/views/avatars/BaseAvatar.js
+++ b/src/components/views/avatars/BaseAvatar.js
@@ -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 ]
diff --git a/src/components/views/elements/Pill.js b/src/components/views/elements/Pill.js
index a85f83d78c..067c377eaa 100644
--- a/src/components/views/elements/Pill.js
+++ b/src/components/views/elements/Pill.js
@@ -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);
},
diff --git a/src/components/views/messages/MImageBody.js b/src/components/views/messages/MImageBody.js
index 608f48112e..f5515fad90 100644
--- a/src/components/views/messages/MImageBody.js
+++ b/src/components/views/messages/MImageBody.js
@@ -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 (
+
+ { _t("This image cannot be displayed.") }
+
+ );
+ }
+
const contentUrl = this._getContentUrl();
let thumbUrl;
if (this._isGif() && SettingsStore.getValue("autoplayGifsAndVideos")) {
@@ -231,6 +270,7 @@ module.exports = React.createClass({
diff --git a/test/components/views/elements/MemberEventListSummary-test.js b/test/components/views/elements/MemberEventListSummary-test.js
index 436133c717..b593923ef9 100644
--- a/test/components/views/elements/MemberEventListSummary-test.js
+++ b/test/components/views/elements/MemberEventListSummary-test.js
@@ -1,12 +1,15 @@
-const expect = require('expect');
-const React = require('react');
-const ReactDOM = require("react-dom");
-const ReactTestUtils = require('react-addons-test-utils');
-const sdk = require('matrix-react-sdk');
-const MemberEventListSummary = sdk.getComponent('views.elements.MemberEventListSummary');
+import expect from 'expect';
+import React from 'react';
+import ReactTestUtils from 'react-addons-test-utils';
+import sdk from 'matrix-react-sdk';
import * as languageHandler from '../../../../src/languageHandler';
+import * as testUtils from '../../../test-utils';
+
+// Give MELS a matrixClient in its child context
+const MemberEventListSummary = testUtils.wrapInMatrixClientContext(
+ sdk.getComponent('views.elements.MemberEventListSummary'),
+);
-const testUtils = require('../../../test-utils');
describe('MemberEventListSummary', function() {
let sandbox;
@@ -113,7 +116,6 @@ describe('MemberEventListSummary', function() {
renderer.render();
const result = renderer.getRenderOutput();
- expect(result.type).toBe('div');
expect(result.props.children).toEqual([
Expanded membership
,
]);
@@ -136,7 +138,6 @@ describe('MemberEventListSummary', function() {
renderer.render();
const result = renderer.getRenderOutput();
- expect(result.type).toBe('div');
expect(result.props.children).toEqual([
Expanded membership
,
Expanded membership
,
diff --git a/test/test-utils.js b/test/test-utils.js
index 0b536f5766..5753c02665 100644
--- a/test/test-utils.js
+++ b/test/test-utils.js
@@ -2,7 +2,8 @@
import sinon from 'sinon';
import Promise from 'bluebird';
-
+import React from 'react';
+import PropTypes from 'prop-types';
import peg from '../src/MatrixClientPeg';
import dis from '../src/dispatcher';
import jssdk from 'matrix-js-sdk';
@@ -265,3 +266,26 @@ export function getDispatchForStore(store) {
dis._isDispatching = false;
};
}
+
+export function wrapInMatrixClientContext(WrappedComponent) {
+ class Wrapper extends React.Component {
+ static childContextTypes = {
+ matrixClient: PropTypes.object,
+ }
+
+ getChildContext() {
+ return {
+ matrixClient: this._matrixClient,
+ };
+ }
+
+ componentWillMount() {
+ this._matrixClient = peg.get();
+ }
+
+ render() {
+ return ;
+ }
+ }
+ return Wrapper;
+}