diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index de96935838..4a28faaac4 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -29,6 +29,7 @@ import classnames from 'classnames'; import GroupStoreCache from '../../stores/GroupStoreCache'; import GroupStore from '../../stores/GroupStore'; +import FlairStore from '../../stores/FlairStore'; import { showGroupAddRoomDialog } from '../../GroupAddressPicker'; import GeminiScrollbar from 'react-gemini-scrollbar'; import {makeGroupPermalink, makeUserPermalink} from "../../matrix-to"; @@ -429,6 +430,7 @@ export default React.createClass({ editing: false, saving: false, uploadingAvatar: false, + avatarChanged: false, membershipBusy: false, publicityBusy: false, inviterProfile: null, @@ -590,6 +592,10 @@ export default React.createClass({ this.setState({ uploadingAvatar: false, profileForm: newProfileForm, + + // Indicate that FlairStore needs to be poked to show this change + // in TagTile (TagPanel), Flair and GroupTile (MyGroups). + avatarChanged: true, }); }).catch((e) => { this.setState({uploadingAvatar: false}); @@ -615,6 +621,11 @@ export default React.createClass({ }); dis.dispatch({action: 'panel_disable'}); this._initGroupStore(this.props.groupId); + + if (this.state.avatarChanged) { + // XXX: Evil - poking a store should be done from an async action + FlairStore.refreshGroupProfile(this._matrixClient, this.props.groupId); + } }).catch((e) => { this.setState({ saving: false, @@ -625,6 +636,10 @@ export default React.createClass({ title: _t('Error'), description: _t('Failed to update community'), }); + }).finally(() => { + this.setState({ + avatarChanged: false, + }); }).done(); }, diff --git a/src/components/structures/TagPanel.js b/src/components/structures/TagPanel.js index 8db2a12aff..4bfebc59b8 100644 --- a/src/components/structures/TagPanel.js +++ b/src/components/structures/TagPanel.js @@ -45,7 +45,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.context.matrixClient.on("sync", this._onClientSync); this._tagOrderStoreToken = TagOrderStore.addListener(() => { if (this.unmounted) { @@ -63,7 +63,7 @@ const TagPanel = React.createClass({ componentWillUnmount() { this.unmounted = true; this.context.matrixClient.removeListener("Group.myMembership", this._onGroupMyMembership); - this.context.matrixClient.removeListener("sync", this.onClientSync); + this.context.matrixClient.removeListener("sync", this._onClientSync); if (this._filterStoreToken) { this._filterStoreToken.remove(); } @@ -74,7 +74,7 @@ const TagPanel = React.createClass({ dis.dispatch(GroupActions.fetchJoinedGroups(this.context.matrixClient)); }, - onClientSync(syncState, prevState) { + _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; diff --git a/src/components/views/elements/TagTile.js b/src/components/views/elements/TagTile.js index 8d801d986d..0a328bed57 100644 --- a/src/components/views/elements/TagTile.js +++ b/src/components/views/elements/TagTile.js @@ -55,20 +55,29 @@ export default React.createClass({ componentWillMount() { this.unmounted = false; if (this.props.tag[0] === '+') { - FlairStore.getGroupProfileCached( - this.context.matrixClient, - this.props.tag, - ).then((profile) => { - if (this.unmounted) return; - this.setState({profile}); - }).catch((err) => { - console.warn('Could not fetch group profile for ' + this.props.tag, err); - }); + FlairStore.addListener('updateGroupProfile', this._onFlairStoreUpdated); + this._onFlairStoreUpdated(); } }, componentWillUnmount() { this.unmounted = true; + if (this.props.tag[0] === '+') { + FlairStore.removeListener('updateGroupProfile', this._onFlairStoreUpdated); + } + }, + + _onFlairStoreUpdated() { + if (this.unmounted) return; + FlairStore.getGroupProfileCached( + this.context.matrixClient, + this.props.tag, + ).then((profile) => { + if (this.unmounted) return; + this.setState({profile}); + }).catch((err) => { + console.warn('Could not fetch group profile for ' + this.props.tag, err); + }); }, onClick: function(e) { diff --git a/src/stores/FlairStore.js b/src/stores/FlairStore.js index 7a3aa31e4e..4ef29ae4e1 100644 --- a/src/stores/FlairStore.js +++ b/src/stores/FlairStore.js @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +import EventEmitter from 'events'; import Promise from 'bluebird'; const BULK_REQUEST_DEBOUNCE_MS = 200; @@ -28,8 +29,9 @@ const GROUP_PROFILES_CACHE_BUST_MS = 1800000; // 30 mins /** * Stores data used by */ -class FlairStore { +class FlairStore extends EventEmitter { constructor(matrixClient) { + super(); this._matrixClient = matrixClient; this._userGroups = { // $userId: ['+group1:domain', '+group2:domain', ...] @@ -175,12 +177,23 @@ class FlairStore { }; delete this._groupProfilesPromise[groupId]; + /// XXX: This is verging on recreating a third "Flux"-looking Store. We really + /// should replace FlairStore with a Flux store and some async actions. + this.emit('updateGroupProfile'); + setTimeout(() => { - delete this._groupProfiles[groupId]; + this.refreshGroupProfile(matrixClient, groupId); }, GROUP_PROFILES_CACHE_BUST_MS); return this._groupProfiles[groupId]; } + + refreshGroupProfile(matrixClient, groupId) { + // Invalidate the cache + delete this._groupProfiles[groupId]; + // Fetch new profile data, and cache it + return this.getGroupProfileCached(matrixClient, groupId); + } } if (global.singletonFlairStore === undefined) {