Inline documentation
parent
b08ab6cd12
commit
0c7e0a264b
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2018 New Vector Ltd
|
Copyright 2018, 2019 New Vector Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -19,10 +19,22 @@ import DMRoomMap from '../utils/DMRoomMap';
|
||||||
import Unread from '../Unread';
|
import Unread from '../Unread';
|
||||||
import SettingsStore from "../settings/SettingsStore";
|
import SettingsStore from "../settings/SettingsStore";
|
||||||
|
|
||||||
const CATEGORY_RED = "red";
|
/*
|
||||||
const CATEGORY_GREY = "grey";
|
Room sorting algorithm:
|
||||||
const CATEGORY_BOLD = "bold";
|
* Always prefer to have red > grey > bold > idle
|
||||||
const CATEGORY_IDLE = "idle";
|
* The room being viewed should be sticky (not jump down to the idle list)
|
||||||
|
* When switching to a new room, sort the last sticky room to the top of the idle list.
|
||||||
|
|
||||||
|
The approach taken by the store is to generate an initial representation of all the
|
||||||
|
tagged lists (accepting that it'll take a little bit longer to calculate) and make
|
||||||
|
small changes to that over time. This results in quick changes to the room list while
|
||||||
|
also having update operations feel more like popping/pushing to a stack.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const CATEGORY_RED = "red"; // Mentions in the room
|
||||||
|
const CATEGORY_GREY = "grey"; // Unread notified messages (not mentions)
|
||||||
|
const CATEGORY_BOLD = "bold"; // Unread messages (not notified, 'Mentions Only' rooms)
|
||||||
|
const CATEGORY_IDLE = "idle"; // Nothing of interest
|
||||||
|
|
||||||
const CATEGORY_ORDER = [CATEGORY_RED, CATEGORY_GREY, CATEGORY_BOLD, CATEGORY_IDLE];
|
const CATEGORY_ORDER = [CATEGORY_RED, CATEGORY_GREY, CATEGORY_BOLD, CATEGORY_IDLE];
|
||||||
const LIST_ORDERS = {
|
const LIST_ORDERS = {
|
||||||
|
@ -70,6 +82,10 @@ class RoomListStore extends Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
_setState(newState) {
|
_setState(newState) {
|
||||||
|
// If we're changing the lists, transparently change the presentation lists (which
|
||||||
|
// is given to requesting components). This dramatically simplifies our code elsewhere
|
||||||
|
// while also ensuring we don't need to update all the calling components to support
|
||||||
|
// categories.
|
||||||
if (newState['lists']) {
|
if (newState['lists']) {
|
||||||
const presentationLists = {};
|
const presentationLists = {};
|
||||||
for (const key of Object.keys(newState['lists'])) {
|
for (const key of Object.keys(newState['lists'])) {
|
||||||
|
@ -205,20 +221,24 @@ class RoomListStore extends Store {
|
||||||
|
|
||||||
// Note: it is important that we set a new stickyRoomId before setting the old room
|
// Note: it is important that we set a new stickyRoomId before setting the old room
|
||||||
// to IDLE. If we don't, the wrong room gets counted as sticky.
|
// to IDLE. If we don't, the wrong room gets counted as sticky.
|
||||||
const currentSticky = this._state.stickyRoomId;
|
const currentStickyId = this._state.stickyRoomId;
|
||||||
this._setState({stickyRoomId: payload.room_id});
|
this._setState({stickyRoomId: payload.room_id});
|
||||||
if (currentSticky) {
|
if (currentStickyId) {
|
||||||
this._setRoomCategory(this._matrixClient.getRoom(currentSticky), CATEGORY_IDLE);
|
this._setRoomCategory(this._matrixClient.getRoom(currentStickyId), CATEGORY_IDLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
_roomUpdateTriggered(roomId) {
|
_roomUpdateTriggered(roomId) {
|
||||||
const room = this._matrixClient.getRoom(roomId);
|
const room = this._matrixClient.getRoom(roomId);
|
||||||
if (!room) return;
|
if (!room) return;
|
||||||
|
|
||||||
|
// We don't calculate categories for sticky rooms because we have a moderate
|
||||||
|
// interest in trying to maintain the category that they were last in before
|
||||||
|
// being artificially flagged as IDLE. Also, this reduces the amount of time
|
||||||
|
// we spend in _setRoomCategory ever so slightly.
|
||||||
if (this._state.stickyRoomId !== room.roomId) {
|
if (this._state.stickyRoomId !== room.roomId) {
|
||||||
const category = this._calculateCategory(room);
|
const category = this._calculateCategory(room);
|
||||||
this._setRoomCategory(room, category);
|
this._setRoomCategory(room, category);
|
||||||
|
@ -227,8 +247,8 @@ class RoomListStore extends Store {
|
||||||
|
|
||||||
_setRoomCategory(room, category) {
|
_setRoomCategory(room, category) {
|
||||||
const listsClone = {};
|
const listsClone = {};
|
||||||
const targetCatIndex = CATEGORY_ORDER.indexOf(category);
|
const targetCategoryIndex = CATEGORY_ORDER.indexOf(category);
|
||||||
const targetTs = this._tsOfNewestEvent(room);
|
const targetTimestamp = this._tsOfNewestEvent(room);
|
||||||
|
|
||||||
const myMembership = room.getMyMembership();
|
const myMembership = room.getMyMembership();
|
||||||
let doInsert = true;
|
let doInsert = true;
|
||||||
|
@ -247,35 +267,43 @@ class RoomListStore extends Store {
|
||||||
// We need to update all instances of a room to ensure that they are correctly organized
|
// We need to update all instances of a room to ensure that they are correctly organized
|
||||||
// in the list. We do this by shallow-cloning the entire `lists` object using a single
|
// in the list. We do this by shallow-cloning the entire `lists` object using a single
|
||||||
// iterator. Within the loop, we also rebuild the list of rooms per tag (key) so that the
|
// iterator. Within the loop, we also rebuild the list of rooms per tag (key) so that the
|
||||||
// updated room gets slotted into the right spot.
|
// updated room gets slotted into the right spot. This sacrifices code clarity for not
|
||||||
|
// iterating on potentially large collections multiple times.
|
||||||
|
|
||||||
let inserted = false;
|
let inserted = false;
|
||||||
for (const key of Object.keys(this._state.lists)) {
|
for (const key of Object.keys(this._state.lists)) {
|
||||||
listsClone[key] = [];
|
listsClone[key] = [];
|
||||||
let pushedEntry = false;
|
let pushedEntry = false;
|
||||||
const hasRoom = !!this._state.lists[key].find((e) => e.room.roomId === room.roomId);
|
const hasRoom = !!this._state.lists[key].find((e) => e.room.roomId === room.roomId);
|
||||||
|
|
||||||
|
// We track where the boundary within listsClone[key] is just in case our timestamp
|
||||||
|
// ordering fails. If we can't stick the room in at the correct place in the category
|
||||||
|
// grouping based on timestamp, we'll stick it at the top of the group which will be
|
||||||
|
// the index we track here.
|
||||||
let desiredCategoryBoundaryIndex = 0;
|
let desiredCategoryBoundaryIndex = 0;
|
||||||
let foundBoundary = false;
|
let foundBoundary = false;
|
||||||
|
|
||||||
for (const entry of this._state.lists[key]) {
|
for (const entry of this._state.lists[key]) {
|
||||||
// if the list is a recent list, and the room appears in this list, and we're not looking at a sticky
|
// if the list is a recent list, and the room appears in this list, and we're not looking at a sticky
|
||||||
// room (sticky rooms have unreliable categories), try to slot the new room in
|
// room (sticky rooms have unreliable categories), try to slot the new room in
|
||||||
if (LIST_ORDERS[key] === 'recent' && hasRoom && entry.room.roomId !== this._state.stickyRoomId) {
|
if (LIST_ORDERS[key] === 'recent' && hasRoom && entry.room.roomId !== this._state.stickyRoomId) {
|
||||||
const inTag = targetTags.length === 0 || targetTags.includes(key);
|
const inTag = targetTags.length === 0 || targetTags.includes(key);
|
||||||
if (!pushedEntry && doInsert && inTag) {
|
if (!pushedEntry && doInsert && inTag) {
|
||||||
const entryTs = this._tsOfNewestEvent(entry.room);
|
const entryTimestamp = this._tsOfNewestEvent(entry.room);
|
||||||
const entryCategory = CATEGORY_ORDER.indexOf(entry.category);
|
const entryCategoryIndex = CATEGORY_ORDER.indexOf(entry.category);
|
||||||
|
|
||||||
if (entryCategory >= targetCatIndex && !foundBoundary) {
|
// As per above, check if we're meeting that boundary we wanted to locate.
|
||||||
|
if (entryCategoryIndex >= targetCategoryIndex && !foundBoundary) {
|
||||||
desiredCategoryBoundaryIndex = listsClone[key].length - 1;
|
desiredCategoryBoundaryIndex = listsClone[key].length - 1;
|
||||||
foundBoundary = true;
|
foundBoundary = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we've hit the top of a boundary (either because there's no rooms in the target or
|
// If we've hit the top of a boundary beyond our target category, insert at the top of
|
||||||
// we've reached the grouping of rooms), insert our room ahead of the others in the category.
|
// the grouping to ensure the room isn't slotted incorrectly. Otherwise, try to insert
|
||||||
// This ensures that our room is on top (more recent) than the others.
|
// based on most recent timestamp.
|
||||||
const changedBoundary = entryCategory >= targetCatIndex;
|
const changedBoundary = entryCategoryIndex > targetCategoryIndex;
|
||||||
const currentCategory = entryCategory === targetCatIndex;
|
const currentCategory = entryCategoryIndex === targetCategoryIndex;
|
||||||
if (changedBoundary || (false && currentCategory && targetTs >= entryTs)) {
|
if (changedBoundary || (false && currentCategory && targetTimestamp >= entryTimestamp)) {
|
||||||
if (changedBoundary && false) {
|
if (changedBoundary && false) {
|
||||||
// If we changed a boundary, then we've gone too far - go to the top of the last
|
// If we changed a boundary, then we've gone too far - go to the top of the last
|
||||||
// section instead.
|
// section instead.
|
||||||
|
@ -317,8 +345,9 @@ class RoomListStore extends Store {
|
||||||
}
|
}
|
||||||
for (const tag of tags) {
|
for (const tag of tags) {
|
||||||
for (let i = 0; i < listsClone[tag].length; i++) {
|
for (let i = 0; i < listsClone[tag].length; i++) {
|
||||||
|
// Just find the top of our category grouping and insert it there.
|
||||||
const catIdxAtPosition = CATEGORY_ORDER.indexOf(listsClone[tag][i].category);
|
const catIdxAtPosition = CATEGORY_ORDER.indexOf(listsClone[tag][i].category);
|
||||||
if (catIdxAtPosition >= targetCatIndex) {
|
if (catIdxAtPosition >= targetCategoryIndex) {
|
||||||
listsClone[tag].splice(i, 0, {room: room, category: category});
|
listsClone[tag].splice(i, 0, {room: room, category: category});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -379,7 +408,9 @@ class RoomListStore extends Store {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// We use this cache in the recents comparator because _tsOfNewestEvent can take a while
|
// We use this cache in the recents comparator because _tsOfNewestEvent can take a while. This
|
||||||
|
// cache only needs to survive the sort operation below and should not be implemented outside
|
||||||
|
// of this function, otherwise the room lists will almost certainly be out of date and wrong.
|
||||||
const latestEventTsCache = {}; // roomId => timestamp
|
const latestEventTsCache = {}; // roomId => timestamp
|
||||||
|
|
||||||
Object.keys(lists).forEach((listKey) => {
|
Object.keys(lists).forEach((listKey) => {
|
||||||
|
|
Loading…
Reference in New Issue