Merge pull request #5027 from matrix-org/t3chguy/room-list/13981

When the room view isn't active don't highlight it in room list
pull/21833/head
Michael Telatynski 2020-08-05 09:29:25 +01:00 committed by GitHub
commit 3675128061
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 100 additions and 84 deletions

View File

@ -26,6 +26,7 @@ import RoomListLayoutStore from "../stores/room-list/RoomListLayoutStore";
import {IntegrationManagers} from "../integrations/IntegrationManagers";
import {ModalManager} from "../Modal";
import SettingsStore from "../settings/SettingsStore";
import {ActiveRoomObserver} from "../ActiveRoomObserver";
declare global {
interface Window {
@ -41,6 +42,7 @@ declare global {
mxRebrandListener: RebrandListener;
mxRoomListStore: RoomListStoreClass;
mxRoomListLayoutStore: RoomListLayoutStore;
mxActiveRoomObserver: ActiveRoomObserver;
mxPlatformPeg: PlatformPeg;
mxIntegrationManagers: typeof IntegrationManagers;
singletonModalManager: ModalManager;

View File

@ -16,6 +16,8 @@ limitations under the License.
import RoomViewStore from './stores/RoomViewStore';
type Listener = (isActive: boolean) => void;
/**
* Consumes changes from the RoomViewStore and notifies specific things
* about when the active room changes. Unlike listening for RoomViewStore
@ -25,57 +27,57 @@ import RoomViewStore from './stores/RoomViewStore';
* TODO: If we introduce an observer for something else, factor out
* the adding / removing of listeners & emitting into a common class.
*/
class ActiveRoomObserver {
constructor() {
this._listeners = {}; // key=roomId, value=function(isActive:boolean)
export class ActiveRoomObserver {
private listeners: {[key: string]: Listener[]} = {};
private _activeRoomId = RoomViewStore.getRoomId();
private readonly roomStoreToken: string;
this._activeRoomId = RoomViewStore.getRoomId();
// TODO: We could self-destruct when the last listener goes away, or at least
// stop listening.
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate.bind(this));
constructor() {
// TODO: We could self-destruct when the last listener goes away, or at least stop listening.
this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate);
}
get activeRoomId(): string {
public get activeRoomId(): string {
return this._activeRoomId;
}
addListener(roomId, listener) {
if (!this._listeners[roomId]) this._listeners[roomId] = [];
this._listeners[roomId].push(listener);
public addListener(roomId, listener) {
if (!this.listeners[roomId]) this.listeners[roomId] = [];
this.listeners[roomId].push(listener);
}
removeListener(roomId, listener) {
if (this._listeners[roomId]) {
const i = this._listeners[roomId].indexOf(listener);
public removeListener(roomId, listener) {
if (this.listeners[roomId]) {
const i = this.listeners[roomId].indexOf(listener);
if (i > -1) {
this._listeners[roomId].splice(i, 1);
this.listeners[roomId].splice(i, 1);
}
} else {
console.warn("Unregistering unrecognised listener (roomId=" + roomId + ")");
}
}
_emit(roomId, isActive: boolean) {
if (!this._listeners[roomId]) return;
private emit(roomId, isActive: boolean) {
if (!this.listeners[roomId]) return;
for (const l of this._listeners[roomId]) {
for (const l of this.listeners[roomId]) {
l.call(null, isActive);
}
}
_onRoomViewStoreUpdate() {
private onRoomViewStoreUpdate = () => {
// emit for the old room ID
if (this._activeRoomId) this._emit(this._activeRoomId, false);
if (this._activeRoomId) this.emit(this._activeRoomId, false);
// update our cache
this._activeRoomId = RoomViewStore.getRoomId();
// and emit for the new one
if (this._activeRoomId) this._emit(this._activeRoomId, true);
}
if (this._activeRoomId) this.emit(this._activeRoomId, true);
};
}
if (global.mx_ActiveRoomObserver === undefined) {
global.mx_ActiveRoomObserver = new ActiveRoomObserver();
if (window.mxActiveRoomObserver === undefined) {
window.mxActiveRoomObserver = new ActiveRoomObserver();
}
export default global.mx_ActiveRoomObserver;
export default window.mxActiveRoomObserver;

View File

@ -15,13 +15,17 @@ 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 dis from '../dispatcher/dispatcher';
import React from "react";
import {Store} from 'flux/utils';
import dis from '../dispatcher/dispatcher';
import {MatrixClientPeg} from '../MatrixClientPeg';
import * as sdk from '../index';
import Modal from '../Modal';
import { _t } from '../languageHandler';
import { getCachedRoomIDForAlias, storeRoomAliasInCache } from '../RoomAliasCache';
import {ActionPayload} from "../dispatcher/payloads";
const INITIAL_STATE = {
// Whether we're joining the currently viewed room (see isJoining())
@ -33,6 +37,7 @@ const INITIAL_STATE = {
// The event to scroll to when the room is first viewed
initialEventId: null,
initialEventPixelOffset: null,
// Whether to highlight the initial event
isInitialEventHighlighted: false,
@ -46,6 +51,10 @@ const INITIAL_STATE = {
forwardingEvent: null,
quotingEvent: null,
replyingToEvent: null,
shouldPeek: false,
};
/**
@ -53,21 +62,20 @@ const INITIAL_STATE = {
* with a subset of the js-sdk.
* ```
*/
class RoomViewStore extends Store {
class RoomViewStore extends Store<ActionPayload> {
private state = INITIAL_STATE; // initialize state
constructor() {
super(dis);
// Initialise state
this._state = INITIAL_STATE;
}
_setState(newState) {
setState(newState: Partial<typeof INITIAL_STATE>) {
// If values haven't changed, there's nothing to do.
// This only tries a shallow comparison, so unchanged objects will slip
// through, but that's probably okay for now.
let stateChanged = false;
for (const key of Object.keys(newState)) {
if (this._state[key] !== newState[key]) {
if (this.state[key] !== newState[key]) {
stateChanged = true;
break;
}
@ -76,7 +84,7 @@ class RoomViewStore extends Store {
return;
}
this._state = Object.assign(this._state, newState);
this.state = Object.assign(this.state, newState);
this.__emitChange();
}
@ -89,59 +97,63 @@ class RoomViewStore extends Store {
// - event_offset: 100
// - highlighted: true
case 'view_room':
this._viewRoom(payload);
this.viewRoom(payload);
break;
// for these events blank out the roomId as we are no longer in the RoomView
case 'view_create_group':
case 'view_welcome_page':
case 'view_home_page':
case 'view_my_groups':
case 'view_group':
this._setState({
this.setState({
roomId: null,
roomAlias: null,
});
break;
case 'view_room_error':
this._viewRoomError(payload);
this.viewRoomError(payload);
break;
case 'will_join':
this._setState({
this.setState({
joining: true,
});
break;
case 'cancel_join':
this._setState({
this.setState({
joining: false,
});
break;
// join_room:
// - opts: options for joinRoom
case 'join_room':
this._joinRoom(payload);
this.joinRoom(payload);
break;
case 'join_room_error':
this._joinRoomError(payload);
this.joinRoomError(payload);
break;
case 'join_room_ready':
this._setState({ shouldPeek: false });
this.setState({ shouldPeek: false });
break;
case 'on_client_not_viable':
case 'on_logged_out':
this.reset();
break;
case 'forward_event':
this._setState({
this.setState({
forwardingEvent: payload.event,
});
break;
case 'reply_to_event':
// If currently viewed room does not match the room in which we wish to reply then change rooms
// this can happen when performing a search across all rooms
if (payload.event && payload.event.getRoomId() !== this._state.roomId) {
if (payload.event && payload.event.getRoomId() !== this.state.roomId) {
dis.dispatch({
action: 'view_room',
room_id: payload.event.getRoomId(),
replyingToEvent: payload.event,
});
} else {
this._setState({
this.setState({
replyingToEvent: payload.event,
});
}
@ -149,14 +161,14 @@ class RoomViewStore extends Store {
case 'open_room_settings': {
const RoomSettingsDialog = sdk.getComponent("dialogs.RoomSettingsDialog");
Modal.createTrackedDialog('Room settings', '', RoomSettingsDialog, {
roomId: payload.room_id || this._state.roomId,
roomId: payload.room_id || this.state.roomId,
}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
break;
}
}
}
async _viewRoom(payload) {
private async viewRoom(payload: ActionPayload) {
if (payload.room_id) {
const newState = {
roomId: payload.room_id,
@ -181,18 +193,18 @@ class RoomViewStore extends Store {
newState.replyingToEvent = payload.replyingToEvent;
}
if (this._state.forwardingEvent) {
if (this.state.forwardingEvent) {
dis.dispatch({
action: 'send_event',
room_id: newState.roomId,
event: this._state.forwardingEvent,
event: this.state.forwardingEvent,
});
}
this._setState(newState);
this.setState(newState);
if (payload.auto_join) {
this._joinRoom(payload);
this.joinRoom(payload);
}
} else if (payload.room_alias) {
// Try the room alias to room ID navigation cache first to avoid
@ -201,7 +213,7 @@ class RoomViewStore extends Store {
if (!roomId) {
// Room alias cache miss, so let's ask the homeserver. Resolve the alias
// and then do a second dispatch with the room ID acquired.
this._setState({
this.setState({
roomId: null,
initialEventId: null,
initialEventPixelOffset: null,
@ -238,8 +250,8 @@ class RoomViewStore extends Store {
}
}
_viewRoomError(payload) {
this._setState({
private viewRoomError(payload: ActionPayload) {
this.setState({
roomId: payload.room_id,
roomAlias: payload.room_alias,
roomLoading: false,
@ -247,12 +259,12 @@ class RoomViewStore extends Store {
});
}
_joinRoom(payload) {
this._setState({
private joinRoom(payload: ActionPayload) {
this.setState({
joining: true,
});
MatrixClientPeg.get().joinRoom(
this._state.roomAlias || this._state.roomId, payload.opts,
this.state.roomAlias || this.state.roomId, payload.opts,
).then(() => {
// We do *not* clear the 'joining' flag because the Room object and/or our 'joined' member event may not
// have come down the sync stream yet, and that's the point at which we'd consider the user joined to the
@ -273,7 +285,7 @@ class RoomViewStore extends Store {
{_t("Please contact your homeserver administrator.")}
</div>;
} else if (err.httpStatus === 404) {
const invitingUserId = this._getInvitingUserId(this._state.roomId);
const invitingUserId = this.getInvitingUserId(this.state.roomId);
// only provide a better error message for invites
if (invitingUserId) {
// if the inviting user is on the same HS, there can only be one cause: they left.
@ -292,7 +304,7 @@ class RoomViewStore extends Store {
});
}
_getInvitingUserId(roomId) {
private getInvitingUserId(roomId: string): string {
const cli = MatrixClientPeg.get();
const room = cli.getRoom(roomId);
if (room && room.getMyMembership() === "invite") {
@ -302,45 +314,45 @@ class RoomViewStore extends Store {
}
}
_joinRoomError(payload) {
this._setState({
private joinRoomError(payload: ActionPayload) {
this.setState({
joining: false,
joinError: payload.err,
});
}
reset() {
this._state = Object.assign({}, INITIAL_STATE);
public reset() {
this.state = Object.assign({}, INITIAL_STATE);
}
// The room ID of the room currently being viewed
getRoomId() {
return this._state.roomId;
public getRoomId() {
return this.state.roomId;
}
// The event to scroll to when the room is first viewed
getInitialEventId() {
return this._state.initialEventId;
public getInitialEventId() {
return this.state.initialEventId;
}
// Whether to highlight the initial event
isInitialEventHighlighted() {
return this._state.isInitialEventHighlighted;
public isInitialEventHighlighted() {
return this.state.isInitialEventHighlighted;
}
// The room alias of the room (or null if not originally specified in view_room)
getRoomAlias() {
return this._state.roomAlias;
public getRoomAlias() {
return this.state.roomAlias;
}
// Whether the current room is loading (true whilst resolving an alias)
isRoomLoading() {
return this._state.roomLoading;
public isRoomLoading() {
return this.state.roomLoading;
}
// Any error that has occurred during loading
getRoomLoadError() {
return this._state.roomLoadError;
public getRoomLoadError() {
return this.state.roomLoadError;
}
// True if we're expecting the user to be joined to the room currently being
@ -366,27 +378,27 @@ class RoomViewStore extends Store {
// // show join prompt
// }
// }
isJoining() {
return this._state.joining;
public isJoining() {
return this.state.joining;
}
// Any error that has occurred during joining
getJoinError() {
return this._state.joinError;
public getJoinError() {
return this.state.joinError;
}
// The mxEvent if one is about to be forwarded
getForwardingEvent() {
return this._state.forwardingEvent;
public getForwardingEvent() {
return this.state.forwardingEvent;
}
// The mxEvent if one is currently being replied to/quoted
getQuotingEvent() {
return this._state.replyingToEvent;
public getQuotingEvent() {
return this.state.replyingToEvent;
}
shouldPeek() {
return this._state.shouldPeek;
public shouldPeek() {
return this.state.shouldPeek;
}
}