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
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
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
3 changes: 2 additions & 1 deletion src/components/structures/LoggedInView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ import { monitorSyncedPushRules } from "../../utils/pushRules/monitorSyncedPushR
import { type ConfigOptions } from "../../SdkConfig";
import { MatrixClientContextProvider } from "./MatrixClientContextProvider";
import { Landmark, LandmarkNavigation } from "../../accessibility/LandmarkNavigation";
import { SDKContext } from "../../contexts/SDKContext.ts";
import { SDKContext, SdkContextClass } from "../../contexts/SDKContext.ts";

// We need to fetch each pinned message individually (if we don't already have it)
// so each pinned message may trigger a request. Limit the number per room for sanity.
Expand Down Expand Up @@ -690,6 +690,7 @@ class LoggedInView extends React.Component<IProps, IState> {
key={this.props.currentRoomId || "roomview"}
justCreatedOpts={this.props.roomJustCreatedOpts}
forceTimeline={this.props.forceTimeline}
roomViewStore={SdkContextClass.instance.roomViewStore}
/>
);
break;
Expand Down
55 changes: 35 additions & 20 deletions src/components/structures/RoomView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ import { ScopedRoomContextProvider, useScopedRoomContext } from "../../contexts/
import { DeclineAndBlockInviteDialog } from "../views/dialogs/DeclineAndBlockInviteDialog";
import { type FocusMessageSearchPayload } from "../../dispatcher/payloads/FocusMessageSearchPayload.ts";
import { isRoomEncrypted } from "../../hooks/useIsEncrypted";
import { type RoomViewStore } from "../../stores/RoomViewStore.tsx";

const DEBUG = false;
const PREVENT_MULTIPLE_JITSI_WITHIN = 30_000;
Expand All @@ -157,11 +158,14 @@ interface IRoomProps {

// Called with the credentials of a registered user (if they were a ROU that transitioned to PWLU)
onRegistered?(credentials: IMatrixClientCreds): void;
roomViewStore: RoomViewStore;
}

export { MainSplitContentType };

export interface IRoomState {
// The room view store for the room we are displaying
roomViewStore: RoomViewStore;
room?: Room;
roomId?: string;
roomAlias?: string;
Expand Down Expand Up @@ -394,6 +398,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {

const llMembers = context.client.hasLazyLoadMembersEnabled();
this.state = {
roomViewStore: props.roomViewStore,
roomId: undefined,
roomLoading: true,
peekLoading: false,
Expand Down Expand Up @@ -525,7 +530,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
};

private getMainSplitContentType = (room: Room): MainSplitContentType => {
if (this.context.roomViewStore.isViewingCall() || isVideoRoom(room)) {
if (this.state.roomViewStore.isViewingCall() || isVideoRoom(room)) {
return MainSplitContentType.Call;
}
if (this.context.widgetLayoutStore.hasMaximisedWidget(room)) {
Expand All @@ -539,8 +544,8 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
return;
}

const roomLoadError = this.context.roomViewStore.getRoomLoadError() ?? undefined;
if (!initial && !roomLoadError && this.state.roomId !== this.context.roomViewStore.getRoomId()) {
const roomLoadError = this.state.roomViewStore.getRoomLoadError() ?? undefined;
if (!initial && !roomLoadError && this.state.roomId !== this.state.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
Expand All @@ -554,30 +559,38 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
// it was, it means we're about to be unmounted.
return;
}

const roomId = this.context.roomViewStore.getRoomId() ?? null;
const roomViewStore = this.state.roomViewStore;
const roomId = roomViewStore.getRoomId() ?? null;
const roomAlias = roomViewStore.getRoomAlias() ?? undefined;
const roomLoading = roomViewStore.isRoomLoading();
const joining = roomViewStore.isJoining();
const replyToEvent = roomViewStore.getQuotingEvent() ?? undefined;
const shouldPeek = this.state.matrixClientIsReady && roomViewStore.shouldPeek();
const wasContextSwitch = roomViewStore.getWasContextSwitch();
const promptAskToJoin = roomViewStore.promptAskToJoin();
const viewRoomOpts = roomViewStore.getViewRoomOpts();
const room = this.context.client?.getRoom(roomId ?? undefined) ?? undefined;

const newState: Partial<IRoomState> = {
roomId: roomId ?? undefined,
roomAlias: this.context.roomViewStore.getRoomAlias() ?? undefined,
roomLoading: this.context.roomViewStore.isRoomLoading(),
roomAlias: roomAlias,
roomLoading: roomLoading,
roomLoadError,
joining: this.context.roomViewStore.isJoining(),
replyToEvent: this.context.roomViewStore.getQuotingEvent() ?? undefined,
joining: joining,
replyToEvent: replyToEvent,
// we should only peek once we have a ready client
shouldPeek: this.state.matrixClientIsReady && this.context.roomViewStore.shouldPeek(),
shouldPeek: shouldPeek,
showReadReceipts: SettingsStore.getValue("showReadReceipts", roomId),
showRedactions: SettingsStore.getValue("showRedactions", roomId),
showJoinLeaves: SettingsStore.getValue("showJoinLeaves", roomId),
showAvatarChanges: SettingsStore.getValue("showAvatarChanges", roomId),
showDisplaynameChanges: SettingsStore.getValue("showDisplaynameChanges", roomId),
wasContextSwitch: this.context.roomViewStore.getWasContextSwitch(),
wasContextSwitch: wasContextSwitch,
mainSplitContentType: room ? this.getMainSplitContentType(room) : undefined,
initialEventId: undefined, // default to clearing this, will get set later in the method if needed
showRightPanel: roomId ? this.context.rightPanelStore.isOpenForRoom(roomId) : false,
promptAskToJoin: this.context.roomViewStore.promptAskToJoin(),
viewRoomOpts: this.context.roomViewStore.getViewRoomOpts(),
promptAskToJoin: promptAskToJoin,
viewRoomOpts: viewRoomOpts,
};

if (
Expand All @@ -593,7 +606,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
newState.showRightPanel = false;
}

const initialEventId = this.context.roomViewStore.getInitialEventId() ?? this.state.initialEventId;
const initialEventId = this.state.roomViewStore.getInitialEventId() ?? this.state.initialEventId;
if (initialEventId) {
let initialEvent = room?.findEventById(initialEventId);
// The event does not exist in the current sync data
Expand All @@ -619,13 +632,13 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
action: Action.ShowThread,
rootEvent: thread.rootEvent,
initialEvent,
highlighted: this.context.roomViewStore.isInitialEventHighlighted(),
scroll_into_view: this.context.roomViewStore.initialEventScrollIntoView(),
highlighted: this.state.roomViewStore.isInitialEventHighlighted(),
scroll_into_view: this.state.roomViewStore.initialEventScrollIntoView(),
});
} else {
newState.initialEventId = initialEventId;
newState.isInitialEventHighlighted = this.context.roomViewStore.isInitialEventHighlighted();
newState.initialEventScrollIntoView = this.context.roomViewStore.initialEventScrollIntoView();
newState.isInitialEventHighlighted = this.state.roomViewStore.isInitialEventHighlighted();
newState.initialEventScrollIntoView = this.state.roomViewStore.initialEventScrollIntoView();
}
}

Expand Down Expand Up @@ -885,7 +898,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
this.context.client.on(MatrixEventEvent.Decrypted, this.onEventDecrypted);
}
// Start listening for RoomViewStore updates
this.context.roomViewStore.on(UPDATE_EVENT, this.onRoomViewStoreUpdate);
this.state.roomViewStore.on(UPDATE_EVENT, this.onRoomViewStoreUpdate);

this.context.rightPanelStore.on(UPDATE_EVENT, this.onRightPanelStoreUpdate);

Expand Down Expand Up @@ -1002,7 +1015,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {

window.removeEventListener("beforeunload", this.onPageUnload);

this.context.roomViewStore.off(UPDATE_EVENT, this.onRoomViewStoreUpdate);
this.state.roomViewStore.off(UPDATE_EVENT, this.onRoomViewStoreUpdate);

this.context.rightPanelStore.off(UPDATE_EVENT, this.onRightPanelStoreUpdate);
WidgetEchoStore.removeListener(UPDATE_EVENT, this.onWidgetEchoStoreUpdate);
Expand Down Expand Up @@ -1110,6 +1123,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
[payload.file],
roomId,
undefined,
this.state.replyToEvent,
this.context.client,
);
}
Expand Down Expand Up @@ -2029,6 +2043,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
Loading
Loading