Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
38f6e14
Pass roomViewStore to the RoomView and add to the RoomContext.
langleyd Oct 8, 2025
a6adb53
lint
langleyd Oct 8, 2025
6838f53
lint
langleyd Oct 8, 2025
1f3d177
Make constants more DRY
langleyd Oct 8, 2025
bdb3caa
Make constants more DRY
langleyd Oct 8, 2025
f9d780c
Commend non-null assertion on roomViewStore property of the RoomContext
langleyd Oct 22, 2025
4952bd9
Merge branch 'develop' of github.com:vector-im/element-web into langl…
langleyd Oct 22, 2025
6fe4d02
update ContentMessages.ts
langleyd Oct 22, 2025
cd75de3
Add docs, change param order, remove unneeded import.
langleyd Oct 22, 2025
a2272c5
update PlaybackQueue.ts
langleyd Oct 22, 2025
97ffb7e
Update SpaceHierarchy.tsx
langleyd Oct 22, 2025
39604e9
Update ThreadView.tsx
langleyd Oct 22, 2025
0bbbba8
Update useStickyRoomList.tsx
langleyd Oct 22, 2025
a43d8f7
Update RoomCallBanner.tsx
langleyd Oct 22, 2025
a7a1eb6
Update DateSeparator.tsx
langleyd Oct 22, 2025
9ee4272
Update TimelineCard.tsx
langleyd Oct 22, 2025
555c9ae
Update UserInfoBasicOptions
langleyd Oct 22, 2025
2c47f7d
Update useRoomCall.tsx
langleyd Oct 22, 2025
51c6b20
Update slask-commands/utils.ts
langleyd Oct 22, 2025
7354e76
add roomId callback dependencies
langleyd Oct 23, 2025
baca083
Merge branch 'develop' of github.com:vector-im/element-web into langl…
langleyd Oct 23, 2025
90de963
SlashCommands iterate
langleyd Oct 23, 2025
0d9aea4
lint
langleyd Oct 23, 2025
fc4705a
Update PlaybackQueue, MVoiceMessageBody and UserInfoBasicOptionsView …
langleyd Oct 23, 2025
15a846a
Fix DateSeparator test
langleyd Oct 23, 2025
4cde0bd
Update RoomHeader-test.tsx
langleyd Oct 23, 2025
0621fc3
Update RoomCallBanner-test.tsx
langleyd Oct 23, 2025
0a41186
lint
langleyd Oct 23, 2025
484f87d
Add ts docs
langleyd Oct 23, 2025
691b0af
Update utils-test.tsx
langleyd Oct 23, 2025
0a53aed
Update RoomListViewModel-test.tsx
langleyd Oct 23, 2025
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
13 changes: 11 additions & 2 deletions src/ContentMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ import UploadConfirmDialog from "./components/views/dialogs/UploadConfirmDialog"
import { createThumbnail } from "./utils/image-media";
import { attachMentions, attachRelation } from "./utils/messages.ts";
import { doMaybeLocalRoomAction } from "./utils/local-room";
import { SdkContextClass } from "./contexts/SDKContext";
import { blobIsAnimated } from "./utils/Image.ts";

// scraped out of a macOS hidpi (5660ppm) screenshot png
Expand Down Expand Up @@ -428,10 +427,21 @@ export default class ContentMessages {
return this.mediaConfig?.["m.upload.size"] ?? null;
}

/**
* Sends a list of files to a room.
* @param files - The files to send.
* @param roomId - The ID of the room to send the files to.
* @param relation - The relation to the event being replied to.
* @param replyToEvent - The event being replied to, if any.
* @param matrixClient - The Matrix client to use for sending the files.
* @param context - The context in which the files are being sent.
* @returns A promise that resolves when the files have been sent.
*/
public async sendContentListToRoom(
files: File[],
roomId: string,
relation: IEventRelation | undefined,
replyToEvent: MatrixEvent | undefined,
matrixClient: MatrixClient,
context = TimelineRenderingType.Room,
): Promise<void> {
Expand All @@ -440,7 +450,6 @@ export default class ContentMessages {
return;
}

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, undefined, "mx_Dialog_spinner");
Expand Down
9 changes: 5 additions & 4 deletions src/SlashCommands.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -732,8 +732,8 @@ export const Commands = [
new Command({
command: "help",
description: _td("slash_command|help"),
runFn: function () {
Modal.createDialog(SlashCommandHelpDialog);
runFn: function (cli, roomId, threadId, args) {
Modal.createDialog(SlashCommandHelpDialog, { roomId });
return success();
},
category: CommandCategories.advanced,
Expand Down Expand Up @@ -967,14 +967,15 @@ interface ICmd {

/**
* Process the given text for /commands and returns a parsed command that can be used for running the operation.
* @param {string} roomId The room ID where the command was issued.
* @param {string} input The raw text input by the user.
* @return {ICmd} The parsed command object.
* Returns an empty object if the input didn't match a command.
*/
export function getCommand(input: string): ICmd {
export function getCommand(roomId: string, input: string): ICmd {
const { cmd, args } = parseCommandString(input);

if (cmd && CommandMap.has(cmd) && CommandMap.get(cmd)!.isEnabled(MatrixClientPeg.get())) {
if (cmd && CommandMap.has(cmd) && CommandMap.get(cmd)!.isEnabled(MatrixClientPeg.get(), roomId)) {
return {
cmd: CommandMap.get(cmd),
args,
Expand Down
24 changes: 19 additions & 5 deletions src/audio/PlaybackQueue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { MatrixClientPeg } from "../MatrixClientPeg";
import { arrayFastClone } from "../utils/arrays";
import { PlaybackManager } from "./PlaybackManager";
import { isVoiceMessage } from "../utils/EventUtils";
import { SdkContextClass } from "../contexts/SDKContext";
import { type RoomViewStore } from "../stores/RoomViewStore";

/**
* Audio playback queue management for a given room. This keeps track of where the user
Expand All @@ -38,10 +38,18 @@ export class PlaybackQueue {
private currentPlaybackId: string | null = null; // event ID, broken out from above for ease of use
private recentFullPlays = new Set<string>(); // event IDs

public constructor(private room: Room) {
/**
* Create a PlaybackQueue for a given room.
* @param room The room
* @param roomViewStore The RoomViewStore instance
*/
public constructor(
private room: Room,
private roomViewStore: RoomViewStore,
) {
this.loadClocks();

SdkContextClass.instance.roomViewStore.addRoomListener(this.room.roomId, (isActive) => {
this.roomViewStore.addRoomListener(this.room.roomId, (isActive) => {
if (!isActive) return;

// Reset the state of the playbacks before they start mounting and enqueuing updates.
Expand All @@ -53,14 +61,20 @@ export class PlaybackQueue {
});
}

public static forRoom(roomId: string): PlaybackQueue {
/**
* Get the PlaybackQueue for a given room, creating it if necessary.
* @param roomId The ID of the room
* @param roomViewStore The RoomViewStore instance
* @returns The PlaybackQueue for the room
*/
public static forRoom(roomId: string, roomViewStore: RoomViewStore): PlaybackQueue {
const cli = MatrixClientPeg.safeGet();
const room = cli.getRoom(roomId);
if (!room) throw new Error("Unknown room");
if (PlaybackQueue.queues.has(room.roomId)) {
return PlaybackQueue.queues.get(room.roomId)!;
}
const queue = new PlaybackQueue(room);
const queue = new PlaybackQueue(room, roomViewStore);
PlaybackQueue.queues.set(room.roomId, queue);
return queue;
}
Expand Down
7 changes: 4 additions & 3 deletions src/autocomplete/CommandProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@ const COMMAND_RE = /(^\/\w*)(?: .*)?/g;

export default class CommandProvider extends AutocompleteProvider {
public matcher: QueryMatcher<Command>;

private room: Room;
public constructor(room: Room, renderingType?: TimelineRenderingType) {
super({ commandRegex: COMMAND_RE, renderingType });
this.matcher = new QueryMatcher(Commands, {
keys: ["command", "args", "description"],
funcs: [({ aliases }) => aliases.join(" ")], // aliases
context: renderingType,
});
this.room = room;
}

public async getCompletions(
Expand All @@ -51,7 +52,7 @@ export default class CommandProvider extends AutocompleteProvider {
if (command[0] !== command[1]) {
// The input looks like a command with arguments, perform exact match
const name = command[1].slice(1); // strip leading `/`
if (CommandMap.has(name) && CommandMap.get(name)!.isEnabled(cli)) {
if (CommandMap.has(name) && CommandMap.get(name)!.isEnabled(cli, this.room.roomId)) {
// some commands, namely `me` don't suit having the usage shown whilst typing their arguments
if (CommandMap.get(name)!.hideCompletionAfterSpace) return [];
matches = [CommandMap.get(name)!];
Expand All @@ -70,7 +71,7 @@ export default class CommandProvider extends AutocompleteProvider {
return matches
.filter((cmd) => {
const display = !cmd.renderingTypes || cmd.renderingTypes.includes(this.renderingType);
return cmd.isEnabled(cli) && display;
return cmd.isEnabled(cli, this.room.roomId) && display;
})
.map((result) => {
let completion = result.getCommand() + " ";
Expand Down
2 changes: 2 additions & 0 deletions src/components/structures/RoomView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1128,6 +1128,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
[payload.file],
roomId,
undefined,
this.state.replyToEvent,
this.context.client,
);
}
Expand Down Expand Up @@ -2047,6 +2048,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
Array.from(dataTransfer.files),
roomId,
undefined,
this.state.replyToEvent,
this.context.client,
TimelineRenderingType.Room,
);
Expand Down
27 changes: 21 additions & 6 deletions src/components/structures/SpaceHierarchy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,11 @@ import { type JoinRoomReadyPayload } from "../../dispatcher/payloads/JoinRoomRea
import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
import { getKeyBindingsManager } from "../../KeyBindingsManager";
import { getTopic } from "../../hooks/room/useTopic";
import { SdkContextClass } from "../../contexts/SDKContext";
import { getDisplayAliasForAliasSet } from "../../Rooms";
import SettingsStore from "../../settings/SettingsStore";
import { filterBoolean } from "../../utils/arrays.ts";
import { type RoomViewStore } from "../../stores/RoomViewStore.tsx";
import RoomContext from "../../contexts/RoomContext.ts";

interface IProps {
space: Room;
Expand Down Expand Up @@ -404,7 +405,20 @@ export const showRoom = (cli: MatrixClient, hierarchy: RoomHierarchy, roomId: st
});
};

export const joinRoom = async (cli: MatrixClient, hierarchy: RoomHierarchy, roomId: string): Promise<unknown> => {
/**
* Join a room.
* @param cli The Matrix client
* @param roomViewStore The RoomViewStore instance
* @param hierarchy The RoomHierarchy instance
* @param roomId The ID of the room to join
* @returns A promise that resolves when the room has been joined
*/
export const joinRoom = async (
cli: MatrixClient,
roomViewStore: RoomViewStore,
hierarchy: RoomHierarchy,
roomId: string,
): Promise<unknown> => {
// Don't let the user view a room they won't be able to either peek or join:
// fail earlier so they don't have to click back to the directory.
if (cli.isGuest()) {
Expand All @@ -418,10 +432,10 @@ export const joinRoom = async (cli: MatrixClient, hierarchy: RoomHierarchy, room
});
} catch (err: unknown) {
if (err instanceof MatrixError) {
SdkContextClass.instance.roomViewStore.showJoinRoomError(err, roomId);
roomViewStore.showJoinRoomError(err, roomId);
} else {
logger.warn("Got a non-MatrixError while joining room", err);
SdkContextClass.instance.roomViewStore.showJoinRoomError(
roomViewStore.showJoinRoomError(
new MatrixError({
error: _t("error|unknown"),
}),
Expand Down Expand Up @@ -761,6 +775,7 @@ const ManageButtons: React.FC<IManageButtonsProps> = ({ hierarchy, selected, set

const SpaceHierarchy: React.FC<IProps> = ({ space, initialText = "", showRoom, additionalButtons }) => {
const cli = useContext(MatrixClientContext);
const roomContext = useContext(RoomContext);
const [query, setQuery] = useState(initialText);

const [selected, setSelected] = useState(new Map<string, Set<string>>()); // Map<parentId, Set<childId>>
Expand Down Expand Up @@ -855,10 +870,10 @@ const SpaceHierarchy: React.FC<IProps> = ({ space, initialText = "", showRoom, a
onJoinRoomClick={async (roomId, parents) => {
for (const parent of parents) {
if (cli.getRoom(parent)?.getMyMembership() !== KnownMembership.Join) {
await joinRoom(cli, hierarchy, parent);
await joinRoom(cli, roomContext.roomViewStore, hierarchy, parent);
}
}
await joinRoom(cli, hierarchy, roomId);
await joinRoom(cli, roomContext.roomViewStore, hierarchy, roomId);
}}
/>
</>
Expand Down
4 changes: 2 additions & 2 deletions src/components/structures/ThreadView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ import { type ButtonEvent } from "../views/elements/AccessibleButton";
import Spinner from "../views/elements/Spinner";
import { type ComposerInsertPayload, ComposerType } from "../../dispatcher/payloads/ComposerInsertPayload";
import Heading from "../views/typography/Heading";
import { SdkContextClass } from "../../contexts/SDKContext";
import { type ThreadPayload } from "../../dispatcher/payloads/ThreadPayload";
import { ScopedRoomContextProvider } from "../../contexts/ScopedRoomContext.tsx";

Expand Down Expand Up @@ -124,7 +123,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
const roomId = this.props.mxEvent.getRoomId();
SettingsStore.unwatchSetting(this.layoutWatcherRef);

const hasRoomChanged = SdkContextClass.instance.roomViewStore.getRoomId() !== roomId;
const hasRoomChanged = this.context.roomViewStore.getRoomId() !== roomId;
if (this.props.initialEvent && !hasRoomChanged) {
dis.dispatch<ViewRoomPayload>({
action: Action.ViewRoom,
Expand Down Expand Up @@ -334,6 +333,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
Array.from(dataTransfer.files),
roomId,
this.threadRelation,
this.context.replyToEvent,
MatrixClientPeg.safeGet(),
TimelineRenderingType.Thread,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import PosthogTrackers from "../../../../PosthogTrackers";
import { ShareDialog } from "../../../views/dialogs/ShareDialog";
import { type ComposerInsertPayload } from "../../../../dispatcher/payloads/ComposerInsertPayload";
import { Action } from "../../../../dispatcher/actions";
import { SdkContextClass } from "../../../../contexts/SDKContext";
import { TimelineRenderingType } from "../../../../contexts/RoomContext";
import MultiInviter from "../../../../utils/MultiInviter";
import { type ViewRoomPayload } from "../../../../dispatcher/payloads/ViewRoomPayload";
Expand All @@ -41,7 +40,7 @@ export interface UserInfoBasicOptionsState {
// Method called when a share user button is clicked, will display modal with profile to share
onShareUserClick: () => void;
// Method called when a invite button is clicked, will display modal to invite user
onInviteUserButton: (evt: Event) => Promise<void>;
onInviteUserButton: (roomId: string, evt: Event) => Promise<void>;
// Method called when the DM button is clicked, will open a DM with the selected member
onOpenDmForUser: (member: Member) => Promise<void>;
}
Expand Down Expand Up @@ -91,15 +90,12 @@ export const useUserInfoBasicOptionsViewModel = (room: Room, member: User | Room
});
};

const onInviteUserButton = async (ev: Event): Promise<void> => {
const onInviteUserButton = async (roomId: string, ev: Event): Promise<void> => {
try {
const roomId =
member instanceof RoomMember && member.roomId
? member.roomId
: SdkContextClass.instance.roomViewStore.getRoomId();
const memberOrRoomRoomId = member instanceof RoomMember && member.roomId ? member.roomId : roomId;

// We use a MultiInviter to re-use the invite logic, even though we're only inviting one user.
const inviter = new MultiInviter(cli, roomId || "");
const inviter = new MultiInviter(cli, memberOrRoomRoomId || "");
await inviter.invite([member.userId]).then(() => {
if (inviter.getCompletionState(member.userId) !== "invited") {
const errorStringFromInviterUtility = inviter.getErrorText(member.userId);
Expand All @@ -108,7 +104,7 @@ export const useUserInfoBasicOptionsViewModel = (room: Room, member: User | Room
} else {
throw new UserFriendlyError("slash_command|invite_failed", {
user: member.userId,
roomId,
memberOrRoomRoomId,
cause: undefined,
});
}
Expand Down
9 changes: 5 additions & 4 deletions src/components/viewmodels/roomlist/useStickyRoomList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@

import { useCallback, useEffect, useRef, useState } from "react";

import { SdkContextClass } from "../../../contexts/SDKContext";
import { useDispatcher } from "../../../hooks/useDispatcher";
import dispatcher from "../../../dispatcher/dispatcher";
import { Action } from "../../../dispatcher/actions";
import type { Room } from "matrix-js-sdk/src/matrix";
import type { Optional } from "matrix-events-sdk";
import SpaceStore from "../../../stores/spaces/SpaceStore";
import { type RoomsResult } from "../../../stores/room-list-v3/RoomListStoreV3";
import { useScopedRoomContext } from "../../../contexts/ScopedRoomContext";

function getIndexByRoomId(rooms: Room[], roomId: Optional<string>): number | undefined {
const index = rooms.findIndex((room) => room.roomId === roomId);
Expand Down Expand Up @@ -87,8 +87,9 @@ export interface StickyRoomListResult {
* @see {@link StickyRoomListResult} details what this hook returns..
*/
export function useStickyRoomList(roomsResult: RoomsResult): StickyRoomListResult {
const { roomId } = useScopedRoomContext("roomId");
const [listState, setListState] = useState<StickyRoomListResult>({
activeIndex: getIndexByRoomId(roomsResult.rooms, SdkContextClass.instance.roomViewStore.getRoomId()),
activeIndex: getIndexByRoomId(roomsResult.rooms, roomId),
roomsResult: roomsResult,
});

Expand All @@ -97,7 +98,7 @@ export function useStickyRoomList(roomsResult: RoomsResult): StickyRoomListResul
const updateRoomsAndIndex = useCallback(
(newRoomId: string | null, isRoomChange: boolean = false) => {
setListState((current) => {
const activeRoomId = newRoomId ?? SdkContextClass.instance.roomViewStore.getRoomId();
const activeRoomId = newRoomId ?? roomId;
const newActiveIndex = getIndexByRoomId(roomsResult.rooms, activeRoomId);
const oldIndex = current.activeIndex;
const { newIndex, newRooms } = getRoomsWithStickyRoom(
Expand All @@ -109,7 +110,7 @@ export function useStickyRoomList(roomsResult: RoomsResult): StickyRoomListResul
return { activeIndex: newIndex, roomsResult: { ...roomsResult, rooms: newRooms } };
});
},
[roomsResult],
[roomsResult, roomId],
);

// Re-calculate the index when the active room has changed.
Expand Down
6 changes: 3 additions & 3 deletions src/components/views/beacon/RoomCallBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { useCall } from "../../../hooks/useCall";
import { useEventEmitterState } from "../../../hooks/useEventEmitter";
import { OwnBeaconStore, OwnBeaconStoreEvent } from "../../../stores/OwnBeaconStore";
import { SessionDuration } from "../voip/CallDuration";
import { SdkContextClass } from "../../../contexts/SDKContext";
import { useScopedRoomContext } from "../../../contexts/ScopedRoomContext";

interface RoomCallBannerProps {
roomId: Room["roomId"];
Expand Down Expand Up @@ -83,7 +83,7 @@ interface Props {

const RoomCallBanner: React.FC<Props> = ({ roomId }) => {
const call = useCall(roomId);

const { roomViewStore } = useScopedRoomContext("roomViewStore");
// this section is to check if we have a live location share. If so, we dont show the call banner
const isMonitoringLiveLocation = useEventEmitterState(
OwnBeaconStore.instance,
Expand All @@ -100,7 +100,7 @@ const RoomCallBanner: React.FC<Props> = ({ roomId }) => {
}

// Check if the call is already showing. No banner is needed in this case.
if (SdkContextClass.instance.roomViewStore.isViewingCall()) {
if (roomViewStore.isViewingCall()) {
return null;
}

Expand Down
Loading
Loading