Step 8.4.2: Refactor ActiveRoomObserver
out of existence
The `RoomTile` was the last class to use it. Note that we also update the RVS to change its `instance` declaration type to fix a few tests.
This commit is contained in:
parent
109ecbf070
commit
d89fcf17fb
4 changed files with 50 additions and 102 deletions
2
src/@types/global.d.ts
vendored
2
src/@types/global.d.ts
vendored
|
@ -29,7 +29,6 @@ import RoomListLayoutStore from "../stores/room-list/RoomListLayoutStore";
|
||||||
import { IntegrationManagers } from "../integrations/IntegrationManagers";
|
import { IntegrationManagers } from "../integrations/IntegrationManagers";
|
||||||
import { ModalManager } from "../Modal";
|
import { ModalManager } from "../Modal";
|
||||||
import SettingsStore from "../settings/SettingsStore";
|
import SettingsStore from "../settings/SettingsStore";
|
||||||
import { ActiveRoomObserver } from "../ActiveRoomObserver";
|
|
||||||
import { Notifier } from "../Notifier";
|
import { Notifier } from "../Notifier";
|
||||||
import type { Renderer } from "react-dom";
|
import type { Renderer } from "react-dom";
|
||||||
import RightPanelStore from "../stores/right-panel/RightPanelStore";
|
import RightPanelStore from "../stores/right-panel/RightPanelStore";
|
||||||
|
@ -82,7 +81,6 @@ declare global {
|
||||||
mxDeviceListener: DeviceListener;
|
mxDeviceListener: DeviceListener;
|
||||||
mxRoomListStore: RoomListStoreClass;
|
mxRoomListStore: RoomListStoreClass;
|
||||||
mxRoomListLayoutStore: RoomListLayoutStore;
|
mxRoomListLayoutStore: RoomListLayoutStore;
|
||||||
mxActiveRoomObserver: ActiveRoomObserver;
|
|
||||||
mxPlatformPeg: PlatformPeg;
|
mxPlatformPeg: PlatformPeg;
|
||||||
mxIntegrationManagers: typeof IntegrationManagers;
|
mxIntegrationManagers: typeof IntegrationManagers;
|
||||||
singletonModalManager: ModalManager;
|
singletonModalManager: ModalManager;
|
||||||
|
|
|
@ -1,85 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 - 2022 The Matrix.org Foundation C.I.C.
|
|
||||||
|
|
||||||
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 { logger } from "matrix-js-sdk/src/logger";
|
|
||||||
|
|
||||||
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
|
|
||||||
* changes, you can subscribe to only changes relevant to a particular
|
|
||||||
* room.
|
|
||||||
*
|
|
||||||
* TODO: If we introduce an observer for something else, factor out
|
|
||||||
* the adding / removing of listeners & emitting into a common class.
|
|
||||||
*/
|
|
||||||
export class ActiveRoomObserver {
|
|
||||||
private listeners: {[key: string]: Listener[]} = {};
|
|
||||||
private _activeRoomId = RoomViewStore.instance.getRoomId();
|
|
||||||
private readonly roomStoreToken: EventSubscription;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
// TODO: We could self-destruct when the last listener goes away, or at least stop listening.
|
|
||||||
this.roomStoreToken = RoomViewStore.instance.addListener(this.onRoomViewStoreUpdate);
|
|
||||||
}
|
|
||||||
|
|
||||||
public get activeRoomId(): string {
|
|
||||||
return this._activeRoomId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public addListener(roomId, listener) {
|
|
||||||
if (!this.listeners[roomId]) this.listeners[roomId] = [];
|
|
||||||
this.listeners[roomId].push(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);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.warn("Unregistering unrecognised listener (roomId=" + roomId + ")");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private emit(roomId, isActive: boolean) {
|
|
||||||
if (!this.listeners[roomId]) return;
|
|
||||||
|
|
||||||
for (const l of this.listeners[roomId]) {
|
|
||||||
l.call(null, isActive);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private onRoomViewStoreUpdate = () => {
|
|
||||||
// emit for the old room ID
|
|
||||||
if (this._activeRoomId) this.emit(this._activeRoomId, false);
|
|
||||||
|
|
||||||
// update our cache
|
|
||||||
this._activeRoomId = RoomViewStore.instance.getRoomId();
|
|
||||||
|
|
||||||
// and emit for the new one
|
|
||||||
if (this._activeRoomId) this.emit(this._activeRoomId, true);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (window.mxActiveRoomObserver === undefined) {
|
|
||||||
window.mxActiveRoomObserver = new ActiveRoomObserver();
|
|
||||||
}
|
|
||||||
export default window.mxActiveRoomObserver;
|
|
|
@ -28,7 +28,6 @@ import dis from '../../../dispatcher/dispatcher';
|
||||||
import defaultDispatcher from '../../../dispatcher/dispatcher';
|
import defaultDispatcher from '../../../dispatcher/dispatcher';
|
||||||
import { Action } from "../../../dispatcher/actions";
|
import { Action } from "../../../dispatcher/actions";
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import ActiveRoomObserver from "../../../ActiveRoomObserver";
|
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import { ChevronFace, ContextMenuTooltipButton } from "../../structures/ContextMenu";
|
import { ChevronFace, ContextMenuTooltipButton } from "../../structures/ContextMenu";
|
||||||
import { DefaultTagID, TagID } from "../../../stores/room-list/models";
|
import { DefaultTagID, TagID } from "../../../stores/room-list/models";
|
||||||
|
@ -61,6 +60,7 @@ import PosthogTrackers from "../../../PosthogTrackers";
|
||||||
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||||
|
import { RoomViewStore } from "../../../stores/RoomViewStore";
|
||||||
|
|
||||||
enum VoiceConnectionState {
|
enum VoiceConnectionState {
|
||||||
Disconnected,
|
Disconnected,
|
||||||
|
@ -110,7 +110,7 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
selected: ActiveRoomObserver.activeRoomId === this.props.room.roomId,
|
selected: RoomViewStore.instance.getRoomId() === this.props.room.roomId,
|
||||||
notificationsMenuPosition: null,
|
notificationsMenuPosition: null,
|
||||||
generalMenuPosition: null,
|
generalMenuPosition: null,
|
||||||
// generatePreview() will return nothing if the user has previews disabled
|
// generatePreview() will return nothing if the user has previews disabled
|
||||||
|
@ -177,7 +177,7 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
this.updateVoiceMembers();
|
this.updateVoiceMembers();
|
||||||
|
|
||||||
ActiveRoomObserver.addListener(this.props.room.roomId, this.onActiveRoomUpdate);
|
RoomViewStore.instance.addRoomListener(this.props.room.roomId, this.onActiveRoomUpdate);
|
||||||
this.dispatcherRef = defaultDispatcher.register(this.onAction);
|
this.dispatcherRef = defaultDispatcher.register(this.onAction);
|
||||||
MessagePreviewStore.instance.on(
|
MessagePreviewStore.instance.on(
|
||||||
MessagePreviewStore.getPreviewChangedEventName(this.props.room),
|
MessagePreviewStore.getPreviewChangedEventName(this.props.room),
|
||||||
|
@ -185,21 +185,18 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
||||||
);
|
);
|
||||||
this.notificationState.on(NotificationStateEvents.Update, this.onNotificationUpdate);
|
this.notificationState.on(NotificationStateEvents.Update, this.onNotificationUpdate);
|
||||||
this.roomProps.on(PROPERTY_UPDATED, this.onRoomPropertyUpdate);
|
this.roomProps.on(PROPERTY_UPDATED, this.onRoomPropertyUpdate);
|
||||||
this.props.room?.on(RoomEvent.Name, this.onRoomNameUpdate);
|
this.props.room.on(RoomEvent.Name, this.onRoomNameUpdate);
|
||||||
this.props.room?.currentState?.on(RoomStateEvent.Events, this.updateVoiceMembers);
|
this.props.room.currentState.on(RoomStateEvent.Events, this.updateVoiceMembers);
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentWillUnmount() {
|
public componentWillUnmount() {
|
||||||
if (this.props.room) {
|
RoomViewStore.instance.removeRoomListener(this.props.room.roomId, this.onActiveRoomUpdate);
|
||||||
ActiveRoomObserver.removeListener(this.props.room.roomId, this.onActiveRoomUpdate);
|
MessagePreviewStore.instance.off(
|
||||||
MessagePreviewStore.instance.off(
|
MessagePreviewStore.getPreviewChangedEventName(this.props.room),
|
||||||
MessagePreviewStore.getPreviewChangedEventName(this.props.room),
|
this.onRoomPreviewChanged,
|
||||||
this.onRoomPreviewChanged,
|
);
|
||||||
);
|
this.props.room.off(RoomEvent.Name, this.onRoomNameUpdate);
|
||||||
this.props.room.currentState.off(RoomStateEvent.Events, this.updateVoiceMembers);
|
this.props.room.currentState.off(RoomStateEvent.Events, this.updateVoiceMembers);
|
||||||
this.props.room.off(RoomEvent.Name, this.onRoomNameUpdate);
|
|
||||||
}
|
|
||||||
ActiveRoomObserver.removeListener(this.props.room.roomId, this.onActiveRoomUpdate);
|
|
||||||
defaultDispatcher.unregister(this.dispatcherRef);
|
defaultDispatcher.unregister(this.dispatcherRef);
|
||||||
this.notificationState.off(NotificationStateEvents.Update, this.onNotificationUpdate);
|
this.notificationState.off(NotificationStateEvents.Update, this.onNotificationUpdate);
|
||||||
this.roomProps.off(PROPERTY_UPDATED, this.onRoomPropertyUpdate);
|
this.roomProps.off(PROPERTY_UPDATED, this.onRoomPropertyUpdate);
|
||||||
|
|
|
@ -79,20 +79,52 @@ const INITIAL_STATE = {
|
||||||
wasContextSwitch: false,
|
wasContextSwitch: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type Listener = (isActive: boolean) => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class for storing application state for RoomView. This is the RoomView's interface
|
* A class for storing application state for RoomView. This is the RoomView's interface
|
||||||
* with a subset of the js-sdk.
|
* with a subset of the js-sdk.
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export class RoomViewStore extends Store<ActionPayload> {
|
export class RoomViewStore extends Store<ActionPayload> {
|
||||||
|
// Important: This cannot be a dynamic getter (lazily-constructed instance) because
|
||||||
|
// otherwise we'll miss view_room dispatches during startup, breaking relaunches of
|
||||||
|
// the app. We need to eagerly create the instance.
|
||||||
public static readonly instance = new RoomViewStore();
|
public static readonly instance = new RoomViewStore();
|
||||||
|
|
||||||
private state = INITIAL_STATE; // initialize state
|
private state = INITIAL_STATE; // initialize state
|
||||||
|
|
||||||
|
// Keep these out of state to avoid causing excessive/recursive updates
|
||||||
|
private roomIdActivityListeners: Record<string, Listener[]> = {};
|
||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
super(dis);
|
super(dis);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public addRoomListener(roomId: string, fn: Listener) {
|
||||||
|
if (!this.roomIdActivityListeners[roomId]) this.roomIdActivityListeners[roomId] = [];
|
||||||
|
this.roomIdActivityListeners[roomId].push(fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeRoomListener(roomId: string, fn: Listener) {
|
||||||
|
if (this.roomIdActivityListeners[roomId]) {
|
||||||
|
const i = this.roomIdActivityListeners[roomId].indexOf(fn);
|
||||||
|
if (i > -1) {
|
||||||
|
this.roomIdActivityListeners[roomId].splice(i, 1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.warn("Unregistering unrecognised listener (roomId=" + roomId + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private emitForRoom(roomId: string, isActive: boolean) {
|
||||||
|
if (!this.roomIdActivityListeners[roomId]) return;
|
||||||
|
|
||||||
|
for (const fn of this.roomIdActivityListeners[roomId]) {
|
||||||
|
fn.call(null, isActive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private setState(newState: Partial<typeof INITIAL_STATE>) {
|
private setState(newState: Partial<typeof INITIAL_STATE>) {
|
||||||
// If values haven't changed, there's nothing to do.
|
// If values haven't changed, there's nothing to do.
|
||||||
// This only tries a shallow comparison, so unchanged objects will slip
|
// This only tries a shallow comparison, so unchanged objects will slip
|
||||||
|
@ -108,7 +140,13 @@ export class RoomViewStore extends Store<ActionPayload> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const lastRoomId = this.state.roomId;
|
||||||
this.state = Object.assign(this.state, newState);
|
this.state = Object.assign(this.state, newState);
|
||||||
|
if (lastRoomId !== this.state.roomId) {
|
||||||
|
if (lastRoomId) this.emitForRoom(lastRoomId, false);
|
||||||
|
if (this.state.roomId) this.emitForRoom(this.state.roomId, true);
|
||||||
|
}
|
||||||
|
|
||||||
this.__emitChange();
|
this.__emitChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue