Fix sticky room disappearing/jumping in search results
Fixes https://github.com/vector-im/riot-web/issues/14124 Fixes https://github.com/vector-im/riot-web/issues/14154 (which was technically supposed to say that the sticky room when filtering was always last) This is all a bit complicated, but the theory is that we end up with a stable list even through filtering. There's some notes within, though I suspect it'll be difficult to understand :(
This commit is contained in:
parent
74e4ea7d48
commit
f93d67fc65
1 changed files with 41 additions and 6 deletions
|
@ -170,12 +170,16 @@ export class Algorithm extends EventEmitter {
|
||||||
// When we do have a room though, we expect to be able to find it
|
// When we do have a room though, we expect to be able to find it
|
||||||
const tag = this.roomIdsToTags[val.roomId][0];
|
const tag = this.roomIdsToTags[val.roomId][0];
|
||||||
if (!tag) throw new Error(`${val.roomId} does not belong to a tag and cannot be sticky`);
|
if (!tag) throw new Error(`${val.roomId} does not belong to a tag and cannot be sticky`);
|
||||||
let position = this.cachedRooms[tag].indexOf(val);
|
|
||||||
|
// We specifically do NOT use the ordered rooms set as it contains the sticky room, which
|
||||||
|
// means we'll be off by 1 when the user is switching rooms. This leads to visual jumping
|
||||||
|
// when the user is moving south in the list (not north, because of math).
|
||||||
|
let position = this.getOrderedRoomsWithoutSticky()[tag].indexOf(val);
|
||||||
if (position < 0) throw new Error(`${val.roomId} does not appear to be known and cannot be sticky`);
|
if (position < 0) throw new Error(`${val.roomId} does not appear to be known and cannot be sticky`);
|
||||||
|
|
||||||
// 🐉 Here be dragons.
|
// 🐉 Here be dragons.
|
||||||
// Before we can go through with lying to the underlying algorithm about a room
|
// Before we can go through with lying to the underlying algorithm about a room
|
||||||
// we need to ensure that when we do we're ready for the innevitable sticky room
|
// we need to ensure that when we do we're ready for the inevitable sticky room
|
||||||
// update we'll receive. To prepare for that, we first remove the sticky room and
|
// update we'll receive. To prepare for that, we first remove the sticky room and
|
||||||
// recalculate the state ourselves so that when the underlying algorithm calls for
|
// recalculate the state ourselves so that when the underlying algorithm calls for
|
||||||
// the same thing it no-ops. After we're done calling the algorithm, we'll issue
|
// the same thing it no-ops. After we're done calling the algorithm, we'll issue
|
||||||
|
@ -208,6 +212,12 @@ export class Algorithm extends EventEmitter {
|
||||||
position: position,
|
position: position,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// We update the filtered rooms just in case, as otherwise users will end up visiting
|
||||||
|
// a room while filtering and it'll disappear. We don't update the filter earlier in
|
||||||
|
// this function simply because we don't have to.
|
||||||
|
this.recalculateFilteredRoomsForTag(tag);
|
||||||
|
if (lastStickyRoom && lastStickyRoom.tag !== tag) this.recalculateFilteredRoomsForTag(tag);
|
||||||
this.recalculateStickyRoom();
|
this.recalculateStickyRoom();
|
||||||
|
|
||||||
// Finally, trigger an update
|
// Finally, trigger an update
|
||||||
|
@ -231,9 +241,7 @@ export class Algorithm extends EventEmitter {
|
||||||
// We optimize our lookups by trying to reduce sample size as much as possible
|
// We optimize our lookups by trying to reduce sample size as much as possible
|
||||||
// to the rooms we know will be deduped by the Set.
|
// to the rooms we know will be deduped by the Set.
|
||||||
const rooms = this.cachedRooms[tagId].map(r => r); // cheap clone
|
const rooms = this.cachedRooms[tagId].map(r => r); // cheap clone
|
||||||
if (this._stickyRoom && this._stickyRoom.tag === tagId && this._stickyRoom.room) {
|
this.tryInsertStickyRoomToFilterSet(rooms, tagId);
|
||||||
rooms.push(this._stickyRoom.room);
|
|
||||||
}
|
|
||||||
let remainingRooms = rooms.map(r => r);
|
let remainingRooms = rooms.map(r => r);
|
||||||
let allowedRoomsInThisTag = [];
|
let allowedRoomsInThisTag = [];
|
||||||
let lastFilterPriority = orderedFilters[0].relativePriority;
|
let lastFilterPriority = orderedFilters[0].relativePriority;
|
||||||
|
@ -263,6 +271,7 @@ export class Algorithm extends EventEmitter {
|
||||||
this.emit(LIST_UPDATED_EVENT);
|
this.emit(LIST_UPDATED_EVENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Remove or use.
|
||||||
protected addPossiblyFilteredRoomsToTag(tagId: TagID, added: Room[]): void {
|
protected addPossiblyFilteredRoomsToTag(tagId: TagID, added: Room[]): void {
|
||||||
const filters = this.allowedByFilter.keys();
|
const filters = this.allowedByFilter.keys();
|
||||||
for (const room of added) {
|
for (const room of added) {
|
||||||
|
@ -281,7 +290,8 @@ export class Algorithm extends EventEmitter {
|
||||||
protected recalculateFilteredRoomsForTag(tagId: TagID): void {
|
protected recalculateFilteredRoomsForTag(tagId: TagID): void {
|
||||||
console.log(`Recalculating filtered rooms for ${tagId}`);
|
console.log(`Recalculating filtered rooms for ${tagId}`);
|
||||||
delete this.filteredRooms[tagId];
|
delete this.filteredRooms[tagId];
|
||||||
const rooms = this.cachedRooms[tagId];
|
const rooms = this.cachedRooms[tagId].map(r => r); // cheap clone
|
||||||
|
this.tryInsertStickyRoomToFilterSet(rooms, tagId);
|
||||||
const filteredRooms = rooms.filter(r => this.allowedRoomsByFilters.has(r));
|
const filteredRooms = rooms.filter(r => this.allowedRoomsByFilters.has(r));
|
||||||
if (filteredRooms.length > 0) {
|
if (filteredRooms.length > 0) {
|
||||||
this.filteredRooms[tagId] = filteredRooms;
|
this.filteredRooms[tagId] = filteredRooms;
|
||||||
|
@ -289,6 +299,17 @@ export class Algorithm extends EventEmitter {
|
||||||
console.log(`[DEBUG] ${filteredRooms.length}/${rooms.length} rooms filtered into ${tagId}`);
|
console.log(`[DEBUG] ${filteredRooms.length}/${rooms.length} rooms filtered into ${tagId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected tryInsertStickyRoomToFilterSet(rooms: Room[], tagId: TagID) {
|
||||||
|
if (!this._stickyRoom || !this._stickyRoom.room || this._stickyRoom.tag !== tagId) return;
|
||||||
|
|
||||||
|
const position = this._stickyRoom.position;
|
||||||
|
if (position >= rooms.length) {
|
||||||
|
rooms.push(this._stickyRoom.room);
|
||||||
|
} else {
|
||||||
|
rooms.splice(position, 0, this._stickyRoom.room);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recalculate the sticky room position. If this is being called in relation to
|
* Recalculate the sticky room position. If this is being called in relation to
|
||||||
* a specific tag being updated, it should be given to this function to optimize
|
* a specific tag being updated, it should be given to this function to optimize
|
||||||
|
@ -377,6 +398,20 @@ export class Algorithm extends EventEmitter {
|
||||||
return this.filteredRooms;
|
return this.filteredRooms;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This returns the same as getOrderedRooms(), but without the sticky room
|
||||||
|
* map as it causes issues for sticky room handling (see sticky room handling
|
||||||
|
* for more information).
|
||||||
|
* @returns {ITagMap} The cached list of rooms, ordered,
|
||||||
|
* for each tag. May be empty, but never null/undefined.
|
||||||
|
*/
|
||||||
|
private getOrderedRoomsWithoutSticky(): ITagMap {
|
||||||
|
if (!this.hasFilters) {
|
||||||
|
return this.cachedRooms;
|
||||||
|
}
|
||||||
|
return this.filteredRooms;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Seeds the Algorithm with a set of rooms. The algorithm will discard all
|
* Seeds the Algorithm with a set of rooms. The algorithm will discard all
|
||||||
* previously known information and instead use these rooms instead.
|
* previously known information and instead use these rooms instead.
|
||||||
|
|
Loading…
Reference in a new issue