Sort rooms in the add existing to space dialog based on recency

This commit is contained in:
Michael Telatynski 2021-04-28 23:39:24 +01:00
parent d203e8f129
commit bed52319bc
2 changed files with 77 additions and 70 deletions

View file

@ -21,79 +21,83 @@ import { MatrixClientPeg } from "../../../../MatrixClientPeg";
import * as Unread from "../../../../Unread";
import { EffectiveMembership, getEffectiveMembership } from "../../../../utils/membership";
export const sortRooms = (rooms: Room[]): Room[] => {
// We cache the timestamp lookup to avoid iterating forever on the timeline
// of events. This cache only survives a single sort though.
// We wouldn't need this if `.sort()` didn't constantly try and compare all
// of the rooms to each other.
// TODO: We could probably improve the sorting algorithm here by finding changes.
// See https://github.com/vector-im/element-web/issues/14459
// For example, if we spent a little bit of time to determine which elements have
// actually changed (probably needs to be done higher up?) then we could do an
// insertion sort or similar on the limited set of changes.
// TODO: Don't assume we're using the same client as the peg
// See https://github.com/vector-im/element-web/issues/14458
let myUserId = '';
if (MatrixClientPeg.get()) {
myUserId = MatrixClientPeg.get().getUserId();
}
const tsCache: { [roomId: string]: number } = {};
const getLastTs = (r: Room) => {
if (tsCache[r.roomId]) {
return tsCache[r.roomId];
}
const ts = (() => {
// Apparently we can have rooms without timelines, at least under testing
// environments. Just return MAX_INT when this happens.
if (!r || !r.timeline) {
return Number.MAX_SAFE_INTEGER;
}
// If the room hasn't been joined yet, it probably won't have a timeline to
// parse. We'll still fall back to the timeline if this fails, but chances
// are we'll at least have our own membership event to go off of.
const effectiveMembership = getEffectiveMembership(r.getMyMembership());
if (effectiveMembership !== EffectiveMembership.Join) {
const membershipEvent = r.currentState.getStateEvents("m.room.member", myUserId);
if (membershipEvent && !Array.isArray(membershipEvent)) {
return membershipEvent.getTs();
}
}
for (let i = r.timeline.length - 1; i >= 0; --i) {
const ev = r.timeline[i];
if (!ev.getTs()) continue; // skip events that don't have timestamps (tests only?)
if (ev.getSender() === myUserId || Unread.eventTriggersUnreadCount(ev)) {
return ev.getTs();
}
}
// we might only have events that don't trigger the unread indicator,
// in which case use the oldest event even if normally it wouldn't count.
// This is better than just assuming the last event was forever ago.
if (r.timeline.length && r.timeline[0].getTs()) {
return r.timeline[0].getTs();
} else {
return Number.MAX_SAFE_INTEGER;
}
})();
tsCache[r.roomId] = ts;
return ts;
};
return rooms.sort((a, b) => {
return getLastTs(b) - getLastTs(a);
});
};
/**
* Sorts rooms according to the last event's timestamp in each room that seems
* useful to the user.
*/
export class RecentAlgorithm implements IAlgorithm {
public async sortRooms(rooms: Room[], tagId: TagID): Promise<Room[]> {
// We cache the timestamp lookup to avoid iterating forever on the timeline
// of events. This cache only survives a single sort though.
// We wouldn't need this if `.sort()` didn't constantly try and compare all
// of the rooms to each other.
// TODO: We could probably improve the sorting algorithm here by finding changes.
// See https://github.com/vector-im/element-web/issues/14459
// For example, if we spent a little bit of time to determine which elements have
// actually changed (probably needs to be done higher up?) then we could do an
// insertion sort or similar on the limited set of changes.
// TODO: Don't assume we're using the same client as the peg
// See https://github.com/vector-im/element-web/issues/14458
let myUserId = '';
if (MatrixClientPeg.get()) {
myUserId = MatrixClientPeg.get().getUserId();
}
const tsCache: { [roomId: string]: number } = {};
const getLastTs = (r: Room) => {
if (tsCache[r.roomId]) {
return tsCache[r.roomId];
}
const ts = (() => {
// Apparently we can have rooms without timelines, at least under testing
// environments. Just return MAX_INT when this happens.
if (!r || !r.timeline) {
return Number.MAX_SAFE_INTEGER;
}
// If the room hasn't been joined yet, it probably won't have a timeline to
// parse. We'll still fall back to the timeline if this fails, but chances
// are we'll at least have our own membership event to go off of.
const effectiveMembership = getEffectiveMembership(r.getMyMembership());
if (effectiveMembership !== EffectiveMembership.Join) {
const membershipEvent = r.currentState.getStateEvents("m.room.member", myUserId);
if (membershipEvent && !Array.isArray(membershipEvent)) {
return membershipEvent.getTs();
}
}
for (let i = r.timeline.length - 1; i >= 0; --i) {
const ev = r.timeline[i];
if (!ev.getTs()) continue; // skip events that don't have timestamps (tests only?)
if (ev.getSender() === myUserId || Unread.eventTriggersUnreadCount(ev)) {
return ev.getTs();
}
}
// we might only have events that don't trigger the unread indicator,
// in which case use the oldest event even if normally it wouldn't count.
// This is better than just assuming the last event was forever ago.
if (r.timeline.length && r.timeline[0].getTs()) {
return r.timeline[0].getTs();
} else {
return Number.MAX_SAFE_INTEGER;
}
})();
tsCache[r.roomId] = ts;
return ts;
};
return rooms.sort((a, b) => {
return getLastTs(b) - getLastTs(a);
});
return sortRooms(rooms);
}
}