Remove virtual rooms (#29635)

* Remove virtual rooms from the timelinePanel and RoomView

* Remove VoipUserMapper

* Remove some unneeded imports

* Remove tovirtual slash command test

* Remove getSupportsVirtualRooms and virtualLookup

* lint

* Remove PROTOCOL_SIP_NATIVE

* Remove native/virtual looks fields and fix tests

* Remove unused lookup fields
This commit is contained in:
David Langley
2025-04-16 09:36:34 +01:00
committed by GitHub
parent 8b06714a02
commit aa821a5b6f
18 changed files with 68 additions and 1047 deletions

View File

@@ -120,8 +120,6 @@ import { isVideoRoom } from "../../utils/video-rooms";
import { SDKContext } from "../../contexts/SDKContext";
import { RoomSearchView } from "./RoomSearchView";
import eventSearch, { type SearchInfo, SearchScope } from "../../Searching";
import VoipUserMapper from "../../VoipUserMapper";
import { isCallEvent } from "./LegacyCallEventGrouper";
import { WidgetType } from "../../widgets/WidgetType";
import WidgetUtils from "../../utils/WidgetUtils";
import { shouldEncryptRoomWithSingle3rdPartyInvite } from "../../utils/room/shouldEncryptRoomWithSingle3rdPartyInvite";
@@ -165,7 +163,6 @@ export { MainSplitContentType };
export interface IRoomState {
room?: Room;
virtualRoom?: Room;
roomId?: string;
roomAlias?: string;
roomLoading: boolean;
@@ -1344,12 +1341,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
return this.messagePanel.canResetTimeline();
};
private loadVirtualRoom = async (room?: Room): Promise<void> => {
const virtualRoom = room?.roomId && (await VoipUserMapper.sharedInstance().getVirtualRoomForRoom(room?.roomId));
this.setState({ virtualRoom: virtualRoom || undefined });
};
// called when state.room is first initialised (either at initial load,
// after a successful peek, or after we join the room).
private onRoomLoaded = (room: Room): void => {
@@ -1362,7 +1353,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
this.calculateRecommendedVersion(room);
this.updatePermissions(room);
this.checkWidgets(room);
this.loadVirtualRoom(room);
this.updateRoomEncrypted(room);
if (
@@ -2444,8 +2434,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
<TimelinePanel
ref={this.gatherTimelinePanelRef}
timelineSet={this.state.room.getUnfilteredTimelineSet()}
overlayTimelineSet={this.state.virtualRoom?.getUnfilteredTimelineSet()}
overlayTimelineSetFilter={isCallEvent}
showReadReceipts={this.state.showReadReceipts}
manageReadReceipts={!this.state.isPeeking}
sendReadReceiptOnLoad={!this.state.wasContextSwitch}

View File

@@ -30,7 +30,7 @@ import {
ThreadEvent,
ReceiptType,
} from "matrix-js-sdk/src/matrix";
import { debounce, findLastIndex } from "lodash";
import { debounce } from "lodash";
import { logger } from "matrix-js-sdk/src/logger";
import SettingsStore from "../../settings/SettingsStore";
@@ -73,25 +73,12 @@ const debuglog = (...args: any[]): void => {
}
};
const overlaysBefore = (overlayEvent: MatrixEvent, mainEvent: MatrixEvent): boolean =>
overlayEvent.localTimestamp < mainEvent.localTimestamp;
const overlaysAfter = (overlayEvent: MatrixEvent, mainEvent: MatrixEvent): boolean =>
overlayEvent.localTimestamp >= mainEvent.localTimestamp;
interface IProps {
// The js-sdk EventTimelineSet object for the timeline sequence we are
// representing. This may or may not have a room, depending on what it's
// a timeline representing. If it has a room, we maintain RRs etc for
// that room.
timelineSet: EventTimelineSet;
// overlay events from a second timelineset on the main timeline
// added to support virtual rooms
// events from the overlay timeline set will be added by localTimestamp
// into the main timeline
overlayTimelineSet?: EventTimelineSet;
// filter events from overlay timeline
overlayTimelineSetFilter?: (event: MatrixEvent) => boolean;
showReadReceipts?: boolean;
// Enable managing RRs and RMs. These require the timelineSet to have a room.
manageReadReceipts?: boolean;
@@ -251,7 +238,6 @@ class TimelinePanel extends React.Component<IProps, IState> {
private readonly messagePanel = createRef<MessagePanel>();
private dispatcherRef?: string;
private timelineWindow?: TimelineWindow;
private overlayTimelineWindow?: TimelineWindow;
private unmounted = false;
private readReceiptActivityTimer: Timer | null = null;
private readMarkerActivityTimer: Timer | null = null;
@@ -349,16 +335,12 @@ class TimelinePanel extends React.Component<IProps, IState> {
const differentEventId = prevProps.eventId != this.props.eventId;
const differentHighlightedEventId = prevProps.highlightedEventId != this.props.highlightedEventId;
const differentAvoidJump = prevProps.eventScrollIntoView && !this.props.eventScrollIntoView;
const differentOverlayTimeline = prevProps.overlayTimelineSet !== this.props.overlayTimelineSet;
if (differentEventId || differentHighlightedEventId || differentAvoidJump) {
logger.log(
`TimelinePanel switching to eventId ${this.props.eventId} (was ${prevProps.eventId}), ` +
`scrollIntoView: ${this.props.eventScrollIntoView} (was ${prevProps.eventScrollIntoView})`,
);
this.initTimeline(this.props);
} else if (differentOverlayTimeline) {
logger.log(`TimelinePanel updating overlay timeline.`);
this.initTimeline(this.props);
}
}
@@ -509,24 +491,9 @@ class TimelinePanel extends React.Component<IProps, IState> {
// this particular event should be the first or last to be unpaginated.
const eventId = scrollToken;
// The event in question could belong to either the main timeline or
// overlay timeline; let's check both
const mainEvents = this.timelineWindow!.getEvents();
const overlayEvents = this.overlayTimelineWindow?.getEvents() ?? [];
let marker = mainEvents.findIndex((ev) => ev.getId() === eventId);
let overlayMarker: number;
if (marker === -1) {
// The event must be from the overlay timeline instead
overlayMarker = overlayEvents.findIndex((ev) => ev.getId() === eventId);
marker = backwards
? findLastIndex(mainEvents, (ev) => overlaysAfter(overlayEvents[overlayMarker], ev))
: mainEvents.findIndex((ev) => overlaysBefore(overlayEvents[overlayMarker], ev));
} else {
overlayMarker = backwards
? findLastIndex(overlayEvents, (ev) => overlaysBefore(ev, mainEvents[marker]))
: overlayEvents.findIndex((ev) => overlaysAfter(ev, mainEvents[marker]));
}
const marker = mainEvents.findIndex((ev) => ev.getId() === eventId);
// The number of events to unpaginate from the main timeline
let count: number;
@@ -536,24 +503,11 @@ class TimelinePanel extends React.Component<IProps, IState> {
count = backwards ? marker + 1 : mainEvents.length - marker;
}
// The number of events to unpaginate from the overlay timeline
let overlayCount: number;
if (overlayMarker === -1) {
overlayCount = 0;
} else {
overlayCount = backwards ? overlayMarker + 1 : overlayEvents.length - overlayMarker;
}
if (count > 0) {
debuglog("Unpaginating", count, "in direction", dir);
this.timelineWindow!.unpaginate(count, backwards);
}
if (overlayCount > 0) {
debuglog("Unpaginating", count, "from overlay timeline in direction", dir);
this.overlayTimelineWindow!.unpaginate(overlayCount, backwards);
}
const { events, liveEvents } = this.getEvents();
this.buildLegacyCallEventGroupers(events);
this.setState({
@@ -610,10 +564,6 @@ class TimelinePanel extends React.Component<IProps, IState> {
return false;
}
if (this.overlayTimelineWindow) {
await this.extendOverlayWindowToCoverMainWindow();
}
debuglog("paginate complete backwards:" + backwards + "; success:" + r);
const { events, liveEvents } = this.getEvents();
@@ -705,10 +655,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
data: IRoomTimelineData,
): void => {
// ignore events for other timeline sets
if (
data.timeline.getTimelineSet() !== this.props.timelineSet &&
data.timeline.getTimelineSet() !== this.props.overlayTimelineSet
) {
if (data.timeline.getTimelineSet() !== this.props.timelineSet) {
return;
}
@@ -748,69 +695,60 @@ class TimelinePanel extends React.Component<IProps, IState> {
// timeline window.
//
// see https://github.com/vector-im/vector-web/issues/1035
this.timelineWindow!.paginate(EventTimeline.FORWARDS, 1, false)
.then(() => {
if (this.overlayTimelineWindow) {
return this.overlayTimelineWindow.paginate(EventTimeline.FORWARDS, 1, false);
this.timelineWindow!.paginate(EventTimeline.FORWARDS, 1, false).then(() => {
if (this.unmounted) {
return;
}
const { events, liveEvents } = this.getEvents();
this.buildLegacyCallEventGroupers(events);
const lastLiveEvent = liveEvents[liveEvents.length - 1];
const updatedState: Partial<IState> = {
events,
liveEvents,
};
let callRMUpdated = false;
if (this.props.manageReadMarkers) {
// when a new event arrives when the user is not watching the
// window, but the window is in its auto-scroll mode, make sure the
// read marker is visible.
//
// We ignore events we have sent ourselves; we don't want to see the
// read-marker when a remote echo of an event we have just sent takes
// more than the timeout on userActiveRecently.
//
const myUserId = MatrixClientPeg.safeGet().credentials.userId;
callRMUpdated = false;
if (ev.getSender() !== myUserId && !UserActivity.sharedInstance().userActiveRecently()) {
updatedState.readMarkerVisible = true;
} else if (lastLiveEvent && this.getReadMarkerPosition() === 0) {
// we know we're stuckAtBottom, so we can advance the RM
// immediately, to save a later render cycle
this.setReadMarker(lastLiveEvent.getId() ?? null, lastLiveEvent.getTs(), true);
updatedState.readMarkerVisible = false;
updatedState.readMarkerEventId = lastLiveEvent.getId();
callRMUpdated = true;
}
})
.then(() => {
if (this.unmounted) {
return;
}
this.setState(updatedState as IState, () => {
this.messagePanel.current?.updateTimelineMinHeight();
if (callRMUpdated) {
this.props.onReadMarkerUpdated?.();
}
const { events, liveEvents } = this.getEvents();
this.buildLegacyCallEventGroupers(events);
const lastLiveEvent = liveEvents[liveEvents.length - 1];
const updatedState: Partial<IState> = {
events,
liveEvents,
};
let callRMUpdated = false;
if (this.props.manageReadMarkers) {
// when a new event arrives when the user is not watching the
// window, but the window is in its auto-scroll mode, make sure the
// read marker is visible.
//
// We ignore events we have sent ourselves; we don't want to see the
// read-marker when a remote echo of an event we have just sent takes
// more than the timeout on userActiveRecently.
//
const myUserId = MatrixClientPeg.safeGet().credentials.userId;
callRMUpdated = false;
if (ev.getSender() !== myUserId && !UserActivity.sharedInstance().userActiveRecently()) {
updatedState.readMarkerVisible = true;
} else if (lastLiveEvent && this.getReadMarkerPosition() === 0) {
// we know we're stuckAtBottom, so we can advance the RM
// immediately, to save a later render cycle
this.setReadMarker(lastLiveEvent.getId() ?? null, lastLiveEvent.getTs(), true);
updatedState.readMarkerVisible = false;
updatedState.readMarkerEventId = lastLiveEvent.getId();
callRMUpdated = true;
}
}
this.setState(updatedState as IState, () => {
this.messagePanel.current?.updateTimelineMinHeight();
if (callRMUpdated) {
this.props.onReadMarkerUpdated?.();
}
});
});
});
};
private hasTimelineSetFor(roomId: string | undefined): boolean {
return (
(roomId !== undefined && roomId === this.props.timelineSet.room?.roomId) ||
roomId === this.props.overlayTimelineSet?.room?.roomId
);
return roomId !== undefined && roomId === this.props.timelineSet.room?.roomId;
}
private onRoomTimelineReset = (room: Room | undefined, timelineSet: EventTimelineSet): void => {
if (timelineSet !== this.props.timelineSet && timelineSet !== this.props.overlayTimelineSet) return;
if (timelineSet !== this.props.timelineSet) return;
if (this.canResetTimeline()) {
this.loadTimeline();
@@ -1475,48 +1413,6 @@ class TimelinePanel extends React.Component<IProps, IState> {
});
}
private async extendOverlayWindowToCoverMainWindow(): Promise<void> {
const mainWindow = this.timelineWindow!;
const overlayWindow = this.overlayTimelineWindow!;
const mainEvents = mainWindow.getEvents();
if (mainEvents.length > 0) {
let paginationRequests: Promise<unknown>[];
// Keep paginating until the main window is covered
do {
paginationRequests = [];
const overlayEvents = overlayWindow.getEvents();
if (
overlayWindow.canPaginate(EventTimeline.BACKWARDS) &&
(overlayEvents.length === 0 ||
overlaysAfter(overlayEvents[0], mainEvents[0]) ||
!mainWindow.canPaginate(EventTimeline.BACKWARDS))
) {
// Paginating backwards could reveal more events to be overlaid in the main window
paginationRequests.push(
this.onPaginationRequest(overlayWindow, EventTimeline.BACKWARDS, PAGINATE_SIZE),
);
}
if (
overlayWindow.canPaginate(EventTimeline.FORWARDS) &&
(overlayEvents.length === 0 ||
overlaysBefore(overlayEvents.at(-1)!, mainEvents.at(-1)!) ||
!mainWindow.canPaginate(EventTimeline.FORWARDS))
) {
// Paginating forwards could reveal more events to be overlaid in the main window
paginationRequests.push(
this.onPaginationRequest(overlayWindow, EventTimeline.FORWARDS, PAGINATE_SIZE),
);
}
await Promise.all(paginationRequests);
} while (paginationRequests.length > 0);
}
}
/**
* (re)-load the event timeline, and initialise the scroll state, centered
* around the given event.
@@ -1536,9 +1432,6 @@ class TimelinePanel extends React.Component<IProps, IState> {
private loadTimeline(eventId?: string, pixelOffset?: number, offsetBase?: number, scrollIntoView = true): void {
const cli = MatrixClientPeg.safeGet();
this.timelineWindow = new TimelineWindow(cli, this.props.timelineSet, { windowLimit: this.props.timelineCap });
this.overlayTimelineWindow = this.props.overlayTimelineSet
? new TimelineWindow(cli, this.props.overlayTimelineSet, { windowLimit: this.props.timelineCap })
: undefined;
const onLoaded = (): void => {
if (this.unmounted) return;
@@ -1554,14 +1447,8 @@ class TimelinePanel extends React.Component<IProps, IState> {
this.setState(
{
canBackPaginate:
(this.timelineWindow?.canPaginate(EventTimeline.BACKWARDS) ||
this.overlayTimelineWindow?.canPaginate(EventTimeline.BACKWARDS)) ??
false,
canForwardPaginate:
(this.timelineWindow?.canPaginate(EventTimeline.FORWARDS) ||
this.overlayTimelineWindow?.canPaginate(EventTimeline.FORWARDS)) ??
false,
canBackPaginate: this.timelineWindow?.canPaginate(EventTimeline.BACKWARDS) ?? false,
canForwardPaginate: this.timelineWindow?.canPaginate(EventTimeline.FORWARDS) ?? false,
timelineLoading: false,
},
() => {
@@ -1636,7 +1523,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
// This is a hot-path optimization by skipping a promise tick
// by repeating a no-op sync branch in
// TimelineSet.getTimelineForEvent & MatrixClient.getEventTimeline
if (this.props.timelineSet.getTimelineForEvent(eventId) && !this.overlayTimelineWindow) {
if (this.props.timelineSet.getTimelineForEvent(eventId)) {
// if we've got an eventId, and the timeline exists, we can skip
// the promise tick.
this.timelineWindow.load(eventId, INITIAL_SIZE);
@@ -1645,14 +1532,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
return;
}
const prom = this.timelineWindow.load(eventId, INITIAL_SIZE).then(async (): Promise<void> => {
if (this.overlayTimelineWindow) {
// TODO: use timestampToEvent to load the overlay timeline
// with more correct position when main TL eventId is truthy
await this.overlayTimelineWindow.load(undefined, INITIAL_SIZE);
await this.extendOverlayWindowToCoverMainWindow();
}
});
const prom = this.timelineWindow.load(eventId, INITIAL_SIZE);
this.buildLegacyCallEventGroupers();
this.setState({
events: [],
@@ -1683,38 +1563,9 @@ class TimelinePanel extends React.Component<IProps, IState> {
this.reloadEvents();
}
// get the list of events from the timeline windows and the pending event list
// get the list of events from the timeline window and the pending event list
private getEvents(): Pick<IState, "events" | "liveEvents"> {
const mainEvents = this.timelineWindow!.getEvents();
let overlayEvents = this.overlayTimelineWindow?.getEvents() ?? [];
if (this.props.overlayTimelineSetFilter !== undefined) {
overlayEvents = overlayEvents.filter(this.props.overlayTimelineSetFilter);
}
// maintain the main timeline event order as returned from the HS
// merge overlay events at approximately the right position based on local timestamp
const events = overlayEvents.reduce(
(acc: MatrixEvent[], overlayEvent: MatrixEvent) => {
// find the first main tl event with a later timestamp
const index = acc.findIndex((event) => overlaysBefore(overlayEvent, event));
// insert overlay event into timeline at approximately the right place
// if it's beyond the edge of the main window, hide it so that expanding
// the main window doesn't cause new events to pop in and change its position
if (index === -1) {
if (!this.timelineWindow!.canPaginate(EventTimeline.FORWARDS)) {
acc.push(overlayEvent);
}
} else if (index === 0) {
if (!this.timelineWindow!.canPaginate(EventTimeline.BACKWARDS)) {
acc.unshift(overlayEvent);
}
} else {
acc.splice(index, 0, overlayEvent);
}
return acc;
},
[...mainEvents],
);
const events = this.timelineWindow!.getEvents();
// We want the last event to be decrypted first
const client = MatrixClientPeg.safeGet();