mirror of
https://github.com/element-hq/element-web
synced 2024-11-26 03:05:51 +03:00
Merge pull request #6391 from matrix-org/t3chguy/fix/14508.1
This commit is contained in:
commit
aaa9040634
11 changed files with 144 additions and 172 deletions
|
@ -408,10 +408,10 @@ export default class RoomSublist extends React.Component<IProps, IState> {
|
|||
this.setState({ addRoomContextMenuPosition: null });
|
||||
};
|
||||
|
||||
private onUnreadFirstChanged = async () => {
|
||||
private onUnreadFirstChanged = () => {
|
||||
const isUnreadFirst = RoomListStore.instance.getListOrder(this.props.tagId) === ListAlgorithm.Importance;
|
||||
const newAlgorithm = isUnreadFirst ? ListAlgorithm.Natural : ListAlgorithm.Importance;
|
||||
await RoomListStore.instance.setListOrder(this.props.tagId, newAlgorithm);
|
||||
RoomListStore.instance.setListOrder(this.props.tagId, newAlgorithm);
|
||||
this.forceUpdate(); // because if the sublist doesn't have any changes then we will miss the list order change
|
||||
};
|
||||
|
||||
|
|
|
@ -132,8 +132,8 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
|
|||
// Update any settings here, as some may have happened before we were logically ready.
|
||||
console.log("Regenerating room lists: Startup");
|
||||
await this.readAndCacheSettingsFromStore();
|
||||
await this.regenerateAllLists({ trigger: false });
|
||||
await this.handleRVSUpdate({ trigger: false }); // fake an RVS update to adjust sticky room, if needed
|
||||
this.regenerateAllLists({ trigger: false });
|
||||
this.handleRVSUpdate({ trigger: false }); // fake an RVS update to adjust sticky room, if needed
|
||||
|
||||
this.updateFn.mark(); // we almost certainly want to trigger an update.
|
||||
this.updateFn.trigger();
|
||||
|
@ -150,7 +150,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
|
|||
await this.updateState({
|
||||
tagsEnabled,
|
||||
});
|
||||
await this.updateAlgorithmInstances();
|
||||
this.updateAlgorithmInstances();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -158,23 +158,23 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
|
|||
* @param trigger Set to false to prevent a list update from being sent. Should only
|
||||
* be used if the calling code will manually trigger the update.
|
||||
*/
|
||||
private async handleRVSUpdate({ trigger = true }) {
|
||||
private handleRVSUpdate({ trigger = true }) {
|
||||
if (!this.matrixClient) return; // We assume there won't be RVS updates without a client
|
||||
|
||||
const activeRoomId = RoomViewStore.getRoomId();
|
||||
if (!activeRoomId && this.algorithm.stickyRoom) {
|
||||
await this.algorithm.setStickyRoom(null);
|
||||
this.algorithm.setStickyRoom(null);
|
||||
} else if (activeRoomId) {
|
||||
const activeRoom = this.matrixClient.getRoom(activeRoomId);
|
||||
if (!activeRoom) {
|
||||
console.warn(`${activeRoomId} is current in RVS but missing from client - clearing sticky room`);
|
||||
await this.algorithm.setStickyRoom(null);
|
||||
this.algorithm.setStickyRoom(null);
|
||||
} else if (activeRoom !== this.algorithm.stickyRoom) {
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.log(`Changing sticky room to ${activeRoomId}`);
|
||||
}
|
||||
await this.algorithm.setStickyRoom(activeRoom);
|
||||
this.algorithm.setStickyRoom(activeRoom);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -226,7 +226,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
|
|||
console.log("Regenerating room lists: Settings changed");
|
||||
await this.readAndCacheSettingsFromStore();
|
||||
|
||||
await this.regenerateAllLists({ trigger: false }); // regenerate the lists now
|
||||
this.regenerateAllLists({ trigger: false }); // regenerate the lists now
|
||||
this.updateFn.trigger();
|
||||
}
|
||||
}
|
||||
|
@ -368,7 +368,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
|
|||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.log(`[RoomListDebug] Clearing sticky room due to room upgrade`);
|
||||
}
|
||||
await this.algorithm.setStickyRoom(null);
|
||||
this.algorithm.setStickyRoom(null);
|
||||
}
|
||||
|
||||
// Note: we hit the algorithm instead of our handleRoomUpdate() function to
|
||||
|
@ -377,7 +377,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
|
|||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.log(`[RoomListDebug] Removing previous room from room list`);
|
||||
}
|
||||
await this.algorithm.handleRoomUpdate(prevRoom, RoomUpdateCause.RoomRemoved);
|
||||
this.algorithm.handleRoomUpdate(prevRoom, RoomUpdateCause.RoomRemoved);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -433,7 +433,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
|
|||
return; // don't do anything on new/moved rooms which ought not to be shown
|
||||
}
|
||||
|
||||
const shouldUpdate = await this.algorithm.handleRoomUpdate(room, cause);
|
||||
const shouldUpdate = this.algorithm.handleRoomUpdate(room, cause);
|
||||
if (shouldUpdate) {
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
|
@ -462,13 +462,13 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
|
|||
|
||||
// Reset the sticky room before resetting the known rooms so the algorithm
|
||||
// doesn't freak out.
|
||||
await this.algorithm.setStickyRoom(null);
|
||||
await this.algorithm.setKnownRooms(rooms);
|
||||
this.algorithm.setStickyRoom(null);
|
||||
this.algorithm.setKnownRooms(rooms);
|
||||
|
||||
// Set the sticky room back, if needed, now that we have updated the store.
|
||||
// This will use relative stickyness to the new room set.
|
||||
if (stickyIsStillPresent) {
|
||||
await this.algorithm.setStickyRoom(currentSticky);
|
||||
this.algorithm.setStickyRoom(currentSticky);
|
||||
}
|
||||
|
||||
// Finally, mark an update and resume updates from the algorithm
|
||||
|
@ -477,12 +477,12 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
|
|||
}
|
||||
|
||||
public async setTagSorting(tagId: TagID, sort: SortAlgorithm) {
|
||||
await this.setAndPersistTagSorting(tagId, sort);
|
||||
this.setAndPersistTagSorting(tagId, sort);
|
||||
this.updateFn.trigger();
|
||||
}
|
||||
|
||||
private async setAndPersistTagSorting(tagId: TagID, sort: SortAlgorithm) {
|
||||
await this.algorithm.setTagSorting(tagId, sort);
|
||||
private setAndPersistTagSorting(tagId: TagID, sort: SortAlgorithm) {
|
||||
this.algorithm.setTagSorting(tagId, sort);
|
||||
// TODO: Per-account? https://github.com/vector-im/element-web/issues/14114
|
||||
localStorage.setItem(`mx_tagSort_${tagId}`, sort);
|
||||
}
|
||||
|
@ -520,13 +520,13 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
|
|||
return tagSort;
|
||||
}
|
||||
|
||||
public async setListOrder(tagId: TagID, order: ListAlgorithm) {
|
||||
await this.setAndPersistListOrder(tagId, order);
|
||||
public setListOrder(tagId: TagID, order: ListAlgorithm) {
|
||||
this.setAndPersistListOrder(tagId, order);
|
||||
this.updateFn.trigger();
|
||||
}
|
||||
|
||||
private async setAndPersistListOrder(tagId: TagID, order: ListAlgorithm) {
|
||||
await this.algorithm.setListOrdering(tagId, order);
|
||||
private setAndPersistListOrder(tagId: TagID, order: ListAlgorithm) {
|
||||
this.algorithm.setListOrdering(tagId, order);
|
||||
// TODO: Per-account? https://github.com/vector-im/element-web/issues/14114
|
||||
localStorage.setItem(`mx_listOrder_${tagId}`, order);
|
||||
}
|
||||
|
@ -563,7 +563,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
|
|||
return listOrder;
|
||||
}
|
||||
|
||||
private async updateAlgorithmInstances() {
|
||||
private updateAlgorithmInstances() {
|
||||
// We'll require an update, so mark for one. Marking now also prevents the calls
|
||||
// to setTagSorting and setListOrder from causing triggers.
|
||||
this.updateFn.mark();
|
||||
|
@ -576,10 +576,10 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
|
|||
const listOrder = this.calculateListOrder(tag);
|
||||
|
||||
if (tagSort !== definedSort) {
|
||||
await this.setAndPersistTagSorting(tag, tagSort);
|
||||
this.setAndPersistTagSorting(tag, tagSort);
|
||||
}
|
||||
if (listOrder !== definedOrder) {
|
||||
await this.setAndPersistListOrder(tag, listOrder);
|
||||
this.setAndPersistListOrder(tag, listOrder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -632,7 +632,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
|
|||
* @param trigger Set to false to prevent a list update from being sent. Should only
|
||||
* be used if the calling code will manually trigger the update.
|
||||
*/
|
||||
public async regenerateAllLists({ trigger = true }) {
|
||||
public regenerateAllLists({ trigger = true }) {
|
||||
console.warn("Regenerating all room lists");
|
||||
|
||||
const rooms = this.getPlausibleRooms();
|
||||
|
@ -656,8 +656,8 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
|
|||
RoomListLayoutStore.instance.ensureLayoutExists(tagId);
|
||||
}
|
||||
|
||||
await this.algorithm.populateTags(sorts, orders);
|
||||
await this.algorithm.setKnownRooms(rooms);
|
||||
this.algorithm.populateTags(sorts, orders);
|
||||
this.algorithm.setKnownRooms(rooms);
|
||||
|
||||
this.initialListsGenerated = true;
|
||||
|
||||
|
|
|
@ -16,8 +16,9 @@ limitations under the License.
|
|||
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
|
||||
import DMRoomMap from "../../../utils/DMRoomMap";
|
||||
import { EventEmitter } from "events";
|
||||
|
||||
import DMRoomMap from "../../../utils/DMRoomMap";
|
||||
import { arrayDiff, arrayHasDiff } from "../../../utils/arrays";
|
||||
import { DefaultTagID, RoomUpdateCause, TagID } from "../models";
|
||||
import {
|
||||
|
@ -122,8 +123,8 @@ export class Algorithm extends EventEmitter {
|
|||
* Awaitable version of the sticky room setter.
|
||||
* @param val The new room to sticky.
|
||||
*/
|
||||
public async setStickyRoom(val: Room) {
|
||||
await this.updateStickyRoom(val);
|
||||
public setStickyRoom(val: Room) {
|
||||
this.updateStickyRoom(val);
|
||||
}
|
||||
|
||||
public getTagSorting(tagId: TagID): SortAlgorithm {
|
||||
|
@ -131,13 +132,13 @@ export class Algorithm extends EventEmitter {
|
|||
return this.sortAlgorithms[tagId];
|
||||
}
|
||||
|
||||
public async setTagSorting(tagId: TagID, sort: SortAlgorithm) {
|
||||
public setTagSorting(tagId: TagID, sort: SortAlgorithm) {
|
||||
if (!tagId) throw new Error("Tag ID must be defined");
|
||||
if (!sort) throw new Error("Algorithm must be defined");
|
||||
this.sortAlgorithms[tagId] = sort;
|
||||
|
||||
const algorithm: OrderingAlgorithm = this.algorithms[tagId];
|
||||
await algorithm.setSortAlgorithm(sort);
|
||||
algorithm.setSortAlgorithm(sort);
|
||||
this._cachedRooms[tagId] = algorithm.orderedRooms;
|
||||
this.recalculateFilteredRoomsForTag(tagId); // update filter to re-sort the list
|
||||
this.recalculateStickyRoom(tagId); // update sticky room to make sure it appears if needed
|
||||
|
@ -148,7 +149,7 @@ export class Algorithm extends EventEmitter {
|
|||
return this.listAlgorithms[tagId];
|
||||
}
|
||||
|
||||
public async setListOrdering(tagId: TagID, order: ListAlgorithm) {
|
||||
public setListOrdering(tagId: TagID, order: ListAlgorithm) {
|
||||
if (!tagId) throw new Error("Tag ID must be defined");
|
||||
if (!order) throw new Error("Algorithm must be defined");
|
||||
this.listAlgorithms[tagId] = order;
|
||||
|
@ -156,7 +157,7 @@ export class Algorithm extends EventEmitter {
|
|||
const algorithm = getListAlgorithmInstance(order, tagId, this.sortAlgorithms[tagId]);
|
||||
this.algorithms[tagId] = algorithm;
|
||||
|
||||
await algorithm.setRooms(this._cachedRooms[tagId]);
|
||||
algorithm.setRooms(this._cachedRooms[tagId]);
|
||||
this._cachedRooms[tagId] = algorithm.orderedRooms;
|
||||
this.recalculateFilteredRoomsForTag(tagId); // update filter to re-sort the list
|
||||
this.recalculateStickyRoom(tagId); // update sticky room to make sure it appears if needed
|
||||
|
@ -183,31 +184,25 @@ export class Algorithm extends EventEmitter {
|
|||
}
|
||||
}
|
||||
|
||||
private async handleFilterChange() {
|
||||
await this.recalculateFilteredRooms();
|
||||
private handleFilterChange() {
|
||||
this.recalculateFilteredRooms();
|
||||
|
||||
// re-emit the update so the list store can fire an off-cycle update if needed
|
||||
if (this.updatesInhibited) return;
|
||||
this.emit(FILTER_CHANGED);
|
||||
}
|
||||
|
||||
private async updateStickyRoom(val: Room) {
|
||||
try {
|
||||
return await this.doUpdateStickyRoom(val);
|
||||
} finally {
|
||||
this._lastStickyRoom = null; // clear to indicate we're done changing
|
||||
}
|
||||
private updateStickyRoom(val: Room) {
|
||||
this.doUpdateStickyRoom(val);
|
||||
this._lastStickyRoom = null; // clear to indicate we're done changing
|
||||
}
|
||||
|
||||
private async doUpdateStickyRoom(val: Room) {
|
||||
private doUpdateStickyRoom(val: Room) {
|
||||
if (SpaceStore.spacesEnabled && val?.isSpaceRoom() && val.getMyMembership() !== "invite") {
|
||||
// no-op sticky rooms for spaces - they're effectively virtual rooms
|
||||
val = null;
|
||||
}
|
||||
|
||||
// Note throughout: We need async so we can wait for handleRoomUpdate() to do its thing,
|
||||
// otherwise we risk duplicating rooms.
|
||||
|
||||
if (val && !VisibilityProvider.instance.isRoomVisible(val)) {
|
||||
val = null; // the room isn't visible - lie to the rest of this function
|
||||
}
|
||||
|
@ -223,7 +218,7 @@ export class Algorithm extends EventEmitter {
|
|||
this._stickyRoom = null; // clear before we go to update the algorithm
|
||||
|
||||
// Lie to the algorithm and re-add the room to the algorithm
|
||||
await this.handleRoomUpdate(stickyRoom, RoomUpdateCause.NewRoom);
|
||||
this.handleRoomUpdate(stickyRoom, RoomUpdateCause.NewRoom);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
|
@ -269,10 +264,10 @@ export class Algorithm extends EventEmitter {
|
|||
// referential checks as the references can differ through the lifecycle.
|
||||
if (lastStickyRoom && lastStickyRoom.room && lastStickyRoom.room.roomId !== val.roomId) {
|
||||
// Lie to the algorithm and re-add the room to the algorithm
|
||||
await this.handleRoomUpdate(lastStickyRoom.room, RoomUpdateCause.NewRoom);
|
||||
this.handleRoomUpdate(lastStickyRoom.room, RoomUpdateCause.NewRoom);
|
||||
}
|
||||
// Lie to the algorithm and remove the room from it's field of view
|
||||
await this.handleRoomUpdate(val, RoomUpdateCause.RoomRemoved);
|
||||
this.handleRoomUpdate(val, RoomUpdateCause.RoomRemoved);
|
||||
|
||||
// Check for tag & position changes while we're here. We also check the room to ensure
|
||||
// it is still the same room.
|
||||
|
@ -462,9 +457,8 @@ export class Algorithm extends EventEmitter {
|
|||
* them.
|
||||
* @param {ITagSortingMap} tagSortingMap The tags to generate.
|
||||
* @param {IListOrderingMap} listOrderingMap The ordering of those tags.
|
||||
* @returns {Promise<*>} A promise which resolves when complete.
|
||||
*/
|
||||
public async populateTags(tagSortingMap: ITagSortingMap, listOrderingMap: IListOrderingMap): Promise<any> {
|
||||
public populateTags(tagSortingMap: ITagSortingMap, listOrderingMap: IListOrderingMap): void {
|
||||
if (!tagSortingMap) throw new Error(`Sorting map cannot be null or empty`);
|
||||
if (!listOrderingMap) throw new Error(`Ordering ma cannot be null or empty`);
|
||||
if (arrayHasDiff(Object.keys(tagSortingMap), Object.keys(listOrderingMap))) {
|
||||
|
@ -513,9 +507,8 @@ export class Algorithm extends EventEmitter {
|
|||
* Seeds the Algorithm with a set of rooms. The algorithm will discard all
|
||||
* previously known information and instead use these rooms instead.
|
||||
* @param {Room[]} rooms The rooms to force the algorithm to use.
|
||||
* @returns {Promise<*>} A promise which resolves when complete.
|
||||
*/
|
||||
public async setKnownRooms(rooms: Room[]): Promise<any> {
|
||||
public setKnownRooms(rooms: Room[]): void {
|
||||
if (isNullOrUndefined(rooms)) throw new Error(`Array of rooms cannot be null`);
|
||||
if (!this.sortAlgorithms) throw new Error(`Cannot set known rooms without a tag sorting map`);
|
||||
|
||||
|
@ -529,7 +522,7 @@ export class Algorithm extends EventEmitter {
|
|||
// Before we go any further we need to clear (but remember) the sticky room to
|
||||
// avoid accidentally duplicating it in the list.
|
||||
const oldStickyRoom = this._stickyRoom;
|
||||
await this.updateStickyRoom(null);
|
||||
if (oldStickyRoom) this.updateStickyRoom(null);
|
||||
|
||||
this.rooms = rooms;
|
||||
|
||||
|
@ -541,7 +534,7 @@ export class Algorithm extends EventEmitter {
|
|||
|
||||
// If we can avoid doing work, do so.
|
||||
if (!rooms.length) {
|
||||
await this.generateFreshTags(newTags); // just in case it wants to do something
|
||||
this.generateFreshTags(newTags); // just in case it wants to do something
|
||||
this.cachedRooms = newTags;
|
||||
return;
|
||||
}
|
||||
|
@ -578,7 +571,7 @@ export class Algorithm extends EventEmitter {
|
|||
}
|
||||
}
|
||||
|
||||
await this.generateFreshTags(newTags);
|
||||
this.generateFreshTags(newTags);
|
||||
|
||||
this.cachedRooms = newTags; // this recalculates the filtered rooms for us
|
||||
this.updateTagsFromCache();
|
||||
|
@ -587,7 +580,7 @@ export class Algorithm extends EventEmitter {
|
|||
// it was. It's entirely possible that it changed lists though, so if it did then
|
||||
// we also have to update the position of it.
|
||||
if (oldStickyRoom && oldStickyRoom.room) {
|
||||
await this.updateStickyRoom(oldStickyRoom.room);
|
||||
this.updateStickyRoom(oldStickyRoom.room);
|
||||
if (this._stickyRoom && this._stickyRoom.room) { // just in case the update doesn't go according to plan
|
||||
if (this._stickyRoom.tag !== oldStickyRoom.tag) {
|
||||
// We put the sticky room at the top of the list to treat it as an obvious tag change.
|
||||
|
@ -652,16 +645,15 @@ export class Algorithm extends EventEmitter {
|
|||
* @param {ITagMap} updatedTagMap The tag map which needs populating. Each tag
|
||||
* will already have the rooms which belong to it - they just need ordering. Must
|
||||
* be mutated in place.
|
||||
* @returns {Promise<*>} A promise which resolves when complete.
|
||||
*/
|
||||
private async generateFreshTags(updatedTagMap: ITagMap): Promise<any> {
|
||||
private generateFreshTags(updatedTagMap: ITagMap): void {
|
||||
if (!this.algorithms) throw new Error("Not ready: no algorithms to determine tags from");
|
||||
|
||||
for (const tag of Object.keys(updatedTagMap)) {
|
||||
const algorithm: OrderingAlgorithm = this.algorithms[tag];
|
||||
if (!algorithm) throw new Error(`No algorithm for ${tag}`);
|
||||
|
||||
await algorithm.setRooms(updatedTagMap[tag]);
|
||||
algorithm.setRooms(updatedTagMap[tag]);
|
||||
updatedTagMap[tag] = algorithm.orderedRooms;
|
||||
}
|
||||
}
|
||||
|
@ -673,11 +665,10 @@ export class Algorithm extends EventEmitter {
|
|||
* may no-op this request if no changes are required.
|
||||
* @param {Room} room The room which might have affected sorting.
|
||||
* @param {RoomUpdateCause} cause The reason for the update being triggered.
|
||||
* @returns {Promise<boolean>} A promise which resolve to true or false
|
||||
* depending on whether or not getOrderedRooms() should be called after
|
||||
* processing.
|
||||
* @returns {Promise<boolean>} A boolean of whether or not getOrderedRooms()
|
||||
* should be called after processing.
|
||||
*/
|
||||
public async handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise<boolean> {
|
||||
public handleRoomUpdate(room: Room, cause: RoomUpdateCause): boolean {
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.log(`Handle room update for ${room.roomId} called with cause ${cause}`);
|
||||
|
@ -685,9 +676,9 @@ export class Algorithm extends EventEmitter {
|
|||
if (!this.algorithms) throw new Error("Not ready: no algorithms to determine tags from");
|
||||
|
||||
// Note: check the isSticky against the room ID just in case the reference is wrong
|
||||
const isSticky = this._stickyRoom && this._stickyRoom.room && this._stickyRoom.room.roomId === room.roomId;
|
||||
const isSticky = this._stickyRoom?.room?.roomId === room.roomId;
|
||||
if (cause === RoomUpdateCause.NewRoom) {
|
||||
const isForLastSticky = this._lastStickyRoom && this._lastStickyRoom.room === room;
|
||||
const isForLastSticky = this._lastStickyRoom?.room === room;
|
||||
const roomTags = this.roomIdsToTags[room.roomId];
|
||||
const hasTags = roomTags && roomTags.length > 0;
|
||||
|
||||
|
@ -744,7 +735,7 @@ export class Algorithm extends EventEmitter {
|
|||
}
|
||||
const algorithm: OrderingAlgorithm = this.algorithms[rmTag];
|
||||
if (!algorithm) throw new Error(`No algorithm for ${rmTag}`);
|
||||
await algorithm.handleRoomUpdate(room, RoomUpdateCause.RoomRemoved);
|
||||
algorithm.handleRoomUpdate(room, RoomUpdateCause.RoomRemoved);
|
||||
this._cachedRooms[rmTag] = algorithm.orderedRooms;
|
||||
this.recalculateFilteredRoomsForTag(rmTag); // update filter to re-sort the list
|
||||
this.recalculateStickyRoom(rmTag); // update sticky room to make sure it moves if needed
|
||||
|
@ -756,7 +747,7 @@ export class Algorithm extends EventEmitter {
|
|||
}
|
||||
const algorithm: OrderingAlgorithm = this.algorithms[addTag];
|
||||
if (!algorithm) throw new Error(`No algorithm for ${addTag}`);
|
||||
await algorithm.handleRoomUpdate(room, RoomUpdateCause.NewRoom);
|
||||
algorithm.handleRoomUpdate(room, RoomUpdateCause.NewRoom);
|
||||
this._cachedRooms[addTag] = algorithm.orderedRooms;
|
||||
}
|
||||
|
||||
|
@ -789,7 +780,7 @@ export class Algorithm extends EventEmitter {
|
|||
};
|
||||
} else {
|
||||
// We have to clear the lock as the sticky room change will trigger updates.
|
||||
await this.setStickyRoom(room);
|
||||
this.setStickyRoom(room);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -852,7 +843,7 @@ export class Algorithm extends EventEmitter {
|
|||
const algorithm: OrderingAlgorithm = this.algorithms[tag];
|
||||
if (!algorithm) throw new Error(`No algorithm for ${tag}`);
|
||||
|
||||
await algorithm.handleRoomUpdate(room, cause);
|
||||
algorithm.handleRoomUpdate(room, cause);
|
||||
this._cachedRooms[tag] = algorithm.orderedRooms;
|
||||
|
||||
// Flag that we've done something
|
||||
|
|
|
@ -94,15 +94,15 @@ export class ImportanceAlgorithm extends OrderingAlgorithm {
|
|||
return state.color;
|
||||
}
|
||||
|
||||
public async setRooms(rooms: Room[]): Promise<any> {
|
||||
public setRooms(rooms: Room[]): void {
|
||||
if (this.sortingAlgorithm === SortAlgorithm.Manual) {
|
||||
this.cachedOrderedRooms = await sortRoomsWithAlgorithm(rooms, this.tagId, this.sortingAlgorithm);
|
||||
this.cachedOrderedRooms = sortRoomsWithAlgorithm(rooms, this.tagId, this.sortingAlgorithm);
|
||||
} else {
|
||||
// Every other sorting type affects the categories, not the whole tag.
|
||||
const categorized = this.categorizeRooms(rooms);
|
||||
for (const category of Object.keys(categorized)) {
|
||||
const roomsToOrder = categorized[category];
|
||||
categorized[category] = await sortRoomsWithAlgorithm(roomsToOrder, this.tagId, this.sortingAlgorithm);
|
||||
categorized[category] = sortRoomsWithAlgorithm(roomsToOrder, this.tagId, this.sortingAlgorithm);
|
||||
}
|
||||
|
||||
const newlyOrganized: Room[] = [];
|
||||
|
@ -118,12 +118,12 @@ export class ImportanceAlgorithm extends OrderingAlgorithm {
|
|||
}
|
||||
}
|
||||
|
||||
private async handleSplice(room: Room, cause: RoomUpdateCause): Promise<boolean> {
|
||||
private handleSplice(room: Room, cause: RoomUpdateCause): boolean {
|
||||
if (cause === RoomUpdateCause.NewRoom) {
|
||||
const category = this.getRoomCategory(room);
|
||||
this.alterCategoryPositionBy(category, 1, this.indices);
|
||||
this.cachedOrderedRooms.splice(this.indices[category], 0, room); // splice in the new room (pre-adjusted)
|
||||
await this.sortCategory(category);
|
||||
this.sortCategory(category);
|
||||
} else if (cause === RoomUpdateCause.RoomRemoved) {
|
||||
const roomIdx = this.getRoomIndex(room);
|
||||
if (roomIdx === -1) {
|
||||
|
@ -141,55 +141,49 @@ export class ImportanceAlgorithm extends OrderingAlgorithm {
|
|||
return true;
|
||||
}
|
||||
|
||||
public async handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise<boolean> {
|
||||
try {
|
||||
await this.updateLock.acquireAsync();
|
||||
|
||||
if (cause === RoomUpdateCause.NewRoom || cause === RoomUpdateCause.RoomRemoved) {
|
||||
return this.handleSplice(room, cause);
|
||||
}
|
||||
|
||||
if (cause !== RoomUpdateCause.Timeline && cause !== RoomUpdateCause.ReadReceipt) {
|
||||
throw new Error(`Unsupported update cause: ${cause}`);
|
||||
}
|
||||
|
||||
const category = this.getRoomCategory(room);
|
||||
if (this.sortingAlgorithm === SortAlgorithm.Manual) {
|
||||
return; // Nothing to do here.
|
||||
}
|
||||
|
||||
const roomIdx = this.getRoomIndex(room);
|
||||
if (roomIdx === -1) {
|
||||
throw new Error(`Room ${room.roomId} has no index in ${this.tagId}`);
|
||||
}
|
||||
|
||||
// Try to avoid doing array operations if we don't have to: only move rooms within
|
||||
// the categories if we're jumping categories
|
||||
const oldCategory = this.getCategoryFromIndices(roomIdx, this.indices);
|
||||
if (oldCategory !== category) {
|
||||
// Move the room and update the indices
|
||||
this.moveRoomIndexes(1, oldCategory, category, this.indices);
|
||||
this.cachedOrderedRooms.splice(roomIdx, 1); // splice out the old index (fixed position)
|
||||
this.cachedOrderedRooms.splice(this.indices[category], 0, room); // splice in the new room (pre-adjusted)
|
||||
// Note: if moveRoomIndexes() is called after the splice then the insert operation
|
||||
// will happen in the wrong place. Because we would have already adjusted the index
|
||||
// for the category, we don't need to determine how the room is moving in the list.
|
||||
// If we instead tried to insert before updating the indices, we'd have to determine
|
||||
// whether the room was moving later (towards IDLE) or earlier (towards RED) from its
|
||||
// current position, as it'll affect the category's start index after we remove the
|
||||
// room from the array.
|
||||
}
|
||||
|
||||
// Sort the category now that we've dumped the room in
|
||||
await this.sortCategory(category);
|
||||
|
||||
return true; // change made
|
||||
} finally {
|
||||
await this.updateLock.release();
|
||||
public handleRoomUpdate(room: Room, cause: RoomUpdateCause): boolean {
|
||||
if (cause === RoomUpdateCause.NewRoom || cause === RoomUpdateCause.RoomRemoved) {
|
||||
return this.handleSplice(room, cause);
|
||||
}
|
||||
|
||||
if (cause !== RoomUpdateCause.Timeline && cause !== RoomUpdateCause.ReadReceipt) {
|
||||
throw new Error(`Unsupported update cause: ${cause}`);
|
||||
}
|
||||
|
||||
const category = this.getRoomCategory(room);
|
||||
if (this.sortingAlgorithm === SortAlgorithm.Manual) {
|
||||
return; // Nothing to do here.
|
||||
}
|
||||
|
||||
const roomIdx = this.getRoomIndex(room);
|
||||
if (roomIdx === -1) {
|
||||
throw new Error(`Room ${room.roomId} has no index in ${this.tagId}`);
|
||||
}
|
||||
|
||||
// Try to avoid doing array operations if we don't have to: only move rooms within
|
||||
// the categories if we're jumping categories
|
||||
const oldCategory = this.getCategoryFromIndices(roomIdx, this.indices);
|
||||
if (oldCategory !== category) {
|
||||
// Move the room and update the indices
|
||||
this.moveRoomIndexes(1, oldCategory, category, this.indices);
|
||||
this.cachedOrderedRooms.splice(roomIdx, 1); // splice out the old index (fixed position)
|
||||
this.cachedOrderedRooms.splice(this.indices[category], 0, room); // splice in the new room (pre-adjusted)
|
||||
// Note: if moveRoomIndexes() is called after the splice then the insert operation
|
||||
// will happen in the wrong place. Because we would have already adjusted the index
|
||||
// for the category, we don't need to determine how the room is moving in the list.
|
||||
// If we instead tried to insert before updating the indices, we'd have to determine
|
||||
// whether the room was moving later (towards IDLE) or earlier (towards RED) from its
|
||||
// current position, as it'll affect the category's start index after we remove the
|
||||
// room from the array.
|
||||
}
|
||||
|
||||
// Sort the category now that we've dumped the room in
|
||||
this.sortCategory(category);
|
||||
|
||||
return true; // change made
|
||||
}
|
||||
|
||||
private async sortCategory(category: NotificationColor) {
|
||||
private sortCategory(category: NotificationColor) {
|
||||
// This should be relatively quick because the room is usually inserted at the top of the
|
||||
// category, and most popular sorting algorithms will deal with trying to keep the active
|
||||
// room at the top/start of the category. For the few algorithms that will have to move the
|
||||
|
@ -201,7 +195,7 @@ export class ImportanceAlgorithm extends OrderingAlgorithm {
|
|||
const startIdx = this.indices[category];
|
||||
const numSort = nextCategoryStartIdx - startIdx; // splice() returns up to the max, so MAX_SAFE_INT is fine
|
||||
const unsortedSlice = this.cachedOrderedRooms.splice(startIdx, numSort);
|
||||
const sorted = await sortRoomsWithAlgorithm(unsortedSlice, this.tagId, this.sortingAlgorithm);
|
||||
const sorted = sortRoomsWithAlgorithm(unsortedSlice, this.tagId, this.sortingAlgorithm);
|
||||
this.cachedOrderedRooms.splice(startIdx, 0, ...sorted);
|
||||
}
|
||||
|
||||
|
|
|
@ -29,42 +29,32 @@ export class NaturalAlgorithm extends OrderingAlgorithm {
|
|||
super(tagId, initialSortingAlgorithm);
|
||||
}
|
||||
|
||||
public async setRooms(rooms: Room[]): Promise<any> {
|
||||
this.cachedOrderedRooms = await sortRoomsWithAlgorithm(rooms, this.tagId, this.sortingAlgorithm);
|
||||
public setRooms(rooms: Room[]): void {
|
||||
this.cachedOrderedRooms = sortRoomsWithAlgorithm(rooms, this.tagId, this.sortingAlgorithm);
|
||||
}
|
||||
|
||||
public async handleRoomUpdate(room, cause): Promise<boolean> {
|
||||
try {
|
||||
await this.updateLock.acquireAsync();
|
||||
|
||||
const isSplice = cause === RoomUpdateCause.NewRoom || cause === RoomUpdateCause.RoomRemoved;
|
||||
const isInPlace = cause === RoomUpdateCause.Timeline || cause === RoomUpdateCause.ReadReceipt;
|
||||
if (!isSplice && !isInPlace) {
|
||||
throw new Error(`Unsupported update cause: ${cause}`);
|
||||
}
|
||||
|
||||
if (cause === RoomUpdateCause.NewRoom) {
|
||||
this.cachedOrderedRooms.push(room);
|
||||
} else if (cause === RoomUpdateCause.RoomRemoved) {
|
||||
const idx = this.getRoomIndex(room);
|
||||
if (idx >= 0) {
|
||||
this.cachedOrderedRooms.splice(idx, 1);
|
||||
} else {
|
||||
console.warn(`Tried to remove unknown room from ${this.tagId}: ${room.roomId}`);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Optimize this to avoid useless operations: https://github.com/vector-im/element-web/issues/14457
|
||||
// For example, we can skip updates to alphabetic (sometimes) and manually ordered tags
|
||||
this.cachedOrderedRooms = await sortRoomsWithAlgorithm(
|
||||
this.cachedOrderedRooms,
|
||||
this.tagId,
|
||||
this.sortingAlgorithm,
|
||||
);
|
||||
|
||||
return true;
|
||||
} finally {
|
||||
await this.updateLock.release();
|
||||
public handleRoomUpdate(room, cause): boolean {
|
||||
const isSplice = cause === RoomUpdateCause.NewRoom || cause === RoomUpdateCause.RoomRemoved;
|
||||
const isInPlace = cause === RoomUpdateCause.Timeline || cause === RoomUpdateCause.ReadReceipt;
|
||||
if (!isSplice && !isInPlace) {
|
||||
throw new Error(`Unsupported update cause: ${cause}`);
|
||||
}
|
||||
|
||||
if (cause === RoomUpdateCause.NewRoom) {
|
||||
this.cachedOrderedRooms.push(room);
|
||||
} else if (cause === RoomUpdateCause.RoomRemoved) {
|
||||
const idx = this.getRoomIndex(room);
|
||||
if (idx >= 0) {
|
||||
this.cachedOrderedRooms.splice(idx, 1);
|
||||
} else {
|
||||
console.warn(`Tried to remove unknown room from ${this.tagId}: ${room.roomId}`);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Optimize this to avoid useless operations: https://github.com/vector-im/element-web/issues/14457
|
||||
// For example, we can skip updates to alphabetic (sometimes) and manually ordered tags
|
||||
this.cachedOrderedRooms = sortRoomsWithAlgorithm(this.cachedOrderedRooms, this.tagId, this.sortingAlgorithm);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { RoomUpdateCause, TagID } from "../../models";
|
||||
import { SortAlgorithm } from "../models";
|
||||
import AwaitLock from "await-lock";
|
||||
|
||||
/**
|
||||
* Represents a list ordering algorithm. Subclasses should populate the
|
||||
|
@ -26,7 +25,6 @@ import AwaitLock from "await-lock";
|
|||
export abstract class OrderingAlgorithm {
|
||||
protected cachedOrderedRooms: Room[];
|
||||
protected sortingAlgorithm: SortAlgorithm;
|
||||
protected readonly updateLock = new AwaitLock();
|
||||
|
||||
protected constructor(protected tagId: TagID, initialSortingAlgorithm: SortAlgorithm) {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
|
@ -45,21 +43,20 @@ export abstract class OrderingAlgorithm {
|
|||
* @param newAlgorithm The new algorithm. Must be defined.
|
||||
* @returns Resolves when complete.
|
||||
*/
|
||||
public async setSortAlgorithm(newAlgorithm: SortAlgorithm) {
|
||||
public setSortAlgorithm(newAlgorithm: SortAlgorithm) {
|
||||
if (!newAlgorithm) throw new Error("A sorting algorithm must be defined");
|
||||
this.sortingAlgorithm = newAlgorithm;
|
||||
|
||||
// Force regeneration of the rooms
|
||||
await this.setRooms(this.orderedRooms);
|
||||
this.setRooms(this.orderedRooms);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the rooms the algorithm should be handling, implying a reconstruction
|
||||
* of the ordering.
|
||||
* @param rooms The rooms to use going forward.
|
||||
* @returns Resolves when complete.
|
||||
*/
|
||||
public abstract setRooms(rooms: Room[]): Promise<any>;
|
||||
public abstract setRooms(rooms: Room[]): void;
|
||||
|
||||
/**
|
||||
* Handle a room update. The Algorithm will only call this for causes which
|
||||
|
@ -69,7 +66,7 @@ export abstract class OrderingAlgorithm {
|
|||
* @param cause The cause of the update.
|
||||
* @returns True if the update requires the Algorithm to update the presentation layers.
|
||||
*/
|
||||
public abstract handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise<boolean>;
|
||||
public abstract handleRoomUpdate(room: Room, cause: RoomUpdateCause): boolean;
|
||||
|
||||
protected getRoomIndex(room: Room): number {
|
||||
let roomIdx = this.cachedOrderedRooms.indexOf(room);
|
||||
|
|
|
@ -23,7 +23,7 @@ import { compare } from "../../../../utils/strings";
|
|||
* Sorts rooms according to the browser's determination of alphabetic.
|
||||
*/
|
||||
export class AlphabeticAlgorithm implements IAlgorithm {
|
||||
public async sortRooms(rooms: Room[], tagId: TagID): Promise<Room[]> {
|
||||
public sortRooms(rooms: Room[], tagId: TagID): Room[] {
|
||||
return rooms.sort((a, b) => {
|
||||
return compare(a.name, b.name);
|
||||
});
|
||||
|
|
|
@ -25,7 +25,7 @@ export interface IAlgorithm {
|
|||
* Sorts the given rooms according to the sorting rules of the algorithm.
|
||||
* @param {Room[]} rooms The rooms to sort.
|
||||
* @param {TagID} tagId The tag ID in which the rooms are being sorted.
|
||||
* @returns {Promise<Room[]>} Resolves to the sorted rooms.
|
||||
* @returns {Room[]} Returns the sorted rooms.
|
||||
*/
|
||||
sortRooms(rooms: Room[], tagId: TagID): Promise<Room[]>;
|
||||
sortRooms(rooms: Room[], tagId: TagID): Room[];
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ import { IAlgorithm } from "./IAlgorithm";
|
|||
* Sorts rooms according to the tag's `order` property on the room.
|
||||
*/
|
||||
export class ManualAlgorithm implements IAlgorithm {
|
||||
public async sortRooms(rooms: Room[], tagId: TagID): Promise<Room[]> {
|
||||
public sortRooms(rooms: Room[], tagId: TagID): Room[] {
|
||||
const getOrderProp = (r: Room) => r.tags[tagId].order || 0;
|
||||
return rooms.sort((a, b) => {
|
||||
return getOrderProp(a) - getOrderProp(b);
|
||||
|
|
|
@ -97,7 +97,7 @@ export const sortRooms = (rooms: Room[]): Room[] => {
|
|||
* useful to the user.
|
||||
*/
|
||||
export class RecentAlgorithm implements IAlgorithm {
|
||||
public async sortRooms(rooms: Room[], tagId: TagID): Promise<Room[]> {
|
||||
public sortRooms(rooms: Room[], tagId: TagID): Room[] {
|
||||
return sortRooms(rooms);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,8 +46,8 @@ export function getSortingAlgorithmInstance(algorithm: SortAlgorithm): IAlgorith
|
|||
* @param {Room[]} rooms The rooms to sort.
|
||||
* @param {TagID} tagId The tag in which the sorting is occurring.
|
||||
* @param {SortAlgorithm} algorithm The algorithm to use for sorting.
|
||||
* @returns {Promise<Room[]>} Resolves to the sorted rooms.
|
||||
* @returns {Room[]} Returns the sorted rooms.
|
||||
*/
|
||||
export function sortRoomsWithAlgorithm(rooms: Room[], tagId: TagID, algorithm: SortAlgorithm): Promise<Room[]> {
|
||||
export function sortRoomsWithAlgorithm(rooms: Room[], tagId: TagID, algorithm: SortAlgorithm): Room[] {
|
||||
return getSortingAlgorithmInstance(algorithm).sortRooms(rooms, tagId);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue