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 { decorateStartSendingTime, sendRoundTripMetric } from "./sendTimePerformanceMetrics";
import { TimelineRenderingType } from "./contexts/RoomContext";
import { RoomViewStore } from "./stores/RoomViewStore";
import { addReplyToMessageContent } from "./utils/Reply";
import ErrorDialog from "./components/views/dialogs/ErrorDialog";
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 { attachRelation } from "./components/views/rooms/SendMessageComposer";
import { doMaybeLocalRoomAction } from "./utils/local-room";
import { SdkContextClass } from "./contexts/SDKContext";
// scraped out of a macOS hidpi (5660ppm) screenshot png
// 5669 px (x-axis) , 5669 px (y-axis) , per metre
@ -361,7 +361,7 @@ export default class ContentMessages {
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
const modal = Modal.createDialog(Spinner, null, 'mx_Dialog_spinner');
await this.ensureMediaConfigFetched(matrixClient);

View file

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

View file

@ -272,12 +272,12 @@ import { logger } from "matrix-js-sdk/src/logger";
import { MatrixClientPeg } from './MatrixClientPeg';
import dis from './dispatcher/dispatcher';
import WidgetUtils from './utils/WidgetUtils';
import { RoomViewStore } from './stores/RoomViewStore';
import { _t } from './languageHandler';
import { IntegrationManagers } from "./integrations/IntegrationManagers";
import { WidgetType } from "./widgets/WidgetType";
import { objectClone } from "./utils/objects";
import { EffectiveMembership, getEffectiveMembership } from './utils/membership';
import { SdkContextClass } from './contexts/SDKContext';
enum Action {
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 }));
return;
}

View file

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

View file

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

View file

@ -44,21 +44,18 @@ import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks';
import ResizeNotifier from '../../utils/ResizeNotifier';
import ContentMessages from '../../ContentMessages';
import Modal from '../../Modal';
import LegacyCallHandler, { LegacyCallHandlerEvent } from '../../LegacyCallHandler';
import { LegacyCallHandlerEvent } from '../../LegacyCallHandler';
import dis, { defaultDispatcher } from '../../dispatcher/dispatcher';
import * as Rooms from '../../Rooms';
import eventSearch, { searchPagination } from '../../Searching';
import MainSplit from './MainSplit';
import RightPanel from './RightPanel';
import { RoomViewStore } from '../../stores/RoomViewStore';
import RoomScrollStateStore, { ScrollState } from '../../stores/RoomScrollStateStore';
import WidgetEchoStore from '../../stores/WidgetEchoStore';
import SettingsStore from "../../settings/SettingsStore";
import { Layout } from "../../settings/enums/Layout";
import AccessibleButton from "../views/elements/AccessibleButton";
import RightPanelStore from "../../stores/right-panel/RightPanelStore";
import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
import MatrixClientContext, { MatrixClientProps, withMatrixClientHOC } from "../../contexts/MatrixClientContext";
import { E2EStatus, shieldStatusForRoom } from '../../utils/ShieldUtils';
import { Action } from "../../dispatcher/actions";
import { IMatrixClientCreds } from "../../MatrixClientPeg";
@ -76,12 +73,10 @@ import { IOOBData, IThreepidInvite } from "../../stores/ThreepidInviteStore";
import EffectsOverlay from "../views/elements/EffectsOverlay";
import { containsEmoji } from '../../effects/utils';
import { CHAT_EFFECTS } from '../../effects';
import WidgetStore from "../../stores/WidgetStore";
import { CallView } from "../views/voip/CallView";
import { UPDATE_EVENT } from "../../stores/AsyncStore";
import Notifier from "../../Notifier";
import { showToast as showNotificationsToast } from "../../toasts/DesktopNotificationsToast";
import { RoomNotificationStateStore } from "../../stores/notifications/RoomNotificationStateStore";
import { Container, WidgetLayoutStore } from "../../stores/widgets/WidgetLayoutStore";
import { getKeyBindingsManager } from '../../KeyBindingsManager';
import { objectHasDiff } from "../../utils/objects";
@ -120,6 +115,7 @@ import { RoomStatusBarUnsentMessages } from './RoomStatusBarUnsentMessages';
import { LargeLoader } from './LargeLoader';
import { VoiceBroadcastInfoEventType } from '../../voice-broadcast';
import { isVideoRoom } from '../../utils/video-rooms';
import { SDKContext } from '../../contexts/SDKContext';
import { CallStore, CallStoreEvent } from "../../stores/CallStore";
import { Call } from "../../models/Call";
@ -133,7 +129,7 @@ if (DEBUG) {
debuglog = logger.log.bind(console);
}
interface IRoomProps extends MatrixClientProps {
interface IRoomProps {
threepidInvite: IThreepidInvite;
oobData?: IOOBData;
@ -381,13 +377,13 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
private messagePanel: TimelinePanel;
private roomViewBody = createRef<HTMLDivElement>();
static contextType = MatrixClientContext;
public context!: React.ContextType<typeof MatrixClientContext>;
static contextType = SDKContext;
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);
const llMembers = context.hasLazyLoadMembersEnabled();
const llMembers = context.client.hasLazyLoadMembersEnabled();
this.state = {
roomId: null,
roomLoading: true,
@ -422,7 +418,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
showJoinLeaves: true,
showAvatarChanges: true,
showDisplaynameChanges: true,
matrixClientIsReady: context?.isInitialSyncComplete(),
matrixClientIsReady: context.client?.isInitialSyncComplete(),
mainSplitContentType: MainSplitContentType.Timeline,
timelineRenderingType: TimelineRenderingType.Room,
liveTimeline: undefined,
@ -430,25 +426,25 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
};
this.dispatcherRef = dis.register(this.onAction);
context.on(ClientEvent.Room, this.onRoom);
context.on(RoomEvent.Timeline, this.onRoomTimeline);
context.on(RoomEvent.TimelineReset, this.onRoomTimelineReset);
context.on(RoomEvent.Name, this.onRoomName);
context.on(RoomStateEvent.Events, this.onRoomStateEvents);
context.on(RoomStateEvent.Update, this.onRoomStateUpdate);
context.on(RoomEvent.MyMembership, this.onMyMembership);
context.on(CryptoEvent.KeyBackupStatus, this.onKeyBackupStatus);
context.on(CryptoEvent.DeviceVerificationChanged, this.onDeviceVerificationChanged);
context.on(CryptoEvent.UserTrustStatusChanged, this.onUserVerificationChanged);
context.on(CryptoEvent.KeysChanged, this.onCrossSigningKeysChanged);
context.on(MatrixEventEvent.Decrypted, this.onEventDecrypted);
context.client.on(ClientEvent.Room, this.onRoom);
context.client.on(RoomEvent.Timeline, this.onRoomTimeline);
context.client.on(RoomEvent.TimelineReset, this.onRoomTimelineReset);
context.client.on(RoomEvent.Name, this.onRoomName);
context.client.on(RoomStateEvent.Events, this.onRoomStateEvents);
context.client.on(RoomStateEvent.Update, this.onRoomStateUpdate);
context.client.on(RoomEvent.MyMembership, this.onMyMembership);
context.client.on(CryptoEvent.KeyBackupStatus, this.onKeyBackupStatus);
context.client.on(CryptoEvent.DeviceVerificationChanged, this.onDeviceVerificationChanged);
context.client.on(CryptoEvent.UserTrustStatusChanged, this.onUserVerificationChanged);
context.client.on(CryptoEvent.KeysChanged, this.onCrossSigningKeysChanged);
context.client.on(MatrixEventEvent.Decrypted, this.onEventDecrypted);
// 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);
WidgetStore.instance.on(UPDATE_EVENT, this.onWidgetStoreUpdate);
context.widgetStore.on(UPDATE_EVENT, this.onWidgetStoreUpdate);
CallStore.instance.on(CallStoreEvent.ActiveCalls, this.onActiveCalls);
@ -501,16 +497,16 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
action: "appsDrawer",
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
RightPanelStore.instance.setCard({ phase: RightPanelPhases.Timeline });
this.context.rightPanelStore.setCard({ phase: RightPanelPhases.Timeline });
}
this.checkWidgets(this.state.room);
};
private checkWidgets = (room: Room): void => {
this.setState({
hasPinnedWidgets: WidgetLayoutStore.instance.hasPinnedWidgets(room),
hasPinnedWidgets: this.context.widgetLayoutStore.hasPinnedWidgets(room),
mainSplitContentType: this.getMainSplitContentType(room),
showApps: this.shouldShowApps(room),
});
@ -518,12 +514,12 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
private getMainSplitContentType = (room: Room) => {
if (
(SettingsStore.getValue("feature_group_calls") && RoomViewStore.instance.isViewingCall())
(SettingsStore.getValue("feature_group_calls") && this.context.roomViewStore.isViewingCall())
|| isVideoRoom(room)
) {
return MainSplitContentType.Call;
}
if (WidgetLayoutStore.instance.hasMaximisedWidget(room)) {
if (this.context.widgetLayoutStore.hasMaximisedWidget(room)) {
return MainSplitContentType.MaximisedWidget;
}
return MainSplitContentType.Timeline;
@ -534,7 +530,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
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
// is being viewed: instead it should just be re-mounted when
// switching rooms. Therefore, if the room ID changes, we
@ -549,45 +545,45 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
return;
}
const roomId = RoomViewStore.instance.getRoomId();
const room = this.context.getRoom(roomId);
const roomId = this.context.roomViewStore.getRoomId();
const room = this.context.client.getRoom(roomId);
// This convoluted type signature ensures we get IntelliSense *and* correct typing
const newState: Partial<IRoomState> & Pick<IRoomState, any> = {
roomId,
roomAlias: RoomViewStore.instance.getRoomAlias(),
roomLoading: RoomViewStore.instance.isRoomLoading(),
roomLoadError: RoomViewStore.instance.getRoomLoadError(),
joining: RoomViewStore.instance.isJoining(),
replyToEvent: RoomViewStore.instance.getQuotingEvent(),
roomAlias: this.context.roomViewStore.getRoomAlias(),
roomLoading: this.context.roomViewStore.isRoomLoading(),
roomLoadError: this.context.roomViewStore.getRoomLoadError(),
joining: this.context.roomViewStore.isJoining(),
replyToEvent: this.context.roomViewStore.getQuotingEvent(),
// 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),
showRedactions: SettingsStore.getValue("showRedactions", roomId),
showJoinLeaves: SettingsStore.getValue("showJoinLeaves", roomId),
showAvatarChanges: SettingsStore.getValue("showAvatarChanges", roomId),
showDisplaynameChanges: SettingsStore.getValue("showDisplaynameChanges", roomId),
wasContextSwitch: RoomViewStore.instance.getWasContextSwitch(),
wasContextSwitch: this.context.roomViewStore.getWasContextSwitch(),
mainSplitContentType: room === null ? undefined : this.getMainSplitContentType(room),
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),
};
if (
this.state.mainSplitContentType !== MainSplitContentType.Timeline
&& newState.mainSplitContentType === MainSplitContentType.Timeline
&& RightPanelStore.instance.isOpen
&& RightPanelStore.instance.currentCard.phase === RightPanelPhases.Timeline
&& RightPanelStore.instance.roomPhaseHistory.some(card => (card.phase === RightPanelPhases.Timeline))
&& this.context.rightPanelStore.isOpen
&& this.context.rightPanelStore.currentCard.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
RightPanelStore.instance.setCard({ phase: RightPanelPhases.RoomSummary });
RightPanelStore.instance.togglePanel(this.state.roomId ?? null);
this.context.rightPanelStore.setCard({ phase: RightPanelPhases.RoomSummary });
this.context.rightPanelStore.togglePanel(this.state.roomId ?? null);
newState.showRightPanel = false;
}
const initialEventId = RoomViewStore.instance.getInitialEventId();
const initialEventId = this.context.roomViewStore.getInitialEventId();
if (initialEventId) {
let initialEvent = room?.findEventById(initialEventId);
// 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
if (!initialEvent) {
initialEvent = await fetchInitialEvent(
this.context,
this.context.client,
roomId,
initialEventId,
);
@ -616,21 +612,21 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
action: Action.ShowThread,
rootEvent: thread.rootEvent,
initialEvent,
highlighted: RoomViewStore.instance.isInitialEventHighlighted(),
scroll_into_view: RoomViewStore.instance.initialEventScrollIntoView(),
highlighted: this.context.roomViewStore.isInitialEventHighlighted(),
scroll_into_view: this.context.roomViewStore.initialEventScrollIntoView(),
});
} else {
newState.initialEventId = initialEventId;
newState.isInitialEventHighlighted = RoomViewStore.instance.isInitialEventHighlighted();
newState.initialEventScrollIntoView = RoomViewStore.instance.initialEventScrollIntoView();
newState.isInitialEventHighlighted = this.context.roomViewStore.isInitialEventHighlighted();
newState.initialEventScrollIntoView = this.context.roomViewStore.initialEventScrollIntoView();
if (thread && initialEvent?.isThreadRoot) {
dis.dispatch<ShowThreadPayload>({
action: Action.ShowThread,
rootEvent: thread.rootEvent,
initialEvent,
highlighted: RoomViewStore.instance.isInitialEventHighlighted(),
scroll_into_view: RoomViewStore.instance.initialEventScrollIntoView(),
highlighted: this.context.roomViewStore.isInitialEventHighlighted(),
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) {
// 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
@ -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
// the RoomView instance
if (initial) {
newState.room = this.context.getRoom(newState.roomId);
newState.room = this.context.client.getRoom(newState.roomId);
if (newState.room) {
newState.showApps = this.shouldShowApps(newState.room);
this.onRoomLoaded(newState.room);
@ -784,7 +780,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
peekLoading: true,
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) {
return;
}
@ -817,7 +813,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
});
} else if (room) {
// Stop peeking because we have joined this room previously
this.context.stopPeeking();
this.context.client.stopPeeking();
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.
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;
}
@ -848,7 +844,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
callState: callState,
});
LegacyCallHandler.instance.on(LegacyCallHandlerEvent.CallState, this.onCallState);
this.context.legacyCallHandler.on(LegacyCallHandlerEvent.CallState, this.onCallState);
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.)
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
if (this.state.roomId) {
@ -893,47 +889,47 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
}
if (this.state.shouldPeek) {
this.context.stopPeeking();
this.context.client.stopPeeking();
}
// stop tracking room changes to format permalinks
this.stopAllPermalinkCreators();
dis.unregister(this.dispatcherRef);
if (this.context) {
this.context.removeListener(ClientEvent.Room, this.onRoom);
this.context.removeListener(RoomEvent.Timeline, this.onRoomTimeline);
this.context.removeListener(RoomEvent.TimelineReset, this.onRoomTimelineReset);
this.context.removeListener(RoomEvent.Name, this.onRoomName);
this.context.removeListener(RoomStateEvent.Events, this.onRoomStateEvents);
this.context.removeListener(RoomEvent.MyMembership, this.onMyMembership);
this.context.removeListener(RoomStateEvent.Update, this.onRoomStateUpdate);
this.context.removeListener(CryptoEvent.KeyBackupStatus, this.onKeyBackupStatus);
this.context.removeListener(CryptoEvent.DeviceVerificationChanged, this.onDeviceVerificationChanged);
this.context.removeListener(CryptoEvent.UserTrustStatusChanged, this.onUserVerificationChanged);
this.context.removeListener(CryptoEvent.KeysChanged, this.onCrossSigningKeysChanged);
this.context.removeListener(MatrixEventEvent.Decrypted, this.onEventDecrypted);
if (this.context.client) {
this.context.client.removeListener(ClientEvent.Room, this.onRoom);
this.context.client.removeListener(RoomEvent.Timeline, this.onRoomTimeline);
this.context.client.removeListener(RoomEvent.TimelineReset, this.onRoomTimelineReset);
this.context.client.removeListener(RoomEvent.Name, this.onRoomName);
this.context.client.removeListener(RoomStateEvent.Events, this.onRoomStateEvents);
this.context.client.removeListener(RoomEvent.MyMembership, this.onMyMembership);
this.context.client.removeListener(RoomStateEvent.Update, this.onRoomStateUpdate);
this.context.client.removeListener(CryptoEvent.KeyBackupStatus, this.onKeyBackupStatus);
this.context.client.removeListener(CryptoEvent.DeviceVerificationChanged, this.onDeviceVerificationChanged);
this.context.client.removeListener(CryptoEvent.UserTrustStatusChanged, this.onUserVerificationChanged);
this.context.client.removeListener(CryptoEvent.KeysChanged, this.onCrossSigningKeysChanged);
this.context.client.removeListener(MatrixEventEvent.Decrypted, this.onEventDecrypted);
}
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);
WidgetStore.instance.removeListener(UPDATE_EVENT, this.onWidgetStoreUpdate);
this.context.widgetStore.removeListener(UPDATE_EVENT, this.onWidgetStoreUpdate);
this.props.resizeNotifier.off("isResizing", this.onIsResizing);
if (this.state.room) {
WidgetLayoutStore.instance.off(
this.context.widgetLayoutStore.off(
WidgetLayoutStore.emissionForRoom(this.state.room),
this.onWidgetLayoutChange,
);
}
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
this.updateRoomMembers.cancel();
@ -944,13 +940,13 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
if (this.viewsLocalRoom) {
// 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 = () => {
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;
case 'picture_snapshot':
ContentMessages.sharedInstance().sendContentListToRoom(
[payload.file], this.state.room.roomId, null, this.context);
[payload.file], this.state.room.roomId, null, this.context.client);
break;
case 'notifier_enabled':
case Action.UploadStarted:
@ -1043,7 +1039,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
case 'MatrixActions.sync':
if (!this.state.matrixClientIsReady) {
this.setState({
matrixClientIsReady: this.context?.isInitialSyncComplete(),
matrixClientIsReady: this.context.client?.isInitialSyncComplete(),
}, () => {
// send another "initial" RVS update to trigger peeking if needed
this.onRoomViewStoreUpdate(true);
@ -1112,7 +1108,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
private onLocalRoomEvent(roomId: string) {
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) => {
@ -1145,7 +1141,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
this.handleEffects(ev);
}
if (ev.getSender() !== this.context.credentials.userId) {
if (ev.getSender() !== this.context.client.credentials.userId) {
// update unread count when scrolled up
if (!this.state.searchResults && this.state.atEndOfLiveTimeline) {
// no change
@ -1165,7 +1161,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
};
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;
CHAT_EFFECTS.forEach(effect => {
@ -1202,7 +1198,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
private onRoomLoaded = (room: Room) => {
if (this.unmounted) return;
// 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.updatePreviewUrlVisibility(room);
@ -1214,10 +1210,10 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
if (
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
RightPanelStore.instance.setCard({ phase: RightPanelPhases.Timeline }, true, room.roomId);
this.context.rightPanelStore.setCard({ phase: RightPanelPhases.Timeline }, true, room.roomId);
}
this.setState({
@ -1244,7 +1240,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
private async loadMembersIfJoined(room: Room) {
// lazy load members if enabled
if (this.context.hasLazyLoadMembersEnabled()) {
if (this.context.client.hasLazyLoadMembersEnabled()) {
if (room && room.getMyMembership() === 'join') {
try {
await room.loadMembersIfNeeded();
@ -1270,7 +1266,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
private updatePreviewUrlVisibility({ roomId }: Room) {
// 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({
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
if (this.state.room) {
WidgetLayoutStore.instance.off(
this.context.widgetLayoutStore.off(
WidgetLayoutStore.emissionForRoom(this.state.room),
this.onWidgetLayoutChange,
);
@ -1320,15 +1316,15 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
};
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,
// so we don't know what the answer is. Let's error on the safe side and show
// a warning for this case.
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 */
e2eStatus = await shieldStatusForRoom(this.context, room);
e2eStatus = await shieldStatusForRoom(this.context.client, room);
}
if (this.unmounted) return;
@ -1374,7 +1370,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
private updatePermissions(room: Room) {
if (room) {
const me = this.context.getUserId();
const me = this.context.client.getUserId();
const canReact = (
room.getMyMembership() === "join" &&
room.currentState.maySendEvent(EventType.Reaction, me)
@ -1442,7 +1438,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
private onJoinButtonClicked = () => {
// 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
// (If we failed to peek, we may not have a valid room object.)
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) {
if (this.context.isGuest()) {
if (this.context.client.isGuest()) {
dis.dispatch({ action: 'require_registration' });
return;
}
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) => {
if (error.name === "UnknownDeviceError") {
// Let the staus bar handle this
@ -1578,7 +1574,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
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
// XXX: This won't work for results coming from Seshat which won't include the bundled relationship
for (const result of results.results) {
@ -1586,7 +1582,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
const bundledRelationship = event
.getServerAggregatedRelation<IThreadBundledRelationship>(THREAD_RELATION_TYPE.name);
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);
if (thread) {
event.setThread(thread);
@ -1658,7 +1654,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
const mxEv = result.context.getEvent();
const roomId = mxEv.getRoomId();
const room = this.context.getRoom(roomId);
const room = this.context.client.getRoom(roomId);
if (!room) {
// 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,
@ -1715,7 +1711,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
this.setState({
rejecting: true,
});
this.context.leave(this.state.roomId).then(() => {
this.context.client.leave(this.state.roomId).then(() => {
dis.dispatch({ action: Action.ViewHomePage });
this.setState({
rejecting: false,
@ -1742,13 +1738,13 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
});
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 ignoredUsers = this.context.getIgnoredUsers();
const ignoredUsers = this.context.client.getIgnoredUsers();
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 });
this.setState({
rejecting: false,
@ -1911,7 +1907,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
if (!this.state.room) {
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,
@ -1924,7 +1920,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
const createEvent = this.state.room.currentState.getStateEvents(EventType.RoomCreate, "");
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() {
@ -1953,7 +1949,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
Array.from(dataTransfer.files),
this.state.room?.roomId ?? this.state.roomId,
null,
this.context,
this.context.client,
TimelineRenderingType.Room,
);
@ -1970,7 +1966,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
}
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}>
<LocalRoomCreateLoader
names={names}
@ -2081,7 +2077,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
</ErrorBoundary>
);
} else {
const myUserId = this.context.credentials.userId;
const myUserId = this.context.client.credentials.userId;
const myMember = this.state.room.getMember(myUserId);
const inviteEvent = myMember ? myMember.events.member : null;
let inviterName = _t("Unknown");
@ -2162,7 +2158,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
const showRoomUpgradeBar = (
roomVersionRecommendation &&
roomVersionRecommendation.needsUpgrade &&
this.state.room.userMayUpgradeRoom(this.context.credentials.userId)
this.state.room.userMayUpgradeRoom(this.context.client.credentials.userId)
);
const hiddenHighlightCount = this.getHiddenHighlightCount();
@ -2174,7 +2170,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
searchInProgress={this.state.searchInProgress}
onCancelClick={this.onCancelSearchClick}
onSearch={this.onSearch}
isRoomEncrypted={this.context.isRoomEncrypted(this.state.room.roomId)}
isRoomEncrypted={this.context.client.isRoomEncrypted(this.state.room.roomId)}
/>;
} else if (showRoomUpgradeBar) {
aux = <RoomUpgradeWarningBar room={this.state.room} />;
@ -2236,7 +2232,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
const auxPanel = (
<AuxPanel
room={this.state.room}
userId={this.context.credentials.userId}
userId={this.context.client.credentials.userId}
showApps={this.state.showApps}
resizeNotifier={this.props.resizeNotifier}
>
@ -2397,7 +2393,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
mainSplitBody = <>
<AppsDrawer
room={this.state.room}
userId={this.context.credentials.userId}
userId={this.context.client.credentials.userId}
resizeNotifier={this.props.resizeNotifier}
showApps={true}
/>
@ -2451,7 +2447,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
onAppsClick = null;
onForgetClick = 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;
}
viewingCall = true;
@ -2493,5 +2489,4 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
}
}
const RoomViewWithMatrixClient = withMatrixClientHOC(RoomView);
export default RoomViewWithMatrixClient;
export default RoomView;

View file

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

View file

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

View file

@ -24,13 +24,13 @@ import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { Action } from "../../../dispatcher/actions";
import { Call, ConnectionState, ElementCall } from "../../../models/Call";
import { useCall } from "../../../hooks/useCall";
import { RoomViewStore } from "../../../stores/RoomViewStore";
import { useEventEmitterState } from "../../../hooks/useEventEmitter";
import {
OwnBeaconStore,
OwnBeaconStoreEvent,
} from "../../../stores/OwnBeaconStore";
import { CallDurationFromEvent } from "../voip/CallDuration";
import { SdkContextClass } from "../../../contexts/SDKContext";
interface RoomCallBannerProps {
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.
if (RoomViewStore.instance.isViewingCall()) {
if (SdkContextClass.instance.roomViewStore.isViewingCall()) {
return null;
}

View file

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

View file

@ -66,7 +66,7 @@ import { BreadcrumbsStore } from "../../../../stores/BreadcrumbsStore";
import { RoomNotificationState } from "../../../../stores/notifications/RoomNotificationState";
import { RoomNotificationStateStore } from "../../../../stores/notifications/RoomNotificationStateStore";
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 SpaceStore from "../../../../stores/spaces/SpaceStore";
import { DirectoryMember, Member, startDmOnFirstMessage } from "../../../../utils/direct-messages";
@ -1060,7 +1060,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
</h4>
<div>
{ BreadcrumbsStore.instance.rooms
.filter(r => r.roomId !== RoomViewStore.instance.getRoomId())
.filter(r => r.roomId !== SdkContextClass.instance.roomViewStore.getRoomId())
.map(room => (
<TooltipOption
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 { OwnProfileStore } from '../../../stores/OwnProfileStore';
import { UPDATE_EVENT } from '../../../stores/AsyncStore';
import { RoomViewStore } from '../../../stores/RoomViewStore';
import WidgetUtils from '../../../utils/WidgetUtils';
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { ActionPayload } from "../../../dispatcher/payloads";
import { Action } from '../../../dispatcher/actions';
import { ElementWidgetCapabilities } from '../../../stores/widgets/ElementWidgetCapabilities';
import { WidgetMessagingStore } from '../../../stores/widgets/WidgetMessagingStore';
import { SdkContextClass } from '../../../contexts/SDKContext';
interface IProps {
app: IApp;
@ -175,7 +175,7 @@ export default class AppTile extends React.Component<IProps, IState> {
);
if (isActiveWidget) {
// 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.
this.endWidgetActions();
} 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 { ActionPayload } from '../../../dispatcher/payloads';
import { Action } from '../../../dispatcher/actions';
import { RoomViewStore } from '../../../stores/RoomViewStore';
import ContentMessages from '../../../ContentMessages';
import UploadBar from '../../structures/UploadBar';
import SettingsStore from '../../../settings/SettingsStore';
@ -42,6 +41,7 @@ import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import Measured from '../elements/Measured';
import Heading from '../typography/Heading';
import { UPDATE_EVENT } from '../../../stores/AsyncStore';
import { SdkContextClass } from '../../../contexts/SDKContext';
interface IProps {
room: Room;
@ -91,7 +91,7 @@ export default class TimelineCard extends React.Component<IProps, IState> {
}
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.readReceiptsSettingWatcher = SettingsStore.watchSetting("showReadReceipts", null, (...[,,, value]) =>
this.setState({ showReadReceipts: value as boolean }),
@ -102,7 +102,7 @@ export default class TimelineCard extends React.Component<IProps, IState> {
}
public componentWillUnmount(): void {
RoomViewStore.instance.removeListener(UPDATE_EVENT, this.onRoomViewStoreUpdate);
SdkContextClass.instance.roomViewStore.removeListener(UPDATE_EVENT, this.onRoomViewStoreUpdate);
if (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> => {
const newState: Pick<IState, any> = {
// roomLoading: RoomViewStore.instance.isRoomLoading(),
// roomLoadError: RoomViewStore.instance.getRoomLoadError(),
initialEventId: RoomViewStore.instance.getInitialEventId(),
isInitialEventHighlighted: RoomViewStore.instance.isInitialEventHighlighted(),
replyToEvent: RoomViewStore.instance.getQuotingEvent(),
initialEventId: SdkContextClass.instance.roomViewStore.getInitialEventId(),
isInitialEventHighlighted: SdkContextClass.instance.roomViewStore.isInitialEventHighlighted(),
replyToEvent: SdkContextClass.instance.roomViewStore.getQuotingEvent(),
};
this.setState(newState);

View file

@ -36,7 +36,6 @@ import { _t } from '../../../languageHandler';
import DMRoomMap from '../../../utils/DMRoomMap';
import AccessibleButton, { ButtonEvent } from '../elements/AccessibleButton';
import SdkConfig from '../../../SdkConfig';
import { RoomViewStore } from "../../../stores/RoomViewStore";
import MultiInviter from "../../../utils/MultiInviter";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import E2EIcon from "../rooms/E2EIcon";
@ -77,6 +76,7 @@ import UserIdentifierCustomisations from '../../../customisations/UserIdentifier
import PosthogTrackers from "../../../PosthogTrackers";
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { DirectoryMember, startDmOnFirstMessage } from '../../../utils/direct-messages';
import { SdkContextClass } from '../../../contexts/SDKContext';
export interface IDevice {
deviceId: string;
@ -412,7 +412,7 @@ const UserOptionsSection: React.FC<{
}
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) => {
try {
// 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 { UPDATE_EVENT } from "../../../stores/AsyncStore";
import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore";
import { RoomViewStore } from "../../../stores/RoomViewStore";
import {
isMetaSpace,
ISuggestedRoom,
@ -62,6 +61,7 @@ import IconizedContextMenu, {
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import ExtraTile from "./ExtraTile";
import RoomSublist, { IAuxButtonProps } from "./RoomSublist";
import { SdkContextClass } from "../../../contexts/SDKContext";
interface IProps {
onKeyDown: (ev: React.KeyboardEvent, state: IRovingTabIndexState) => void;
@ -421,7 +421,7 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
public componentDidMount(): void {
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);
RoomListStore.instance.on(LISTS_UPDATE_EVENT, this.updateLists);
this.favouriteMessageWatcher =
@ -436,19 +436,19 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.updateLists);
SettingsStore.unwatchSetting(this.favouriteMessageWatcher);
defaultDispatcher.unregister(this.dispatcherRef);
RoomViewStore.instance.off(UPDATE_EVENT, this.onRoomViewStoreUpdate);
SdkContextClass.instance.roomViewStore.off(UPDATE_EVENT, this.onRoomViewStoreUpdate);
}
private onRoomViewStoreUpdate = () => {
this.setState({
currentRoomId: RoomViewStore.instance.getRoomId(),
currentRoomId: SdkContextClass.instance.roomViewStore.getRoomId(),
});
};
private onAction = (payload: ActionPayload) => {
if (payload.action === Action.ViewRoomDelta) {
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);
if (room) {
defaultDispatcher.dispatch<ViewRoomPayload>({

View file

@ -44,10 +44,10 @@ import PosthogTrackers from "../../../PosthogTrackers";
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
import { RoomViewStore } from "../../../stores/RoomViewStore";
import { RoomTileCallSummary } from "./RoomTileCallSummary";
import { RoomGeneralContextMenu } from "../context_menus/RoomGeneralContextMenu";
import { CallStore, CallStoreEvent } from "../../../stores/CallStore";
import { SdkContextClass } from "../../../contexts/SDKContext";
interface IProps {
room: Room;
@ -86,7 +86,7 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
super(props);
this.state = {
selected: RoomViewStore.instance.getRoomId() === this.props.room.roomId,
selected: SdkContextClass.instance.roomViewStore.getRoomId() === this.props.room.roomId,
notificationsMenuPosition: null,
generalMenuPosition: null,
call: CallStore.instance.getCall(this.props.room.roomId),
@ -146,7 +146,7 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
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);
MessagePreviewStore.instance.on(
MessagePreviewStore.getPreviewChangedEventName(this.props.room),
@ -163,7 +163,7 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
}
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.getPreviewChangedEventName(this.props.room),
this.onRoomPreviewChanged,

View file

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

View file

@ -21,7 +21,6 @@ import classNames from 'classnames';
import { Room } from "matrix-js-sdk/src/models/room";
import LegacyCallView from "./LegacyCallView";
import { RoomViewStore } from '../../../stores/RoomViewStore';
import LegacyCallHandler, { LegacyCallHandlerEvent } from '../../../LegacyCallHandler';
import PersistentApp from "../elements/PersistentApp";
import { MatrixClientPeg } from '../../../MatrixClientPeg';
@ -34,6 +33,7 @@ import ActiveWidgetStore, { ActiveWidgetStoreEvent } from '../../../stores/Activ
import WidgetStore, { IApp } from "../../../stores/WidgetStore";
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { UPDATE_EVENT } from '../../../stores/AsyncStore';
import { SdkContextClass } from '../../../contexts/SDKContext';
import { CallStore } from "../../../stores/CallStore";
import {
VoiceBroadcastRecording,
@ -129,7 +129,7 @@ class PipView extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);
const roomId = RoomViewStore.instance.getRoomId();
const roomId = SdkContextClass.instance.roomViewStore.getRoomId();
const [primaryCall, secondaryCalls] = getPrimarySecondaryCallsForPip(roomId);
@ -147,7 +147,7 @@ class PipView extends React.Component<IProps, IState> {
public componentDidMount() {
LegacyCallHandler.instance.addListener(LegacyCallHandlerEvent.CallChangeRoom, 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);
const room = MatrixClientPeg.get()?.getRoom(this.state.viewedRoomId);
if (room) {
@ -164,7 +164,7 @@ class PipView extends React.Component<IProps, IState> {
LegacyCallHandler.instance.removeListener(LegacyCallHandlerEvent.CallState, this.updateCalls);
const cli = MatrixClientPeg.get();
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);
if (room) {
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 onRoomViewStoreUpdate = () => {
const newRoomId = RoomViewStore.instance.getRoomId();
const newRoomId = SdkContextClass.instance.roomViewStore.getRoomId();
const oldRoomId = this.state.viewedRoomId;
if (newRoomId === oldRoomId) return;
// 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 * as utils from 'matrix-js-sdk/src/utils';
import { MatrixError } from "matrix-js-sdk/src/http-api";
import { logger } from "matrix-js-sdk/src/logger";
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 EventEmitter from "events";
import { defaultDispatcher, MatrixDispatcher } from '../dispatcher/dispatcher';
import { MatrixDispatcher } from '../dispatcher/dispatcher';
import { MatrixClientPeg } from '../MatrixClientPeg';
import Modal from '../Modal';
import { _t } from '../languageHandler';
@ -35,10 +36,8 @@ import { getCachedRoomIDForAlias, storeRoomAliasInCache } from '../RoomAliasCach
import { Action } from "../dispatcher/actions";
import { retry } from "../utils/promise";
import { TimelineRenderingType } from "../contexts/RoomContext";
import { PosthogAnalytics } from "../PosthogAnalytics";
import { ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload";
import DMRoomMap from "../utils/DMRoomMap";
import SpaceStore from "./spaces/SpaceStore";
import { isMetaSpace, MetaSpace } from "./spaces";
import { JoinRoomPayload } from "../dispatcher/payloads/JoinRoomPayload";
import { JoinRoomReadyPayload } from "../dispatcher/payloads/JoinRoomReadyPayload";
@ -47,9 +46,9 @@ import { ViewRoomErrorPayload } from "../dispatcher/payloads/ViewRoomErrorPayloa
import ErrorDialog from "../components/views/dialogs/ErrorDialog";
import { ActiveRoomChangedPayload } from "../dispatcher/payloads/ActiveRoomChangedPayload";
import SettingsStore from "../settings/SettingsStore";
import { SlidingSyncManager } from "../SlidingSyncManager";
import { awaitRoomDownSync } from "../utils/RoomUpgrade";
import { UPDATE_EVENT } from "./AsyncStore";
import { SdkContextClass } from "../contexts/SDKContext";
import { CallStore } from "./CallStore";
const NUM_JOIN_RETRY = 5;
@ -131,17 +130,16 @@ type Listener = (isActive: boolean) => void;
* A class for storing application state for RoomView.
*/
export class RoomViewStore extends EventEmitter {
// Important: This cannot be a dynamic getter (lazily-constructed instance) because
// otherwise we'll miss view_room dispatches during startup, breaking relaunches of
// the app. We need to eagerly create the instance.
public static readonly instance = new RoomViewStore(defaultDispatcher);
private state: State = INITIAL_STATE; // initialize state
// initialize state as a copy of the initial state. We need to copy else one RVS can talk to
// another RVS via INITIAL_STATE as they share the same underlying object. Mostly relevant for tests.
private state = utils.deepCopy(INITIAL_STATE);
private dis: MatrixDispatcher;
private dispatchToken: string;
public constructor(dis: MatrixDispatcher) {
public constructor(
dis: MatrixDispatcher, private readonly stores: SdkContextClass,
) {
super();
this.resetDispatcher(dis);
}
@ -248,7 +246,7 @@ export class RoomViewStore extends EventEmitter {
: numMembers > 1 ? "Two"
: "One";
PosthogAnalytics.instance.trackEvent<JoinedRoomEvent>({
this.stores.posthogAnalytics.trackEvent<JoinedRoomEvent>({
eventName: "JoinedRoom",
trigger: payload.metricsTrigger,
roomSize,
@ -291,17 +289,17 @@ export class RoomViewStore extends EventEmitter {
if (payload.metricsTrigger !== null && payload.room_id !== this.state.roomId) {
let activeSpace: ViewRoomEvent["activeSpace"];
if (SpaceStore.instance.activeSpace === MetaSpace.Home) {
if (this.stores.spaceStore.activeSpace === MetaSpace.Home) {
activeSpace = "Home";
} else if (isMetaSpace(SpaceStore.instance.activeSpace)) {
} else if (isMetaSpace(this.stores.spaceStore.activeSpace)) {
activeSpace = "Meta";
} else {
activeSpace = SpaceStore.instance.activeSpaceRoom.getJoinRule() === JoinRule.Public
activeSpace = this.stores.spaceStore.activeSpaceRoom?.getJoinRule() === JoinRule.Public
? "Public"
: "Private";
}
PosthogAnalytics.instance.trackEvent<ViewRoomEvent>({
this.stores.posthogAnalytics.trackEvent<ViewRoomEvent>({
eventName: "ViewRoom",
trigger: payload.metricsTrigger,
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 (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.
SlidingSyncManager.instance.setRoomVisible(this.state.subscribingRoomId, false);
this.stores.slidingSyncManager.setRoomVisible(this.state.subscribingRoomId, false);
}
this.setState({
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
// 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
// unsubscribe
if (this.state.subscribingRoomId !== payload.room_id) {
SlidingSyncManager.instance.setRoomVisible(payload.room_id, false);
this.stores.slidingSyncManager.setRoomVisible(payload.room_id, false);
return;
}
// 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
// }
// } else {
// if (RoomViewStore.instance.isJoining()) {
// if (this.stores.roomViewStore.isJoining()) {
// // show spinner
// } else {
// // show join prompt

View file

@ -34,7 +34,7 @@ import {
import { ActionPayload } from "../../dispatcher/payloads";
import { Action } from "../../dispatcher/actions";
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
@ -64,7 +64,7 @@ export default class RightPanelStore extends ReadyWatchingStore {
}
protected async onReady(): Promise<any> {
this.viewedRoomId = RoomViewStore.instance.getRoomId();
this.viewedRoomId = SdkContextClass.instance.roomViewStore.getRoomId();
this.matrixClient.on(CryptoEvent.VerificationRequest, this.onVerificationRequestUpdate);
this.loadCacheFromSettings();
this.emitAndUpdateSettings();

View file

@ -27,7 +27,6 @@ import { ActionPayload } from "../../dispatcher/payloads";
import defaultDispatcher from "../../dispatcher/dispatcher";
import { readReceiptChangeIsFor } from "../../utils/read-receipts";
import { FILTER_CHANGED, IFilterCondition } from "./filters/IFilterCondition";
import { RoomViewStore } from "../RoomViewStore";
import { Algorithm, LIST_UPDATED_EVENT } from "./algorithms/Algorithm";
import { EffectiveMembership, getEffectiveMembership } from "../../utils/membership";
import RoomListLayoutStore from "./RoomListLayoutStore";
@ -40,6 +39,7 @@ import { IRoomTimelineActionPayload } from "../../actions/MatrixActionCreators";
import { RoomListStore as Interface, RoomListStoreEvent } from "./Interface";
import { SlidingRoomListStoreClass } from "./SlidingRoomListStore";
import { UPDATE_EVENT } from "../AsyncStore";
import { SdkContextClass } from "../../contexts/SDKContext";
interface IState {
// state is tracked in underlying classes
@ -105,7 +105,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> implements
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(FILTER_CHANGED, this.onAlgorithmFilterUpdated);
this.setupWatchers();
@ -128,7 +128,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> implements
private handleRVSUpdate({ trigger = true }) {
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) {
this.algorithm.setStickyRoom(null);
} else if (activeRoomId) {

View file

@ -29,8 +29,8 @@ import { SlidingSyncManager } from "../../SlidingSyncManager";
import SpaceStore from "../spaces/SpaceStore";
import { MetaSpace, SpaceKey, UPDATE_SELECTED_SPACE } from "../spaces";
import { LISTS_LOADING_EVENT } from "./RoomListStore";
import { RoomViewStore } from "../RoomViewStore";
import { UPDATE_EVENT } from "../AsyncStore";
import { SdkContextClass } from "../../contexts/SDKContext";
interface IState {
// 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
// no sticky room if you aren't viewing a room.
this.stickyRoomId = RoomViewStore.instance.getRoomId();
this.stickyRoomId = SdkContextClass.instance.roomViewStore.getRoomId();
let stickyRoomNewIndex = -1;
const stickyRoomOldIndex = (tagMap[tagId] || []).findIndex((room) => {
return room.roomId === this.stickyRoomId;
@ -273,7 +273,7 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient<IState> impl
private onRoomViewStoreUpdated() {
// 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;
}
@ -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.
this.stickyRoomId = RoomViewStore.instance.getRoomId();
this.stickyRoomId = SdkContextClass.instance.roomViewStore.getRoomId();
if (hasUpdatedAnyList) {
this.emit(LISTS_UPDATE_EVENT);
@ -314,7 +314,7 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient<IState> impl
logger.info("SlidingRoomListStore.onReady");
// 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));
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));
if (SpaceStore.instance.activeSpace) {
this.onSelectedSpaceUpdated(SpaceStore.instance.activeSpace, false);

View file

@ -34,7 +34,6 @@ import { RoomNotificationStateStore } from "../notifications/RoomNotificationSta
import { DefaultTagID } from "../room-list/models";
import { EnhancedMap, mapDiff } from "../../utils/maps";
import { setDiff, setHasDiff } from "../../utils/sets";
import { RoomViewStore } from "../RoomViewStore";
import { Action } from "../../dispatcher/actions";
import { arrayHasDiff, arrayHasOrderChange } from "../../utils/arrays";
import { reorderLexicographically } from "../../utils/stringOrderField";
@ -64,6 +63,7 @@ import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
import { ViewHomePagePayload } from "../../dispatcher/payloads/ViewHomePagePayload";
import { SwitchSpacePayload } from "../../dispatcher/payloads/SwitchSpacePayload";
import { AfterLeaveRoomPayload } from "../../dispatcher/payloads/AfterLeaveRoomPayload";
import { SdkContextClass } from "../../contexts/SDKContext";
interface IState { }
@ -797,7 +797,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
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()) {
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 (newMembership === "join" && room.roomId === RoomViewStore.instance.getRoomId()) {
if (newMembership === "join" && room.roomId === SdkContextClass.instance.roomViewStore.getRoomId()) {
this.switchSpaceIfNeeded(room.roomId);
}
}
@ -875,7 +875,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
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
this.setActiveSpace(room.roomId, false);
} 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 { StopGapWidgetDriver } from "./StopGapWidgetDriver";
import { WidgetMessagingStore } from "./WidgetMessagingStore";
import { RoomViewStore } from "../RoomViewStore";
import { MatrixClientPeg } from "../../MatrixClientPeg";
import { OwnProfileStore } from "../OwnProfileStore";
import WidgetUtils from '../../utils/WidgetUtils';
@ -65,6 +64,7 @@ import { arrayFastClone } from "../../utils/arrays";
import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
import Modal from "../../Modal";
import ErrorDialog from "../../components/views/dialogs/ErrorDialog";
import { SdkContextClass } from "../../contexts/SDKContext";
import { VoiceBroadcastRecordingsStore } from "../../voice-broadcast";
// TODO: Destroy all of this code
@ -185,7 +185,7 @@ export class StopGapWidget extends EventEmitter {
if (this.roomId) return this.roomId;
return RoomViewStore.instance.getRoomId();
return SdkContextClass.instance.roomViewStore.getRoomId();
}
public get widgetApi(): ClientWidgetApi {
@ -381,7 +381,7 @@ export class StopGapWidget extends EventEmitter {
// noinspection JSIgnoredPromiseFromCall
IntegrationManagers.sharedInstance().getPrimaryManager().open(
this.client.getRoom(RoomViewStore.instance.getRoomId()),
this.client.getRoom(SdkContextClass.instance.roomViewStore.getRoomId()),
`type_${integType}`,
integId,
);

View file

@ -53,9 +53,9 @@ import { CHAT_EFFECTS } from "../../effects";
import { containsEmoji } from "../../effects/utils";
import dis from "../../dispatcher/dispatcher";
import SettingsStore from "../../settings/SettingsStore";
import { RoomViewStore } from "../RoomViewStore";
import { ElementWidgetCapabilities } from "./ElementWidgetCapabilities";
import { navigateToPermalink } from "../../utils/permalinks/navigator";
import { SdkContextClass } from "../../contexts/SDKContext";
// TODO: Purge this from the universe
@ -210,7 +210,7 @@ export class StopGapWidgetDriver extends WidgetDriver {
targetRoomId: string = null,
): Promise<ISendEventDetails> {
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");
@ -291,7 +291,7 @@ export class StopGapWidgetDriver extends WidgetDriver {
const targetRooms = roomIds
? (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);
}
@ -430,7 +430,7 @@ export class StopGapWidgetDriver extends WidgetDriver {
): Promise<IReadEventRelationsResult> {
const client = MatrixClientPeg.get();
const dir = direction as Direction;
roomId = roomId ?? RoomViewStore.instance.getRoomId() ?? undefined;
roomId = roomId ?? SdkContextClass.instance.roomViewStore.getRoomId() ?? undefined;
if (typeof roomId !== "string") {
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 Modal from "../Modal";
import RoomSettingsDialog from "../components/views/dialogs/RoomSettingsDialog";
import { RoomViewStore } from "../stores/RoomViewStore";
import ForwardDialog from "../components/views/dialogs/ForwardDialog";
import { MatrixClientPeg } from "../MatrixClientPeg";
import { Action } from "../dispatcher/actions";
@ -32,6 +31,7 @@ import AddExistingToSpaceDialog from "../components/views/dialogs/AddExistingToS
import { ButtonEvent } from "../components/views/elements/AccessibleButton";
import PosthogTrackers from "../PosthogTrackers";
import { showAddExistingSubspace, showCreateNewRoom } from "./space";
import { SdkContextClass } from "../contexts/SDKContext";
/**
* Auxiliary class to listen for dialog opening over the dispatcher and
@ -58,7 +58,7 @@ export class DialogOpener {
switch (payload.action) {
case 'open_room_settings':
Modal.createDialog(RoomSettingsDialog, {
roomId: payload.room_id || RoomViewStore.instance.getRoomId(),
roomId: payload.room_id || SdkContextClass.instance.roomViewStore.getRoomId(),
initialTabId: payload.initial_tab_id,
}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
break;
@ -108,7 +108,7 @@ export class DialogOpener {
onAddSubspaceClick: () => showAddExistingSubspace(space),
space,
onFinished: (added: boolean) => {
if (added && RoomViewStore.instance.getRoomId() === space.roomId) {
if (added && SdkContextClass.instance.roomViewStore.getRoomId() === space.roomId) {
defaultDispatcher.fire(Action.UpdateSpaceHierarchy);
}
},

View file

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

View file

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

View file

@ -21,9 +21,9 @@ import { Command, Commands, getCommand } from '../src/SlashCommands';
import { createTestClient } from './test-utils';
import { MatrixClientPeg } from '../src/MatrixClientPeg';
import { LocalRoom, LOCAL_ROOM_ID_PREFIX } from '../src/models/LocalRoom';
import { RoomViewStore } from '../src/stores/RoomViewStore';
import SettingsStore from '../src/settings/SettingsStore';
import LegacyCallHandler from '../src/LegacyCallHandler';
import { SdkContextClass } from '../src/contexts/SDKContext';
describe('SlashCommands', () => {
let client: MatrixClient;
@ -38,14 +38,14 @@ describe('SlashCommands', () => {
};
const setCurrentRoom = (): void => {
mocked(RoomViewStore.instance.getRoomId).mockReturnValue(roomId);
mocked(SdkContextClass.instance.roomViewStore.getRoomId).mockReturnValue(roomId);
mocked(client.getRoom).mockImplementation((rId: string): Room => {
if (rId === roomId) return room;
});
};
const setCurrentLocalRoon = (): void => {
mocked(RoomViewStore.instance.getRoomId).mockReturnValue(localRoomId);
mocked(SdkContextClass.instance.roomViewStore.getRoomId).mockReturnValue(localRoomId);
mocked(client.getRoom).mockImplementation((rId: string): Room => {
if (rId === localRoomId) return localRoom;
});
@ -60,7 +60,7 @@ describe('SlashCommands', () => {
room = new Room(roomId, client, client.getUserId());
localRoom = new LocalRoom(localRoomId, client, client.getUserId());
jest.spyOn(RoomViewStore.instance, "getRoomId");
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId");
});
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 { RoomView as _RoomView } from "../../../src/components/structures/RoomView";
import ResizeNotifier from "../../../src/utils/ResizeNotifier";
import { RoomViewStore } from "../../../src/stores/RoomViewStore";
import SettingsStore from "../../../src/settings/SettingsStore";
import { SettingLevel } from "../../../src/settings/SettingLevel";
import DMRoomMap from "../../../src/utils/DMRoomMap";
import { NotificationState } from "../../../src/stores/notifications/NotificationState";
import RightPanelStore from "../../../src/stores/right-panel/RightPanelStore";
import { RightPanelPhases } from "../../../src/stores/right-panel/RightPanelStorePhases";
import { LocalRoom, LocalRoomState } from "../../../src/models/LocalRoom";
import { DirectoryMember } from "../../../src/utils/direct-messages";
import { createDmLocalRoom } from "../../../src/utils/dm/createDmLocalRoom";
import { UPDATE_EVENT } from "../../../src/stores/AsyncStore";
import { SdkContextClass, SDKContext } from "../../../src/contexts/SDKContext";
const RoomView = wrapInMatrixClientContext(_RoomView);
@ -50,6 +49,7 @@ describe("RoomView", () => {
let cli: MockedObject<MatrixClient>;
let room: Room;
let roomCount = 0;
let stores: SdkContextClass;
beforeEach(async () => {
mockPlatformPeg({ reload: () => {} });
@ -64,7 +64,9 @@ describe("RoomView", () => {
room.on(RoomEvent.TimelineReset, (...args) => cli.emit(RoomEvent.TimelineReset, ...args));
DMRoomMap.makeShared();
RightPanelStore.instance.useUnitTestClient(cli);
stores = new SdkContextClass();
stores.client = cli;
stores.rightPanelStore.useUnitTestClient(cli);
});
afterEach(async () => {
@ -73,15 +75,15 @@ describe("RoomView", () => {
});
const mountRoomView = async (): Promise<ReactWrapper> => {
if (RoomViewStore.instance.getRoomId() !== room.roomId) {
if (stores.roomViewStore.getRoomId() !== room.roomId) {
const switchedRoom = new Promise<void>(resolve => {
const subFn = () => {
if (RoomViewStore.instance.getRoomId()) {
RoomViewStore.instance.off(UPDATE_EVENT, subFn);
if (stores.roomViewStore.getRoomId()) {
stores.roomViewStore.off(UPDATE_EVENT, subFn);
resolve();
}
};
RoomViewStore.instance.on(UPDATE_EVENT, subFn);
stores.roomViewStore.on(UPDATE_EVENT, subFn);
});
defaultDispatcher.dispatch<ViewRoomPayload>({
@ -94,15 +96,16 @@ describe("RoomView", () => {
}
const roomView = mount(
<RoomView
mxClient={cli}
threepidInvite={null}
oobData={null}
resizeNotifier={new ResizeNotifier()}
justCreatedOpts={null}
forceTimeline={false}
onRegistered={null}
/>,
<SDKContext.Provider value={stores}>
<RoomView
threepidInvite={null}
oobData={null}
resizeNotifier={new ResizeNotifier()}
justCreatedOpts={null}
forceTimeline={false}
onRegistered={null}
/>
</SDKContext.Provider>,
);
await act(() => Promise.resolve()); // Allow state to settle
return roomView;
@ -162,14 +165,14 @@ describe("RoomView", () => {
it("normally doesn't open the chat panel", async () => {
jest.spyOn(NotificationState.prototype, "isUnread", "get").mockReturnValue(false);
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 () => {
jest.spyOn(NotificationState.prototype, "isUnread", "get").mockReturnValue(true);
await mountRoomView();
expect(RightPanelStore.instance.isOpen).toEqual(true);
expect(RightPanelStore.instance.currentCard.phase).toEqual(RightPanelPhases.Timeline);
expect(stores.rightPanelStore.isOpen).toEqual(true);
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 { WidgetMessagingStore } from "../../../../src/stores/widgets/WidgetMessagingStore";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import { RoomViewStore } from "../../../../src/stores/RoomViewStore";
import { ConnectionState } from "../../../../src/models/Call";
import { SdkContextClass } from "../../../../src/contexts/SDKContext";
describe("<RoomCallBanner />", () => {
let client: Mocked<MatrixClient>;
@ -132,7 +132,8 @@ describe("<RoomCallBanner />", () => {
});
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();
const banner = await screen.queryByText("Video call");
expect(banner).toBeFalsy();

View file

@ -21,10 +21,21 @@ import { Action } from '../../src/dispatcher/actions';
import { getMockClientWithEventEmitter, untilDispatch, untilEmission } from '../test-utils';
import SettingsStore from '../../src/settings/SettingsStore';
import { SlidingSyncManager } from '../../src/SlidingSyncManager';
import { PosthogAnalytics } from '../../src/PosthogAnalytics';
import { TimelineRenderingType } from '../../src/contexts/RoomContext';
import { MatrixDispatcher } from '../../src/dispatcher/dispatcher';
import { UPDATE_EVENT } from '../../src/stores/AsyncStore';
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', () => {
const mock = {
@ -51,6 +62,9 @@ describe('RoomViewStore', function() {
isGuest: jest.fn(),
});
const room = new Room(roomId, mockClient, userId);
let roomViewStore: RoomViewStore;
let slidingSyncManager: SlidingSyncManager;
let dis: MatrixDispatcher;
beforeEach(function() {
@ -60,10 +74,17 @@ describe('RoomViewStore', function() {
mockClient.getRoom.mockReturnValue(room);
mockClient.isGuest.mockReturnValue(false);
// Reset the state of the store
// Make the RVS to test
dis = new MatrixDispatcher();
RoomViewStore.instance.reset();
RoomViewStore.instance.resetDispatcher(dis);
slidingSyncManager = new MockSlidingSyncManager();
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 () => {
@ -71,14 +92,14 @@ describe('RoomViewStore', function() {
dis.dispatch({ action: Action.JoinRoom });
await untilDispatch(Action.JoinRoomReady, dis);
expect(mockClient.joinRoom).toHaveBeenCalledWith(roomId, { viaServers: [] });
expect(RoomViewStore.instance.isJoining()).toBe(true);
expect(roomViewStore.isJoining()).toBe(true);
});
it('can auto-join a room', async () => {
dis.dispatch({ action: Action.ViewRoom, room_id: roomId, auto_join: true });
await untilDispatch(Action.JoinRoomReady, dis);
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 () => {
@ -97,7 +118,7 @@ describe('RoomViewStore', function() {
it('invokes room activity listeners when the viewed room changes', async () => {
const roomId2 = "!roomid:2";
const callback = jest.fn();
RoomViewStore.instance.addRoomListener(roomId, callback);
roomViewStore.addRoomListener(roomId, callback);
dis.dispatch({ action: Action.ViewRoom, room_id: roomId });
await untilDispatch(Action.ActiveRoomChanged, dis) as ActiveRoomChangedPayload;
expect(callback).toHaveBeenCalledWith(true);
@ -116,14 +137,14 @@ describe('RoomViewStore', function() {
}, dis);
// roomId is set to id of the room alias
expect(RoomViewStore.instance.getRoomId()).toBe(roomId);
expect(roomViewStore.getRoomId()).toBe(roomId);
// join the room
dis.dispatch({ action: Action.JoinRoom }, true);
await untilDispatch(Action.JoinRoomReady, dis);
expect(RoomViewStore.instance.isJoining()).toBeTruthy();
expect(roomViewStore.isJoining()).toBeTruthy();
expect(mockClient.joinRoom).toHaveBeenCalledWith(alias, { viaServers: [] });
});
@ -134,7 +155,7 @@ describe('RoomViewStore', function() {
const payload = await untilDispatch(Action.ViewRoomError, dis);
expect(payload.room_id).toBeNull();
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 () => {
@ -143,8 +164,8 @@ describe('RoomViewStore', function() {
dis.dispatch({ action: Action.ViewRoom, room_id: roomId });
dis.dispatch({ action: Action.JoinRoom });
await untilDispatch(Action.JoinRoomError, dis);
expect(RoomViewStore.instance.isJoining()).toBe(false);
expect(RoomViewStore.instance.getJoinError()).toEqual(joinErr);
expect(roomViewStore.isJoining()).toBe(false);
expect(roomViewStore.getJoinError()).toEqual(joinErr);
});
it('remembers the event being replied to when swapping rooms', async () => {
@ -154,13 +175,13 @@ describe('RoomViewStore', function() {
getRoomId: () => roomId,
};
dis.dispatch({ action: 'reply_to_event', event: replyToEvent, context: TimelineRenderingType.Room });
await untilEmission(RoomViewStore.instance, UPDATE_EVENT);
expect(RoomViewStore.instance.getQuotingEvent()).toEqual(replyToEvent);
await untilEmission(roomViewStore, UPDATE_EVENT);
expect(roomViewStore.getQuotingEvent()).toEqual(replyToEvent);
// 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
dis.dispatch({ action: Action.ViewRoom, room_id: roomId, highlighted: true });
await untilEmission(RoomViewStore.instance, UPDATE_EVENT);
expect(RoomViewStore.instance.getQuotingEvent()).toEqual(replyToEvent);
await untilEmission(roomViewStore, UPDATE_EVENT);
expect(roomViewStore.getQuotingEvent()).toEqual(replyToEvent);
});
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 });
await untilDispatch(Action.ViewRoom, dis);
expect(RoomViewStore.instance.getQuotingEvent()).toEqual(replyToEvent);
expect(RoomViewStore.instance.getRoomId()).toEqual(roomId2);
expect(roomViewStore.getQuotingEvent()).toEqual(replyToEvent);
expect(roomViewStore.getRoomId()).toEqual(roomId2);
});
it('removes the roomId on ViewHomePage', async () => {
dis.dispatch({ action: Action.ViewRoom, room_id: roomId });
await untilDispatch(Action.ActiveRoomChanged, dis);
expect(RoomViewStore.instance.getRoomId()).toEqual(roomId);
expect(roomViewStore.getRoomId()).toEqual(roomId);
dis.dispatch({ action: Action.ViewHomePage });
await untilEmission(RoomViewStore.instance, UPDATE_EVENT);
expect(RoomViewStore.instance.getRoomId()).toBeNull();
await untilEmission(roomViewStore, UPDATE_EVENT);
expect(roomViewStore.getRoomId()).toBeNull();
});
describe('Sliding Sync', function() {
@ -191,23 +212,22 @@ describe('RoomViewStore', function() {
jest.spyOn(SettingsStore, 'getValue').mockImplementation((settingName, roomId, value) => {
return settingName === "feature_sliding_sync"; // this is enabled, everything else is disabled.
});
RoomViewStore.instance.reset();
});
it("subscribes to the room", async () => {
const setRoomVisible = jest.spyOn(SlidingSyncManager.instance, "setRoomVisible").mockReturnValue(
const setRoomVisible = jest.spyOn(slidingSyncManager, "setRoomVisible").mockReturnValue(
Promise.resolve(""),
);
const subscribedRoomId = "!sub1:localhost";
dis.dispatch({ action: Action.ViewRoom, room_id: subscribedRoomId });
await untilDispatch(Action.ActiveRoomChanged, dis);
expect(RoomViewStore.instance.getRoomId()).toBe(subscribedRoomId);
expect(roomViewStore.getRoomId()).toBe(subscribedRoomId);
expect(setRoomVisible).toHaveBeenCalledWith(subscribedRoomId, true);
});
// 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 () => {
const setRoomVisible = jest.spyOn(SlidingSyncManager.instance, "setRoomVisible").mockReturnValue(
const setRoomVisible = jest.spyOn(slidingSyncManager, "setRoomVisible").mockReturnValue(
Promise.resolve(""),
);
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 { Widget, MatrixWidgetType, WidgetKind, WidgetDriver, ITurnServer } from "matrix-widget-api";
import { SdkContextClass } from "../../../src/contexts/SDKContext";
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
import { RoomViewStore } from "../../../src/stores/RoomViewStore";
import { StopGapWidgetDriver } from "../../../src/stores/widgets/StopGapWidgetDriver";
import { stubClient } from "../../test-utils";
@ -201,7 +201,7 @@ describe("StopGapWidgetDriver", () => {
beforeEach(() => { driver = mkDefaultDriver(); });
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({
originalEvent: new MatrixEvent(),