Provide RoomViewStore from the RoomContext (#30980)

* Pass roomViewStore to the RoomView and add to the RoomContext.

* lint

* lint

* Make constants more DRY

* Make constants more DRY

* Commend non-null assertion on roomViewStore property of the RoomContext

* Update tsdocs.
This commit is contained in:
David Langley
2025-10-23 10:03:11 +01:00
committed by GitHub
parent 0e6bacffed
commit bea3574b30
6 changed files with 49 additions and 21 deletions

View File

@@ -68,7 +68,7 @@ import { monitorSyncedPushRules } from "../../utils/pushRules/monitorSyncedPushR
import { type ConfigOptions } from "../../SdkConfig"; import { type ConfigOptions } from "../../SdkConfig";
import { MatrixClientContextProvider } from "./MatrixClientContextProvider"; import { MatrixClientContextProvider } from "./MatrixClientContextProvider";
import { Landmark, LandmarkNavigation } from "../../accessibility/LandmarkNavigation"; import { Landmark, LandmarkNavigation } from "../../accessibility/LandmarkNavigation";
import { SDKContext } from "../../contexts/SDKContext.ts"; import { SDKContext, SdkContextClass } from "../../contexts/SDKContext.ts";
// We need to fetch each pinned message individually (if we don't already have it) // We need to fetch each pinned message individually (if we don't already have it)
// so each pinned message may trigger a request. Limit the number per room for sanity. // so each pinned message may trigger a request. Limit the number per room for sanity.
@@ -690,6 +690,7 @@ class LoggedInView extends React.Component<IProps, IState> {
key={this.props.currentRoomId || "roomview"} key={this.props.currentRoomId || "roomview"}
justCreatedOpts={this.props.roomJustCreatedOpts} justCreatedOpts={this.props.roomJustCreatedOpts}
forceTimeline={this.props.forceTimeline} forceTimeline={this.props.forceTimeline}
roomViewStore={SdkContextClass.instance.roomViewStore}
/> />
); );
break; break;

View File

@@ -134,6 +134,7 @@ import { ScopedRoomContextProvider, useScopedRoomContext } from "../../contexts/
import { DeclineAndBlockInviteDialog } from "../views/dialogs/DeclineAndBlockInviteDialog"; import { DeclineAndBlockInviteDialog } from "../views/dialogs/DeclineAndBlockInviteDialog";
import { type FocusMessageSearchPayload } from "../../dispatcher/payloads/FocusMessageSearchPayload.ts"; import { type FocusMessageSearchPayload } from "../../dispatcher/payloads/FocusMessageSearchPayload.ts";
import { isRoomEncrypted } from "../../hooks/useIsEncrypted"; import { isRoomEncrypted } from "../../hooks/useIsEncrypted";
import { type RoomViewStore } from "../../stores/RoomViewStore.tsx";
const DEBUG = false; const DEBUG = false;
const PREVENT_MULTIPLE_JITSI_WITHIN = 30_000; const PREVENT_MULTIPLE_JITSI_WITHIN = 30_000;
@@ -157,11 +158,19 @@ interface IRoomProps {
// Called with the credentials of a registered user (if they were a ROU that transitioned to PWLU) // Called with the credentials of a registered user (if they were a ROU that transitioned to PWLU)
onRegistered?(credentials: IMatrixClientCreds): void; onRegistered?(credentials: IMatrixClientCreds): void;
/**
* The RoomViewStore instance for the room to be displayed.
*/
roomViewStore: RoomViewStore;
} }
export { MainSplitContentType }; export { MainSplitContentType };
export interface IRoomState { export interface IRoomState {
/**
* The RoomViewStore instance for the room we are displaying
*/
roomViewStore: RoomViewStore;
room?: Room; room?: Room;
roomId?: string; roomId?: string;
roomAlias?: string; roomAlias?: string;
@@ -394,6 +403,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
const llMembers = context.client.hasLazyLoadMembersEnabled(); const llMembers = context.client.hasLazyLoadMembersEnabled();
this.state = { this.state = {
roomViewStore: props.roomViewStore,
roomId: undefined, roomId: undefined,
roomLoading: true, roomLoading: true,
peekLoading: false, peekLoading: false,
@@ -525,7 +535,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
}; };
private getMainSplitContentType = (room: Room): MainSplitContentType => { private getMainSplitContentType = (room: Room): MainSplitContentType => {
if (this.context.roomViewStore.isViewingCall() || isVideoRoom(room)) { if (this.state.roomViewStore.isViewingCall() || isVideoRoom(room)) {
return MainSplitContentType.Call; return MainSplitContentType.Call;
} }
if (this.context.widgetLayoutStore.hasMaximisedWidget(room)) { if (this.context.widgetLayoutStore.hasMaximisedWidget(room)) {
@@ -539,8 +549,8 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
return; return;
} }
const roomLoadError = this.context.roomViewStore.getRoomLoadError() ?? undefined; const roomLoadError = this.state.roomViewStore.getRoomLoadError() ?? undefined;
if (!initial && !roomLoadError && this.state.roomId !== this.context.roomViewStore.getRoomId()) { if (!initial && !roomLoadError && this.state.roomId !== this.state.roomViewStore.getRoomId()) {
// RoomView explicitly does not support changing what room // RoomView explicitly does not support changing what room
// is being viewed: instead it should just be re-mounted when // is being viewed: instead it should just be re-mounted when
// switching rooms. Therefore, if the room ID changes, we // switching rooms. Therefore, if the room ID changes, we
@@ -554,30 +564,38 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
// it was, it means we're about to be unmounted. // it was, it means we're about to be unmounted.
return; return;
} }
const roomViewStore = this.state.roomViewStore;
const roomId = this.context.roomViewStore.getRoomId() ?? null; const roomId = roomViewStore.getRoomId() ?? null;
const roomAlias = roomViewStore.getRoomAlias() ?? undefined;
const roomLoading = roomViewStore.isRoomLoading();
const joining = roomViewStore.isJoining();
const replyToEvent = roomViewStore.getQuotingEvent() ?? undefined;
const shouldPeek = this.state.matrixClientIsReady && roomViewStore.shouldPeek();
const wasContextSwitch = roomViewStore.getWasContextSwitch();
const promptAskToJoin = roomViewStore.promptAskToJoin();
const viewRoomOpts = roomViewStore.getViewRoomOpts();
const room = this.context.client?.getRoom(roomId ?? undefined) ?? undefined; const room = this.context.client?.getRoom(roomId ?? undefined) ?? undefined;
const newState: Partial<IRoomState> = { const newState: Partial<IRoomState> = {
roomId: roomId ?? undefined, roomId: roomId ?? undefined,
roomAlias: this.context.roomViewStore.getRoomAlias() ?? undefined, roomAlias: roomAlias,
roomLoading: this.context.roomViewStore.isRoomLoading(), roomLoading: roomLoading,
roomLoadError, roomLoadError,
joining: this.context.roomViewStore.isJoining(), joining: joining,
replyToEvent: this.context.roomViewStore.getQuotingEvent() ?? undefined, replyToEvent: replyToEvent,
// we should only peek once we have a ready client // we should only peek once we have a ready client
shouldPeek: this.state.matrixClientIsReady && this.context.roomViewStore.shouldPeek(), shouldPeek: shouldPeek,
showReadReceipts: SettingsStore.getValue("showReadReceipts", roomId), showReadReceipts: SettingsStore.getValue("showReadReceipts", roomId),
showRedactions: SettingsStore.getValue("showRedactions", roomId), showRedactions: SettingsStore.getValue("showRedactions", roomId),
showJoinLeaves: SettingsStore.getValue("showJoinLeaves", roomId), showJoinLeaves: SettingsStore.getValue("showJoinLeaves", roomId),
showAvatarChanges: SettingsStore.getValue("showAvatarChanges", roomId), showAvatarChanges: SettingsStore.getValue("showAvatarChanges", roomId),
showDisplaynameChanges: SettingsStore.getValue("showDisplaynameChanges", roomId), showDisplaynameChanges: SettingsStore.getValue("showDisplaynameChanges", roomId),
wasContextSwitch: this.context.roomViewStore.getWasContextSwitch(), wasContextSwitch: wasContextSwitch,
mainSplitContentType: room ? this.getMainSplitContentType(room) : undefined, mainSplitContentType: room ? this.getMainSplitContentType(room) : undefined,
initialEventId: undefined, // default to clearing this, will get set later in the method if needed initialEventId: undefined, // default to clearing this, will get set later in the method if needed
showRightPanel: roomId ? this.context.rightPanelStore.isOpenForRoom(roomId) : false, showRightPanel: roomId ? this.context.rightPanelStore.isOpenForRoom(roomId) : false,
promptAskToJoin: this.context.roomViewStore.promptAskToJoin(), promptAskToJoin: promptAskToJoin,
viewRoomOpts: this.context.roomViewStore.getViewRoomOpts(), viewRoomOpts: viewRoomOpts,
}; };
if ( if (
@@ -593,7 +611,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
newState.showRightPanel = false; newState.showRightPanel = false;
} }
const initialEventId = this.context.roomViewStore.getInitialEventId() ?? this.state.initialEventId; const initialEventId = this.state.roomViewStore.getInitialEventId() ?? this.state.initialEventId;
if (initialEventId) { if (initialEventId) {
let initialEvent = room?.findEventById(initialEventId); let initialEvent = room?.findEventById(initialEventId);
// The event does not exist in the current sync data // The event does not exist in the current sync data
@@ -619,13 +637,13 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
action: Action.ShowThread, action: Action.ShowThread,
rootEvent: thread.rootEvent, rootEvent: thread.rootEvent,
initialEvent, initialEvent,
highlighted: this.context.roomViewStore.isInitialEventHighlighted(), highlighted: this.state.roomViewStore.isInitialEventHighlighted(),
scroll_into_view: this.context.roomViewStore.initialEventScrollIntoView(), scroll_into_view: this.state.roomViewStore.initialEventScrollIntoView(),
}); });
} else { } else {
newState.initialEventId = initialEventId; newState.initialEventId = initialEventId;
newState.isInitialEventHighlighted = this.context.roomViewStore.isInitialEventHighlighted(); newState.isInitialEventHighlighted = this.state.roomViewStore.isInitialEventHighlighted();
newState.initialEventScrollIntoView = this.context.roomViewStore.initialEventScrollIntoView(); newState.initialEventScrollIntoView = this.state.roomViewStore.initialEventScrollIntoView();
} }
} }
@@ -885,7 +903,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
this.context.client.on(MatrixEventEvent.Decrypted, this.onEventDecrypted); this.context.client.on(MatrixEventEvent.Decrypted, this.onEventDecrypted);
} }
// Start listening for RoomViewStore updates // Start listening for RoomViewStore updates
this.context.roomViewStore.on(UPDATE_EVENT, this.onRoomViewStoreUpdate); this.state.roomViewStore.on(UPDATE_EVENT, this.onRoomViewStoreUpdate);
this.context.rightPanelStore.on(UPDATE_EVENT, this.onRightPanelStoreUpdate); this.context.rightPanelStore.on(UPDATE_EVENT, this.onRightPanelStoreUpdate);
@@ -1002,7 +1020,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
window.removeEventListener("beforeunload", this.onPageUnload); window.removeEventListener("beforeunload", this.onPageUnload);
this.context.roomViewStore.off(UPDATE_EVENT, this.onRoomViewStoreUpdate); this.state.roomViewStore.off(UPDATE_EVENT, this.onRoomViewStoreUpdate);
this.context.rightPanelStore.off(UPDATE_EVENT, this.onRightPanelStoreUpdate); this.context.rightPanelStore.off(UPDATE_EVENT, this.onRightPanelStoreUpdate);
WidgetEchoStore.removeListener(UPDATE_EVENT, this.onWidgetEchoStoreUpdate); WidgetEchoStore.removeListener(UPDATE_EVENT, this.onWidgetEchoStoreUpdate);

View File

@@ -75,6 +75,9 @@ const RoomContext = createContext<
promptAskToJoin: false, promptAskToJoin: false,
viewRoomOpts: { buttons: [] }, viewRoomOpts: { buttons: [] },
isRoomEncrypted: null, isRoomEncrypted: null,
// roomViewStore should always be present as it is passed to RoomView constructor.
// In time when we migrate the RoomView to MVVM it will cease to exist(become a ViewModel).
roomViewStore: undefined!,
}); });
RoomContext.displayName = "RoomContext"; RoomContext.displayName = "RoomContext";
export default RoomContext; export default RoomContext;

View File

@@ -14,6 +14,7 @@ import { type IRoomState, MainSplitContentType } from "../../src/components/stru
import { TimelineRenderingType } from "../../src/contexts/RoomContext"; import { TimelineRenderingType } from "../../src/contexts/RoomContext";
import { Layout } from "../../src/settings/enums/Layout"; import { Layout } from "../../src/settings/enums/Layout";
import { mkEvent } from "./test-utils"; import { mkEvent } from "./test-utils";
import { SdkContextClass } from "../../src/contexts/SDKContext";
export const makeMembershipEvent = (roomId: string, userId: string, membership = KnownMembership.Join) => export const makeMembershipEvent = (roomId: string, userId: string, membership = KnownMembership.Join) =>
mkEvent({ mkEvent({
@@ -44,6 +45,7 @@ export const makeRoomWithStateEvents = (
export function getRoomContext(room: Room, override: Partial<IRoomState>): IRoomState { export function getRoomContext(room: Room, override: Partial<IRoomState>): IRoomState {
return { return {
roomViewStore: SdkContextClass.instance.roomViewStore,
room, room,
roomLoading: true, roomLoading: true,
peekLoading: false, peekLoading: false,

View File

@@ -158,6 +158,7 @@ describe("RoomView", () => {
threepidInvite={undefined as any} threepidInvite={undefined as any}
forceTimeline={false} forceTimeline={false}
ref={ref} ref={ref}
roomViewStore={stores.roomViewStore}
/> />
</SDKContext.Provider> </SDKContext.Provider>
</MatrixClientContext.Provider>, </MatrixClientContext.Provider>,
@@ -196,6 +197,7 @@ describe("RoomView", () => {
threepidInvite={undefined} threepidInvite={undefined}
forceTimeline={false} forceTimeline={false}
onRegistered={jest.fn()} onRegistered={jest.fn()}
roomViewStore={stores.roomViewStore}
/> />
</SDKContext.Provider> </SDKContext.Provider>
</MatrixClientContext.Provider>, </MatrixClientContext.Provider>,

View File

@@ -30,6 +30,7 @@ import { mockPlatformPeg } from "../../../../test-utils/platform";
import { doMaybeLocalRoomAction } from "../../../../../src/utils/local-room"; import { doMaybeLocalRoomAction } from "../../../../../src/utils/local-room";
import { addTextToComposer } from "../../../../test-utils/composer"; import { addTextToComposer } from "../../../../test-utils/composer";
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx"; import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
import { SdkContextClass } from "../../../../../src/contexts/SDKContext.ts";
jest.mock("../../../../../src/utils/local-room", () => ({ jest.mock("../../../../../src/utils/local-room", () => ({
doMaybeLocalRoomAction: jest.fn(), doMaybeLocalRoomAction: jest.fn(),
@@ -37,6 +38,7 @@ jest.mock("../../../../../src/utils/local-room", () => ({
describe("<SendMessageComposer/>", () => { describe("<SendMessageComposer/>", () => {
const defaultRoomContext: IRoomState = { const defaultRoomContext: IRoomState = {
roomViewStore: SdkContextClass.instance.roomViewStore,
roomLoading: true, roomLoading: true,
peekLoading: false, peekLoading: false,
shouldPeek: true, shouldPeek: true,