+
:
+
;
+ return
+
+
+ { tip }
+
+ ;
+ },
+});
+
+export default React.createClass({
+ displayName: 'TagPanel',
+
+ contextTypes: {
+ matrixClient: PropTypes.instanceOf(MatrixClient),
+ },
+
+ getInitialState() {
+ return {
+ joinedGroupProfiles: [],
+ selectedTags: [],
+ };
+ },
+
+ componentWillMount: function() {
+ this.mounted = true;
+ this.context.matrixClient.on("Group.myMembership", this._onGroupMyMembership);
+
+ this._filterStoreToken = FilterStore.addListener(() => {
+ if (!this.mounted) {
+ return;
+ }
+ this.setState({
+ selectedTags: FilterStore.getSelectedTags(),
+ });
+ });
+
+ this._fetchJoinedRooms();
+ },
+
+ componentWillUnmount() {
+ this.mounted = false;
+ this.context.matrixClient.removeListener("Group.myMembership", this._onGroupMyMembership);
+ if (this._filterStoreToken) {
+ this._filterStoreToken.remove();
+ }
+ },
+
+ _onGroupMyMembership() {
+ if (!this.mounted) return;
+ this._fetchJoinedRooms();
+ },
+
+ onClick() {
+ dis.dispatch({action: 'deselect_tags'});
+ },
+
+ onCreateGroupClick(ev) {
+ ev.stopPropagation();
+ dis.dispatch({action: 'view_create_group'});
+ },
+
+ async _fetchJoinedRooms() {
+ const joinedGroupResponse = await this.context.matrixClient.getJoinedGroups();
+ const joinedGroupIds = joinedGroupResponse.groups;
+ const joinedGroupProfiles = await Promise.all(joinedGroupIds.map(
+ (groupId) => FlairStore.getGroupProfileCached(this.context.matrixClient, groupId),
+ ));
+ this.setState({joinedGroupProfiles});
+ },
+
+ render() {
+ const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
+ const TintableSvg = sdk.getComponent('elements.TintableSvg');
+ const tags = this.state.joinedGroupProfiles.map((groupProfile, index) => {
+ return
;
+ });
+ return
;
+ },
+});
diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js
index ebe0bdb03f..6157f65c9d 100644
--- a/src/components/views/rooms/RoomList.js
+++ b/src/components/views/rooms/RoomList.js
@@ -28,6 +28,8 @@ const rate_limited_func = require('../../../ratelimitedfunc');
const Rooms = require('../../../Rooms');
import DMRoomMap from '../../../utils/DMRoomMap';
const Receipt = require('../../../utils/Receipt');
+import FilterStore from '../../../stores/FilterStore';
+import GroupStoreCache from '../../../stores/GroupStoreCache';
const HIDE_CONFERENCE_CHANS = true;
@@ -61,6 +63,7 @@ module.exports = React.createClass({
totalRoomCount: null,
lists: {},
incomingCall: null,
+ selectedTags: [],
};
},
@@ -80,6 +83,23 @@ module.exports = React.createClass({
cli.on("accountData", this.onAccountData);
cli.on("Group.myMembership", this._onGroupMyMembership);
+ this._groupStores = {};
+ this._selectedTagsRoomIds = [];
+ this._selectedTagsUserIds = [];
+ // When the selected tags are changed, initialise a group store if necessary
+ this._filterStoreToken = FilterStore.addListener(() => {
+ FilterStore.getSelectedTags().forEach((tag) => {
+ if (tag[0] !== '+' || this._groupStores[tag]) {
+ return;
+ }
+ this._groupStores[tag] = GroupStoreCache.getGroupStore(tag);
+ this._groupStores[tag].registerListener(() => {
+ this.updateSelectedTagsEntities();
+ });
+ });
+ this.updateSelectedTagsEntities();
+ });
+
this.refreshRoomList();
// order of the sublists
@@ -148,6 +168,11 @@ module.exports = React.createClass({
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
MatrixClientPeg.get().removeListener("Group.myMembership", this._onGroupMyMembership);
}
+
+ if (this._filterStoreToken) {
+ this._filterStoreToken.remove();
+ }
+
// cancel any pending calls to the rate_limited_funcs
this._delayedRefreshRoomList.cancelPendingCall();
},
@@ -234,6 +259,41 @@ module.exports = React.createClass({
this.refreshRoomList();
}, 500),
+ // Update which rooms and users should appear in RoomList as dictated by selected tags
+ updateSelectedTagsEntities: function() {
+ if (!this.mounted) return;
+ this._selectedTagsRoomIds = [];
+ this._selectedTagsUserIds = [];
+ FilterStore.getSelectedTags().forEach((tag) => {
+ this._selectedTagsRoomIds = this._selectedTagsRoomIds.concat(
+ this._groupStores[tag].getGroupRooms().map((room) => room.roomId),
+ );
+ // TODO: Check if room has been tagged to the group by the user
+
+ this._selectedTagsUserIds = this._selectedTagsUserIds.concat(
+ this._groupStores[tag].getGroupMembers().map((member) => member.userId),
+ );
+ });
+ this.setState({
+ selectedTags: FilterStore.getSelectedTags(),
+ }, () => {
+ this.refreshRoomList();
+ });
+ },
+
+ isRoomInSelectedTags: function(room, me, dmRoomMap) {
+ // No selected tags = every room is visible in the list
+ if (this.state.selectedTags.length === 0) {
+ return true;
+ }
+ if (this._selectedTagsRoomIds.includes(room.roomId)) {
+ return true;
+ }
+ const dmUserId = dmRoomMap.getUserIdForRoomId(room.roomId);
+ return dmUserId && dmUserId !== me.userId &&
+ this._selectedTagsUserIds.includes(dmUserId);
+ },
+
refreshRoomList: function() {
// TODO: ideally we'd calculate this once at start, and then maintain
// any changes to it incrementally, updating the appropriate sublists
@@ -253,9 +313,7 @@ module.exports = React.createClass({
},
getRoomLists: function() {
- const self = this;
const lists = {};
-
lists["im.vector.fake.invite"] = [];
lists["m.favourite"] = [];
lists["im.vector.fake.recent"] = [];
@@ -264,8 +322,7 @@ module.exports = React.createClass({
lists["im.vector.fake.archived"] = [];
const dmRoomMap = new DMRoomMap(MatrixClientPeg.get());
-
- MatrixClientPeg.get().getRooms().forEach(function(room) {
+ MatrixClientPeg.get().getRooms().forEach((room) => {
const me = room.getMember(MatrixClientPeg.get().credentials.userId);
if (!me) return;
@@ -276,13 +333,18 @@ module.exports = React.createClass({
if (me.membership == "invite") {
lists["im.vector.fake.invite"].push(room);
- } else if (HIDE_CONFERENCE_CHANS && Rooms.isConfCallRoom(room, me, self.props.ConferenceHandler)) {
+ } else if (HIDE_CONFERENCE_CHANS && Rooms.isConfCallRoom(room, me, this.props.ConferenceHandler)) {
// skip past this room & don't put it in any lists
} else if (me.membership == "join" || me.membership === "ban" ||
(me.membership === "leave" && me.events.member.getSender() !== me.events.member.getStateKey())) {
// Used to split rooms via tags
const tagNames = Object.keys(room.tags);
+ // Apply TagPanel filtering, derived from FilterStore
+ if (!this.isRoomInSelectedTags(room, me, dmRoomMap)) {
+ return;
+ }
+
if (tagNames.length) {
for (let i = 0; i < tagNames.length; i++) {
const tagName = tagNames[i];
@@ -474,6 +536,10 @@ module.exports = React.createClass({
},
_getEmptyContent: function(section) {
+ if (this.state.selectedTags.length > 0) {
+ return null;
+ }
+
const RoomDropTarget = sdk.getComponent('rooms.RoomDropTarget');
if (this.props.collapsed) {
diff --git a/src/stores/FilterStore.js b/src/stores/FilterStore.js
new file mode 100644
index 0000000000..6e2a7f4739
--- /dev/null
+++ b/src/stores/FilterStore.js
@@ -0,0 +1,65 @@
+/*
+Copyright 2017 Vector Creations Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+import {Store} from 'flux/utils';
+import dis from '../dispatcher';
+import Analytics from '../Analytics';
+
+const INITIAL_STATE = {
+ tags: [],
+};
+
+/**
+ * A class for storing application state for filtering via TagPanel.
+ */
+class FilterStore extends Store {
+ constructor() {
+ super(dis);
+
+ // Initialise state
+ this._state = INITIAL_STATE;
+ }
+
+ _setState(newState) {
+ this._state = Object.assign(this._state, newState);
+ this.__emitChange();
+ }
+
+ __onDispatch(payload) {
+ switch (payload.action) {
+ case 'select_tag':
+ this._setState({
+ tags: [payload.tag],
+ });
+ Analytics.trackEvent('FilterStore', 'select_tag');
+ break;
+ case 'deselect_tags':
+ this._setState({
+ tags: [],
+ });
+ Analytics.trackEvent('FilterStore', 'deselect_tags');
+ break;
+ }
+ }
+
+ getSelectedTags() {
+ return this._state.tags;
+ }
+}
+
+if (global.singletonFilterStore === undefined) {
+ global.singletonFilterStore = new FilterStore();
+}
+export default global.singletonFilterStore;
diff --git a/src/stores/GroupStoreCache.js b/src/stores/GroupStoreCache.js
index 3264b197d7..8b4286831b 100644
--- a/src/stores/GroupStoreCache.js
+++ b/src/stores/GroupStoreCache.js
@@ -28,7 +28,6 @@ class GroupStoreCache {
// referencing it.
this.groupStore = new GroupStore(groupId);
}
- this.groupStore._fetchSummary();
return this.groupStore;
}
}