diff --git a/src/actions/MatrixActionCreators.js b/src/actions/MatrixActionCreators.js index 86d4a3456c..0f02e7730e 100644 --- a/src/actions/MatrixActionCreators.js +++ b/src/actions/MatrixActionCreators.js @@ -14,7 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { createMatrixActionCreator } from './actionCreators'; +import { + createMatrixActionCreator, + createMatrixSyncActionCreator, +} from './actionCreators'; // Events emitted from the matrixClient that we want to dispatch as actions // via MatrixActionCreators. See createMatrixActionCreator. @@ -30,6 +33,8 @@ export default { this.actionCreators = REGISTERED_EVENTS.map((eventId) => createMatrixActionCreator(matrixClient, eventId), ); + this.actionCreators.push(createMatrixSyncActionCreator(matrixClient)); + this.actionCreatorsStop = this.actionCreators.map((ac) => ac()); }, diff --git a/src/actions/actionCreators.js b/src/actions/actionCreators.js index d3c626c64c..60ff368fc8 100644 --- a/src/actions/actionCreators.js +++ b/src/actions/actionCreators.js @@ -70,3 +70,36 @@ export function createMatrixActionCreator(matrixClient, eventId) { }; }; } + +// TODO: migrate from sync_state to MatrixSync so that more js-sdk events +// become dispatches in the same place. +/** + * Create an action creator that will listen to `sync` events emitted + * by matrixClient and dispatch a corresponding MatrixSync action. E.g: + * { + * action: 'MatrixSync', + * state: 'SYNCING', + * prevState: 'PREPARED' + * } + * @param {MatrixClient} matrixClient the matrix client with which to register + * a listener. + * @returns {function} a function that, when called, will begin to listen to + * dispatches from matrixClient. The result from that + * function can be called to stop listening. + */ +export function createMatrixSyncActionCreator(matrixClient) { + const listener = (state, prevState) => { + dis.dispatch({ + action: 'MatrixSync', + state, + prevState, + matrixClient, + }); + }; + return () => { + matrixClient.on('sync', listener); + return () => { + matrixClient.removeListener('sync', listener); + }; + }; +} diff --git a/src/components/structures/TagPanel.js b/src/components/structures/TagPanel.js index 127a220b26..88bb39406e 100644 --- a/src/components/structures/TagPanel.js +++ b/src/components/structures/TagPanel.js @@ -58,7 +58,7 @@ const TagPanel = React.createClass({ return; } - const orderedTags = TagOrderStore.getOrderedTags() || TagOrderStore.getAllTags(); + const orderedTags = TagOrderStore.getOrderedTags(); const orderedGroupTags = orderedTags.filter((t) => t[0] === '+'); Promise.all(orderedGroupTags.map( (groupId) => FlairStore.getGroupProfileCached(this.context.matrixClient, groupId), diff --git a/src/stores/TagOrderStore.js b/src/stores/TagOrderStore.js index 960839fa06..9741d59d4e 100644 --- a/src/stores/TagOrderStore.js +++ b/src/stores/TagOrderStore.js @@ -18,7 +18,9 @@ import dis from '../dispatcher'; const INITIAL_STATE = { orderedTags: null, - allTags: null, + orderedTagsAccountData: null, + hasSynced: false, + joinedGroupIds: null, }; /** @@ -39,25 +41,42 @@ class TagOrderStore extends Store { __onDispatch(payload) { switch (payload.action) { + // Initialise state after initial sync + case 'MatrixSync': { + if (!(payload.prevState === 'PREPARED' && payload.state === 'SYNCING')) { + break; + } + const tagOrderingEvent = payload.matrixClient.getAccountData('im.vector.web.tag_ordering'); + const tagOrderingEventContent = tagOrderingEvent ? tagOrderingEvent.getContent() : {}; + this._setState({ + orderedTagsAccountData: tagOrderingEventContent.tags || null, + hasSynced: true, + }); + this._updateOrderedTags(); + break; + } // Get ordering from account data case 'MatrixActions.accountData': { if (payload.event_type !== 'im.vector.web.tag_ordering') break; this._setState({ - orderedTags: payload.event_content ? payload.event_content.tags : null, + orderedTagsAccountData: payload.event_content ? payload.event_content.tags : null, }); + this._updateOrderedTags(); break; } // Initialise the state such that if account data is unset, default to joined groups case 'GroupActions.fetchJoinedGroups.success': this._setState({ - allTags: payload.result.groups.sort(), // Sort lexically + joinedGroupIds: payload.result.groups.sort(), // Sort lexically + hasFetchedJoinedGroups: true, }); + this._updateOrderedTags(); break; // Puts payload.tag at payload.targetTag, placing the targetTag before or after the tag case 'order_tag': { if (!payload.tag || !payload.targetTag || payload.tag === payload.targetTag) return; - const tags = this._state.orderedTags || this._state.allTags; + const tags = this._state.orderedTags; let orderedTags = tags.filter((t) => t !== payload.tag); const newIndex = orderedTags.indexOf(payload.targetTag) + (payload.after ? 1 : 0); @@ -72,12 +91,15 @@ class TagOrderStore extends Store { } } - getOrderedTags() { - return this._state.orderedTags; + _updateOrderedTags() { + this._setState({ + orderedTags: this._state.hasSynced && this._state.hasFetchedJoinedGroups ? + this._state.orderedTagsAccountData || this._state.joinedGroupIds : [], + }); } - getAllTags() { - return this._state.allTags; + getOrderedTags() { + return this._state.orderedTags; } }