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({ {content.body} 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; +}