Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions apps/web/src/@types/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ import { type DeepReadonly } from "./common";
import type MatrixChat from "../components/structures/MatrixChat";
import { type InitialCryptoSetupStore } from "../stores/InitialCryptoSetupStore";
import { type ModuleApiType } from "../modules/Api.ts";
import type { RoomListStoreV3Class } from "../stores/room-list-v3/RoomListStoreV3.ts";

/* eslint-disable @typescript-eslint/naming-convention */

Expand Down Expand Up @@ -90,7 +89,6 @@ declare global {
mxContentMessages: ContentMessages;
mxToastStore: ToastStore;
mxDeviceListener: DeviceListener;
getRoomListStoreV3: () => RoomListStoreV3Class;
mxPlatformPeg: PlatformPeg;
mxIntegrationManagers: typeof IntegrationManagers;
singletonModalManager: ModalManager;
Expand Down
8 changes: 3 additions & 5 deletions apps/web/src/components/structures/LoggedInView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ import { hideToast as hideServerLimitToast, showToast as showServerLimitToast }
import { Action } from "../../dispatcher/actions";
import LeftPanel from "./LeftPanel";
import { type ViewRoomDeltaPayload } from "../../dispatcher/payloads/ViewRoomDeltaPayload";
import RoomListStoreV3 from "../../stores/room-list-v3/RoomListStoreV3";
import NonUrgentToastContainer from "./NonUrgentToastContainer";
import { type IOOBData, type IThreepidInvite } from "../../stores/ThreepidInviteStore";
import Modal from "../../Modal";
Expand All @@ -53,7 +52,6 @@ import UserView from "./UserView";
import { mediaFromMxc } from "../../customisations/Media";
import { UserTab } from "../views/dialogs/UserTab";
import { type OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
import RightPanelStore from "../../stores/right-panel/RightPanelStore";
import { TimelineRenderingType } from "../../contexts/RoomContext";
import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
import { type SwitchSpacePayload } from "../../dispatcher/payloads/SwitchSpacePayload";
Expand Down Expand Up @@ -296,7 +294,7 @@ class LoggedInView extends React.Component<IProps, IState> {
};

private onRoomStateEvents = (ev: MatrixEvent): void => {
const serverNoticeList = RoomListStoreV3.instance.getServerNoticeRooms();
const serverNoticeList = this.context.roomListStore.getServerNoticeRooms();
if (serverNoticeList.some((r) => r.roomId === ev.getRoomId())) {
this.updateServerNoticeEvents();
}
Expand Down Expand Up @@ -329,7 +327,7 @@ class LoggedInView extends React.Component<IProps, IState> {
}

private updateServerNoticeEvents = async (): Promise<void> => {
const serverNoticeList = RoomListStoreV3.instance.getServerNoticeRooms();
const serverNoticeList = this.context.roomListStore.getServerNoticeRooms();
if (!serverNoticeList.length) return;

const events: MatrixEvent[] = [];
Expand Down Expand Up @@ -494,7 +492,7 @@ class LoggedInView extends React.Component<IProps, IState> {
break;
case KeyBindingAction.ToggleRoomSidePanel:
if (this.props.page_type === "room_view") {
RightPanelStore.instance.togglePanel(null);
this.context.rightPanelStore.togglePanel(null);
handled = true;
}
break;
Expand Down
3 changes: 1 addition & 2 deletions apps/web/src/components/views/dialogs/InviteDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import { abbreviateUrl } from "../../../utils/UrlUtils";
import IdentityAuthClient from "../../../IdentityAuthClient";
import { showAnyInviteErrors } from "../../../RoomInvite";
import { Action } from "../../../dispatcher/actions";
import RoomListStoreV3 from "../../../stores/room-list-v3/RoomListStoreV3";
import SettingsStore from "../../../settings/SettingsStore";
import { UIFeature } from "../../../settings/UIFeature";
import { SearchResultAvatar } from "../avatars/SearchResultAvatar";
Expand Down Expand Up @@ -279,7 +278,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial

// Also pull in all the rooms that the room list tags as DMs so we don't miss anything: sometimes
// a room is absent from getUniqueRoomsWithIndividuals() above but is still tagged as a DM.
const dmTaggedRooms = RoomListStoreV3.instance.getDmRooms();
const dmTaggedRooms = SDKContextClass.instance.roomListStore.getDmRooms();
const myUserId = MatrixClientPeg.safeGet().getUserId();
for (const dmRoom of dmTaggedRooms) {
const otherMembers = dmRoom.getJoinedMembers().filter((u) => u.userId !== myUserId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,12 @@ export const RoomListPanel: React.FC<RoomListPanelProps> = ({ activeSpace }) =>

const matrixClient = useMatrixClientContext();
const vm = useCreateAutoDisposedViewModel(
() => new RoomListHeaderViewModel({ matrixClient, spaceStore: sdkContext.spaceStore }),
() =>
new RoomListHeaderViewModel({
matrixClient,
spaceStore: sdkContext.spaceStore,
roomListStore: sdkContext.roomListStore,
}),
);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@ export function RoomListView(): JSX.Element {

// Create and auto-dispose ViewModel instance
const vm = useCreateAutoDisposedViewModel(
() => new RoomListViewModel({ client: matrixClient, spaceStore: sdkContext.spaceStore }),
() =>
new RoomListViewModel({
client: matrixClient,
spaceStore: sdkContext.spaceStore,
roomListStore: sdkContext.roomListStore,
}),
);

// Render avatar for each room - memoized to prevent re-renders
Expand Down
10 changes: 10 additions & 0 deletions apps/web/src/contexts/SDKContextClass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { Action } from "../dispatcher/actions.ts";
import { type OnLoggedInPayload } from "../dispatcher/payloads/OnLoggedInPayload.ts";
import Notifier from "../Notifier.ts";
import SettingController from "../settings/controllers/SettingController.ts";
import RoomListStoreV3 from "../stores/room-list-v3/RoomListStoreV3.ts";

/**
* A class which (mostly) lazily initialises stores as and when they are requested, ensuring they remain
Expand Down Expand Up @@ -71,6 +72,7 @@ export class SDKContextClass {
protected _UserProfilesStore?: UserProfilesStore;
protected _ResizeNotifier?: ResizeNotifier;
protected _MultiRoomViewStore?: MultiRoomViewStore;
protected _RoomListStore?: RoomListStoreV3;
protected _Notifier?: Notifier;

public constructor() {
Expand Down Expand Up @@ -198,6 +200,14 @@ export class SDKContextClass {
return this._MultiRoomViewStore;
}

public get roomListStore(): RoomListStoreV3 {
if (!this._RoomListStore) {
this._RoomListStore = new RoomListStoreV3(defaultDispatcher, this);
this._RoomListStore.start();
}
return this._RoomListStore;
}

public get notifier(): Notifier {
if (!this._Notifier) {
this._Notifier = new Notifier(defaultDispatcher, this);
Expand Down
3 changes: 2 additions & 1 deletion apps/web/src/modules/Api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { WidgetApi } from "./WidgetApi.ts";
import { CustomisationsApi } from "./customisationsApi.ts";
import { ComposerApi } from "./ComposerApi.ts";
import defaultDispatcher from "../dispatcher/dispatcher.ts";
import { SDKContextClass } from "../contexts/SDKContextClass.ts";

const legacyCustomisationsFactory = <T extends object>(baseCustomisations: T) => {
let used = false;
Expand Down Expand Up @@ -96,7 +97,7 @@ export class ModuleApi implements Api {
public readonly widget = new WidgetApi();
public readonly rootNode = document.getElementById("matrixchat")!;
public readonly client = new ClientApi();
public readonly stores = new StoresApi();
public readonly stores = new StoresApi(SDKContextClass.instance);
public readonly composer = new ComposerApi(defaultDispatcher);

public createRoot(element: Element): Root {
Expand Down
66 changes: 13 additions & 53 deletions apps/web/src/modules/StoresApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,73 +11,31 @@ import {
Watchable,
} from "@element-hq/element-web-module-api";

import type { RoomListStoreV3Class, RoomListStoreV3Event } from "../stores/room-list-v3/RoomListStoreV3";
import type RoomListStoreV3 from "../stores/room-list-v3/RoomListStoreV3";
import { RoomListStoreV3Event } from "../stores/room-list-v3/RoomListStoreV3";
import { Room as ModuleRoom } from "./models/Room";

interface RlsEvents {
LISTS_LOADED_EVENT: RoomListStoreV3Event.ListsLoaded;
LISTS_UPDATE_EVENT: RoomListStoreV3Event.ListsUpdate;
}
import { type SDKContextClass } from "../contexts/SDKContextClass.ts";

export class RoomListStoreApi implements IRoomListStore {
private rls?: RoomListStoreV3Class;
private LISTS_LOADED_EVENT?: RoomListStoreV3Event.ListsLoaded;
private LISTS_UPDATE_EVENT?: RoomListStoreV3Event.ListsUpdate;
public readonly moduleLoadPromise: Promise<void>;

public constructor() {
this.moduleLoadPromise = this.init();
}

/**
* Load the RLS through a dynamic import. This is necessary to prevent
* circular dependency issues.
*/
private async init(): Promise<void> {
const module = await import("../stores/room-list-v3/RoomListStoreV3");
this.rls = module.default.instance;
this.LISTS_LOADED_EVENT = module.LISTS_LOADED_EVENT;
this.LISTS_UPDATE_EVENT = module.LISTS_UPDATE_EVENT;
}
public constructor(private readonly sdkContext: SDKContextClass) {}

public getRooms(): RoomsWatchable {
return new RoomsWatchable(this.roomListStore, this.events);
}

private get events(): RlsEvents {
if (!this.LISTS_LOADED_EVENT || !this.LISTS_UPDATE_EVENT) {
throw new Error("Event type was not loaded correctly, did you forget to await waitForReady()?");
}
return { LISTS_LOADED_EVENT: this.LISTS_LOADED_EVENT, LISTS_UPDATE_EVENT: this.LISTS_UPDATE_EVENT };
}

private get roomListStore(): RoomListStoreV3Class {
if (!this.rls) {
throw new Error("rls is undefined, did you forget to await waitForReady()?");
}
return this.rls;
return new RoomsWatchable(this.sdkContext.roomListStore);
}

public async waitForReady(): Promise<void> {
// Wait for the module to load first
await this.moduleLoadPromise;

// Check if RLS is already loaded
if (!this.roomListStore.isLoadingRooms) return;
if (!this.sdkContext.roomListStore.isLoadingRooms) return;

// Await a promise that resolves when RLS has loaded
const { promise, resolve } = Promise.withResolvers<void>();
const { LISTS_LOADED_EVENT } = this.events;
this.roomListStore.once(LISTS_LOADED_EVENT, resolve);
this.sdkContext.roomListStore.once(RoomListStoreV3Event.ListsLoaded, resolve);
await promise;
}
}

class RoomsWatchable extends Watchable<Room[]> {
public constructor(
private readonly rls: RoomListStoreV3Class,
private readonly events: RlsEvents,
) {
public constructor(private readonly rls: RoomListStoreV3) {
super(rls.getSortedRooms().map((sdkRoom) => new ModuleRoom(sdkRoom)));
}

Expand All @@ -86,20 +44,22 @@ class RoomsWatchable extends Watchable<Room[]> {
};

protected onFirstWatch(): void {
this.rls.on(this.events.LISTS_UPDATE_EVENT, this.onRlsUpdate);
this.rls.on(RoomListStoreV3Event.ListsUpdate, this.onRlsUpdate);
}

protected onLastWatch(): void {
this.rls.off(this.events.LISTS_UPDATE_EVENT, this.onRlsUpdate);
this.rls.off(RoomListStoreV3Event.ListsUpdate, this.onRlsUpdate);
}
}

export class StoresApi implements IStoresApi {
private roomListStoreApi?: IRoomListStore;

public constructor(private readonly sdkContext: SDKContextClass) {}

public get roomListStore(): IRoomListStore {
if (!this.roomListStoreApi) {
this.roomListStoreApi = new RoomListStoreApi();
this.roomListStoreApi = new RoomListStoreApi(this.sdkContext);
}
return this.roomListStoreApi;
}
Expand Down
36 changes: 11 additions & 25 deletions apps/web/src/stores/room-list-v3/RoomListStoreV3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import type { ActionPayload } from "../../dispatcher/payloads";
import type { Filter, FilterKey } from "./skip-list/filters";
import { AsyncStoreWithClient } from "../AsyncStoreWithClient";
import SettingsStore from "../../settings/SettingsStore";
import defaultDispatcher from "../../dispatcher/dispatcher";
import { RecencySorter } from "./skip-list/sorters/RecencySorter";
import { AlphabeticSorter } from "./skip-list/sorters/AlphabeticSorter";
import { readReceiptChangeIsFor } from "../../utils/read-receipts";
Expand Down Expand Up @@ -48,7 +47,7 @@ import {
reorderSection,
} from "./section";
import { DefaultTagID, type TagID } from "./skip-list/tag";
import { SDKContextClass } from "../../contexts/SDKContextClass.ts";
import { type SDKContextClass } from "../../contexts/SDKContextClass.ts";

/**
* These are the filters passed to the room skip list.
Expand Down Expand Up @@ -104,7 +103,7 @@ export const ROOM_TAGGED_EVENT = RoomListStoreV3Event.RoomTagged;
* This is the third such implementation hence the "V3".
* This store is being actively developed so expect the methods to change in future.
*/
export class RoomListStoreV3Class extends AsyncStoreWithClient<EmptyObject> {
export default class RoomListStoreV3 extends AsyncStoreWithClient<EmptyObject> {
/**
* Contains all the rooms in the active space
*/
Expand All @@ -128,13 +127,16 @@ export class RoomListStoreV3Class extends AsyncStoreWithClient<EmptyObject> {
*/
private pendingEmit = false;

public constructor(dispatcher: MatrixDispatcher) {
public constructor(
dispatcher: MatrixDispatcher,
private readonly sdkContext: SDKContextClass,
) {
super(dispatcher);
this.msc3946ProcessDynamicPredecessor = SettingsStore.getValue("feature_dynamic_room_predecessors");
SDKContextClass.instance.spaceStore.on(UPDATE_SELECTED_SPACE, () => {
this.sdkContext.spaceStore.on(UPDATE_SELECTED_SPACE, () => {
this.onActiveSpaceChanged();
});
SDKContextClass.instance.spaceStore.on(UPDATE_HOME_BEHAVIOUR, () => this.onActiveSpaceChanged());
this.sdkContext.spaceStore.on(UPDATE_HOME_BEHAVIOUR, () => this.onActiveSpaceChanged());
SettingsStore.watchSetting("RoomList.OrderedCustomSections", null, () => this.onOrderedCustomSectionsChange());
this.loadCustomSections();

Expand Down Expand Up @@ -173,7 +175,7 @@ export class RoomListStoreV3Class extends AsyncStoreWithClient<EmptyObject> {
* @param filterKeys Optional array of filters that the rooms must match against.
*/
public getSortedRoomsInActiveSpace(filterKeys?: FilterKey[]): RoomsResult {
const spaceId = SDKContextClass.instance.spaceStore.activeSpace;
const spaceId = this.sdkContext.spaceStore.activeSpace;
const areSectionsEnabled = SettingsStore.getValue("RoomList.showSections");

const sections = areSectionsEnabled
Expand Down Expand Up @@ -238,7 +240,7 @@ export class RoomListStoreV3Class extends AsyncStoreWithClient<EmptyObject> {

this.roomSkipList = new RoomSkipList(sorter, this.getSkipListFilters());

await SDKContextClass.instance.spaceStore.storeReadyPromise;
await this.sdkContext.spaceStore.storeReadyPromise;
const rooms = this.getRooms();
this.roomSkipList.seed(rooms);
this.emit(LISTS_LOADED_EVENT);
Expand Down Expand Up @@ -522,7 +524,7 @@ export class RoomListStoreV3Class extends AsyncStoreWithClient<EmptyObject> {
* Emits {@link SECTION_CREATED_EVENT} if the section was successfully created.
*/
public async createSection(): Promise<string | undefined> {
const tag = await createSection(SDKContextClass.instance.spaceStore.activeSpace);
const tag = await createSection(this.sdkContext.spaceStore.activeSpace);
if (!tag) return;
this.emit(SECTION_CREATED_EVENT, tag);
return tag;
Expand Down Expand Up @@ -573,19 +575,3 @@ export class RoomListStoreV3Class extends AsyncStoreWithClient<EmptyObject> {
this.sortedTags = [DefaultTagID.Favourite, ...reorderable, DefaultTagID.LowPriority];
}
}

export default class RoomListStoreV3 {
private static internalInstance: RoomListStoreV3Class;

public static get instance(): RoomListStoreV3Class {
if (!RoomListStoreV3.internalInstance) {
const instance = new RoomListStoreV3Class(defaultDispatcher);
instance.start();
RoomListStoreV3.internalInstance = instance;
}

return this.internalInstance;
}
}

window.getRoomListStoreV3 = () => RoomListStoreV3.instance;
3 changes: 1 addition & 2 deletions apps/web/src/stores/spaces/SpaceStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ import SettingsStore from "../../../src/settings/SettingsStore";
import { SettingLevel } from "../../settings/SettingLevel";
import { Action } from "../../dispatcher/actions";
import { MatrixClientPeg } from "../../MatrixClientPeg";
import RoomListStoreV3 from "../room-list-v3/RoomListStoreV3";
import { DefaultTagID } from "../room-list-v3/skip-list/tag";
import { RoomNotificationStateStore } from "../notifications/RoomNotificationStateStore";
import { NotificationLevel } from "../notifications/NotificationLevel";
Expand Down Expand Up @@ -1473,7 +1472,7 @@ describe("SpaceStore", () => {
const state = RoomNotificationStateStore.instance.getRoomState(room);
// @ts-ignore
state._level = NotificationLevel.Notification;
vi.spyOn(RoomListStoreV3.instance, "getSortedRoomsInActiveSpace").mockReturnValue({
vi.spyOn(sdkContext.roomListStore, "getSortedRoomsInActiveSpace").mockReturnValue({
spaceId: MetaSpace.Home,
sections: [{ tag: DefaultTagID.Untagged, rooms: [room] }],
});
Expand Down
3 changes: 1 addition & 2 deletions apps/web/src/stores/spaces/SpaceStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import { logger } from "matrix-js-sdk/src/logger";

import { AsyncStoreWithClient } from "../AsyncStoreWithClient";
import { type MatrixDispatcher } from "../../dispatcher/dispatcher.ts";
import RoomListStoreV3 from "../room-list-v3/RoomListStoreV3";
import SettingsStore from "../../settings/SettingsStore";
import DMRoomMap from "../../utils/DMRoomMap";
import { SpaceNotificationState } from "../notifications/SpaceNotificationState";
Expand Down Expand Up @@ -199,7 +198,7 @@ export default class SpaceStore extends AsyncStoreWithClient<EmptyObject> {
let roomId: string | undefined;
if (space === MetaSpace.Home && this.allRoomsInHome) {
const hasMentions = RoomNotificationStateStore.instance.globalState.hasMentions;
const rooms = RoomListStoreV3.instance.getSortedRoomsInActiveSpace().sections.flatMap((s) => s.rooms);
const rooms = this.sdkContext.roomListStore.getSortedRoomsInActiveSpace().sections.flatMap((s) => s.rooms);
for (const room of rooms) {
const state = RoomNotificationStateStore.instance.getRoomState(room);
if (hasMentions ? state.hasMentions : state.isUnread) {
Expand Down
Loading
Loading