UNPKG

matrix-react-sdk

Version:
275 lines (246 loc) 10.8 kB
/* Copyright 2017 New Vector 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/dispatcher'; import GroupStore from './GroupStore'; import Analytics from '../Analytics'; import * as RoomNotifs from "../RoomNotifs"; import {MatrixClientPeg} from '../MatrixClientPeg'; import SettingsStore from "../settings/SettingsStore"; const INITIAL_STATE = { orderedTags: null, orderedTagsAccountData: null, hasSynced: false, joinedGroupIds: null, selectedTags: [], // Last selected tag when shift was not being pressed anchorTag: null, }; /** * A class for storing application state for ordering tags in the GroupFilterPanel. */ class GroupFilterOrderStore extends Store { constructor() { super(dis); // Initialise state this._state = Object.assign({}, INITIAL_STATE); SettingsStore.monitorSetting("TagPanel.enableTagPanel", null); } _setState(newState) { this._state = Object.assign(this._state, newState); this.__emitChange(); } __onDispatch(payload) { switch (payload.action) { // Initialise state after initial sync case 'view_room': { const relatedGroupIds = GroupStore.getGroupIdsForRoomId(payload.room_id); this._updateBadges(relatedGroupIds); break; } case 'MatrixActions.sync': { if (payload.state === 'SYNCING' || payload.state === 'PREPARED') { this._updateBadges(); } if (!(payload.prevState !== 'PREPARED' && payload.state === 'PREPARED')) { break; } const tagOrderingEvent = payload.matrixClient.getAccountData('im.vector.web.tag_ordering'); const tagOrderingEventContent = tagOrderingEvent ? tagOrderingEvent.getContent() : {}; this._setState({ orderedTagsAccountData: tagOrderingEventContent.tags || null, removedTagsAccountData: tagOrderingEventContent.removedTags || null, hasSynced: true, }); this._updateOrderedTags(); break; } // Get ordering from account data case 'MatrixActions.accountData': { if (payload.event_type !== 'im.vector.web.tag_ordering') break; // Ignore remote echos caused by this store so as to avoid setting // state back to old state. if (payload.event_content._storeId === this.getStoreId()) break; this._setState({ orderedTagsAccountData: payload.event_content ? payload.event_content.tags : null, removedTagsAccountData: payload.event_content ? payload.event_content.removedTags : null, }); this._updateOrderedTags(); break; } // Initialise the state such that if account data is unset, default to joined groups case 'GroupActions.fetchJoinedGroups.success': { this._setState({ joinedGroupIds: payload.result.groups.sort(), // Sort lexically hasFetchedJoinedGroups: true, }); this._updateOrderedTags(); break; } case 'TagOrderActions.moveTag.pending': { // Optimistic update of a moved tag this._setState({ orderedTags: payload.request.tags, removedTagsAccountData: payload.request.removedTags, }); break; } case 'TagOrderActions.removeTag.pending': { // Optimistic update of a removed tag this._setState({ removedTagsAccountData: payload.request.removedTags, }); this._updateOrderedTags(); break; } case 'select_tag': { const allowMultiple = !SettingsStore.getValue("feature_communities_v2_prototypes"); let newTags = []; // Shift-click semantics if (payload.shiftKey && allowMultiple) { // Select range of tags let start = this._state.orderedTags.indexOf(this._state.anchorTag); let end = this._state.orderedTags.indexOf(payload.tag); if (start === -1) { start = end; } if (start > end) { const temp = start; start = end; end = temp; } newTags = payload.ctrlOrCmdKey ? this._state.selectedTags : []; newTags = [...new Set( this._state.orderedTags.slice(start, end + 1).concat(newTags), )]; } else { if (payload.ctrlOrCmdKey && allowMultiple) { // Toggle individual tag if (this._state.selectedTags.includes(payload.tag)) { newTags = this._state.selectedTags.filter((t) => t !== payload.tag); } else { newTags = [...this._state.selectedTags, payload.tag]; } } else { if (this._state.selectedTags.length === 1 && this._state.selectedTags.includes(payload.tag)) { // Existing (only) selected tag is being normally clicked again, clear tags newTags = []; } else { // Select individual tag newTags = [payload.tag]; } } // Only set the anchor tag if the tag was previously unselected, otherwise // the next range starts with an unselected tag. if (!this._state.selectedTags.includes(payload.tag)) { this._setState({ anchorTag: payload.tag, }); } } this._setState({ selectedTags: newTags, }); Analytics.trackEvent('FilterStore', 'select_tag'); } break; case 'deselect_tags': if (payload.tag) { // if a tag is passed, only deselect that tag this._setState({ selectedTags: this._state.selectedTags.filter(tag => tag !== payload.tag), }); } else { this._setState({ selectedTags: [], }); } Analytics.trackEvent('FilterStore', 'deselect_tags'); break; case 'on_client_not_viable': case 'on_logged_out': { // Reset state without pushing an update to the view, which generally assumes that // the matrix client isn't `null` and so causing a re-render will cause NPEs. this._state = Object.assign({}, INITIAL_STATE); break; } case 'setting_updated': if (payload.settingName === 'TagPanel.enableTagPanel' && !payload.newValue) { this._setState({ selectedTags: [], }); Analytics.trackEvent('FilterStore', 'disable_tags'); } break; } } _updateBadges(groupIds = this._state.joinedGroupIds) { if (groupIds && groupIds.length) { const client = MatrixClientPeg.get(); const changedBadges = {}; groupIds.forEach(groupId => { const rooms = GroupStore.getGroupRooms(groupId) .map(r => client.getRoom(r.roomId)) // to Room objects .filter(r => r !== null && r !== undefined); // filter out rooms we haven't joined from the group const badge = rooms && RoomNotifs.aggregateNotificationCount(rooms); changedBadges[groupId] = (badge && badge.count !== 0) ? badge : undefined; }); const newBadges = Object.assign({}, this._state.badges, changedBadges); this._setState({badges: newBadges}); } } _updateOrderedTags() { this._setState({ orderedTags: this._state.hasSynced && this._state.hasFetchedJoinedGroups ? this._mergeGroupsAndTags() : null, }); } _mergeGroupsAndTags() { const groupIds = this._state.joinedGroupIds || []; const tags = this._state.orderedTagsAccountData || []; const removedTags = new Set(this._state.removedTagsAccountData || []); const tagsToKeep = tags.filter( (t) => (t[0] !== '+' || groupIds.includes(t)) && !removedTags.has(t), ); const groupIdsToAdd = groupIds.filter( (groupId) => !tags.includes(groupId) && !removedTags.has(groupId), ); return tagsToKeep.concat(groupIdsToAdd); } getGroupBadge(groupId) { const badges = this._state.badges; return badges && badges[groupId]; } getOrderedTags() { return this._state.orderedTags; } getRemovedTagsAccountData() { return this._state.removedTagsAccountData; } getStoreId() { // Generate a random ID to prevent this store from clobbering its // state with redundant remote echos. if (!this._id) this._id = Math.random().toString(16).slice(2, 10); return this._id; } getSelectedTags() { return this._state.selectedTags; } } if (global.singletonGroupFilterOrderStore === undefined) { global.singletonGroupFilterOrderStore = new GroupFilterOrderStore(); } export default global.singletonGroupFilterOrderStore;