Store refactor: use non-global stores in components (#9293)

* Add Stores and StoresContext and use it in MatrixChat and RoomView

Added a new kind of class:
- Add God object `Stores` which will hold refs to all known stores and the `MatrixClient`. This object is NOT a singleton.
- Add `StoresContext` to hold onto a ref of `Stores` for use inside components.

`StoresContext` is created via:
- Create `Stores` in `MatrixChat`, assigning the `MatrixClient` when we have one set. Currently sets the RVS to `RoomViewStore.instance`.
- Wrap `MatrixChat`s `render()` function in a `StoresContext.Provider` so it can be used anywhere.

`StoresContext` is currently only used in `RoomView` via the following changes:
- Remove the HOC, which redundantly set `mxClient` as a prop. We don't need this as `RoomView` was using the client from `this.context`.
- Change the type of context accepted from `MatrixClientContext` to `StoresContext`.
- Modify alllll the places where `this.context` is used to interact with the client and suffix `.client`.
- Modify places where we use `RoomViewStore.instance` and replace them with `this.context.roomViewStore`.

This makes `RoomView` use a non-global instance of RVS.

* Linting

* SDKContext and make client an optional constructor arg

* Move SDKContext to /src/contexts

* Inject all RVS deps

* Linting

* Remove reset calls; deep copy the INITIAL_STATE to avoid test pollution

* DI singletons used in RoomView; DI them in RoomView-test too

* Initial RoomViewStore.instance after all files are imported to avoid cyclical deps

* Lazily init stores to allow for circular dependencies

Rather than stores accepting a list of other stores in their constructors,
which doesn't work when A needs B and B needs A, make new-style stores simply
accept Stores. When a store needs another store, they access it via `Stores`
which then lazily constructs that store if it needs it. This breaks the
circular dependency at constructor time, without needing to introduce
wiring diagrams or any complex DI framework.

* Delete RoomViewStore.instance

Replaced with Stores.instance.roomViewStore

* Linting

* Move OverridableStores to test/TestStores

* Rejig how eager stores get made; don't automatically do it else tests break

* Linting

* Linting and review comments

* Fix new code to use Stores.instance

* s/Stores/SdkContextClass/g

* Update docs

* Remove unused imports

* Update src/stores/RoomViewStore.tsx

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>

* Remove empty c'tor to make sonar happy

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
kegsay 2022-10-19 13:07:03 +01:00 committed by GitHub
parent 84f2974b57
commit e946674df3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 467 additions and 275 deletions

View file

@ -43,7 +43,6 @@ import { RoomUpload } from "./models/RoomUpload";
import SettingsStore from "./settings/SettingsStore"; import SettingsStore from "./settings/SettingsStore";
import { decorateStartSendingTime, sendRoundTripMetric } from "./sendTimePerformanceMetrics"; import { decorateStartSendingTime, sendRoundTripMetric } from "./sendTimePerformanceMetrics";
import { TimelineRenderingType } from "./contexts/RoomContext"; import { TimelineRenderingType } from "./contexts/RoomContext";
import { RoomViewStore } from "./stores/RoomViewStore";
import { addReplyToMessageContent } from "./utils/Reply"; import { addReplyToMessageContent } from "./utils/Reply";
import ErrorDialog from "./components/views/dialogs/ErrorDialog"; import ErrorDialog from "./components/views/dialogs/ErrorDialog";
import UploadFailureDialog from "./components/views/dialogs/UploadFailureDialog"; import UploadFailureDialog from "./components/views/dialogs/UploadFailureDialog";
@ -51,6 +50,7 @@ import UploadConfirmDialog from "./components/views/dialogs/UploadConfirmDialog"
import { createThumbnail } from "./utils/image-media"; import { createThumbnail } from "./utils/image-media";
import { attachRelation } from "./components/views/rooms/SendMessageComposer"; import { attachRelation } from "./components/views/rooms/SendMessageComposer";
import { doMaybeLocalRoomAction } from "./utils/local-room"; import { doMaybeLocalRoomAction } from "./utils/local-room";
import { SdkContextClass } from "./contexts/SDKContext";
// scraped out of a macOS hidpi (5660ppm) screenshot png // scraped out of a macOS hidpi (5660ppm) screenshot png
// 5669 px (x-axis) , 5669 px (y-axis) , per metre // 5669 px (x-axis) , 5669 px (y-axis) , per metre
@ -361,7 +361,7 @@ export default class ContentMessages {
return; return;
} }
const replyToEvent = RoomViewStore.instance.getQuotingEvent(); const replyToEvent = SdkContextClass.instance.roomViewStore.getQuotingEvent();
if (!this.mediaConfig) { // hot-path optimization to not flash a spinner if we don't need to if (!this.mediaConfig) { // hot-path optimization to not flash a spinner if we don't need to
const modal = Modal.createDialog(Spinner, null, 'mx_Dialog_spinner'); const modal = Modal.createDialog(Spinner, null, 'mx_Dialog_spinner');
await this.ensureMediaConfigFetched(matrixClient); await this.ensureMediaConfigFetched(matrixClient);

View file

@ -41,12 +41,12 @@ import SettingsStore from "./settings/SettingsStore";
import { hideToast as hideNotificationsToast } from "./toasts/DesktopNotificationsToast"; import { hideToast as hideNotificationsToast } from "./toasts/DesktopNotificationsToast";
import { SettingLevel } from "./settings/SettingLevel"; import { SettingLevel } from "./settings/SettingLevel";
import { isPushNotifyDisabled } from "./settings/controllers/NotificationControllers"; import { isPushNotifyDisabled } from "./settings/controllers/NotificationControllers";
import { RoomViewStore } from "./stores/RoomViewStore";
import UserActivity from "./UserActivity"; import UserActivity from "./UserActivity";
import { mediaFromMxc } from "./customisations/Media"; import { mediaFromMxc } from "./customisations/Media";
import ErrorDialog from "./components/views/dialogs/ErrorDialog"; import ErrorDialog from "./components/views/dialogs/ErrorDialog";
import LegacyCallHandler from "./LegacyCallHandler"; import LegacyCallHandler from "./LegacyCallHandler";
import VoipUserMapper from "./VoipUserMapper"; import VoipUserMapper from "./VoipUserMapper";
import { SdkContextClass } from "./contexts/SDKContext";
import { localNotificationsAreSilenced } from "./utils/notifications"; import { localNotificationsAreSilenced } from "./utils/notifications";
import { getIncomingCallToastKey, IncomingCallToast } from "./toasts/IncomingCallToast"; import { getIncomingCallToastKey, IncomingCallToast } from "./toasts/IncomingCallToast";
import ToastStore from "./stores/ToastStore"; import ToastStore from "./stores/ToastStore";
@ -435,7 +435,7 @@ export const Notifier = {
if (actions?.notify) { if (actions?.notify) {
this._performCustomEventHandling(ev); this._performCustomEventHandling(ev);
if (RoomViewStore.instance.getRoomId() === room.roomId && if (SdkContextClass.instance.roomViewStore.getRoomId() === room.roomId &&
UserActivity.sharedInstance().userActiveRecently() && UserActivity.sharedInstance().userActiveRecently() &&
!Modal.hasDialogs() !Modal.hasDialogs()
) { ) {

View file

@ -272,12 +272,12 @@ import { logger } from "matrix-js-sdk/src/logger";
import { MatrixClientPeg } from './MatrixClientPeg'; import { MatrixClientPeg } from './MatrixClientPeg';
import dis from './dispatcher/dispatcher'; import dis from './dispatcher/dispatcher';
import WidgetUtils from './utils/WidgetUtils'; import WidgetUtils from './utils/WidgetUtils';
import { RoomViewStore } from './stores/RoomViewStore';
import { _t } from './languageHandler'; import { _t } from './languageHandler';
import { IntegrationManagers } from "./integrations/IntegrationManagers"; import { IntegrationManagers } from "./integrations/IntegrationManagers";
import { WidgetType } from "./widgets/WidgetType"; import { WidgetType } from "./widgets/WidgetType";
import { objectClone } from "./utils/objects"; import { objectClone } from "./utils/objects";
import { EffectiveMembership, getEffectiveMembership } from './utils/membership'; import { EffectiveMembership, getEffectiveMembership } from './utils/membership';
import { SdkContextClass } from './contexts/SDKContext';
enum Action { enum Action {
CloseScalar = "close_scalar", CloseScalar = "close_scalar",
@ -721,7 +721,7 @@ const onMessage = function(event: MessageEvent<any>): void {
} }
} }
if (roomId !== RoomViewStore.instance.getRoomId()) { if (roomId !== SdkContextClass.instance.roomViewStore.getRoomId()) {
sendError(event, _t('Room %(roomId)s not visible', { roomId: roomId })); sendError(event, _t('Room %(roomId)s not visible', { roomId: roomId }));
return; return;
} }

View file

@ -62,7 +62,6 @@ import InfoDialog from "./components/views/dialogs/InfoDialog";
import SlashCommandHelpDialog from "./components/views/dialogs/SlashCommandHelpDialog"; import SlashCommandHelpDialog from "./components/views/dialogs/SlashCommandHelpDialog";
import { shouldShowComponent } from "./customisations/helpers/UIComponents"; import { shouldShowComponent } from "./customisations/helpers/UIComponents";
import { TimelineRenderingType } from './contexts/RoomContext'; import { TimelineRenderingType } from './contexts/RoomContext';
import { RoomViewStore } from "./stores/RoomViewStore";
import { XOR } from "./@types/common"; import { XOR } from "./@types/common";
import { PosthogAnalytics } from "./PosthogAnalytics"; import { PosthogAnalytics } from "./PosthogAnalytics";
import { ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload"; import { ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload";
@ -70,6 +69,7 @@ import VoipUserMapper from './VoipUserMapper';
import { htmlSerializeFromMdIfNeeded } from './editor/serialize'; import { htmlSerializeFromMdIfNeeded } from './editor/serialize';
import { leaveRoomBehaviour } from "./utils/leave-behaviour"; import { leaveRoomBehaviour } from "./utils/leave-behaviour";
import { isLocalRoom } from './utils/localRoom/isLocalRoom'; import { isLocalRoom } from './utils/localRoom/isLocalRoom';
import { SdkContextClass } from './contexts/SDKContext';
// XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816 // XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816
interface HTMLInputEvent extends Event { interface HTMLInputEvent extends Event {
@ -209,7 +209,7 @@ function successSync(value: any) {
const isCurrentLocalRoom = (): boolean => { const isCurrentLocalRoom = (): boolean => {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const room = cli.getRoom(RoomViewStore.instance.getRoomId()); const room = cli.getRoom(SdkContextClass.instance.roomViewStore.getRoomId());
return isLocalRoom(room); return isLocalRoom(room);
}; };
@ -868,7 +868,7 @@ export const Commands = [
description: _td('Define the power level of a user'), description: _td('Define the power level of a user'),
isEnabled(): boolean { isEnabled(): boolean {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const room = cli.getRoom(RoomViewStore.instance.getRoomId()); const room = cli.getRoom(SdkContextClass.instance.roomViewStore.getRoomId());
return room?.currentState.maySendStateEvent(EventType.RoomPowerLevels, cli.getUserId()) return room?.currentState.maySendStateEvent(EventType.RoomPowerLevels, cli.getUserId())
&& !isLocalRoom(room); && !isLocalRoom(room);
}, },
@ -909,7 +909,7 @@ export const Commands = [
description: _td('Deops user with given id'), description: _td('Deops user with given id'),
isEnabled(): boolean { isEnabled(): boolean {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const room = cli.getRoom(RoomViewStore.instance.getRoomId()); const room = cli.getRoom(SdkContextClass.instance.roomViewStore.getRoomId());
return room?.currentState.maySendStateEvent(EventType.RoomPowerLevels, cli.getUserId()) return room?.currentState.maySendStateEvent(EventType.RoomPowerLevels, cli.getUserId())
&& !isLocalRoom(room); && !isLocalRoom(room);
}, },

View file

@ -25,7 +25,7 @@ import { MatrixClientPeg } from "../MatrixClientPeg";
import { arrayFastClone } from "../utils/arrays"; import { arrayFastClone } from "../utils/arrays";
import { PlaybackManager } from "./PlaybackManager"; import { PlaybackManager } from "./PlaybackManager";
import { isVoiceMessage } from "../utils/EventUtils"; import { isVoiceMessage } from "../utils/EventUtils";
import { RoomViewStore } from "../stores/RoomViewStore"; import { SdkContextClass } from "../contexts/SDKContext";
/** /**
* Audio playback queue management for a given room. This keeps track of where the user * Audio playback queue management for a given room. This keeps track of where the user
@ -51,7 +51,7 @@ export class PlaybackQueue {
constructor(private room: Room) { constructor(private room: Room) {
this.loadClocks(); this.loadClocks();
RoomViewStore.instance.addRoomListener(this.room.roomId, (isActive) => { SdkContextClass.instance.roomViewStore.addRoomListener(this.room.roomId, (isActive) => {
if (!isActive) return; if (!isActive) return;
// Reset the state of the playbacks before they start mounting and enqueuing updates. // Reset the state of the playbacks before they start mounting and enqueuing updates.

View file

@ -137,6 +137,7 @@ import { TimelineRenderingType } from "../../contexts/RoomContext";
import { UseCaseSelection } from '../views/elements/UseCaseSelection'; import { UseCaseSelection } from '../views/elements/UseCaseSelection';
import { ValidatedServerConfig } from '../../utils/ValidatedServerConfig'; import { ValidatedServerConfig } from '../../utils/ValidatedServerConfig';
import { isLocalRoom } from '../../utils/localRoom/isLocalRoom'; import { isLocalRoom } from '../../utils/localRoom/isLocalRoom';
import { SdkContextClass, SDKContext } from '../../contexts/SDKContext';
import { viewUserDeviceSettings } from '../../actions/handlers/viewUserDeviceSettings'; import { viewUserDeviceSettings } from '../../actions/handlers/viewUserDeviceSettings';
// legacy export // legacy export
@ -238,9 +239,12 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
private readonly dispatcherRef: string; private readonly dispatcherRef: string;
private readonly themeWatcher: ThemeWatcher; private readonly themeWatcher: ThemeWatcher;
private readonly fontWatcher: FontWatcher; private readonly fontWatcher: FontWatcher;
private readonly stores: SdkContextClass;
constructor(props: IProps) { constructor(props: IProps) {
super(props); super(props);
this.stores = SdkContextClass.instance;
this.stores.constructEagerStores();
this.state = { this.state = {
view: Views.LOADING, view: Views.LOADING,
@ -762,6 +766,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
Modal.createDialog(DialPadModal, {}, "mx_Dialog_dialPadWrapper"); Modal.createDialog(DialPadModal, {}, "mx_Dialog_dialPadWrapper");
break; break;
case Action.OnLoggedIn: case Action.OnLoggedIn:
this.stores.client = MatrixClientPeg.get();
if ( if (
// Skip this handling for token login as that always calls onLoggedIn itself // Skip this handling for token login as that always calls onLoggedIn itself
!this.tokenLogin && !this.tokenLogin &&
@ -2087,7 +2092,9 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
} }
return <ErrorBoundary> return <ErrorBoundary>
{ view } <SDKContext.Provider value={this.stores}>
{ view }
</SDKContext.Provider>
</ErrorBoundary>; </ErrorBoundary>;
} }
} }

View file

@ -44,21 +44,18 @@ import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks';
import ResizeNotifier from '../../utils/ResizeNotifier'; import ResizeNotifier from '../../utils/ResizeNotifier';
import ContentMessages from '../../ContentMessages'; import ContentMessages from '../../ContentMessages';
import Modal from '../../Modal'; import Modal from '../../Modal';
import LegacyCallHandler, { LegacyCallHandlerEvent } from '../../LegacyCallHandler'; import { LegacyCallHandlerEvent } from '../../LegacyCallHandler';
import dis, { defaultDispatcher } from '../../dispatcher/dispatcher'; import dis, { defaultDispatcher } from '../../dispatcher/dispatcher';
import * as Rooms from '../../Rooms'; import * as Rooms from '../../Rooms';
import eventSearch, { searchPagination } from '../../Searching'; import eventSearch, { searchPagination } from '../../Searching';
import MainSplit from './MainSplit'; import MainSplit from './MainSplit';
import RightPanel from './RightPanel'; import RightPanel from './RightPanel';
import { RoomViewStore } from '../../stores/RoomViewStore';
import RoomScrollStateStore, { ScrollState } from '../../stores/RoomScrollStateStore'; import RoomScrollStateStore, { ScrollState } from '../../stores/RoomScrollStateStore';
import WidgetEchoStore from '../../stores/WidgetEchoStore'; import WidgetEchoStore from '../../stores/WidgetEchoStore';
import SettingsStore from "../../settings/SettingsStore"; import SettingsStore from "../../settings/SettingsStore";
import { Layout } from "../../settings/enums/Layout"; import { Layout } from "../../settings/enums/Layout";
import AccessibleButton from "../views/elements/AccessibleButton"; import AccessibleButton from "../views/elements/AccessibleButton";
import RightPanelStore from "../../stores/right-panel/RightPanelStore";
import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext"; import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
import MatrixClientContext, { MatrixClientProps, withMatrixClientHOC } from "../../contexts/MatrixClientContext";
import { E2EStatus, shieldStatusForRoom } from '../../utils/ShieldUtils'; import { E2EStatus, shieldStatusForRoom } from '../../utils/ShieldUtils';
import { Action } from "../../dispatcher/actions"; import { Action } from "../../dispatcher/actions";
import { IMatrixClientCreds } from "../../MatrixClientPeg"; import { IMatrixClientCreds } from "../../MatrixClientPeg";
@ -76,12 +73,10 @@ import { IOOBData, IThreepidInvite } from "../../stores/ThreepidInviteStore";
import EffectsOverlay from "../views/elements/EffectsOverlay"; import EffectsOverlay from "../views/elements/EffectsOverlay";
import { containsEmoji } from '../../effects/utils'; import { containsEmoji } from '../../effects/utils';
import { CHAT_EFFECTS } from '../../effects'; import { CHAT_EFFECTS } from '../../effects';
import WidgetStore from "../../stores/WidgetStore";
import { CallView } from "../views/voip/CallView"; import { CallView } from "../views/voip/CallView";
import { UPDATE_EVENT } from "../../stores/AsyncStore"; import { UPDATE_EVENT } from "../../stores/AsyncStore";
import Notifier from "../../Notifier"; import Notifier from "../../Notifier";
import { showToast as showNotificationsToast } from "../../toasts/DesktopNotificationsToast"; import { showToast as showNotificationsToast } from "../../toasts/DesktopNotificationsToast";
import { RoomNotificationStateStore } from "../../stores/notifications/RoomNotificationStateStore";
import { Container, WidgetLayoutStore } from "../../stores/widgets/WidgetLayoutStore"; import { Container, WidgetLayoutStore } from "../../stores/widgets/WidgetLayoutStore";
import { getKeyBindingsManager } from '../../KeyBindingsManager'; import { getKeyBindingsManager } from '../../KeyBindingsManager';
import { objectHasDiff } from "../../utils/objects"; import { objectHasDiff } from "../../utils/objects";
@ -120,6 +115,7 @@ import { RoomStatusBarUnsentMessages } from './RoomStatusBarUnsentMessages';
import { LargeLoader } from './LargeLoader'; import { LargeLoader } from './LargeLoader';
import { VoiceBroadcastInfoEventType } from '../../voice-broadcast'; import { VoiceBroadcastInfoEventType } from '../../voice-broadcast';
import { isVideoRoom } from '../../utils/video-rooms'; import { isVideoRoom } from '../../utils/video-rooms';
import { SDKContext } from '../../contexts/SDKContext';
import { CallStore, CallStoreEvent } from "../../stores/CallStore"; import { CallStore, CallStoreEvent } from "../../stores/CallStore";
import { Call } from "../../models/Call"; import { Call } from "../../models/Call";
@ -133,7 +129,7 @@ if (DEBUG) {
debuglog = logger.log.bind(console); debuglog = logger.log.bind(console);
} }
interface IRoomProps extends MatrixClientProps { interface IRoomProps {
threepidInvite: IThreepidInvite; threepidInvite: IThreepidInvite;
oobData?: IOOBData; oobData?: IOOBData;
@ -381,13 +377,13 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
private messagePanel: TimelinePanel; private messagePanel: TimelinePanel;
private roomViewBody = createRef<HTMLDivElement>(); private roomViewBody = createRef<HTMLDivElement>();
static contextType = MatrixClientContext; static contextType = SDKContext;
public context!: React.ContextType<typeof MatrixClientContext>; public context!: React.ContextType<typeof SDKContext>;
constructor(props: IRoomProps, context: React.ContextType<typeof MatrixClientContext>) { constructor(props: IRoomProps, context: React.ContextType<typeof SDKContext>) {
super(props, context); super(props, context);
const llMembers = context.hasLazyLoadMembersEnabled(); const llMembers = context.client.hasLazyLoadMembersEnabled();
this.state = { this.state = {
roomId: null, roomId: null,
roomLoading: true, roomLoading: true,
@ -422,7 +418,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
showJoinLeaves: true, showJoinLeaves: true,
showAvatarChanges: true, showAvatarChanges: true,
showDisplaynameChanges: true, showDisplaynameChanges: true,
matrixClientIsReady: context?.isInitialSyncComplete(), matrixClientIsReady: context.client?.isInitialSyncComplete(),
mainSplitContentType: MainSplitContentType.Timeline, mainSplitContentType: MainSplitContentType.Timeline,
timelineRenderingType: TimelineRenderingType.Room, timelineRenderingType: TimelineRenderingType.Room,
liveTimeline: undefined, liveTimeline: undefined,
@ -430,25 +426,25 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
}; };
this.dispatcherRef = dis.register(this.onAction); this.dispatcherRef = dis.register(this.onAction);
context.on(ClientEvent.Room, this.onRoom); context.client.on(ClientEvent.Room, this.onRoom);
context.on(RoomEvent.Timeline, this.onRoomTimeline); context.client.on(RoomEvent.Timeline, this.onRoomTimeline);
context.on(RoomEvent.TimelineReset, this.onRoomTimelineReset); context.client.on(RoomEvent.TimelineReset, this.onRoomTimelineReset);
context.on(RoomEvent.Name, this.onRoomName); context.client.on(RoomEvent.Name, this.onRoomName);
context.on(RoomStateEvent.Events, this.onRoomStateEvents); context.client.on(RoomStateEvent.Events, this.onRoomStateEvents);
context.on(RoomStateEvent.Update, this.onRoomStateUpdate); context.client.on(RoomStateEvent.Update, this.onRoomStateUpdate);
context.on(RoomEvent.MyMembership, this.onMyMembership); context.client.on(RoomEvent.MyMembership, this.onMyMembership);
context.on(CryptoEvent.KeyBackupStatus, this.onKeyBackupStatus); context.client.on(CryptoEvent.KeyBackupStatus, this.onKeyBackupStatus);
context.on(CryptoEvent.DeviceVerificationChanged, this.onDeviceVerificationChanged); context.client.on(CryptoEvent.DeviceVerificationChanged, this.onDeviceVerificationChanged);
context.on(CryptoEvent.UserTrustStatusChanged, this.onUserVerificationChanged); context.client.on(CryptoEvent.UserTrustStatusChanged, this.onUserVerificationChanged);
context.on(CryptoEvent.KeysChanged, this.onCrossSigningKeysChanged); context.client.on(CryptoEvent.KeysChanged, this.onCrossSigningKeysChanged);
context.on(MatrixEventEvent.Decrypted, this.onEventDecrypted); context.client.on(MatrixEventEvent.Decrypted, this.onEventDecrypted);
// Start listening for RoomViewStore updates // Start listening for RoomViewStore updates
RoomViewStore.instance.on(UPDATE_EVENT, this.onRoomViewStoreUpdate); context.roomViewStore.on(UPDATE_EVENT, this.onRoomViewStoreUpdate);
RightPanelStore.instance.on(UPDATE_EVENT, this.onRightPanelStoreUpdate); context.rightPanelStore.on(UPDATE_EVENT, this.onRightPanelStoreUpdate);
WidgetEchoStore.on(UPDATE_EVENT, this.onWidgetEchoStoreUpdate); WidgetEchoStore.on(UPDATE_EVENT, this.onWidgetEchoStoreUpdate);
WidgetStore.instance.on(UPDATE_EVENT, this.onWidgetStoreUpdate); context.widgetStore.on(UPDATE_EVENT, this.onWidgetStoreUpdate);
CallStore.instance.on(CallStoreEvent.ActiveCalls, this.onActiveCalls); CallStore.instance.on(CallStoreEvent.ActiveCalls, this.onActiveCalls);
@ -501,16 +497,16 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
action: "appsDrawer", action: "appsDrawer",
show: true, show: true,
}); });
if (WidgetLayoutStore.instance.hasMaximisedWidget(this.state.room)) { if (this.context.widgetLayoutStore.hasMaximisedWidget(this.state.room)) {
// Show chat in right panel when a widget is maximised // Show chat in right panel when a widget is maximised
RightPanelStore.instance.setCard({ phase: RightPanelPhases.Timeline }); this.context.rightPanelStore.setCard({ phase: RightPanelPhases.Timeline });
} }
this.checkWidgets(this.state.room); this.checkWidgets(this.state.room);
}; };
private checkWidgets = (room: Room): void => { private checkWidgets = (room: Room): void => {
this.setState({ this.setState({
hasPinnedWidgets: WidgetLayoutStore.instance.hasPinnedWidgets(room), hasPinnedWidgets: this.context.widgetLayoutStore.hasPinnedWidgets(room),
mainSplitContentType: this.getMainSplitContentType(room), mainSplitContentType: this.getMainSplitContentType(room),
showApps: this.shouldShowApps(room), showApps: this.shouldShowApps(room),
}); });
@ -518,12 +514,12 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
private getMainSplitContentType = (room: Room) => { private getMainSplitContentType = (room: Room) => {
if ( if (
(SettingsStore.getValue("feature_group_calls") && RoomViewStore.instance.isViewingCall()) (SettingsStore.getValue("feature_group_calls") && this.context.roomViewStore.isViewingCall())
|| isVideoRoom(room) || isVideoRoom(room)
) { ) {
return MainSplitContentType.Call; return MainSplitContentType.Call;
} }
if (WidgetLayoutStore.instance.hasMaximisedWidget(room)) { if (this.context.widgetLayoutStore.hasMaximisedWidget(room)) {
return MainSplitContentType.MaximisedWidget; return MainSplitContentType.MaximisedWidget;
} }
return MainSplitContentType.Timeline; return MainSplitContentType.Timeline;
@ -534,7 +530,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
return; return;
} }
if (!initial && this.state.roomId !== RoomViewStore.instance.getRoomId()) { if (!initial && this.state.roomId !== this.context.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
@ -549,45 +545,45 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
return; return;
} }
const roomId = RoomViewStore.instance.getRoomId(); const roomId = this.context.roomViewStore.getRoomId();
const room = this.context.getRoom(roomId); const room = this.context.client.getRoom(roomId);
// This convoluted type signature ensures we get IntelliSense *and* correct typing // This convoluted type signature ensures we get IntelliSense *and* correct typing
const newState: Partial<IRoomState> & Pick<IRoomState, any> = { const newState: Partial<IRoomState> & Pick<IRoomState, any> = {
roomId, roomId,
roomAlias: RoomViewStore.instance.getRoomAlias(), roomAlias: this.context.roomViewStore.getRoomAlias(),
roomLoading: RoomViewStore.instance.isRoomLoading(), roomLoading: this.context.roomViewStore.isRoomLoading(),
roomLoadError: RoomViewStore.instance.getRoomLoadError(), roomLoadError: this.context.roomViewStore.getRoomLoadError(),
joining: RoomViewStore.instance.isJoining(), joining: this.context.roomViewStore.isJoining(),
replyToEvent: RoomViewStore.instance.getQuotingEvent(), replyToEvent: this.context.roomViewStore.getQuotingEvent(),
// we should only peek once we have a ready client // we should only peek once we have a ready client
shouldPeek: this.state.matrixClientIsReady && RoomViewStore.instance.shouldPeek(), shouldPeek: this.state.matrixClientIsReady && this.context.roomViewStore.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: RoomViewStore.instance.getWasContextSwitch(), wasContextSwitch: this.context.roomViewStore.getWasContextSwitch(),
mainSplitContentType: room === null ? undefined : this.getMainSplitContentType(room), mainSplitContentType: room === null ? undefined : this.getMainSplitContentType(room),
initialEventId: null, // default to clearing this, will get set later in the method if needed initialEventId: null, // default to clearing this, will get set later in the method if needed
showRightPanel: RightPanelStore.instance.isOpenForRoom(roomId), showRightPanel: this.context.rightPanelStore.isOpenForRoom(roomId),
activeCall: CallStore.instance.getActiveCall(roomId), activeCall: CallStore.instance.getActiveCall(roomId),
}; };
if ( if (
this.state.mainSplitContentType !== MainSplitContentType.Timeline this.state.mainSplitContentType !== MainSplitContentType.Timeline
&& newState.mainSplitContentType === MainSplitContentType.Timeline && newState.mainSplitContentType === MainSplitContentType.Timeline
&& RightPanelStore.instance.isOpen && this.context.rightPanelStore.isOpen
&& RightPanelStore.instance.currentCard.phase === RightPanelPhases.Timeline && this.context.rightPanelStore.currentCard.phase === RightPanelPhases.Timeline
&& RightPanelStore.instance.roomPhaseHistory.some(card => (card.phase === RightPanelPhases.Timeline)) && this.context.rightPanelStore.roomPhaseHistory.some(card => (card.phase === RightPanelPhases.Timeline))
) { ) {
// We're returning to the main timeline, so hide the right panel timeline // We're returning to the main timeline, so hide the right panel timeline
RightPanelStore.instance.setCard({ phase: RightPanelPhases.RoomSummary }); this.context.rightPanelStore.setCard({ phase: RightPanelPhases.RoomSummary });
RightPanelStore.instance.togglePanel(this.state.roomId ?? null); this.context.rightPanelStore.togglePanel(this.state.roomId ?? null);
newState.showRightPanel = false; newState.showRightPanel = false;
} }
const initialEventId = RoomViewStore.instance.getInitialEventId(); const initialEventId = this.context.roomViewStore.getInitialEventId();
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
@ -600,7 +596,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
// becomes available to fetch a whole thread // becomes available to fetch a whole thread
if (!initialEvent) { if (!initialEvent) {
initialEvent = await fetchInitialEvent( initialEvent = await fetchInitialEvent(
this.context, this.context.client,
roomId, roomId,
initialEventId, initialEventId,
); );
@ -616,21 +612,21 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
action: Action.ShowThread, action: Action.ShowThread,
rootEvent: thread.rootEvent, rootEvent: thread.rootEvent,
initialEvent, initialEvent,
highlighted: RoomViewStore.instance.isInitialEventHighlighted(), highlighted: this.context.roomViewStore.isInitialEventHighlighted(),
scroll_into_view: RoomViewStore.instance.initialEventScrollIntoView(), scroll_into_view: this.context.roomViewStore.initialEventScrollIntoView(),
}); });
} else { } else {
newState.initialEventId = initialEventId; newState.initialEventId = initialEventId;
newState.isInitialEventHighlighted = RoomViewStore.instance.isInitialEventHighlighted(); newState.isInitialEventHighlighted = this.context.roomViewStore.isInitialEventHighlighted();
newState.initialEventScrollIntoView = RoomViewStore.instance.initialEventScrollIntoView(); newState.initialEventScrollIntoView = this.context.roomViewStore.initialEventScrollIntoView();
if (thread && initialEvent?.isThreadRoot) { if (thread && initialEvent?.isThreadRoot) {
dis.dispatch<ShowThreadPayload>({ dis.dispatch<ShowThreadPayload>({
action: Action.ShowThread, action: Action.ShowThread,
rootEvent: thread.rootEvent, rootEvent: thread.rootEvent,
initialEvent, initialEvent,
highlighted: RoomViewStore.instance.isInitialEventHighlighted(), highlighted: this.context.roomViewStore.isInitialEventHighlighted(),
scroll_into_view: RoomViewStore.instance.initialEventScrollIntoView(), scroll_into_view: this.context.roomViewStore.initialEventScrollIntoView(),
}); });
} }
} }
@ -657,7 +653,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
if (!initial && this.state.shouldPeek && !newState.shouldPeek) { if (!initial && this.state.shouldPeek && !newState.shouldPeek) {
// Stop peeking because we have joined this room now // Stop peeking because we have joined this room now
this.context.stopPeeking(); this.context.client.stopPeeking();
} }
// Temporary logging to diagnose https://github.com/vector-im/element-web/issues/4307 // Temporary logging to diagnose https://github.com/vector-im/element-web/issues/4307
@ -674,7 +670,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
// NB: This does assume that the roomID will not change for the lifetime of // NB: This does assume that the roomID will not change for the lifetime of
// the RoomView instance // the RoomView instance
if (initial) { if (initial) {
newState.room = this.context.getRoom(newState.roomId); newState.room = this.context.client.getRoom(newState.roomId);
if (newState.room) { if (newState.room) {
newState.showApps = this.shouldShowApps(newState.room); newState.showApps = this.shouldShowApps(newState.room);
this.onRoomLoaded(newState.room); this.onRoomLoaded(newState.room);
@ -784,7 +780,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
peekLoading: true, peekLoading: true,
isPeeking: true, // this will change to false if peeking fails isPeeking: true, // this will change to false if peeking fails
}); });
this.context.peekInRoom(roomId).then((room) => { this.context.client.peekInRoom(roomId).then((room) => {
if (this.unmounted) { if (this.unmounted) {
return; return;
} }
@ -817,7 +813,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
}); });
} else if (room) { } else if (room) {
// Stop peeking because we have joined this room previously // Stop peeking because we have joined this room previously
this.context.stopPeeking(); this.context.client.stopPeeking();
this.setState({ isPeeking: false }); this.setState({ isPeeking: false });
} }
} }
@ -835,7 +831,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
// Otherwise (in case the user set hideWidgetDrawer by clicking the button) follow the parameter. // Otherwise (in case the user set hideWidgetDrawer by clicking the button) follow the parameter.
const isManuallyShown = hideWidgetDrawer ? hideWidgetDrawer === "false": true; const isManuallyShown = hideWidgetDrawer ? hideWidgetDrawer === "false": true;
const widgets = WidgetLayoutStore.instance.getContainerWidgets(room, Container.Top); const widgets = this.context.widgetLayoutStore.getContainerWidgets(room, Container.Top);
return isManuallyShown && widgets.length > 0; return isManuallyShown && widgets.length > 0;
} }
@ -848,7 +844,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
callState: callState, callState: callState,
}); });
LegacyCallHandler.instance.on(LegacyCallHandlerEvent.CallState, this.onCallState); this.context.legacyCallHandler.on(LegacyCallHandlerEvent.CallState, this.onCallState);
window.addEventListener('beforeunload', this.onPageUnload); window.addEventListener('beforeunload', this.onPageUnload);
} }
@ -885,7 +881,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
// (We could use isMounted, but facebook have deprecated that.) // (We could use isMounted, but facebook have deprecated that.)
this.unmounted = true; this.unmounted = true;
LegacyCallHandler.instance.removeListener(LegacyCallHandlerEvent.CallState, this.onCallState); this.context.legacyCallHandler.removeListener(LegacyCallHandlerEvent.CallState, this.onCallState);
// update the scroll map before we get unmounted // update the scroll map before we get unmounted
if (this.state.roomId) { if (this.state.roomId) {
@ -893,47 +889,47 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
} }
if (this.state.shouldPeek) { if (this.state.shouldPeek) {
this.context.stopPeeking(); this.context.client.stopPeeking();
} }
// stop tracking room changes to format permalinks // stop tracking room changes to format permalinks
this.stopAllPermalinkCreators(); this.stopAllPermalinkCreators();
dis.unregister(this.dispatcherRef); dis.unregister(this.dispatcherRef);
if (this.context) { if (this.context.client) {
this.context.removeListener(ClientEvent.Room, this.onRoom); this.context.client.removeListener(ClientEvent.Room, this.onRoom);
this.context.removeListener(RoomEvent.Timeline, this.onRoomTimeline); this.context.client.removeListener(RoomEvent.Timeline, this.onRoomTimeline);
this.context.removeListener(RoomEvent.TimelineReset, this.onRoomTimelineReset); this.context.client.removeListener(RoomEvent.TimelineReset, this.onRoomTimelineReset);
this.context.removeListener(RoomEvent.Name, this.onRoomName); this.context.client.removeListener(RoomEvent.Name, this.onRoomName);
this.context.removeListener(RoomStateEvent.Events, this.onRoomStateEvents); this.context.client.removeListener(RoomStateEvent.Events, this.onRoomStateEvents);
this.context.removeListener(RoomEvent.MyMembership, this.onMyMembership); this.context.client.removeListener(RoomEvent.MyMembership, this.onMyMembership);
this.context.removeListener(RoomStateEvent.Update, this.onRoomStateUpdate); this.context.client.removeListener(RoomStateEvent.Update, this.onRoomStateUpdate);
this.context.removeListener(CryptoEvent.KeyBackupStatus, this.onKeyBackupStatus); this.context.client.removeListener(CryptoEvent.KeyBackupStatus, this.onKeyBackupStatus);
this.context.removeListener(CryptoEvent.DeviceVerificationChanged, this.onDeviceVerificationChanged); this.context.client.removeListener(CryptoEvent.DeviceVerificationChanged, this.onDeviceVerificationChanged);
this.context.removeListener(CryptoEvent.UserTrustStatusChanged, this.onUserVerificationChanged); this.context.client.removeListener(CryptoEvent.UserTrustStatusChanged, this.onUserVerificationChanged);
this.context.removeListener(CryptoEvent.KeysChanged, this.onCrossSigningKeysChanged); this.context.client.removeListener(CryptoEvent.KeysChanged, this.onCrossSigningKeysChanged);
this.context.removeListener(MatrixEventEvent.Decrypted, this.onEventDecrypted); this.context.client.removeListener(MatrixEventEvent.Decrypted, this.onEventDecrypted);
} }
window.removeEventListener('beforeunload', this.onPageUnload); window.removeEventListener('beforeunload', this.onPageUnload);
RoomViewStore.instance.off(UPDATE_EVENT, this.onRoomViewStoreUpdate); this.context.roomViewStore.off(UPDATE_EVENT, this.onRoomViewStoreUpdate);
RightPanelStore.instance.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);
WidgetStore.instance.removeListener(UPDATE_EVENT, this.onWidgetStoreUpdate); this.context.widgetStore.removeListener(UPDATE_EVENT, this.onWidgetStoreUpdate);
this.props.resizeNotifier.off("isResizing", this.onIsResizing); this.props.resizeNotifier.off("isResizing", this.onIsResizing);
if (this.state.room) { if (this.state.room) {
WidgetLayoutStore.instance.off( this.context.widgetLayoutStore.off(
WidgetLayoutStore.emissionForRoom(this.state.room), WidgetLayoutStore.emissionForRoom(this.state.room),
this.onWidgetLayoutChange, this.onWidgetLayoutChange,
); );
} }
CallStore.instance.off(CallStoreEvent.ActiveCalls, this.onActiveCalls); CallStore.instance.off(CallStoreEvent.ActiveCalls, this.onActiveCalls);
LegacyCallHandler.instance.off(LegacyCallHandlerEvent.CallState, this.onCallState); this.context.legacyCallHandler.off(LegacyCallHandlerEvent.CallState, this.onCallState);
// cancel any pending calls to the throttled updated // cancel any pending calls to the throttled updated
this.updateRoomMembers.cancel(); this.updateRoomMembers.cancel();
@ -944,13 +940,13 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
if (this.viewsLocalRoom) { if (this.viewsLocalRoom) {
// clean up if this was a local room // clean up if this was a local room
this.props.mxClient.store.removeRoom(this.state.room.roomId); this.context.client.store.removeRoom(this.state.room.roomId);
} }
} }
private onRightPanelStoreUpdate = () => { private onRightPanelStoreUpdate = () => {
this.setState({ this.setState({
showRightPanel: RightPanelStore.instance.isOpenForRoom(this.state.roomId), showRightPanel: this.context.rightPanelStore.isOpenForRoom(this.state.roomId),
}); });
}; };
@ -1017,7 +1013,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
break; break;
case 'picture_snapshot': case 'picture_snapshot':
ContentMessages.sharedInstance().sendContentListToRoom( ContentMessages.sharedInstance().sendContentListToRoom(
[payload.file], this.state.room.roomId, null, this.context); [payload.file], this.state.room.roomId, null, this.context.client);
break; break;
case 'notifier_enabled': case 'notifier_enabled':
case Action.UploadStarted: case Action.UploadStarted:
@ -1043,7 +1039,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
case 'MatrixActions.sync': case 'MatrixActions.sync':
if (!this.state.matrixClientIsReady) { if (!this.state.matrixClientIsReady) {
this.setState({ this.setState({
matrixClientIsReady: this.context?.isInitialSyncComplete(), matrixClientIsReady: this.context.client?.isInitialSyncComplete(),
}, () => { }, () => {
// send another "initial" RVS update to trigger peeking if needed // send another "initial" RVS update to trigger peeking if needed
this.onRoomViewStoreUpdate(true); this.onRoomViewStoreUpdate(true);
@ -1112,7 +1108,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
private onLocalRoomEvent(roomId: string) { private onLocalRoomEvent(roomId: string) {
if (roomId !== this.state.room.roomId) return; if (roomId !== this.state.room.roomId) return;
createRoomFromLocalRoom(this.props.mxClient, this.state.room as LocalRoom); createRoomFromLocalRoom(this.context.client, this.state.room as LocalRoom);
} }
private onRoomTimeline = (ev: MatrixEvent, room: Room | null, toStartOfTimeline: boolean, removed, data) => { private onRoomTimeline = (ev: MatrixEvent, room: Room | null, toStartOfTimeline: boolean, removed, data) => {
@ -1145,7 +1141,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
this.handleEffects(ev); this.handleEffects(ev);
} }
if (ev.getSender() !== this.context.credentials.userId) { if (ev.getSender() !== this.context.client.credentials.userId) {
// update unread count when scrolled up // update unread count when scrolled up
if (!this.state.searchResults && this.state.atEndOfLiveTimeline) { if (!this.state.searchResults && this.state.atEndOfLiveTimeline) {
// no change // no change
@ -1165,7 +1161,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
}; };
private handleEffects = (ev: MatrixEvent) => { private handleEffects = (ev: MatrixEvent) => {
const notifState = RoomNotificationStateStore.instance.getRoomState(this.state.room); const notifState = this.context.roomNotificationStateStore.getRoomState(this.state.room);
if (!notifState.isUnread) return; if (!notifState.isUnread) return;
CHAT_EFFECTS.forEach(effect => { CHAT_EFFECTS.forEach(effect => {
@ -1202,7 +1198,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
private onRoomLoaded = (room: Room) => { private onRoomLoaded = (room: Room) => {
if (this.unmounted) return; if (this.unmounted) return;
// Attach a widget store listener only when we get a room // Attach a widget store listener only when we get a room
WidgetLayoutStore.instance.on(WidgetLayoutStore.emissionForRoom(room), this.onWidgetLayoutChange); this.context.widgetLayoutStore.on(WidgetLayoutStore.emissionForRoom(room), this.onWidgetLayoutChange);
this.calculatePeekRules(room); this.calculatePeekRules(room);
this.updatePreviewUrlVisibility(room); this.updatePreviewUrlVisibility(room);
@ -1214,10 +1210,10 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
if ( if (
this.getMainSplitContentType(room) !== MainSplitContentType.Timeline this.getMainSplitContentType(room) !== MainSplitContentType.Timeline
&& RoomNotificationStateStore.instance.getRoomState(room).isUnread && this.context.roomNotificationStateStore.getRoomState(room).isUnread
) { ) {
// Automatically open the chat panel to make unread messages easier to discover // Automatically open the chat panel to make unread messages easier to discover
RightPanelStore.instance.setCard({ phase: RightPanelPhases.Timeline }, true, room.roomId); this.context.rightPanelStore.setCard({ phase: RightPanelPhases.Timeline }, true, room.roomId);
} }
this.setState({ this.setState({
@ -1244,7 +1240,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
private async loadMembersIfJoined(room: Room) { private async loadMembersIfJoined(room: Room) {
// lazy load members if enabled // lazy load members if enabled
if (this.context.hasLazyLoadMembersEnabled()) { if (this.context.client.hasLazyLoadMembersEnabled()) {
if (room && room.getMyMembership() === 'join') { if (room && room.getMyMembership() === 'join') {
try { try {
await room.loadMembersIfNeeded(); await room.loadMembersIfNeeded();
@ -1270,7 +1266,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
private updatePreviewUrlVisibility({ roomId }: Room) { private updatePreviewUrlVisibility({ roomId }: Room) {
// URL Previews in E2EE rooms can be a privacy leak so use a different setting which is per-room explicit // URL Previews in E2EE rooms can be a privacy leak so use a different setting which is per-room explicit
const key = this.context.isRoomEncrypted(roomId) ? 'urlPreviewsEnabled_e2ee' : 'urlPreviewsEnabled'; const key = this.context.client.isRoomEncrypted(roomId) ? 'urlPreviewsEnabled_e2ee' : 'urlPreviewsEnabled';
this.setState({ this.setState({
showUrlPreview: SettingsStore.getValue(key, roomId), showUrlPreview: SettingsStore.getValue(key, roomId),
}); });
@ -1283,7 +1279,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
// Detach the listener if the room is changing for some reason // Detach the listener if the room is changing for some reason
if (this.state.room) { if (this.state.room) {
WidgetLayoutStore.instance.off( this.context.widgetLayoutStore.off(
WidgetLayoutStore.emissionForRoom(this.state.room), WidgetLayoutStore.emissionForRoom(this.state.room),
this.onWidgetLayoutChange, this.onWidgetLayoutChange,
); );
@ -1320,15 +1316,15 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
}; };
private async updateE2EStatus(room: Room) { private async updateE2EStatus(room: Room) {
if (!this.context.isRoomEncrypted(room.roomId)) return; if (!this.context.client.isRoomEncrypted(room.roomId)) return;
// If crypto is not currently enabled, we aren't tracking devices at all, // If crypto is not currently enabled, we aren't tracking devices at all,
// so we don't know what the answer is. Let's error on the safe side and show // so we don't know what the answer is. Let's error on the safe side and show
// a warning for this case. // a warning for this case.
let e2eStatus = E2EStatus.Warning; let e2eStatus = E2EStatus.Warning;
if (this.context.isCryptoEnabled()) { if (this.context.client.isCryptoEnabled()) {
/* At this point, the user has encryption on and cross-signing on */ /* At this point, the user has encryption on and cross-signing on */
e2eStatus = await shieldStatusForRoom(this.context, room); e2eStatus = await shieldStatusForRoom(this.context.client, room);
} }
if (this.unmounted) return; if (this.unmounted) return;
@ -1374,7 +1370,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
private updatePermissions(room: Room) { private updatePermissions(room: Room) {
if (room) { if (room) {
const me = this.context.getUserId(); const me = this.context.client.getUserId();
const canReact = ( const canReact = (
room.getMyMembership() === "join" && room.getMyMembership() === "join" &&
room.currentState.maySendEvent(EventType.Reaction, me) room.currentState.maySendEvent(EventType.Reaction, me)
@ -1442,7 +1438,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
private onJoinButtonClicked = () => { private onJoinButtonClicked = () => {
// If the user is a ROU, allow them to transition to a PWLU // If the user is a ROU, allow them to transition to a PWLU
if (this.context?.isGuest()) { if (this.context.client?.isGuest()) {
// Join this room once the user has registered and logged in // Join this room once the user has registered and logged in
// (If we failed to peek, we may not have a valid room object.) // (If we failed to peek, we may not have a valid room object.)
dis.dispatch<DoAfterSyncPreparedPayload<ViewRoomPayload>>({ dis.dispatch<DoAfterSyncPreparedPayload<ViewRoomPayload>>({
@ -1499,13 +1495,13 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
}; };
private injectSticker(url: string, info: object, text: string, threadId: string | null) { private injectSticker(url: string, info: object, text: string, threadId: string | null) {
if (this.context.isGuest()) { if (this.context.client.isGuest()) {
dis.dispatch({ action: 'require_registration' }); dis.dispatch({ action: 'require_registration' });
return; return;
} }
ContentMessages.sharedInstance() ContentMessages.sharedInstance()
.sendStickerContentToRoom(url, this.state.room.roomId, threadId, info, text, this.context) .sendStickerContentToRoom(url, this.state.room.roomId, threadId, info, text, this.context.client)
.then(undefined, (error) => { .then(undefined, (error) => {
if (error.name === "UnknownDeviceError") { if (error.name === "UnknownDeviceError") {
// Let the staus bar handle this // Let the staus bar handle this
@ -1578,7 +1574,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
return b.length - a.length; return b.length - a.length;
}); });
if (this.context.supportsExperimentalThreads()) { if (this.context.client.supportsExperimentalThreads()) {
// Process all thread roots returned in this batch of search results // Process all thread roots returned in this batch of search results
// XXX: This won't work for results coming from Seshat which won't include the bundled relationship // XXX: This won't work for results coming from Seshat which won't include the bundled relationship
for (const result of results.results) { for (const result of results.results) {
@ -1586,7 +1582,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
const bundledRelationship = event const bundledRelationship = event
.getServerAggregatedRelation<IThreadBundledRelationship>(THREAD_RELATION_TYPE.name); .getServerAggregatedRelation<IThreadBundledRelationship>(THREAD_RELATION_TYPE.name);
if (!bundledRelationship || event.getThread()) continue; if (!bundledRelationship || event.getThread()) continue;
const room = this.context.getRoom(event.getRoomId()); const room = this.context.client.getRoom(event.getRoomId());
const thread = room.findThreadForEvent(event); const thread = room.findThreadForEvent(event);
if (thread) { if (thread) {
event.setThread(thread); event.setThread(thread);
@ -1658,7 +1654,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
const mxEv = result.context.getEvent(); const mxEv = result.context.getEvent();
const roomId = mxEv.getRoomId(); const roomId = mxEv.getRoomId();
const room = this.context.getRoom(roomId); const room = this.context.client.getRoom(roomId);
if (!room) { if (!room) {
// if we do not have the room in js-sdk stores then hide it as we cannot easily show it // if we do not have the room in js-sdk stores then hide it as we cannot easily show it
// As per the spec, an all rooms search can create this condition, // As per the spec, an all rooms search can create this condition,
@ -1715,7 +1711,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
this.setState({ this.setState({
rejecting: true, rejecting: true,
}); });
this.context.leave(this.state.roomId).then(() => { this.context.client.leave(this.state.roomId).then(() => {
dis.dispatch({ action: Action.ViewHomePage }); dis.dispatch({ action: Action.ViewHomePage });
this.setState({ this.setState({
rejecting: false, rejecting: false,
@ -1742,13 +1738,13 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
}); });
try { try {
const myMember = this.state.room.getMember(this.context.getUserId()); const myMember = this.state.room.getMember(this.context.client.getUserId());
const inviteEvent = myMember.events.member; const inviteEvent = myMember.events.member;
const ignoredUsers = this.context.getIgnoredUsers(); const ignoredUsers = this.context.client.getIgnoredUsers();
ignoredUsers.push(inviteEvent.getSender()); // de-duped internally in the js-sdk ignoredUsers.push(inviteEvent.getSender()); // de-duped internally in the js-sdk
await this.context.setIgnoredUsers(ignoredUsers); await this.context.client.setIgnoredUsers(ignoredUsers);
await this.context.leave(this.state.roomId); await this.context.client.leave(this.state.roomId);
dis.dispatch({ action: Action.ViewHomePage }); dis.dispatch({ action: Action.ViewHomePage });
this.setState({ this.setState({
rejecting: false, rejecting: false,
@ -1911,7 +1907,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
if (!this.state.room) { if (!this.state.room) {
return null; return null;
} }
return LegacyCallHandler.instance.getCallForRoom(this.state.room.roomId); return this.context.legacyCallHandler.getCallForRoom(this.state.room.roomId);
} }
// this has to be a proper method rather than an unnamed function, // this has to be a proper method rather than an unnamed function,
@ -1924,7 +1920,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
const createEvent = this.state.room.currentState.getStateEvents(EventType.RoomCreate, ""); const createEvent = this.state.room.currentState.getStateEvents(EventType.RoomCreate, "");
if (!createEvent || !createEvent.getContent()['predecessor']) return null; if (!createEvent || !createEvent.getContent()['predecessor']) return null;
return this.context.getRoom(createEvent.getContent()['predecessor']['room_id']); return this.context.client.getRoom(createEvent.getContent()['predecessor']['room_id']);
} }
getHiddenHighlightCount() { getHiddenHighlightCount() {
@ -1953,7 +1949,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
Array.from(dataTransfer.files), Array.from(dataTransfer.files),
this.state.room?.roomId ?? this.state.roomId, this.state.room?.roomId ?? this.state.roomId,
null, null,
this.context, this.context.client,
TimelineRenderingType.Room, TimelineRenderingType.Room,
); );
@ -1970,7 +1966,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
} }
private renderLocalRoomCreateLoader(): ReactElement { private renderLocalRoomCreateLoader(): ReactElement {
const names = this.state.room.getDefaultRoomName(this.props.mxClient.getUserId()); const names = this.state.room.getDefaultRoomName(this.context.client.getUserId());
return <RoomContext.Provider value={this.state}> return <RoomContext.Provider value={this.state}>
<LocalRoomCreateLoader <LocalRoomCreateLoader
names={names} names={names}
@ -2081,7 +2077,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
</ErrorBoundary> </ErrorBoundary>
); );
} else { } else {
const myUserId = this.context.credentials.userId; const myUserId = this.context.client.credentials.userId;
const myMember = this.state.room.getMember(myUserId); const myMember = this.state.room.getMember(myUserId);
const inviteEvent = myMember ? myMember.events.member : null; const inviteEvent = myMember ? myMember.events.member : null;
let inviterName = _t("Unknown"); let inviterName = _t("Unknown");
@ -2162,7 +2158,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
const showRoomUpgradeBar = ( const showRoomUpgradeBar = (
roomVersionRecommendation && roomVersionRecommendation &&
roomVersionRecommendation.needsUpgrade && roomVersionRecommendation.needsUpgrade &&
this.state.room.userMayUpgradeRoom(this.context.credentials.userId) this.state.room.userMayUpgradeRoom(this.context.client.credentials.userId)
); );
const hiddenHighlightCount = this.getHiddenHighlightCount(); const hiddenHighlightCount = this.getHiddenHighlightCount();
@ -2174,7 +2170,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
searchInProgress={this.state.searchInProgress} searchInProgress={this.state.searchInProgress}
onCancelClick={this.onCancelSearchClick} onCancelClick={this.onCancelSearchClick}
onSearch={this.onSearch} onSearch={this.onSearch}
isRoomEncrypted={this.context.isRoomEncrypted(this.state.room.roomId)} isRoomEncrypted={this.context.client.isRoomEncrypted(this.state.room.roomId)}
/>; />;
} else if (showRoomUpgradeBar) { } else if (showRoomUpgradeBar) {
aux = <RoomUpgradeWarningBar room={this.state.room} />; aux = <RoomUpgradeWarningBar room={this.state.room} />;
@ -2236,7 +2232,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
const auxPanel = ( const auxPanel = (
<AuxPanel <AuxPanel
room={this.state.room} room={this.state.room}
userId={this.context.credentials.userId} userId={this.context.client.credentials.userId}
showApps={this.state.showApps} showApps={this.state.showApps}
resizeNotifier={this.props.resizeNotifier} resizeNotifier={this.props.resizeNotifier}
> >
@ -2397,7 +2393,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
mainSplitBody = <> mainSplitBody = <>
<AppsDrawer <AppsDrawer
room={this.state.room} room={this.state.room}
userId={this.context.credentials.userId} userId={this.context.client.credentials.userId}
resizeNotifier={this.props.resizeNotifier} resizeNotifier={this.props.resizeNotifier}
showApps={true} showApps={true}
/> />
@ -2451,7 +2447,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
onAppsClick = null; onAppsClick = null;
onForgetClick = null; onForgetClick = null;
onSearchClick = null; onSearchClick = null;
if (this.state.room.canInvite(this.context.credentials.userId)) { if (this.state.room.canInvite(this.context.client.credentials.userId)) {
onInviteClick = this.onInviteClick; onInviteClick = this.onInviteClick;
} }
viewingCall = true; viewingCall = true;
@ -2493,5 +2489,4 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
} }
} }
const RoomViewWithMatrixClient = withMatrixClientHOC(RoomView); export default RoomView;
export default RoomViewWithMatrixClient;

View file

@ -60,13 +60,13 @@ import MatrixClientContext from "../../contexts/MatrixClientContext";
import { useTypedEventEmitterState } from "../../hooks/useEventEmitter"; import { useTypedEventEmitterState } from "../../hooks/useEventEmitter";
import { IOOBData } from "../../stores/ThreepidInviteStore"; import { IOOBData } from "../../stores/ThreepidInviteStore";
import { awaitRoomDownSync } from "../../utils/RoomUpgrade"; import { awaitRoomDownSync } from "../../utils/RoomUpgrade";
import { RoomViewStore } from "../../stores/RoomViewStore";
import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload"; import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
import { JoinRoomReadyPayload } from "../../dispatcher/payloads/JoinRoomReadyPayload"; import { JoinRoomReadyPayload } from "../../dispatcher/payloads/JoinRoomReadyPayload";
import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts"; import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
import { getKeyBindingsManager } from "../../KeyBindingsManager"; import { getKeyBindingsManager } from "../../KeyBindingsManager";
import { Alignment } from "../views/elements/Tooltip"; import { Alignment } from "../views/elements/Tooltip";
import { getTopic } from "../../hooks/room/useTopic"; import { getTopic } from "../../hooks/room/useTopic";
import { SdkContextClass } from "../../contexts/SDKContext";
interface IProps { interface IProps {
space: Room; space: Room;
@ -378,7 +378,7 @@ export const joinRoom = (cli: MatrixClient, hierarchy: RoomHierarchy, roomId: st
metricsTrigger: "SpaceHierarchy", metricsTrigger: "SpaceHierarchy",
}); });
}, err => { }, err => {
RoomViewStore.instance.showJoinRoomError(err, roomId); SdkContextClass.instance.roomViewStore.showJoinRoomError(err, roomId);
}); });
return prom; return prom;

View file

@ -51,10 +51,10 @@ import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
import Measured from '../views/elements/Measured'; import Measured from '../views/elements/Measured';
import PosthogTrackers from "../../PosthogTrackers"; import PosthogTrackers from "../../PosthogTrackers";
import { ButtonEvent } from "../views/elements/AccessibleButton"; import { ButtonEvent } from "../views/elements/AccessibleButton";
import { RoomViewStore } from '../../stores/RoomViewStore';
import Spinner from "../views/elements/Spinner"; import Spinner from "../views/elements/Spinner";
import { ComposerInsertPayload, ComposerType } from "../../dispatcher/payloads/ComposerInsertPayload"; import { ComposerInsertPayload, ComposerType } from "../../dispatcher/payloads/ComposerInsertPayload";
import Heading from '../views/typography/Heading'; import Heading from '../views/typography/Heading';
import { SdkContextClass } from '../../contexts/SDKContext';
interface IProps { interface IProps {
room: Room; room: Room;
@ -113,7 +113,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
room.removeListener(ThreadEvent.New, this.onNewThread); room.removeListener(ThreadEvent.New, this.onNewThread);
SettingsStore.unwatchSetting(this.layoutWatcherRef); SettingsStore.unwatchSetting(this.layoutWatcherRef);
const hasRoomChanged = RoomViewStore.instance.getRoomId() !== roomId; const hasRoomChanged = SdkContextClass.instance.roomViewStore.getRoomId() !== roomId;
if (this.props.isInitialEventHighlighted && !hasRoomChanged) { if (this.props.isInitialEventHighlighted && !hasRoomChanged) {
dis.dispatch<ViewRoomPayload>({ dis.dispatch<ViewRoomPayload>({
action: Action.ViewRoom, action: Action.ViewRoom,

View file

@ -24,13 +24,13 @@ import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { Action } from "../../../dispatcher/actions"; import { Action } from "../../../dispatcher/actions";
import { Call, ConnectionState, ElementCall } from "../../../models/Call"; import { Call, ConnectionState, ElementCall } from "../../../models/Call";
import { useCall } from "../../../hooks/useCall"; import { useCall } from "../../../hooks/useCall";
import { RoomViewStore } from "../../../stores/RoomViewStore";
import { useEventEmitterState } from "../../../hooks/useEventEmitter"; import { useEventEmitterState } from "../../../hooks/useEventEmitter";
import { import {
OwnBeaconStore, OwnBeaconStore,
OwnBeaconStoreEvent, OwnBeaconStoreEvent,
} from "../../../stores/OwnBeaconStore"; } from "../../../stores/OwnBeaconStore";
import { CallDurationFromEvent } from "../voip/CallDuration"; import { CallDurationFromEvent } from "../voip/CallDuration";
import { SdkContextClass } from "../../../contexts/SDKContext";
interface RoomCallBannerProps { interface RoomCallBannerProps {
roomId: Room["roomId"]; roomId: Room["roomId"];
@ -114,7 +114,7 @@ const RoomCallBanner: React.FC<Props> = ({ roomId }) => {
} }
// Check if the call is already showing. No banner is needed in this case. // Check if the call is already showing. No banner is needed in this case.
if (RoomViewStore.instance.isViewingCall()) { if (SdkContextClass.instance.roomViewStore.isViewingCall()) {
return null; return null;
} }

View file

@ -37,7 +37,6 @@ import Modal from "../../../Modal";
import ExportDialog from "../dialogs/ExportDialog"; import ExportDialog from "../dialogs/ExportDialog";
import { useFeatureEnabled } from "../../../hooks/useSettings"; import { useFeatureEnabled } from "../../../hooks/useSettings";
import { usePinnedEvents } from "../right_panel/PinnedMessagesCard"; import { usePinnedEvents } from "../right_panel/PinnedMessagesCard";
import { RoomViewStore } from "../../../stores/RoomViewStore";
import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases'; import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases';
import { ROOM_NOTIFICATIONS_TAB } from "../dialogs/RoomSettingsDialog"; import { ROOM_NOTIFICATIONS_TAB } from "../dialogs/RoomSettingsDialog";
import { useEventEmitterState } from "../../../hooks/useEventEmitter"; import { useEventEmitterState } from "../../../hooks/useEventEmitter";
@ -50,6 +49,7 @@ import { getKeyBindingsManager } from "../../../KeyBindingsManager";
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts"; import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import DevtoolsDialog from "../dialogs/DevtoolsDialog"; import DevtoolsDialog from "../dialogs/DevtoolsDialog";
import { SdkContextClass } from "../../../contexts/SDKContext";
interface IProps extends IContextMenuProps { interface IProps extends IContextMenuProps {
room: Room; room: Room;
@ -332,7 +332,7 @@ const RoomContextMenu = ({ room, onFinished, ...props }: IProps) => {
}; };
const ensureViewingRoom = (ev: ButtonEvent) => { const ensureViewingRoom = (ev: ButtonEvent) => {
if (RoomViewStore.instance.getRoomId() === room.roomId) return; if (SdkContextClass.instance.roomViewStore.getRoomId() === room.roomId) return;
dis.dispatch<ViewRoomPayload>({ dis.dispatch<ViewRoomPayload>({
action: Action.ViewRoom, action: Action.ViewRoom,
room_id: room.roomId, room_id: room.roomId,
@ -377,7 +377,7 @@ const RoomContextMenu = ({ room, onFinished, ...props }: IProps) => {
ev.stopPropagation(); ev.stopPropagation();
Modal.createDialog(DevtoolsDialog, { Modal.createDialog(DevtoolsDialog, {
roomId: RoomViewStore.instance.getRoomId(), roomId: SdkContextClass.instance.roomViewStore.getRoomId(),
}, "mx_DevtoolsDialog_wrapper"); }, "mx_DevtoolsDialog_wrapper");
onFinished(); onFinished();
}} }}

View file

@ -66,7 +66,7 @@ import { BreadcrumbsStore } from "../../../../stores/BreadcrumbsStore";
import { RoomNotificationState } from "../../../../stores/notifications/RoomNotificationState"; import { RoomNotificationState } from "../../../../stores/notifications/RoomNotificationState";
import { RoomNotificationStateStore } from "../../../../stores/notifications/RoomNotificationStateStore"; import { RoomNotificationStateStore } from "../../../../stores/notifications/RoomNotificationStateStore";
import { RecentAlgorithm } from "../../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm"; import { RecentAlgorithm } from "../../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm";
import { RoomViewStore } from "../../../../stores/RoomViewStore"; import { SdkContextClass } from "../../../../contexts/SDKContext";
import { getMetaSpaceName } from "../../../../stores/spaces"; import { getMetaSpaceName } from "../../../../stores/spaces";
import SpaceStore from "../../../../stores/spaces/SpaceStore"; import SpaceStore from "../../../../stores/spaces/SpaceStore";
import { DirectoryMember, Member, startDmOnFirstMessage } from "../../../../utils/direct-messages"; import { DirectoryMember, Member, startDmOnFirstMessage } from "../../../../utils/direct-messages";
@ -1060,7 +1060,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
</h4> </h4>
<div> <div>
{ BreadcrumbsStore.instance.rooms { BreadcrumbsStore.instance.rooms
.filter(r => r.roomId !== RoomViewStore.instance.getRoomId()) .filter(r => r.roomId !== SdkContextClass.instance.roomViewStore.getRoomId())
.map(room => ( .map(room => (
<TooltipOption <TooltipOption
id={`mx_SpotlightDialog_button_recentlyViewed_${room.roomId}`} id={`mx_SpotlightDialog_button_recentlyViewed_${room.roomId}`}

View file

@ -43,13 +43,13 @@ import { IApp } from "../../../stores/WidgetStore";
import { Container, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore"; import { Container, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
import { OwnProfileStore } from '../../../stores/OwnProfileStore'; import { OwnProfileStore } from '../../../stores/OwnProfileStore';
import { UPDATE_EVENT } from '../../../stores/AsyncStore'; import { UPDATE_EVENT } from '../../../stores/AsyncStore';
import { RoomViewStore } from '../../../stores/RoomViewStore';
import WidgetUtils from '../../../utils/WidgetUtils'; import WidgetUtils from '../../../utils/WidgetUtils';
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { ActionPayload } from "../../../dispatcher/payloads"; import { ActionPayload } from "../../../dispatcher/payloads";
import { Action } from '../../../dispatcher/actions'; import { Action } from '../../../dispatcher/actions';
import { ElementWidgetCapabilities } from '../../../stores/widgets/ElementWidgetCapabilities'; import { ElementWidgetCapabilities } from '../../../stores/widgets/ElementWidgetCapabilities';
import { WidgetMessagingStore } from '../../../stores/widgets/WidgetMessagingStore'; import { WidgetMessagingStore } from '../../../stores/widgets/WidgetMessagingStore';
import { SdkContextClass } from '../../../contexts/SDKContext';
interface IProps { interface IProps {
app: IApp; app: IApp;
@ -175,7 +175,7 @@ export default class AppTile extends React.Component<IProps, IState> {
); );
if (isActiveWidget) { if (isActiveWidget) {
// We just left the room that the active widget was from. // We just left the room that the active widget was from.
if (this.props.room && RoomViewStore.instance.getRoomId() !== this.props.room.roomId) { if (this.props.room && SdkContextClass.instance.roomViewStore.getRoomId() !== this.props.room.roomId) {
// If we are not actively looking at the room then destroy this widget entirely. // If we are not actively looking at the room then destroy this widget entirely.
this.endWidgetActions(); this.endWidgetActions();
} else if (WidgetType.JITSI.matches(this.props.app.type)) { } else if (WidgetType.JITSI.matches(this.props.app.type)) {

View file

@ -33,7 +33,6 @@ import dis from '../../../dispatcher/dispatcher';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import { ActionPayload } from '../../../dispatcher/payloads'; import { ActionPayload } from '../../../dispatcher/payloads';
import { Action } from '../../../dispatcher/actions'; import { Action } from '../../../dispatcher/actions';
import { RoomViewStore } from '../../../stores/RoomViewStore';
import ContentMessages from '../../../ContentMessages'; import ContentMessages from '../../../ContentMessages';
import UploadBar from '../../structures/UploadBar'; import UploadBar from '../../structures/UploadBar';
import SettingsStore from '../../../settings/SettingsStore'; import SettingsStore from '../../../settings/SettingsStore';
@ -42,6 +41,7 @@ import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import Measured from '../elements/Measured'; import Measured from '../elements/Measured';
import Heading from '../typography/Heading'; import Heading from '../typography/Heading';
import { UPDATE_EVENT } from '../../../stores/AsyncStore'; import { UPDATE_EVENT } from '../../../stores/AsyncStore';
import { SdkContextClass } from '../../../contexts/SDKContext';
interface IProps { interface IProps {
room: Room; room: Room;
@ -91,7 +91,7 @@ export default class TimelineCard extends React.Component<IProps, IState> {
} }
public componentDidMount(): void { public componentDidMount(): void {
RoomViewStore.instance.addListener(UPDATE_EVENT, this.onRoomViewStoreUpdate); SdkContextClass.instance.roomViewStore.addListener(UPDATE_EVENT, this.onRoomViewStoreUpdate);
this.dispatcherRef = dis.register(this.onAction); this.dispatcherRef = dis.register(this.onAction);
this.readReceiptsSettingWatcher = SettingsStore.watchSetting("showReadReceipts", null, (...[,,, value]) => this.readReceiptsSettingWatcher = SettingsStore.watchSetting("showReadReceipts", null, (...[,,, value]) =>
this.setState({ showReadReceipts: value as boolean }), this.setState({ showReadReceipts: value as boolean }),
@ -102,7 +102,7 @@ export default class TimelineCard extends React.Component<IProps, IState> {
} }
public componentWillUnmount(): void { public componentWillUnmount(): void {
RoomViewStore.instance.removeListener(UPDATE_EVENT, this.onRoomViewStoreUpdate); SdkContextClass.instance.roomViewStore.removeListener(UPDATE_EVENT, this.onRoomViewStoreUpdate);
if (this.readReceiptsSettingWatcher) { if (this.readReceiptsSettingWatcher) {
SettingsStore.unwatchSetting(this.readReceiptsSettingWatcher); SettingsStore.unwatchSetting(this.readReceiptsSettingWatcher);
@ -116,12 +116,9 @@ export default class TimelineCard extends React.Component<IProps, IState> {
private onRoomViewStoreUpdate = async (initial?: boolean): Promise<void> => { private onRoomViewStoreUpdate = async (initial?: boolean): Promise<void> => {
const newState: Pick<IState, any> = { const newState: Pick<IState, any> = {
// roomLoading: RoomViewStore.instance.isRoomLoading(), initialEventId: SdkContextClass.instance.roomViewStore.getInitialEventId(),
// roomLoadError: RoomViewStore.instance.getRoomLoadError(), isInitialEventHighlighted: SdkContextClass.instance.roomViewStore.isInitialEventHighlighted(),
replyToEvent: SdkContextClass.instance.roomViewStore.getQuotingEvent(),
initialEventId: RoomViewStore.instance.getInitialEventId(),
isInitialEventHighlighted: RoomViewStore.instance.isInitialEventHighlighted(),
replyToEvent: RoomViewStore.instance.getQuotingEvent(),
}; };
this.setState(newState); this.setState(newState);

View file

@ -36,7 +36,6 @@ import { _t } from '../../../languageHandler';
import DMRoomMap from '../../../utils/DMRoomMap'; import DMRoomMap from '../../../utils/DMRoomMap';
import AccessibleButton, { ButtonEvent } from '../elements/AccessibleButton'; import AccessibleButton, { ButtonEvent } from '../elements/AccessibleButton';
import SdkConfig from '../../../SdkConfig'; import SdkConfig from '../../../SdkConfig';
import { RoomViewStore } from "../../../stores/RoomViewStore";
import MultiInviter from "../../../utils/MultiInviter"; import MultiInviter from "../../../utils/MultiInviter";
import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { MatrixClientPeg } from "../../../MatrixClientPeg";
import E2EIcon from "../rooms/E2EIcon"; import E2EIcon from "../rooms/E2EIcon";
@ -77,6 +76,7 @@ import UserIdentifierCustomisations from '../../../customisations/UserIdentifier
import PosthogTrackers from "../../../PosthogTrackers"; import PosthogTrackers from "../../../PosthogTrackers";
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { DirectoryMember, startDmOnFirstMessage } from '../../../utils/direct-messages'; import { DirectoryMember, startDmOnFirstMessage } from '../../../utils/direct-messages';
import { SdkContextClass } from '../../../contexts/SDKContext';
export interface IDevice { export interface IDevice {
deviceId: string; deviceId: string;
@ -412,7 +412,7 @@ const UserOptionsSection: React.FC<{
} }
if (canInvite && (member?.membership ?? 'leave') === 'leave' && shouldShowComponent(UIComponent.InviteUsers)) { if (canInvite && (member?.membership ?? 'leave') === 'leave' && shouldShowComponent(UIComponent.InviteUsers)) {
const roomId = member && member.roomId ? member.roomId : RoomViewStore.instance.getRoomId(); const roomId = member && member.roomId ? member.roomId : SdkContextClass.instance.roomViewStore.getRoomId();
const onInviteUserButton = async (ev: ButtonEvent) => { const onInviteUserButton = async (ev: ButtonEvent) => {
try { try {
// We use a MultiInviter to re-use the invite logic, even though we're only inviting one user. // We use a MultiInviter to re-use the invite logic, even though we're only inviting one user.

View file

@ -38,7 +38,6 @@ import { ITagMap } from "../../../stores/room-list/algorithms/models";
import { DefaultTagID, TagID } from "../../../stores/room-list/models"; import { DefaultTagID, TagID } from "../../../stores/room-list/models";
import { UPDATE_EVENT } from "../../../stores/AsyncStore"; import { UPDATE_EVENT } from "../../../stores/AsyncStore";
import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore"; import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore";
import { RoomViewStore } from "../../../stores/RoomViewStore";
import { import {
isMetaSpace, isMetaSpace,
ISuggestedRoom, ISuggestedRoom,
@ -62,6 +61,7 @@ import IconizedContextMenu, {
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import ExtraTile from "./ExtraTile"; import ExtraTile from "./ExtraTile";
import RoomSublist, { IAuxButtonProps } from "./RoomSublist"; import RoomSublist, { IAuxButtonProps } from "./RoomSublist";
import { SdkContextClass } from "../../../contexts/SDKContext";
interface IProps { interface IProps {
onKeyDown: (ev: React.KeyboardEvent, state: IRovingTabIndexState) => void; onKeyDown: (ev: React.KeyboardEvent, state: IRovingTabIndexState) => void;
@ -421,7 +421,7 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
public componentDidMount(): void { public componentDidMount(): void {
this.dispatcherRef = defaultDispatcher.register(this.onAction); this.dispatcherRef = defaultDispatcher.register(this.onAction);
RoomViewStore.instance.on(UPDATE_EVENT, this.onRoomViewStoreUpdate); SdkContextClass.instance.roomViewStore.on(UPDATE_EVENT, this.onRoomViewStoreUpdate);
SpaceStore.instance.on(UPDATE_SUGGESTED_ROOMS, this.updateSuggestedRooms); SpaceStore.instance.on(UPDATE_SUGGESTED_ROOMS, this.updateSuggestedRooms);
RoomListStore.instance.on(LISTS_UPDATE_EVENT, this.updateLists); RoomListStore.instance.on(LISTS_UPDATE_EVENT, this.updateLists);
this.favouriteMessageWatcher = this.favouriteMessageWatcher =
@ -436,19 +436,19 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.updateLists); RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.updateLists);
SettingsStore.unwatchSetting(this.favouriteMessageWatcher); SettingsStore.unwatchSetting(this.favouriteMessageWatcher);
defaultDispatcher.unregister(this.dispatcherRef); defaultDispatcher.unregister(this.dispatcherRef);
RoomViewStore.instance.off(UPDATE_EVENT, this.onRoomViewStoreUpdate); SdkContextClass.instance.roomViewStore.off(UPDATE_EVENT, this.onRoomViewStoreUpdate);
} }
private onRoomViewStoreUpdate = () => { private onRoomViewStoreUpdate = () => {
this.setState({ this.setState({
currentRoomId: RoomViewStore.instance.getRoomId(), currentRoomId: SdkContextClass.instance.roomViewStore.getRoomId(),
}); });
}; };
private onAction = (payload: ActionPayload) => { private onAction = (payload: ActionPayload) => {
if (payload.action === Action.ViewRoomDelta) { if (payload.action === Action.ViewRoomDelta) {
const viewRoomDeltaPayload = payload as ViewRoomDeltaPayload; const viewRoomDeltaPayload = payload as ViewRoomDeltaPayload;
const currentRoomId = RoomViewStore.instance.getRoomId(); const currentRoomId = SdkContextClass.instance.roomViewStore.getRoomId();
const room = this.getRoomDelta(currentRoomId, viewRoomDeltaPayload.delta, viewRoomDeltaPayload.unread); const room = this.getRoomDelta(currentRoomId, viewRoomDeltaPayload.delta, viewRoomDeltaPayload.unread);
if (room) { if (room) {
defaultDispatcher.dispatch<ViewRoomPayload>({ defaultDispatcher.dispatch<ViewRoomPayload>({

View file

@ -44,10 +44,10 @@ 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";
import { RoomTileCallSummary } from "./RoomTileCallSummary"; import { RoomTileCallSummary } from "./RoomTileCallSummary";
import { RoomGeneralContextMenu } from "../context_menus/RoomGeneralContextMenu"; import { RoomGeneralContextMenu } from "../context_menus/RoomGeneralContextMenu";
import { CallStore, CallStoreEvent } from "../../../stores/CallStore"; import { CallStore, CallStoreEvent } from "../../../stores/CallStore";
import { SdkContextClass } from "../../../contexts/SDKContext";
interface IProps { interface IProps {
room: Room; room: Room;
@ -86,7 +86,7 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
super(props); super(props);
this.state = { this.state = {
selected: RoomViewStore.instance.getRoomId() === this.props.room.roomId, selected: SdkContextClass.instance.roomViewStore.getRoomId() === this.props.room.roomId,
notificationsMenuPosition: null, notificationsMenuPosition: null,
generalMenuPosition: null, generalMenuPosition: null,
call: CallStore.instance.getCall(this.props.room.roomId), call: CallStore.instance.getCall(this.props.room.roomId),
@ -146,7 +146,7 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
this.scrollIntoView(); this.scrollIntoView();
} }
RoomViewStore.instance.addRoomListener(this.props.room.roomId, this.onActiveRoomUpdate); SdkContextClass.instance.roomViewStore.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),
@ -163,7 +163,7 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
} }
public componentWillUnmount() { public componentWillUnmount() {
RoomViewStore.instance.removeRoomListener(this.props.room.roomId, this.onActiveRoomUpdate); SdkContextClass.instance.roomViewStore.removeRoomListener(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,

View file

@ -36,7 +36,7 @@ import { Icon as FavoriteIcon } from '../../../../res/img/element-icons/roomlist
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import Modal from "../../../Modal"; import Modal from "../../../Modal";
import DevtoolsDialog from "../dialogs/DevtoolsDialog"; import DevtoolsDialog from "../dialogs/DevtoolsDialog";
import { RoomViewStore } from "../../../stores/RoomViewStore"; import { SdkContextClass } from "../../../contexts/SDKContext";
const QuickSettingsButton = ({ isPanelCollapsed = false }) => { const QuickSettingsButton = ({ isPanelCollapsed = false }) => {
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu<HTMLDivElement>(); const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu<HTMLDivElement>();
@ -72,7 +72,7 @@ const QuickSettingsButton = ({ isPanelCollapsed = false }) => {
onClick={() => { onClick={() => {
closeMenu(); closeMenu();
Modal.createDialog(DevtoolsDialog, { Modal.createDialog(DevtoolsDialog, {
roomId: RoomViewStore.instance.getRoomId(), roomId: SdkContextClass.instance.roomViewStore.getRoomId(),
}, "mx_DevtoolsDialog_wrapper"); }, "mx_DevtoolsDialog_wrapper");
}} }}
kind="danger_outline" kind="danger_outline"

View file

@ -21,7 +21,6 @@ import classNames from 'classnames';
import { Room } from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import LegacyCallView from "./LegacyCallView"; import LegacyCallView from "./LegacyCallView";
import { RoomViewStore } from '../../../stores/RoomViewStore';
import LegacyCallHandler, { LegacyCallHandlerEvent } from '../../../LegacyCallHandler'; import LegacyCallHandler, { LegacyCallHandlerEvent } from '../../../LegacyCallHandler';
import PersistentApp from "../elements/PersistentApp"; import PersistentApp from "../elements/PersistentApp";
import { MatrixClientPeg } from '../../../MatrixClientPeg'; import { MatrixClientPeg } from '../../../MatrixClientPeg';
@ -34,6 +33,7 @@ import ActiveWidgetStore, { ActiveWidgetStoreEvent } from '../../../stores/Activ
import WidgetStore, { IApp } from "../../../stores/WidgetStore"; import WidgetStore, { IApp } from "../../../stores/WidgetStore";
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { UPDATE_EVENT } from '../../../stores/AsyncStore'; import { UPDATE_EVENT } from '../../../stores/AsyncStore';
import { SdkContextClass } from '../../../contexts/SDKContext';
import { CallStore } from "../../../stores/CallStore"; import { CallStore } from "../../../stores/CallStore";
import { import {
VoiceBroadcastRecording, VoiceBroadcastRecording,
@ -129,7 +129,7 @@ class PipView extends React.Component<IProps, IState> {
constructor(props: IProps) { constructor(props: IProps) {
super(props); super(props);
const roomId = RoomViewStore.instance.getRoomId(); const roomId = SdkContextClass.instance.roomViewStore.getRoomId();
const [primaryCall, secondaryCalls] = getPrimarySecondaryCallsForPip(roomId); const [primaryCall, secondaryCalls] = getPrimarySecondaryCallsForPip(roomId);
@ -147,7 +147,7 @@ class PipView extends React.Component<IProps, IState> {
public componentDidMount() { public componentDidMount() {
LegacyCallHandler.instance.addListener(LegacyCallHandlerEvent.CallChangeRoom, this.updateCalls); LegacyCallHandler.instance.addListener(LegacyCallHandlerEvent.CallChangeRoom, this.updateCalls);
LegacyCallHandler.instance.addListener(LegacyCallHandlerEvent.CallState, this.updateCalls); LegacyCallHandler.instance.addListener(LegacyCallHandlerEvent.CallState, this.updateCalls);
RoomViewStore.instance.addListener(UPDATE_EVENT, this.onRoomViewStoreUpdate); SdkContextClass.instance.roomViewStore.addListener(UPDATE_EVENT, this.onRoomViewStoreUpdate);
MatrixClientPeg.get().on(CallEvent.RemoteHoldUnhold, this.onCallRemoteHold); MatrixClientPeg.get().on(CallEvent.RemoteHoldUnhold, this.onCallRemoteHold);
const room = MatrixClientPeg.get()?.getRoom(this.state.viewedRoomId); const room = MatrixClientPeg.get()?.getRoom(this.state.viewedRoomId);
if (room) { if (room) {
@ -164,7 +164,7 @@ class PipView extends React.Component<IProps, IState> {
LegacyCallHandler.instance.removeListener(LegacyCallHandlerEvent.CallState, this.updateCalls); LegacyCallHandler.instance.removeListener(LegacyCallHandlerEvent.CallState, this.updateCalls);
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
cli?.removeListener(CallEvent.RemoteHoldUnhold, this.onCallRemoteHold); cli?.removeListener(CallEvent.RemoteHoldUnhold, this.onCallRemoteHold);
RoomViewStore.instance.removeListener(UPDATE_EVENT, this.onRoomViewStoreUpdate); SdkContextClass.instance.roomViewStore.removeListener(UPDATE_EVENT, this.onRoomViewStoreUpdate);
const room = cli?.getRoom(this.state.viewedRoomId); const room = cli?.getRoom(this.state.viewedRoomId);
if (room) { if (room) {
WidgetLayoutStore.instance.off(WidgetLayoutStore.emissionForRoom(room), this.updateCalls); WidgetLayoutStore.instance.off(WidgetLayoutStore.emissionForRoom(room), this.updateCalls);
@ -186,7 +186,7 @@ class PipView extends React.Component<IProps, IState> {
private onMove = () => this.movePersistedElement.current?.(); private onMove = () => this.movePersistedElement.current?.();
private onRoomViewStoreUpdate = () => { private onRoomViewStoreUpdate = () => {
const newRoomId = RoomViewStore.instance.getRoomId(); const newRoomId = SdkContextClass.instance.roomViewStore.getRoomId();
const oldRoomId = this.state.viewedRoomId; const oldRoomId = this.state.viewedRoomId;
if (newRoomId === oldRoomId) return; if (newRoomId === oldRoomId) return;
// The WidgetLayoutStore observer always tracks the currently viewed Room, // The WidgetLayoutStore observer always tracks the currently viewed Room,

127
src/contexts/SDKContext.ts Normal file
View file

@ -0,0 +1,127 @@
/*
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 { MatrixClient } from "matrix-js-sdk/src/matrix";
import { createContext } from "react";
import defaultDispatcher from "../dispatcher/dispatcher";
import LegacyCallHandler from "../LegacyCallHandler";
import { PosthogAnalytics } from "../PosthogAnalytics";
import { SlidingSyncManager } from "../SlidingSyncManager";
import { RoomNotificationStateStore } from "../stores/notifications/RoomNotificationStateStore";
import RightPanelStore from "../stores/right-panel/RightPanelStore";
import { RoomViewStore } from "../stores/RoomViewStore";
import SpaceStore, { SpaceStoreClass } from "../stores/spaces/SpaceStore";
import { WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore";
import WidgetStore from "../stores/WidgetStore";
export const SDKContext = createContext<SdkContextClass>(undefined);
SDKContext.displayName = "SDKContext";
/**
* A class which lazily initialises stores as and when they are requested, ensuring they remain
* as singletons scoped to this object.
*/
export class SdkContextClass {
/**
* The global SdkContextClass instance. This is a temporary measure whilst so many stores remain global
* as well. Over time, these stores should accept a `SdkContextClass` instance in their constructor.
* When all stores do this, this static variable can be deleted.
*/
public static readonly instance = new SdkContextClass();
// Optional as we don't have a client on initial load if unregistered. This should be set
// when the MatrixClient is first acquired in the dispatcher event Action.OnLoggedIn.
// It is only safe to set this once, as updating this value will NOT notify components using
// this Context.
public client?: MatrixClient;
// All protected fields to make it easier to derive test stores
protected _RightPanelStore?: RightPanelStore;
protected _RoomNotificationStateStore?: RoomNotificationStateStore;
protected _RoomViewStore?: RoomViewStore;
protected _WidgetLayoutStore?: WidgetLayoutStore;
protected _WidgetStore?: WidgetStore;
protected _PosthogAnalytics?: PosthogAnalytics;
protected _SlidingSyncManager?: SlidingSyncManager;
protected _SpaceStore?: SpaceStoreClass;
protected _LegacyCallHandler?: LegacyCallHandler;
/**
* Automatically construct stores which need to be created eagerly so they can register with
* the dispatcher.
*/
public constructEagerStores() {
this._RoomViewStore = this.roomViewStore;
}
public get legacyCallHandler(): LegacyCallHandler {
if (!this._LegacyCallHandler) {
this._LegacyCallHandler = LegacyCallHandler.instance;
}
return this._LegacyCallHandler;
}
public get rightPanelStore(): RightPanelStore {
if (!this._RightPanelStore) {
this._RightPanelStore = RightPanelStore.instance;
}
return this._RightPanelStore;
}
public get roomNotificationStateStore(): RoomNotificationStateStore {
if (!this._RoomNotificationStateStore) {
this._RoomNotificationStateStore = RoomNotificationStateStore.instance;
}
return this._RoomNotificationStateStore;
}
public get roomViewStore(): RoomViewStore {
if (!this._RoomViewStore) {
this._RoomViewStore = new RoomViewStore(
defaultDispatcher, this,
);
}
return this._RoomViewStore;
}
public get widgetLayoutStore(): WidgetLayoutStore {
if (!this._WidgetLayoutStore) {
this._WidgetLayoutStore = WidgetLayoutStore.instance;
}
return this._WidgetLayoutStore;
}
public get widgetStore(): WidgetStore {
if (!this._WidgetStore) {
this._WidgetStore = WidgetStore.instance;
}
return this._WidgetStore;
}
public get posthogAnalytics(): PosthogAnalytics {
if (!this._PosthogAnalytics) {
this._PosthogAnalytics = PosthogAnalytics.instance;
}
return this._PosthogAnalytics;
}
public get slidingSyncManager(): SlidingSyncManager {
if (!this._SlidingSyncManager) {
this._SlidingSyncManager = SlidingSyncManager.instance;
}
return this._SlidingSyncManager;
}
public get spaceStore(): SpaceStoreClass {
if (!this._SpaceStore) {
this._SpaceStore = SpaceStore.instance;
}
return this._SpaceStore;
}
}

View file

@ -17,6 +17,7 @@ limitations under the License.
*/ */
import React, { ReactNode } from "react"; import React, { ReactNode } from "react";
import * as utils from 'matrix-js-sdk/src/utils';
import { MatrixError } from "matrix-js-sdk/src/http-api"; import { MatrixError } from "matrix-js-sdk/src/http-api";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { ViewRoom as ViewRoomEvent } from "@matrix-org/analytics-events/types/typescript/ViewRoom"; import { ViewRoom as ViewRoomEvent } from "@matrix-org/analytics-events/types/typescript/ViewRoom";
@ -27,7 +28,7 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { Optional } from "matrix-events-sdk"; import { Optional } from "matrix-events-sdk";
import EventEmitter from "events"; import EventEmitter from "events";
import { defaultDispatcher, MatrixDispatcher } from '../dispatcher/dispatcher'; import { MatrixDispatcher } from '../dispatcher/dispatcher';
import { MatrixClientPeg } from '../MatrixClientPeg'; import { MatrixClientPeg } from '../MatrixClientPeg';
import Modal from '../Modal'; import Modal from '../Modal';
import { _t } from '../languageHandler'; import { _t } from '../languageHandler';
@ -35,10 +36,8 @@ import { getCachedRoomIDForAlias, storeRoomAliasInCache } from '../RoomAliasCach
import { Action } from "../dispatcher/actions"; import { Action } from "../dispatcher/actions";
import { retry } from "../utils/promise"; import { retry } from "../utils/promise";
import { TimelineRenderingType } from "../contexts/RoomContext"; import { TimelineRenderingType } from "../contexts/RoomContext";
import { PosthogAnalytics } from "../PosthogAnalytics";
import { ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload"; import { ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload";
import DMRoomMap from "../utils/DMRoomMap"; import DMRoomMap from "../utils/DMRoomMap";
import SpaceStore from "./spaces/SpaceStore";
import { isMetaSpace, MetaSpace } from "./spaces"; import { isMetaSpace, MetaSpace } from "./spaces";
import { JoinRoomPayload } from "../dispatcher/payloads/JoinRoomPayload"; import { JoinRoomPayload } from "../dispatcher/payloads/JoinRoomPayload";
import { JoinRoomReadyPayload } from "../dispatcher/payloads/JoinRoomReadyPayload"; import { JoinRoomReadyPayload } from "../dispatcher/payloads/JoinRoomReadyPayload";
@ -47,9 +46,9 @@ import { ViewRoomErrorPayload } from "../dispatcher/payloads/ViewRoomErrorPayloa
import ErrorDialog from "../components/views/dialogs/ErrorDialog"; import ErrorDialog from "../components/views/dialogs/ErrorDialog";
import { ActiveRoomChangedPayload } from "../dispatcher/payloads/ActiveRoomChangedPayload"; import { ActiveRoomChangedPayload } from "../dispatcher/payloads/ActiveRoomChangedPayload";
import SettingsStore from "../settings/SettingsStore"; import SettingsStore from "../settings/SettingsStore";
import { SlidingSyncManager } from "../SlidingSyncManager";
import { awaitRoomDownSync } from "../utils/RoomUpgrade"; import { awaitRoomDownSync } from "../utils/RoomUpgrade";
import { UPDATE_EVENT } from "./AsyncStore"; import { UPDATE_EVENT } from "./AsyncStore";
import { SdkContextClass } from "../contexts/SDKContext";
import { CallStore } from "./CallStore"; import { CallStore } from "./CallStore";
const NUM_JOIN_RETRY = 5; const NUM_JOIN_RETRY = 5;
@ -131,17 +130,16 @@ type Listener = (isActive: boolean) => void;
* A class for storing application state for RoomView. * A class for storing application state for RoomView.
*/ */
export class RoomViewStore extends EventEmitter { export class RoomViewStore extends EventEmitter {
// Important: This cannot be a dynamic getter (lazily-constructed instance) because // initialize state as a copy of the initial state. We need to copy else one RVS can talk to
// otherwise we'll miss view_room dispatches during startup, breaking relaunches of // another RVS via INITIAL_STATE as they share the same underlying object. Mostly relevant for tests.
// the app. We need to eagerly create the instance. private state = utils.deepCopy(INITIAL_STATE);
public static readonly instance = new RoomViewStore(defaultDispatcher);
private state: State = INITIAL_STATE; // initialize state
private dis: MatrixDispatcher; private dis: MatrixDispatcher;
private dispatchToken: string; private dispatchToken: string;
public constructor(dis: MatrixDispatcher) { public constructor(
dis: MatrixDispatcher, private readonly stores: SdkContextClass,
) {
super(); super();
this.resetDispatcher(dis); this.resetDispatcher(dis);
} }
@ -248,7 +246,7 @@ export class RoomViewStore extends EventEmitter {
: numMembers > 1 ? "Two" : numMembers > 1 ? "Two"
: "One"; : "One";
PosthogAnalytics.instance.trackEvent<JoinedRoomEvent>({ this.stores.posthogAnalytics.trackEvent<JoinedRoomEvent>({
eventName: "JoinedRoom", eventName: "JoinedRoom",
trigger: payload.metricsTrigger, trigger: payload.metricsTrigger,
roomSize, roomSize,
@ -291,17 +289,17 @@ export class RoomViewStore extends EventEmitter {
if (payload.metricsTrigger !== null && payload.room_id !== this.state.roomId) { if (payload.metricsTrigger !== null && payload.room_id !== this.state.roomId) {
let activeSpace: ViewRoomEvent["activeSpace"]; let activeSpace: ViewRoomEvent["activeSpace"];
if (SpaceStore.instance.activeSpace === MetaSpace.Home) { if (this.stores.spaceStore.activeSpace === MetaSpace.Home) {
activeSpace = "Home"; activeSpace = "Home";
} else if (isMetaSpace(SpaceStore.instance.activeSpace)) { } else if (isMetaSpace(this.stores.spaceStore.activeSpace)) {
activeSpace = "Meta"; activeSpace = "Meta";
} else { } else {
activeSpace = SpaceStore.instance.activeSpaceRoom.getJoinRule() === JoinRule.Public activeSpace = this.stores.spaceStore.activeSpaceRoom?.getJoinRule() === JoinRule.Public
? "Public" ? "Public"
: "Private"; : "Private";
} }
PosthogAnalytics.instance.trackEvent<ViewRoomEvent>({ this.stores.posthogAnalytics.trackEvent<ViewRoomEvent>({
eventName: "ViewRoom", eventName: "ViewRoom",
trigger: payload.metricsTrigger, trigger: payload.metricsTrigger,
viaKeyboard: payload.metricsViaKeyboard, viaKeyboard: payload.metricsViaKeyboard,
@ -314,7 +312,7 @@ export class RoomViewStore extends EventEmitter {
if (SettingsStore.getValue("feature_sliding_sync") && this.state.roomId !== payload.room_id) { if (SettingsStore.getValue("feature_sliding_sync") && this.state.roomId !== payload.room_id) {
if (this.state.subscribingRoomId && this.state.subscribingRoomId !== payload.room_id) { if (this.state.subscribingRoomId && this.state.subscribingRoomId !== payload.room_id) {
// unsubscribe from this room, but don't await it as we don't care when this gets done. // unsubscribe from this room, but don't await it as we don't care when this gets done.
SlidingSyncManager.instance.setRoomVisible(this.state.subscribingRoomId, false); this.stores.slidingSyncManager.setRoomVisible(this.state.subscribingRoomId, false);
} }
this.setState({ this.setState({
subscribingRoomId: payload.room_id, subscribingRoomId: payload.room_id,
@ -332,11 +330,11 @@ export class RoomViewStore extends EventEmitter {
}); });
// set this room as the room subscription. We need to await for it as this will fetch // set this room as the room subscription. We need to await for it as this will fetch
// all room state for this room, which is required before we get the state below. // all room state for this room, which is required before we get the state below.
await SlidingSyncManager.instance.setRoomVisible(payload.room_id, true); await this.stores.slidingSyncManager.setRoomVisible(payload.room_id, true);
// Whilst we were subscribing another room was viewed, so stop what we're doing and // Whilst we were subscribing another room was viewed, so stop what we're doing and
// unsubscribe // unsubscribe
if (this.state.subscribingRoomId !== payload.room_id) { if (this.state.subscribingRoomId !== payload.room_id) {
SlidingSyncManager.instance.setRoomVisible(payload.room_id, false); this.stores.slidingSyncManager.setRoomVisible(payload.room_id, false);
return; return;
} }
// Re-fire the payload: we won't re-process it because the prev room ID == payload room ID now // Re-fire the payload: we won't re-process it because the prev room ID == payload room ID now
@ -599,7 +597,7 @@ export class RoomViewStore extends EventEmitter {
// // Not joined // // Not joined
// } // }
// } else { // } else {
// if (RoomViewStore.instance.isJoining()) { // if (this.stores.roomViewStore.isJoining()) {
// // show spinner // // show spinner
// } else { // } else {
// // show join prompt // // show join prompt

View file

@ -34,7 +34,7 @@ import {
import { ActionPayload } from "../../dispatcher/payloads"; import { ActionPayload } from "../../dispatcher/payloads";
import { Action } from "../../dispatcher/actions"; import { Action } from "../../dispatcher/actions";
import { ActiveRoomChangedPayload } from "../../dispatcher/payloads/ActiveRoomChangedPayload"; import { ActiveRoomChangedPayload } from "../../dispatcher/payloads/ActiveRoomChangedPayload";
import { RoomViewStore } from "../RoomViewStore"; import { SdkContextClass } from "../../contexts/SDKContext";
/** /**
* A class for tracking the state of the right panel between layouts and * A class for tracking the state of the right panel between layouts and
@ -64,7 +64,7 @@ export default class RightPanelStore extends ReadyWatchingStore {
} }
protected async onReady(): Promise<any> { protected async onReady(): Promise<any> {
this.viewedRoomId = RoomViewStore.instance.getRoomId(); this.viewedRoomId = SdkContextClass.instance.roomViewStore.getRoomId();
this.matrixClient.on(CryptoEvent.VerificationRequest, this.onVerificationRequestUpdate); this.matrixClient.on(CryptoEvent.VerificationRequest, this.onVerificationRequestUpdate);
this.loadCacheFromSettings(); this.loadCacheFromSettings();
this.emitAndUpdateSettings(); this.emitAndUpdateSettings();

View file

@ -27,7 +27,6 @@ import { ActionPayload } from "../../dispatcher/payloads";
import defaultDispatcher from "../../dispatcher/dispatcher"; import defaultDispatcher from "../../dispatcher/dispatcher";
import { readReceiptChangeIsFor } from "../../utils/read-receipts"; import { readReceiptChangeIsFor } from "../../utils/read-receipts";
import { FILTER_CHANGED, IFilterCondition } from "./filters/IFilterCondition"; import { FILTER_CHANGED, IFilterCondition } from "./filters/IFilterCondition";
import { RoomViewStore } from "../RoomViewStore";
import { Algorithm, LIST_UPDATED_EVENT } from "./algorithms/Algorithm"; import { Algorithm, LIST_UPDATED_EVENT } from "./algorithms/Algorithm";
import { EffectiveMembership, getEffectiveMembership } from "../../utils/membership"; import { EffectiveMembership, getEffectiveMembership } from "../../utils/membership";
import RoomListLayoutStore from "./RoomListLayoutStore"; import RoomListLayoutStore from "./RoomListLayoutStore";
@ -40,6 +39,7 @@ import { IRoomTimelineActionPayload } from "../../actions/MatrixActionCreators";
import { RoomListStore as Interface, RoomListStoreEvent } from "./Interface"; import { RoomListStore as Interface, RoomListStoreEvent } from "./Interface";
import { SlidingRoomListStoreClass } from "./SlidingRoomListStore"; import { SlidingRoomListStoreClass } from "./SlidingRoomListStore";
import { UPDATE_EVENT } from "../AsyncStore"; import { UPDATE_EVENT } from "../AsyncStore";
import { SdkContextClass } from "../../contexts/SDKContext";
interface IState { interface IState {
// state is tracked in underlying classes // state is tracked in underlying classes
@ -105,7 +105,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> implements
this.readyStore.useUnitTestClient(forcedClient); this.readyStore.useUnitTestClient(forcedClient);
} }
RoomViewStore.instance.addListener(UPDATE_EVENT, () => this.handleRVSUpdate({})); SdkContextClass.instance.roomViewStore.addListener(UPDATE_EVENT, () => this.handleRVSUpdate({}));
this.algorithm.on(LIST_UPDATED_EVENT, this.onAlgorithmListUpdated); this.algorithm.on(LIST_UPDATED_EVENT, this.onAlgorithmListUpdated);
this.algorithm.on(FILTER_CHANGED, this.onAlgorithmFilterUpdated); this.algorithm.on(FILTER_CHANGED, this.onAlgorithmFilterUpdated);
this.setupWatchers(); this.setupWatchers();
@ -128,7 +128,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> implements
private handleRVSUpdate({ trigger = true }) { private handleRVSUpdate({ trigger = true }) {
if (!this.matrixClient) return; // We assume there won't be RVS updates without a client if (!this.matrixClient) return; // We assume there won't be RVS updates without a client
const activeRoomId = RoomViewStore.instance.getRoomId(); const activeRoomId = SdkContextClass.instance.roomViewStore.getRoomId();
if (!activeRoomId && this.algorithm.stickyRoom) { if (!activeRoomId && this.algorithm.stickyRoom) {
this.algorithm.setStickyRoom(null); this.algorithm.setStickyRoom(null);
} else if (activeRoomId) { } else if (activeRoomId) {

View file

@ -29,8 +29,8 @@ import { SlidingSyncManager } from "../../SlidingSyncManager";
import SpaceStore from "../spaces/SpaceStore"; import SpaceStore from "../spaces/SpaceStore";
import { MetaSpace, SpaceKey, UPDATE_SELECTED_SPACE } from "../spaces"; import { MetaSpace, SpaceKey, UPDATE_SELECTED_SPACE } from "../spaces";
import { LISTS_LOADING_EVENT } from "./RoomListStore"; import { LISTS_LOADING_EVENT } from "./RoomListStore";
import { RoomViewStore } from "../RoomViewStore";
import { UPDATE_EVENT } from "../AsyncStore"; import { UPDATE_EVENT } from "../AsyncStore";
import { SdkContextClass } from "../../contexts/SDKContext";
interface IState { interface IState {
// state is tracked in underlying classes // state is tracked in underlying classes
@ -207,7 +207,7 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient<IState> impl
// this room will not move due to it being viewed: it is sticky. This can be null to indicate // this room will not move due to it being viewed: it is sticky. This can be null to indicate
// no sticky room if you aren't viewing a room. // no sticky room if you aren't viewing a room.
this.stickyRoomId = RoomViewStore.instance.getRoomId(); this.stickyRoomId = SdkContextClass.instance.roomViewStore.getRoomId();
let stickyRoomNewIndex = -1; let stickyRoomNewIndex = -1;
const stickyRoomOldIndex = (tagMap[tagId] || []).findIndex((room) => { const stickyRoomOldIndex = (tagMap[tagId] || []).findIndex((room) => {
return room.roomId === this.stickyRoomId; return room.roomId === this.stickyRoomId;
@ -273,7 +273,7 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient<IState> impl
private onRoomViewStoreUpdated() { private onRoomViewStoreUpdated() {
// we only care about this to know when the user has clicked on a room to set the stickiness value // we only care about this to know when the user has clicked on a room to set the stickiness value
if (RoomViewStore.instance.getRoomId() === this.stickyRoomId) { if (SdkContextClass.instance.roomViewStore.getRoomId() === this.stickyRoomId) {
return; return;
} }
@ -303,7 +303,7 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient<IState> impl
} }
} }
// in the event we didn't call refreshOrderedLists, it helps to still remember the sticky room ID. // in the event we didn't call refreshOrderedLists, it helps to still remember the sticky room ID.
this.stickyRoomId = RoomViewStore.instance.getRoomId(); this.stickyRoomId = SdkContextClass.instance.roomViewStore.getRoomId();
if (hasUpdatedAnyList) { if (hasUpdatedAnyList) {
this.emit(LISTS_UPDATE_EVENT); this.emit(LISTS_UPDATE_EVENT);
@ -314,7 +314,7 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient<IState> impl
logger.info("SlidingRoomListStore.onReady"); logger.info("SlidingRoomListStore.onReady");
// permanent listeners: never get destroyed. Could be an issue if we want to test this in isolation. // permanent listeners: never get destroyed. Could be an issue if we want to test this in isolation.
SlidingSyncManager.instance.slidingSync.on(SlidingSyncEvent.List, this.onSlidingSyncListUpdate.bind(this)); SlidingSyncManager.instance.slidingSync.on(SlidingSyncEvent.List, this.onSlidingSyncListUpdate.bind(this));
RoomViewStore.instance.addListener(UPDATE_EVENT, this.onRoomViewStoreUpdated.bind(this)); SdkContextClass.instance.roomViewStore.addListener(UPDATE_EVENT, this.onRoomViewStoreUpdated.bind(this));
SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdated.bind(this)); SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdated.bind(this));
if (SpaceStore.instance.activeSpace) { if (SpaceStore.instance.activeSpace) {
this.onSelectedSpaceUpdated(SpaceStore.instance.activeSpace, false); this.onSelectedSpaceUpdated(SpaceStore.instance.activeSpace, false);

View file

@ -34,7 +34,6 @@ import { RoomNotificationStateStore } from "../notifications/RoomNotificationSta
import { DefaultTagID } from "../room-list/models"; import { DefaultTagID } from "../room-list/models";
import { EnhancedMap, mapDiff } from "../../utils/maps"; import { EnhancedMap, mapDiff } from "../../utils/maps";
import { setDiff, setHasDiff } from "../../utils/sets"; import { setDiff, setHasDiff } from "../../utils/sets";
import { RoomViewStore } from "../RoomViewStore";
import { Action } from "../../dispatcher/actions"; import { Action } from "../../dispatcher/actions";
import { arrayHasDiff, arrayHasOrderChange } from "../../utils/arrays"; import { arrayHasDiff, arrayHasOrderChange } from "../../utils/arrays";
import { reorderLexicographically } from "../../utils/stringOrderField"; import { reorderLexicographically } from "../../utils/stringOrderField";
@ -64,6 +63,7 @@ import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
import { ViewHomePagePayload } from "../../dispatcher/payloads/ViewHomePagePayload"; import { ViewHomePagePayload } from "../../dispatcher/payloads/ViewHomePagePayload";
import { SwitchSpacePayload } from "../../dispatcher/payloads/SwitchSpacePayload"; import { SwitchSpacePayload } from "../../dispatcher/payloads/SwitchSpacePayload";
import { AfterLeaveRoomPayload } from "../../dispatcher/payloads/AfterLeaveRoomPayload"; import { AfterLeaveRoomPayload } from "../../dispatcher/payloads/AfterLeaveRoomPayload";
import { SdkContextClass } from "../../contexts/SDKContext";
interface IState { } interface IState { }
@ -797,7 +797,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
this.updateNotificationStates(notificationStatesToUpdate); this.updateNotificationStates(notificationStatesToUpdate);
}; };
private switchSpaceIfNeeded = (roomId = RoomViewStore.instance.getRoomId()) => { private switchSpaceIfNeeded = (roomId = SdkContextClass.instance.roomViewStore.getRoomId()) => {
if (!this.isRoomInSpace(this.activeSpace, roomId) && !this.matrixClient.getRoom(roomId)?.isSpaceRoom()) { if (!this.isRoomInSpace(this.activeSpace, roomId) && !this.matrixClient.getRoom(roomId)?.isSpaceRoom()) {
this.switchToRelatedSpace(roomId); this.switchToRelatedSpace(roomId);
} }
@ -848,7 +848,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
} }
// if the room currently being viewed was just joined then switch to its related space // if the room currently being viewed was just joined then switch to its related space
if (newMembership === "join" && room.roomId === RoomViewStore.instance.getRoomId()) { if (newMembership === "join" && room.roomId === SdkContextClass.instance.roomViewStore.getRoomId()) {
this.switchSpaceIfNeeded(room.roomId); this.switchSpaceIfNeeded(room.roomId);
} }
} }
@ -875,7 +875,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
this.emit(room.roomId); this.emit(room.roomId);
} }
if (membership === "join" && room.roomId === RoomViewStore.instance.getRoomId()) { if (membership === "join" && room.roomId === SdkContextClass.instance.roomViewStore.getRoomId()) {
// if the user was looking at the space and then joined: select that space // if the user was looking at the space and then joined: select that space
this.setActiveSpace(room.roomId, false); this.setActiveSpace(room.roomId, false);
} else if (membership === "leave" && room.roomId === this.activeSpace) { } else if (membership === "leave" && room.roomId === this.activeSpace) {

View file

@ -41,7 +41,6 @@ import { ClientEvent } from "matrix-js-sdk/src/client";
import { _t } from "../../languageHandler"; import { _t } from "../../languageHandler";
import { StopGapWidgetDriver } from "./StopGapWidgetDriver"; import { StopGapWidgetDriver } from "./StopGapWidgetDriver";
import { WidgetMessagingStore } from "./WidgetMessagingStore"; import { WidgetMessagingStore } from "./WidgetMessagingStore";
import { RoomViewStore } from "../RoomViewStore";
import { MatrixClientPeg } from "../../MatrixClientPeg"; import { MatrixClientPeg } from "../../MatrixClientPeg";
import { OwnProfileStore } from "../OwnProfileStore"; import { OwnProfileStore } from "../OwnProfileStore";
import WidgetUtils from '../../utils/WidgetUtils'; import WidgetUtils from '../../utils/WidgetUtils';
@ -65,6 +64,7 @@ import { arrayFastClone } from "../../utils/arrays";
import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload"; import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
import Modal from "../../Modal"; import Modal from "../../Modal";
import ErrorDialog from "../../components/views/dialogs/ErrorDialog"; import ErrorDialog from "../../components/views/dialogs/ErrorDialog";
import { SdkContextClass } from "../../contexts/SDKContext";
import { VoiceBroadcastRecordingsStore } from "../../voice-broadcast"; import { VoiceBroadcastRecordingsStore } from "../../voice-broadcast";
// TODO: Destroy all of this code // TODO: Destroy all of this code
@ -185,7 +185,7 @@ export class StopGapWidget extends EventEmitter {
if (this.roomId) return this.roomId; if (this.roomId) return this.roomId;
return RoomViewStore.instance.getRoomId(); return SdkContextClass.instance.roomViewStore.getRoomId();
} }
public get widgetApi(): ClientWidgetApi { public get widgetApi(): ClientWidgetApi {
@ -381,7 +381,7 @@ export class StopGapWidget extends EventEmitter {
// noinspection JSIgnoredPromiseFromCall // noinspection JSIgnoredPromiseFromCall
IntegrationManagers.sharedInstance().getPrimaryManager().open( IntegrationManagers.sharedInstance().getPrimaryManager().open(
this.client.getRoom(RoomViewStore.instance.getRoomId()), this.client.getRoom(SdkContextClass.instance.roomViewStore.getRoomId()),
`type_${integType}`, `type_${integType}`,
integId, integId,
); );

View file

@ -53,9 +53,9 @@ import { CHAT_EFFECTS } from "../../effects";
import { containsEmoji } from "../../effects/utils"; import { containsEmoji } from "../../effects/utils";
import dis from "../../dispatcher/dispatcher"; import dis from "../../dispatcher/dispatcher";
import SettingsStore from "../../settings/SettingsStore"; import SettingsStore from "../../settings/SettingsStore";
import { RoomViewStore } from "../RoomViewStore";
import { ElementWidgetCapabilities } from "./ElementWidgetCapabilities"; import { ElementWidgetCapabilities } from "./ElementWidgetCapabilities";
import { navigateToPermalink } from "../../utils/permalinks/navigator"; import { navigateToPermalink } from "../../utils/permalinks/navigator";
import { SdkContextClass } from "../../contexts/SDKContext";
// TODO: Purge this from the universe // TODO: Purge this from the universe
@ -210,7 +210,7 @@ export class StopGapWidgetDriver extends WidgetDriver {
targetRoomId: string = null, targetRoomId: string = null,
): Promise<ISendEventDetails> { ): Promise<ISendEventDetails> {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
const roomId = targetRoomId || RoomViewStore.instance.getRoomId(); const roomId = targetRoomId || SdkContextClass.instance.roomViewStore.getRoomId();
if (!client || !roomId) throw new Error("Not in a room or not attached to a client"); if (!client || !roomId) throw new Error("Not in a room or not attached to a client");
@ -291,7 +291,7 @@ export class StopGapWidgetDriver extends WidgetDriver {
const targetRooms = roomIds const targetRooms = roomIds
? (roomIds.includes(Symbols.AnyRoom) ? client.getVisibleRooms() : roomIds.map(r => client.getRoom(r))) ? (roomIds.includes(Symbols.AnyRoom) ? client.getVisibleRooms() : roomIds.map(r => client.getRoom(r)))
: [client.getRoom(RoomViewStore.instance.getRoomId())]; : [client.getRoom(SdkContextClass.instance.roomViewStore.getRoomId())];
return targetRooms.filter(r => !!r); return targetRooms.filter(r => !!r);
} }
@ -430,7 +430,7 @@ export class StopGapWidgetDriver extends WidgetDriver {
): Promise<IReadEventRelationsResult> { ): Promise<IReadEventRelationsResult> {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
const dir = direction as Direction; const dir = direction as Direction;
roomId = roomId ?? RoomViewStore.instance.getRoomId() ?? undefined; roomId = roomId ?? SdkContextClass.instance.roomViewStore.getRoomId() ?? undefined;
if (typeof roomId !== "string") { if (typeof roomId !== "string") {
throw new Error('Error while reading the current room'); throw new Error('Error while reading the current room');

View file

@ -20,7 +20,6 @@ import defaultDispatcher from "../dispatcher/dispatcher";
import { ActionPayload } from "../dispatcher/payloads"; import { ActionPayload } from "../dispatcher/payloads";
import Modal from "../Modal"; import Modal from "../Modal";
import RoomSettingsDialog from "../components/views/dialogs/RoomSettingsDialog"; import RoomSettingsDialog from "../components/views/dialogs/RoomSettingsDialog";
import { RoomViewStore } from "../stores/RoomViewStore";
import ForwardDialog from "../components/views/dialogs/ForwardDialog"; import ForwardDialog from "../components/views/dialogs/ForwardDialog";
import { MatrixClientPeg } from "../MatrixClientPeg"; import { MatrixClientPeg } from "../MatrixClientPeg";
import { Action } from "../dispatcher/actions"; import { Action } from "../dispatcher/actions";
@ -32,6 +31,7 @@ import AddExistingToSpaceDialog from "../components/views/dialogs/AddExistingToS
import { ButtonEvent } from "../components/views/elements/AccessibleButton"; import { ButtonEvent } from "../components/views/elements/AccessibleButton";
import PosthogTrackers from "../PosthogTrackers"; import PosthogTrackers from "../PosthogTrackers";
import { showAddExistingSubspace, showCreateNewRoom } from "./space"; import { showAddExistingSubspace, showCreateNewRoom } from "./space";
import { SdkContextClass } from "../contexts/SDKContext";
/** /**
* Auxiliary class to listen for dialog opening over the dispatcher and * Auxiliary class to listen for dialog opening over the dispatcher and
@ -58,7 +58,7 @@ export class DialogOpener {
switch (payload.action) { switch (payload.action) {
case 'open_room_settings': case 'open_room_settings':
Modal.createDialog(RoomSettingsDialog, { Modal.createDialog(RoomSettingsDialog, {
roomId: payload.room_id || RoomViewStore.instance.getRoomId(), roomId: payload.room_id || SdkContextClass.instance.roomViewStore.getRoomId(),
initialTabId: payload.initial_tab_id, initialTabId: payload.initial_tab_id,
}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true); }, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
break; break;
@ -108,7 +108,7 @@ export class DialogOpener {
onAddSubspaceClick: () => showAddExistingSubspace(space), onAddSubspaceClick: () => showAddExistingSubspace(space),
space, space,
onFinished: (added: boolean) => { onFinished: (added: boolean) => {
if (added && RoomViewStore.instance.getRoomId() === space.roomId) { if (added && SdkContextClass.instance.roomViewStore.getRoomId() === space.roomId) {
defaultDispatcher.fire(Action.UpdateSpaceHierarchy); defaultDispatcher.fire(Action.UpdateSpaceHierarchy);
} }
}, },

View file

@ -27,7 +27,6 @@ import { _t } from "../languageHandler";
import ErrorDialog from "../components/views/dialogs/ErrorDialog"; import ErrorDialog from "../components/views/dialogs/ErrorDialog";
import { isMetaSpace } from "../stores/spaces"; import { isMetaSpace } from "../stores/spaces";
import SpaceStore from "../stores/spaces/SpaceStore"; import SpaceStore from "../stores/spaces/SpaceStore";
import { RoomViewStore } from "../stores/RoomViewStore";
import dis from "../dispatcher/dispatcher"; import dis from "../dispatcher/dispatcher";
import { ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload"; import { ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload";
import { Action } from "../dispatcher/actions"; import { Action } from "../dispatcher/actions";
@ -35,6 +34,7 @@ import { ViewHomePagePayload } from "../dispatcher/payloads/ViewHomePagePayload"
import LeaveSpaceDialog from "../components/views/dialogs/LeaveSpaceDialog"; import LeaveSpaceDialog from "../components/views/dialogs/LeaveSpaceDialog";
import { AfterLeaveRoomPayload } from "../dispatcher/payloads/AfterLeaveRoomPayload"; import { AfterLeaveRoomPayload } from "../dispatcher/payloads/AfterLeaveRoomPayload";
import { bulkSpaceBehaviour } from "./space"; import { bulkSpaceBehaviour } from "./space";
import { SdkContextClass } from "../contexts/SDKContext";
export async function leaveRoomBehaviour(roomId: string, retry = true, spinner = true) { export async function leaveRoomBehaviour(roomId: string, retry = true, spinner = true) {
let spinnerModal: IHandle<any>; let spinnerModal: IHandle<any>;
@ -130,7 +130,7 @@ export async function leaveRoomBehaviour(roomId: string, retry = true, spinner =
if (!isMetaSpace(SpaceStore.instance.activeSpace) && if (!isMetaSpace(SpaceStore.instance.activeSpace) &&
SpaceStore.instance.activeSpace !== roomId && SpaceStore.instance.activeSpace !== roomId &&
RoomViewStore.instance.getRoomId() === roomId SdkContextClass.instance.roomViewStore.getRoomId() === roomId
) { ) {
dis.dispatch<ViewRoomPayload>({ dis.dispatch<ViewRoomPayload>({
action: Action.ViewRoom, action: Action.ViewRoom,

View file

@ -30,7 +30,6 @@ import { showRoomInviteDialog } from "../RoomInvite";
import CreateSubspaceDialog from "../components/views/dialogs/CreateSubspaceDialog"; import CreateSubspaceDialog from "../components/views/dialogs/CreateSubspaceDialog";
import AddExistingSubspaceDialog from "../components/views/dialogs/AddExistingSubspaceDialog"; import AddExistingSubspaceDialog from "../components/views/dialogs/AddExistingSubspaceDialog";
import defaultDispatcher from "../dispatcher/dispatcher"; import defaultDispatcher from "../dispatcher/dispatcher";
import { RoomViewStore } from "../stores/RoomViewStore";
import { Action } from "../dispatcher/actions"; import { Action } from "../dispatcher/actions";
import Spinner from "../components/views/elements/Spinner"; import Spinner from "../components/views/elements/Spinner";
import { shouldShowComponent } from "../customisations/helpers/UIComponents"; import { shouldShowComponent } from "../customisations/helpers/UIComponents";
@ -38,6 +37,7 @@ import { UIComponent } from "../settings/UIFeature";
import { OpenSpacePreferencesPayload, SpacePreferenceTab } from "../dispatcher/payloads/OpenSpacePreferencesPayload"; import { OpenSpacePreferencesPayload, SpacePreferenceTab } from "../dispatcher/payloads/OpenSpacePreferencesPayload";
import { OpenSpaceSettingsPayload } from "../dispatcher/payloads/OpenSpaceSettingsPayload"; import { OpenSpaceSettingsPayload } from "../dispatcher/payloads/OpenSpaceSettingsPayload";
import { OpenAddExistingToSpaceDialogPayload } from "../dispatcher/payloads/OpenAddExistingToSpaceDialogPayload"; import { OpenAddExistingToSpaceDialogPayload } from "../dispatcher/payloads/OpenAddExistingToSpaceDialogPayload";
import { SdkContextClass } from "../contexts/SDKContext";
export const shouldShowSpaceSettings = (space: Room) => { export const shouldShowSpaceSettings = (space: Room) => {
const userId = space.client.getUserId(); const userId = space.client.getUserId();
@ -113,7 +113,7 @@ export const showAddExistingSubspace = (space: Room): void => {
space, space,
onCreateSubspaceClick: () => showCreateNewSubspace(space), onCreateSubspaceClick: () => showCreateNewSubspace(space),
onFinished: (added: boolean) => { onFinished: (added: boolean) => {
if (added && RoomViewStore.instance.getRoomId() === space.roomId) { if (added && SdkContextClass.instance.roomViewStore.getRoomId() === space.roomId) {
defaultDispatcher.fire(Action.UpdateSpaceHierarchy); defaultDispatcher.fire(Action.UpdateSpaceHierarchy);
} }
}, },
@ -125,7 +125,7 @@ export const showCreateNewSubspace = (space: Room): void => {
space, space,
onAddExistingSpaceClick: () => showAddExistingSubspace(space), onAddExistingSpaceClick: () => showAddExistingSubspace(space),
onFinished: (added: boolean) => { onFinished: (added: boolean) => {
if (added && RoomViewStore.instance.getRoomId() === space.roomId) { if (added && SdkContextClass.instance.roomViewStore.getRoomId() === space.roomId) {
defaultDispatcher.fire(Action.UpdateSpaceHierarchy); defaultDispatcher.fire(Action.UpdateSpaceHierarchy);
} }
}, },

View file

@ -21,9 +21,9 @@ import { Command, Commands, getCommand } from '../src/SlashCommands';
import { createTestClient } from './test-utils'; import { createTestClient } from './test-utils';
import { MatrixClientPeg } from '../src/MatrixClientPeg'; import { MatrixClientPeg } from '../src/MatrixClientPeg';
import { LocalRoom, LOCAL_ROOM_ID_PREFIX } from '../src/models/LocalRoom'; import { LocalRoom, LOCAL_ROOM_ID_PREFIX } from '../src/models/LocalRoom';
import { RoomViewStore } from '../src/stores/RoomViewStore';
import SettingsStore from '../src/settings/SettingsStore'; import SettingsStore from '../src/settings/SettingsStore';
import LegacyCallHandler from '../src/LegacyCallHandler'; import LegacyCallHandler from '../src/LegacyCallHandler';
import { SdkContextClass } from '../src/contexts/SDKContext';
describe('SlashCommands', () => { describe('SlashCommands', () => {
let client: MatrixClient; let client: MatrixClient;
@ -38,14 +38,14 @@ describe('SlashCommands', () => {
}; };
const setCurrentRoom = (): void => { const setCurrentRoom = (): void => {
mocked(RoomViewStore.instance.getRoomId).mockReturnValue(roomId); mocked(SdkContextClass.instance.roomViewStore.getRoomId).mockReturnValue(roomId);
mocked(client.getRoom).mockImplementation((rId: string): Room => { mocked(client.getRoom).mockImplementation((rId: string): Room => {
if (rId === roomId) return room; if (rId === roomId) return room;
}); });
}; };
const setCurrentLocalRoon = (): void => { const setCurrentLocalRoon = (): void => {
mocked(RoomViewStore.instance.getRoomId).mockReturnValue(localRoomId); mocked(SdkContextClass.instance.roomViewStore.getRoomId).mockReturnValue(localRoomId);
mocked(client.getRoom).mockImplementation((rId: string): Room => { mocked(client.getRoom).mockImplementation((rId: string): Room => {
if (rId === localRoomId) return localRoom; if (rId === localRoomId) return localRoom;
}); });
@ -60,7 +60,7 @@ describe('SlashCommands', () => {
room = new Room(roomId, client, client.getUserId()); room = new Room(roomId, client, client.getUserId());
localRoom = new LocalRoom(localRoomId, client, client.getUserId()); localRoom = new LocalRoom(localRoomId, client, client.getUserId());
jest.spyOn(RoomViewStore.instance, "getRoomId"); jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId");
}); });
describe('/topic', () => { describe('/topic', () => {

44
test/TestStores.ts Normal file
View file

@ -0,0 +1,44 @@
/*
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 { SdkContextClass } from "../src/contexts/SDKContext";
import { PosthogAnalytics } from "../src/PosthogAnalytics";
import { SlidingSyncManager } from "../src/SlidingSyncManager";
import { RoomNotificationStateStore } from "../src/stores/notifications/RoomNotificationStateStore";
import RightPanelStore from "../src/stores/right-panel/RightPanelStore";
import { RoomViewStore } from "../src/stores/RoomViewStore";
import { SpaceStoreClass } from "../src/stores/spaces/SpaceStore";
import { WidgetLayoutStore } from "../src/stores/widgets/WidgetLayoutStore";
import WidgetStore from "../src/stores/WidgetStore";
/**
* A class which provides the same API as Stores but adds additional unsafe setters which can
* replace individual stores. This is useful for tests which need to mock out stores.
*/
export class TestStores extends SdkContextClass {
public _RightPanelStore?: RightPanelStore;
public _RoomNotificationStateStore?: RoomNotificationStateStore;
public _RoomViewStore?: RoomViewStore;
public _WidgetLayoutStore?: WidgetLayoutStore;
public _WidgetStore?: WidgetStore;
public _PosthogAnalytics?: PosthogAnalytics;
public _SlidingSyncManager?: SlidingSyncManager;
public _SpaceStore?: SpaceStoreClass;
constructor() {
super();
}
}

View file

@ -32,17 +32,16 @@ import { defaultDispatcher } from "../../../src/dispatcher/dispatcher";
import { ViewRoomPayload } from "../../../src/dispatcher/payloads/ViewRoomPayload"; import { ViewRoomPayload } from "../../../src/dispatcher/payloads/ViewRoomPayload";
import { RoomView as _RoomView } from "../../../src/components/structures/RoomView"; import { RoomView as _RoomView } from "../../../src/components/structures/RoomView";
import ResizeNotifier from "../../../src/utils/ResizeNotifier"; import ResizeNotifier from "../../../src/utils/ResizeNotifier";
import { RoomViewStore } from "../../../src/stores/RoomViewStore";
import SettingsStore from "../../../src/settings/SettingsStore"; import SettingsStore from "../../../src/settings/SettingsStore";
import { SettingLevel } from "../../../src/settings/SettingLevel"; import { SettingLevel } from "../../../src/settings/SettingLevel";
import DMRoomMap from "../../../src/utils/DMRoomMap"; import DMRoomMap from "../../../src/utils/DMRoomMap";
import { NotificationState } from "../../../src/stores/notifications/NotificationState"; import { NotificationState } from "../../../src/stores/notifications/NotificationState";
import RightPanelStore from "../../../src/stores/right-panel/RightPanelStore";
import { RightPanelPhases } from "../../../src/stores/right-panel/RightPanelStorePhases"; import { RightPanelPhases } from "../../../src/stores/right-panel/RightPanelStorePhases";
import { LocalRoom, LocalRoomState } from "../../../src/models/LocalRoom"; import { LocalRoom, LocalRoomState } from "../../../src/models/LocalRoom";
import { DirectoryMember } from "../../../src/utils/direct-messages"; import { DirectoryMember } from "../../../src/utils/direct-messages";
import { createDmLocalRoom } from "../../../src/utils/dm/createDmLocalRoom"; import { createDmLocalRoom } from "../../../src/utils/dm/createDmLocalRoom";
import { UPDATE_EVENT } from "../../../src/stores/AsyncStore"; import { UPDATE_EVENT } from "../../../src/stores/AsyncStore";
import { SdkContextClass, SDKContext } from "../../../src/contexts/SDKContext";
const RoomView = wrapInMatrixClientContext(_RoomView); const RoomView = wrapInMatrixClientContext(_RoomView);
@ -50,6 +49,7 @@ describe("RoomView", () => {
let cli: MockedObject<MatrixClient>; let cli: MockedObject<MatrixClient>;
let room: Room; let room: Room;
let roomCount = 0; let roomCount = 0;
let stores: SdkContextClass;
beforeEach(async () => { beforeEach(async () => {
mockPlatformPeg({ reload: () => {} }); mockPlatformPeg({ reload: () => {} });
@ -64,7 +64,9 @@ describe("RoomView", () => {
room.on(RoomEvent.TimelineReset, (...args) => cli.emit(RoomEvent.TimelineReset, ...args)); room.on(RoomEvent.TimelineReset, (...args) => cli.emit(RoomEvent.TimelineReset, ...args));
DMRoomMap.makeShared(); DMRoomMap.makeShared();
RightPanelStore.instance.useUnitTestClient(cli); stores = new SdkContextClass();
stores.client = cli;
stores.rightPanelStore.useUnitTestClient(cli);
}); });
afterEach(async () => { afterEach(async () => {
@ -73,15 +75,15 @@ describe("RoomView", () => {
}); });
const mountRoomView = async (): Promise<ReactWrapper> => { const mountRoomView = async (): Promise<ReactWrapper> => {
if (RoomViewStore.instance.getRoomId() !== room.roomId) { if (stores.roomViewStore.getRoomId() !== room.roomId) {
const switchedRoom = new Promise<void>(resolve => { const switchedRoom = new Promise<void>(resolve => {
const subFn = () => { const subFn = () => {
if (RoomViewStore.instance.getRoomId()) { if (stores.roomViewStore.getRoomId()) {
RoomViewStore.instance.off(UPDATE_EVENT, subFn); stores.roomViewStore.off(UPDATE_EVENT, subFn);
resolve(); resolve();
} }
}; };
RoomViewStore.instance.on(UPDATE_EVENT, subFn); stores.roomViewStore.on(UPDATE_EVENT, subFn);
}); });
defaultDispatcher.dispatch<ViewRoomPayload>({ defaultDispatcher.dispatch<ViewRoomPayload>({
@ -94,15 +96,16 @@ describe("RoomView", () => {
} }
const roomView = mount( const roomView = mount(
<RoomView <SDKContext.Provider value={stores}>
mxClient={cli} <RoomView
threepidInvite={null} threepidInvite={null}
oobData={null} oobData={null}
resizeNotifier={new ResizeNotifier()} resizeNotifier={new ResizeNotifier()}
justCreatedOpts={null} justCreatedOpts={null}
forceTimeline={false} forceTimeline={false}
onRegistered={null} onRegistered={null}
/>, />
</SDKContext.Provider>,
); );
await act(() => Promise.resolve()); // Allow state to settle await act(() => Promise.resolve()); // Allow state to settle
return roomView; return roomView;
@ -162,14 +165,14 @@ describe("RoomView", () => {
it("normally doesn't open the chat panel", async () => { it("normally doesn't open the chat panel", async () => {
jest.spyOn(NotificationState.prototype, "isUnread", "get").mockReturnValue(false); jest.spyOn(NotificationState.prototype, "isUnread", "get").mockReturnValue(false);
await mountRoomView(); await mountRoomView();
expect(RightPanelStore.instance.isOpen).toEqual(false); expect(stores.rightPanelStore.isOpen).toEqual(false);
}); });
it("opens the chat panel if there are unread messages", async () => { it("opens the chat panel if there are unread messages", async () => {
jest.spyOn(NotificationState.prototype, "isUnread", "get").mockReturnValue(true); jest.spyOn(NotificationState.prototype, "isUnread", "get").mockReturnValue(true);
await mountRoomView(); await mountRoomView();
expect(RightPanelStore.instance.isOpen).toEqual(true); expect(stores.rightPanelStore.isOpen).toEqual(true);
expect(RightPanelStore.instance.currentCard.phase).toEqual(RightPanelPhases.Timeline); expect(stores.rightPanelStore.currentCard.phase).toEqual(RightPanelPhases.Timeline);
}); });
}); });

View file

@ -42,8 +42,8 @@ import RoomCallBanner from "../../../../src/components/views/beacon/RoomCallBann
import { CallStore } from "../../../../src/stores/CallStore"; import { CallStore } from "../../../../src/stores/CallStore";
import { WidgetMessagingStore } from "../../../../src/stores/widgets/WidgetMessagingStore"; import { WidgetMessagingStore } from "../../../../src/stores/widgets/WidgetMessagingStore";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg"; import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import { RoomViewStore } from "../../../../src/stores/RoomViewStore";
import { ConnectionState } from "../../../../src/models/Call"; import { ConnectionState } from "../../../../src/models/Call";
import { SdkContextClass } from "../../../../src/contexts/SDKContext";
describe("<RoomCallBanner />", () => { describe("<RoomCallBanner />", () => {
let client: Mocked<MatrixClient>; let client: Mocked<MatrixClient>;
@ -132,7 +132,8 @@ describe("<RoomCallBanner />", () => {
}); });
it("doesn't show banner if the call is shown", async () => { it("doesn't show banner if the call is shown", async () => {
jest.spyOn(RoomViewStore.instance, 'isViewingCall').mockReturnValue(true); jest.spyOn(SdkContextClass.instance.roomViewStore, "isViewingCall");
mocked(SdkContextClass.instance.roomViewStore.isViewingCall).mockReturnValue(true);
await renderBanner(); await renderBanner();
const banner = await screen.queryByText("Video call"); const banner = await screen.queryByText("Video call");
expect(banner).toBeFalsy(); expect(banner).toBeFalsy();

View file

@ -21,10 +21,21 @@ import { Action } from '../../src/dispatcher/actions';
import { getMockClientWithEventEmitter, untilDispatch, untilEmission } from '../test-utils'; import { getMockClientWithEventEmitter, untilDispatch, untilEmission } from '../test-utils';
import SettingsStore from '../../src/settings/SettingsStore'; import SettingsStore from '../../src/settings/SettingsStore';
import { SlidingSyncManager } from '../../src/SlidingSyncManager'; import { SlidingSyncManager } from '../../src/SlidingSyncManager';
import { PosthogAnalytics } from '../../src/PosthogAnalytics';
import { TimelineRenderingType } from '../../src/contexts/RoomContext'; import { TimelineRenderingType } from '../../src/contexts/RoomContext';
import { MatrixDispatcher } from '../../src/dispatcher/dispatcher'; import { MatrixDispatcher } from '../../src/dispatcher/dispatcher';
import { UPDATE_EVENT } from '../../src/stores/AsyncStore'; import { UPDATE_EVENT } from '../../src/stores/AsyncStore';
import { ActiveRoomChangedPayload } from '../../src/dispatcher/payloads/ActiveRoomChangedPayload'; import { ActiveRoomChangedPayload } from '../../src/dispatcher/payloads/ActiveRoomChangedPayload';
import { SpaceStoreClass } from '../../src/stores/spaces/SpaceStore';
import { TestStores } from '../TestStores';
// mock out the injected classes
jest.mock('../../src/PosthogAnalytics');
const MockPosthogAnalytics = <jest.Mock<PosthogAnalytics>><unknown>PosthogAnalytics;
jest.mock('../../src/SlidingSyncManager');
const MockSlidingSyncManager = <jest.Mock<SlidingSyncManager>><unknown>SlidingSyncManager;
jest.mock('../../src/stores/spaces/SpaceStore');
const MockSpaceStore = <jest.Mock<SpaceStoreClass>><unknown>SpaceStoreClass;
jest.mock('../../src/utils/DMRoomMap', () => { jest.mock('../../src/utils/DMRoomMap', () => {
const mock = { const mock = {
@ -51,6 +62,9 @@ describe('RoomViewStore', function() {
isGuest: jest.fn(), isGuest: jest.fn(),
}); });
const room = new Room(roomId, mockClient, userId); const room = new Room(roomId, mockClient, userId);
let roomViewStore: RoomViewStore;
let slidingSyncManager: SlidingSyncManager;
let dis: MatrixDispatcher; let dis: MatrixDispatcher;
beforeEach(function() { beforeEach(function() {
@ -60,10 +74,17 @@ describe('RoomViewStore', function() {
mockClient.getRoom.mockReturnValue(room); mockClient.getRoom.mockReturnValue(room);
mockClient.isGuest.mockReturnValue(false); mockClient.isGuest.mockReturnValue(false);
// Reset the state of the store // Make the RVS to test
dis = new MatrixDispatcher(); dis = new MatrixDispatcher();
RoomViewStore.instance.reset(); slidingSyncManager = new MockSlidingSyncManager();
RoomViewStore.instance.resetDispatcher(dis); const stores = new TestStores();
stores._SlidingSyncManager = slidingSyncManager;
stores._PosthogAnalytics = new MockPosthogAnalytics();
stores._SpaceStore = new MockSpaceStore();
roomViewStore = new RoomViewStore(
dis, stores,
);
stores._RoomViewStore = roomViewStore;
}); });
it('can be used to view a room by ID and join', async () => { it('can be used to view a room by ID and join', async () => {
@ -71,14 +92,14 @@ describe('RoomViewStore', function() {
dis.dispatch({ action: Action.JoinRoom }); dis.dispatch({ action: Action.JoinRoom });
await untilDispatch(Action.JoinRoomReady, dis); await untilDispatch(Action.JoinRoomReady, dis);
expect(mockClient.joinRoom).toHaveBeenCalledWith(roomId, { viaServers: [] }); expect(mockClient.joinRoom).toHaveBeenCalledWith(roomId, { viaServers: [] });
expect(RoomViewStore.instance.isJoining()).toBe(true); expect(roomViewStore.isJoining()).toBe(true);
}); });
it('can auto-join a room', async () => { it('can auto-join a room', async () => {
dis.dispatch({ action: Action.ViewRoom, room_id: roomId, auto_join: true }); dis.dispatch({ action: Action.ViewRoom, room_id: roomId, auto_join: true });
await untilDispatch(Action.JoinRoomReady, dis); await untilDispatch(Action.JoinRoomReady, dis);
expect(mockClient.joinRoom).toHaveBeenCalledWith(roomId, { viaServers: [] }); expect(mockClient.joinRoom).toHaveBeenCalledWith(roomId, { viaServers: [] });
expect(RoomViewStore.instance.isJoining()).toBe(true); expect(roomViewStore.isJoining()).toBe(true);
}); });
it('emits ActiveRoomChanged when the viewed room changes', async () => { it('emits ActiveRoomChanged when the viewed room changes', async () => {
@ -97,7 +118,7 @@ describe('RoomViewStore', function() {
it('invokes room activity listeners when the viewed room changes', async () => { it('invokes room activity listeners when the viewed room changes', async () => {
const roomId2 = "!roomid:2"; const roomId2 = "!roomid:2";
const callback = jest.fn(); const callback = jest.fn();
RoomViewStore.instance.addRoomListener(roomId, callback); roomViewStore.addRoomListener(roomId, callback);
dis.dispatch({ action: Action.ViewRoom, room_id: roomId }); dis.dispatch({ action: Action.ViewRoom, room_id: roomId });
await untilDispatch(Action.ActiveRoomChanged, dis) as ActiveRoomChangedPayload; await untilDispatch(Action.ActiveRoomChanged, dis) as ActiveRoomChangedPayload;
expect(callback).toHaveBeenCalledWith(true); expect(callback).toHaveBeenCalledWith(true);
@ -116,14 +137,14 @@ describe('RoomViewStore', function() {
}, dis); }, dis);
// roomId is set to id of the room alias // roomId is set to id of the room alias
expect(RoomViewStore.instance.getRoomId()).toBe(roomId); expect(roomViewStore.getRoomId()).toBe(roomId);
// join the room // join the room
dis.dispatch({ action: Action.JoinRoom }, true); dis.dispatch({ action: Action.JoinRoom }, true);
await untilDispatch(Action.JoinRoomReady, dis); await untilDispatch(Action.JoinRoomReady, dis);
expect(RoomViewStore.instance.isJoining()).toBeTruthy(); expect(roomViewStore.isJoining()).toBeTruthy();
expect(mockClient.joinRoom).toHaveBeenCalledWith(alias, { viaServers: [] }); expect(mockClient.joinRoom).toHaveBeenCalledWith(alias, { viaServers: [] });
}); });
@ -134,7 +155,7 @@ describe('RoomViewStore', function() {
const payload = await untilDispatch(Action.ViewRoomError, dis); const payload = await untilDispatch(Action.ViewRoomError, dis);
expect(payload.room_id).toBeNull(); expect(payload.room_id).toBeNull();
expect(payload.room_alias).toEqual(alias); expect(payload.room_alias).toEqual(alias);
expect(RoomViewStore.instance.getRoomAlias()).toEqual(alias); expect(roomViewStore.getRoomAlias()).toEqual(alias);
}); });
it('emits JoinRoomError if joining the room fails', async () => { it('emits JoinRoomError if joining the room fails', async () => {
@ -143,8 +164,8 @@ describe('RoomViewStore', function() {
dis.dispatch({ action: Action.ViewRoom, room_id: roomId }); dis.dispatch({ action: Action.ViewRoom, room_id: roomId });
dis.dispatch({ action: Action.JoinRoom }); dis.dispatch({ action: Action.JoinRoom });
await untilDispatch(Action.JoinRoomError, dis); await untilDispatch(Action.JoinRoomError, dis);
expect(RoomViewStore.instance.isJoining()).toBe(false); expect(roomViewStore.isJoining()).toBe(false);
expect(RoomViewStore.instance.getJoinError()).toEqual(joinErr); expect(roomViewStore.getJoinError()).toEqual(joinErr);
}); });
it('remembers the event being replied to when swapping rooms', async () => { it('remembers the event being replied to when swapping rooms', async () => {
@ -154,13 +175,13 @@ describe('RoomViewStore', function() {
getRoomId: () => roomId, getRoomId: () => roomId,
}; };
dis.dispatch({ action: 'reply_to_event', event: replyToEvent, context: TimelineRenderingType.Room }); dis.dispatch({ action: 'reply_to_event', event: replyToEvent, context: TimelineRenderingType.Room });
await untilEmission(RoomViewStore.instance, UPDATE_EVENT); await untilEmission(roomViewStore, UPDATE_EVENT);
expect(RoomViewStore.instance.getQuotingEvent()).toEqual(replyToEvent); expect(roomViewStore.getQuotingEvent()).toEqual(replyToEvent);
// view the same room, should remember the event. // view the same room, should remember the event.
// set the highlighed flag to make sure there is a state change so we get an update event // set the highlighed flag to make sure there is a state change so we get an update event
dis.dispatch({ action: Action.ViewRoom, room_id: roomId, highlighted: true }); dis.dispatch({ action: Action.ViewRoom, room_id: roomId, highlighted: true });
await untilEmission(RoomViewStore.instance, UPDATE_EVENT); await untilEmission(roomViewStore, UPDATE_EVENT);
expect(RoomViewStore.instance.getQuotingEvent()).toEqual(replyToEvent); expect(roomViewStore.getQuotingEvent()).toEqual(replyToEvent);
}); });
it('swaps to the replied event room if it is not the current room', async () => { it('swaps to the replied event room if it is not the current room', async () => {
@ -172,18 +193,18 @@ describe('RoomViewStore', function() {
}; };
dis.dispatch({ action: 'reply_to_event', event: replyToEvent, context: TimelineRenderingType.Room }); dis.dispatch({ action: 'reply_to_event', event: replyToEvent, context: TimelineRenderingType.Room });
await untilDispatch(Action.ViewRoom, dis); await untilDispatch(Action.ViewRoom, dis);
expect(RoomViewStore.instance.getQuotingEvent()).toEqual(replyToEvent); expect(roomViewStore.getQuotingEvent()).toEqual(replyToEvent);
expect(RoomViewStore.instance.getRoomId()).toEqual(roomId2); expect(roomViewStore.getRoomId()).toEqual(roomId2);
}); });
it('removes the roomId on ViewHomePage', async () => { it('removes the roomId on ViewHomePage', async () => {
dis.dispatch({ action: Action.ViewRoom, room_id: roomId }); dis.dispatch({ action: Action.ViewRoom, room_id: roomId });
await untilDispatch(Action.ActiveRoomChanged, dis); await untilDispatch(Action.ActiveRoomChanged, dis);
expect(RoomViewStore.instance.getRoomId()).toEqual(roomId); expect(roomViewStore.getRoomId()).toEqual(roomId);
dis.dispatch({ action: Action.ViewHomePage }); dis.dispatch({ action: Action.ViewHomePage });
await untilEmission(RoomViewStore.instance, UPDATE_EVENT); await untilEmission(roomViewStore, UPDATE_EVENT);
expect(RoomViewStore.instance.getRoomId()).toBeNull(); expect(roomViewStore.getRoomId()).toBeNull();
}); });
describe('Sliding Sync', function() { describe('Sliding Sync', function() {
@ -191,23 +212,22 @@ describe('RoomViewStore', function() {
jest.spyOn(SettingsStore, 'getValue').mockImplementation((settingName, roomId, value) => { jest.spyOn(SettingsStore, 'getValue').mockImplementation((settingName, roomId, value) => {
return settingName === "feature_sliding_sync"; // this is enabled, everything else is disabled. return settingName === "feature_sliding_sync"; // this is enabled, everything else is disabled.
}); });
RoomViewStore.instance.reset();
}); });
it("subscribes to the room", async () => { it("subscribes to the room", async () => {
const setRoomVisible = jest.spyOn(SlidingSyncManager.instance, "setRoomVisible").mockReturnValue( const setRoomVisible = jest.spyOn(slidingSyncManager, "setRoomVisible").mockReturnValue(
Promise.resolve(""), Promise.resolve(""),
); );
const subscribedRoomId = "!sub1:localhost"; const subscribedRoomId = "!sub1:localhost";
dis.dispatch({ action: Action.ViewRoom, room_id: subscribedRoomId }); dis.dispatch({ action: Action.ViewRoom, room_id: subscribedRoomId });
await untilDispatch(Action.ActiveRoomChanged, dis); await untilDispatch(Action.ActiveRoomChanged, dis);
expect(RoomViewStore.instance.getRoomId()).toBe(subscribedRoomId); expect(roomViewStore.getRoomId()).toBe(subscribedRoomId);
expect(setRoomVisible).toHaveBeenCalledWith(subscribedRoomId, true); expect(setRoomVisible).toHaveBeenCalledWith(subscribedRoomId, true);
}); });
// Regression test for an in-the-wild bug where rooms would rapidly switch forever in sliding sync mode // Regression test for an in-the-wild bug where rooms would rapidly switch forever in sliding sync mode
it("doesn't get stuck in a loop if you view rooms quickly", async () => { it("doesn't get stuck in a loop if you view rooms quickly", async () => {
const setRoomVisible = jest.spyOn(SlidingSyncManager.instance, "setRoomVisible").mockReturnValue( const setRoomVisible = jest.spyOn(slidingSyncManager, "setRoomVisible").mockReturnValue(
Promise.resolve(""), Promise.resolve(""),
); );
const subscribedRoomId = "!sub1:localhost"; const subscribedRoomId = "!sub1:localhost";

View file

@ -20,8 +20,8 @@ import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo";
import { Direction, MatrixEvent } from "matrix-js-sdk/src/matrix"; import { Direction, MatrixEvent } from "matrix-js-sdk/src/matrix";
import { Widget, MatrixWidgetType, WidgetKind, WidgetDriver, ITurnServer } from "matrix-widget-api"; import { Widget, MatrixWidgetType, WidgetKind, WidgetDriver, ITurnServer } from "matrix-widget-api";
import { SdkContextClass } from "../../../src/contexts/SDKContext";
import { MatrixClientPeg } from "../../../src/MatrixClientPeg"; import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
import { RoomViewStore } from "../../../src/stores/RoomViewStore";
import { StopGapWidgetDriver } from "../../../src/stores/widgets/StopGapWidgetDriver"; import { StopGapWidgetDriver } from "../../../src/stores/widgets/StopGapWidgetDriver";
import { stubClient } from "../../test-utils"; import { stubClient } from "../../test-utils";
@ -201,7 +201,7 @@ describe("StopGapWidgetDriver", () => {
beforeEach(() => { driver = mkDefaultDriver(); }); beforeEach(() => { driver = mkDefaultDriver(); });
it('reads related events from the current room', async () => { it('reads related events from the current room', async () => {
jest.spyOn(RoomViewStore.instance, 'getRoomId').mockReturnValue('!this-room-id'); jest.spyOn(SdkContextClass.instance.roomViewStore, 'getRoomId').mockReturnValue('!this-room-id');
client.relations.mockResolvedValue({ client.relations.mockResolvedValue({
originalEvent: new MatrixEvent(), originalEvent: new MatrixEvent(),