Step 8.5: Isolate RightPanelStore from RoomViewStore

pull/21833/head
Travis Ralston 2022-03-24 15:50:04 -06:00
parent 66401c844f
commit 4144d0ba57
7 changed files with 91 additions and 29 deletions

View File

@ -259,4 +259,10 @@ export enum Action {
* Fired when clicking user name from group view
*/
ViewStartChatOrReuse = "view_start_chat_or_reuse",
/**
* Fired when the user's active room changed, possibly from/to a non-room view.
* Payload: ActiveRoomChangedPayload
*/
ActiveRoomChanged = "active_room_changed",
}

View File

@ -0,0 +1,27 @@
/*
Copyright 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 { Optional } from "matrix-events-sdk";
import { Action } from "../actions";
import { ActionPayload } from "../payloads";
export interface ActiveRoomChangedPayload extends ActionPayload {
action: Action.ActiveRoomChanged;
oldRoomId: Optional<string>;
newRoomId: Optional<string>;
}

View File

@ -60,7 +60,13 @@ export abstract class ReadyWatchingStore extends EventEmitter implements IDestro
// Default implementation is to do nothing.
}
protected onDispatcherAction(payload: ActionPayload) {
// Default implementation is to do nothing.
}
private onAction = async (payload: ActionPayload) => {
this.onDispatcherAction(payload);
if (payload.action === 'MatrixActions.sync') {
// Only set the client on the transition into the PREPARED state.
// Everything after this is unnecessary (we only need to know once we have a client)

View File

@ -24,7 +24,7 @@ import { ViewRoom as ViewRoomEvent } from "matrix-analytics-events/types/typescr
import { JoinedRoom as JoinedRoomEvent } from "matrix-analytics-events/types/typescript/JoinedRoom";
import { JoinRule } from "matrix-js-sdk/src/@types/partials";
import { Room } from "matrix-js-sdk/src/models/room";
import { ClientEvent } from "matrix-js-sdk/src/client";
import { ClientEvent, MatrixClient } from "matrix-js-sdk/src/client";
import dis from '../dispatcher/dispatcher';
import { MatrixClientPeg } from '../MatrixClientPeg';
@ -46,6 +46,7 @@ import { JoinRoomErrorPayload } from "../dispatcher/payloads/JoinRoomErrorPayloa
import { ViewRoomErrorPayload } from "../dispatcher/payloads/ViewRoomErrorPayload";
import RoomSettingsDialog from "../components/views/dialogs/RoomSettingsDialog";
import ErrorDialog from "../components/views/dialogs/ErrorDialog";
import { ActiveRoomChangedPayload } from "../dispatcher/payloads/ActiveRoomChangedPayload";
const NUM_JOIN_RETRY = 5;
@ -93,6 +94,7 @@ export class RoomViewStore extends Store<ActionPayload> {
public static readonly instance = new RoomViewStore();
private state = INITIAL_STATE; // initialize state
private forcedMatrixClient: MatrixClient;
// Keep these out of state to avoid causing excessive/recursive updates
private roomIdActivityListeners: Record<string, Listener[]> = {};
@ -101,6 +103,14 @@ export class RoomViewStore extends Store<ActionPayload> {
super(dis);
}
private get matrixClient(): MatrixClient {
return this.forcedMatrixClient || MatrixClientPeg.get();
}
public useUnitTestClient(client: MatrixClient) {
this.forcedMatrixClient = client;
}
public addRoomListener(roomId: string, fn: Listener) {
if (!this.roomIdActivityListeners[roomId]) this.roomIdActivityListeners[roomId] = [];
this.roomIdActivityListeners[roomId].push(fn);
@ -145,6 +155,14 @@ export class RoomViewStore extends Store<ActionPayload> {
if (lastRoomId !== this.state.roomId) {
if (lastRoomId) this.emitForRoom(lastRoomId, false);
if (this.state.roomId) this.emitForRoom(this.state.roomId, true);
// Fired so we can reduce dependency on event emitters to this store, which is relatively
// central to the application and can easily cause import cycles.
dis.dispatch({
action: Action.ActiveRoomChanged,
oldRoomId: lastRoomId,
newRoomId: this.state.roomId,
} as ActiveRoomChangedPayload);
}
this.__emitChange();
@ -197,7 +215,7 @@ export class RoomViewStore extends Store<ActionPayload> {
this.setState({ shouldPeek: false });
}
const cli = MatrixClientPeg.get();
const cli = this.matrixClient;
const updateMetrics = () => {
const room = cli.getRoom(payload.roomId);
@ -280,7 +298,7 @@ export class RoomViewStore extends Store<ActionPayload> {
trigger: payload.metricsTrigger,
viaKeyboard: payload.metricsViaKeyboard,
isDM: !!DMRoomMap.shared().getUserIdForRoomId(payload.room_id),
isSpace: MatrixClientPeg.get().getRoom(payload.room_id)?.isSpaceRoom(),
isSpace: this.matrixClient.getRoom(payload.room_id)?.isSpaceRoom(),
activeSpace,
});
}
@ -339,7 +357,7 @@ export class RoomViewStore extends Store<ActionPayload> {
wasContextSwitch: payload.context_switch,
});
try {
const result = await MatrixClientPeg.get().getRoomIdForAlias(payload.room_alias);
const result = await this.matrixClient.getRoomIdForAlias(payload.room_alias);
storeRoomAliasInCache(payload.room_alias, result.room_id);
roomId = result.room_id;
} catch (err) {
@ -376,7 +394,7 @@ export class RoomViewStore extends Store<ActionPayload> {
joining: true,
});
const cli = MatrixClientPeg.get();
const cli = this.matrixClient;
// take a copy of roomAlias & roomId as they may change by the time the join is complete
const { roomAlias, roomId } = this.state;
const address = roomAlias || roomId;
@ -408,7 +426,7 @@ export class RoomViewStore extends Store<ActionPayload> {
}
private getInvitingUserId(roomId: string): string {
const cli = MatrixClientPeg.get();
const cli = this.matrixClient;
const room = cli.getRoom(roomId);
if (room && room.getMyMembership() === "invite") {
const myMember = room.getMember(cli.getUserId());
@ -433,7 +451,7 @@ export class RoomViewStore extends Store<ActionPayload> {
// 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.
if (invitingUserId.endsWith(`:${MatrixClientPeg.get().getDomain()}`)) {
if (invitingUserId.endsWith(`:${this.matrixClient.getDomain()}`)) {
msg = _t("The person who invited you already left the room.");
} else {
msg = _t("The person who invited you already left the room, or their server is offline.");

View File

@ -14,9 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { EventSubscription } from 'fbemitter';
import { logger } from "matrix-js-sdk/src/logger";
import { CryptoEvent } from "matrix-js-sdk/src/crypto";
import { Optional } from "matrix-events-sdk";
import defaultDispatcher from '../../dispatcher/dispatcher';
import { pendingVerificationRequestForUser } from '../../verification';
@ -31,7 +31,9 @@ import {
IRightPanelCard,
IRightPanelForRoom,
} from './RightPanelStoreIPanelState';
import { RoomViewStore } from '../RoomViewStore';
import { ActionPayload } from "../../dispatcher/payloads";
import { Action } from "../../dispatcher/actions";
import { ActiveRoomChangedPayload } from "../../dispatcher/payloads/ActiveRoomChangedPayload";
/**
* A class for tracking the state of the right panel between layouts and
@ -41,30 +43,32 @@ import { RoomViewStore } from '../RoomViewStore';
*/
export default class RightPanelStore extends ReadyWatchingStore {
private static internalInstance: RightPanelStore;
private viewedRoomId: string;
private global?: IRightPanelForRoom = null;
private byRoom: {
[roomId: string]: IRightPanelForRoom;
} = {};
private roomStoreToken: EventSubscription;
private viewedRoomId: Optional<string>;
private constructor() {
super(defaultDispatcher);
}
protected async onReady(): Promise<any> {
this.roomStoreToken = RoomViewStore.instance.addListener(this.onRoomViewStoreUpdate);
this.matrixClient.on(CryptoEvent.VerificationRequest, this.onVerificationRequestUpdate);
this.viewedRoomId = RoomViewStore.instance.getRoomId();
this.loadCacheFromSettings();
this.emitAndUpdateSettings();
}
protected async onNotReady(): Promise<any> {
this.matrixClient.off(CryptoEvent.VerificationRequest, this.onVerificationRequestUpdate);
this.roomStoreToken.remove();
}
protected onDispatcherAction(payload: ActionPayload) {
if (payload.action !== Action.ActiveRoomChanged) return;
const changePayload = <ActiveRoomChangedPayload>payload;
this.handleViewedRoomChange(changePayload.oldRoomId, changePayload.newRoomId);
}
// Getters
@ -336,15 +340,13 @@ export default class RightPanelStore extends ReadyWatchingStore {
}
};
private onRoomViewStoreUpdate = () => {
const oldRoomId = this.viewedRoomId;
this.viewedRoomId = RoomViewStore.instance.getRoomId();
private handleViewedRoomChange(oldRoomId: Optional<string>, newRoomId: Optional<string>) {
this.viewedRoomId = newRoomId;
// load values from byRoomCache with the viewedRoomId.
this.loadCacheFromSettings();
// if we're switching to a room, clear out any stale MemberInfo cards
// when we're switching to a room, clear out any stale MemberInfo cards
// in order to fix https://github.com/vector-im/element-web/issues/21487
if (oldRoomId !== this.viewedRoomId) {
if (this.currentCard?.phase !== RightPanelPhases.EncryptionPanel) {
const panel = this.byRoom[this.viewedRoomId];
if (panel?.history) {
@ -354,7 +356,6 @@ export default class RightPanelStore extends ReadyWatchingStore {
);
}
}
}
// If the right panel stays open mode is used, and the panel was either
// closed or never shown for that room, then force it open and display
@ -374,7 +375,7 @@ export default class RightPanelStore extends ReadyWatchingStore {
};
}
this.emitAndUpdateSettings();
};
}
private get isViewingRoom(): boolean {
return !!this.viewedRoomId;

View File

@ -32,6 +32,7 @@ import { RightPanelPhases } from "../../../src/stores/right-panel/RightPanelStor
import RightPanelStore from "../../../src/stores/right-panel/RightPanelStore";
import { UPDATE_EVENT } from "../../../src/stores/AsyncStore";
import { WidgetLayoutStore } from "../../../src/stores/widgets/WidgetLayoutStore";
import { RoomViewStore } from "../../../src/stores/RoomViewStore";
describe("RightPanel", () => {
it("renders info from only one room during room changes", async () => {
@ -75,6 +76,7 @@ describe("RightPanel", () => {
// @ts-ignore
await WidgetLayoutStore.instance.onReady();
RightPanelStore.instance.useUnitTestClient(cli);
RoomViewStore.instance.useUnitTestClient(cli);
// @ts-ignore
await RightPanelStore.instance.onReady();

View File

@ -36,6 +36,7 @@ import WidgetStore, { IApp } from "../../../../src/stores/WidgetStore";
import AppTile from "../../../../src/components/views/elements/AppTile";
import { Container, WidgetLayoutStore } from "../../../../src/stores/widgets/WidgetLayoutStore";
import AppsDrawer from "../../../../src/components/views/rooms/AppsDrawer";
import { RoomViewStore } from "../../../../src/stores/RoomViewStore";
describe("AppTile", () => {
let cli;
@ -108,6 +109,7 @@ describe("AppTile", () => {
// @ts-ignore
await WidgetLayoutStore.instance.onReady();
RightPanelStore.instance.useUnitTestClient(cli);
RoomViewStore.instance.useUnitTestClient(cli);
// @ts-ignore
await RightPanelStore.instance.onReady();
});