From 5dda7f02cffb88e0621b68429fcc725e4dab8bb6 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 4 May 2020 09:06:34 -0600 Subject: [PATCH] Early handling of dispatched events A possible approach to handling the various triggers for recategorizing rooms. --- src/components/views/rooms/RoomList2.tsx | 1 - src/stores/room-list/RoomListStore2.ts | 41 ++++++++++++++++--- .../algorithms/list_ordering/Algorithm.ts | 41 +++++++++++-------- .../list_ordering/ChaoticAlgorithm.ts | 7 +--- .../list_ordering/ImportanceAlgorithm.ts | 17 ++++---- src/stores/room-list/models.ts | 5 +++ 6 files changed, 75 insertions(+), 37 deletions(-) diff --git a/src/components/views/rooms/RoomList2.tsx b/src/components/views/rooms/RoomList2.tsx index 04cb8a4549..f7788f0003 100644 --- a/src/components/views/rooms/RoomList2.tsx +++ b/src/components/views/rooms/RoomList2.tsx @@ -28,7 +28,6 @@ import { Dispatcher } from "flux"; import { ActionPayload } from "../../../dispatcher-types"; import dis from "../../../dispatcher"; import { RoomSublist2 } from "./RoomSublist2"; -import { isNullOrUndefined } from "matrix-js-sdk/lib/src/utils"; interface IProps { onKeyDown: (ev: React.KeyboardEvent) => void; diff --git a/src/stores/room-list/RoomListStore2.ts b/src/stores/room-list/RoomListStore2.ts index 0b3f61e261..06f97ae53e 100644 --- a/src/stores/room-list/RoomListStore2.ts +++ b/src/stores/room-list/RoomListStore2.ts @@ -18,12 +18,14 @@ limitations under the License. import { MatrixClient } from "matrix-js-sdk/src/client"; import { ActionPayload, defaultDispatcher } from "../../dispatcher-types"; import SettingsStore from "../../settings/SettingsStore"; -import { DefaultTagID, OrderedDefaultTagIDs, TagID } from "./models"; +import { DefaultTagID, OrderedDefaultTagIDs, RoomUpdateCause, TagID } from "./models"; import { Algorithm } from "./algorithms/list_ordering/Algorithm"; import TagOrderStore from "../TagOrderStore"; import { getListAlgorithmInstance } from "./algorithms/list_ordering"; import { AsyncStore } from "../AsyncStore"; import { ITagMap, ITagSortingMap, ListAlgorithm, SortAlgorithm } from "./algorithms/models"; +import { Room } from "matrix-js-sdk/src/models/room"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; interface IState { tagsEnabled?: boolean; @@ -123,7 +125,14 @@ class _RoomListStore extends AsyncStore { await this.regenerateAllLists(); // regenerate the lists now } - } else if (payload.action === 'MatrixActions.Room.receipt') { + } + + if (!this.algorithm) { + // This shouldn't happen because `initialListsGenerated` implies we have an algorithm. + throw new Error("Room list store has no algorithm to process dispatcher update with"); + } + + if (payload.action === 'MatrixActions.Room.receipt') { // First see if the receipt event is for our own user. If it was, trigger // a room update (we probably read the room on a different device). // noinspection JSObjectNullOrUndefined - this.matrixClient can't be null by this point in the lifecycle @@ -132,23 +141,45 @@ class _RoomListStore extends AsyncStore { const receiptUsers = Object.keys(payload.event.getContent()[eventId]['m.read'] || {}); if (receiptUsers.includes(myUserId)) { // TODO: Update room now that it's been read + console.log(payload); return; } } } else if (payload.action === 'MatrixActions.Room.tags') { // TODO: Update room from tags - } else if (payload.action === 'MatrixActions.room.timeline') { - // TODO: Update room from new events + console.log(payload); + } else if (payload.action === 'MatrixActions.Room.timeline') { + const eventPayload = (payload); // TODO: Type out the dispatcher types + + // Ignore non-live events (backfill) + if (!eventPayload.isLiveEvent || !payload.isLiveUnfilteredRoomTimelineEvent) return; + + const roomId = eventPayload.event.getRoomId(); + const room = this.matrixClient.getRoom(roomId); + await this.handleRoomUpdate(room, RoomUpdateCause.Timeline); } else if (payload.action === 'MatrixActions.Event.decrypted') { // TODO: Update room from decrypted event + console.log(payload); } else if (payload.action === 'MatrixActions.accountData' && payload.event_type === 'm.direct') { // TODO: Update DMs + console.log(payload); } else if (payload.action === 'MatrixActions.Room.myMembership') { // TODO: Update room from membership change - } else if (payload.action === 'MatrixActions.room') { + console.log(payload); + } else if (payload.action === 'MatrixActions.Room') { // TODO: Update room from creation/join + console.log(payload); } else if (payload.action === 'view_room') { // TODO: Update sticky room + console.log(payload); + } + } + + private async handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise { + const shouldUpdate = await this.algorithm.handleRoomUpdate(room, cause); + if (shouldUpdate) { + console.log(`[DEBUG] Room "${room.name}" (${room.roomId}) triggered by ${cause} requires list update`); + this.emit(LISTS_UPDATE_EVENT, this); } } diff --git a/src/stores/room-list/algorithms/list_ordering/Algorithm.ts b/src/stores/room-list/algorithms/list_ordering/Algorithm.ts index fd98f34966..e154847847 100644 --- a/src/stores/room-list/algorithms/list_ordering/Algorithm.ts +++ b/src/stores/room-list/algorithms/list_ordering/Algorithm.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { DefaultTagID, TagID } from "../../models"; +import { DefaultTagID, RoomUpdateCause, TagID } from "../../models"; import { Room } from "matrix-js-sdk/src/models/room"; import { isNullOrUndefined } from "matrix-js-sdk/src/utils"; import { EffectiveMembership, splitRoomsByMembership } from "../../membership"; @@ -33,9 +33,8 @@ export abstract class Algorithm { protected cached: ITagMap = {}; protected sortAlgorithms: ITagSortingMap; protected rooms: Room[] = []; - protected roomsByTag: { - // @ts-ignore - TS wants this to be a string but we know better - [tagId: TagID]: Room[]; + protected roomIdsToTags: { + [roomId: string]: TagID[]; } = {}; protected constructor() { @@ -132,6 +131,25 @@ export abstract class Algorithm { await this.generateFreshTags(newTags); this.cached = newTags; + this.updateTagsFromCache(); + } + + /** + * Updates the roomsToTags map + */ + protected updateTagsFromCache() { + const newMap = {}; + + const tags = Object.keys(this.cached); + for (const tagId of tags) { + const rooms = this.cached[tagId]; + for (const room of rooms) { + if (!newMap[room.roomId]) newMap[room.roomId] = []; + newMap[room.roomId].push(tagId); + } + } + + this.roomIdsToTags = newMap; } /** @@ -144,27 +162,16 @@ export abstract class Algorithm { */ protected abstract generateFreshTags(updatedTagMap: ITagMap): Promise; - /** - * Called when the Algorithm wants a whole tag to be reordered. Typically this will - * be done whenever the tag's scope changes (added/removed rooms). - * @param {TagID} tagId The tag ID which changed. - * @param {Room[]} rooms The rooms within the tag, unordered. - * @returns {Promise} Resolves to the ordered rooms in the tag. - */ - // TODO: Do we need this? - protected abstract regenerateTag(tagId: TagID, rooms: Room[]): Promise; - /** * Asks the Algorithm to update its knowledge of a room. For example, when * a user tags a room, joins/creates a room, or leaves a room the Algorithm * should be told that the room's info might have changed. The Algorithm * 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} A promise which resolve to true or false * depending on whether or not getOrderedRooms() should be called after * processing. */ - // TODO: Take a ReasonForChange to better predict the behaviour? - // TODO: Intercept here and handle tag changes automatically? May be best to let the impl do that. - public abstract handleRoomUpdate(room: Room): Promise; + public abstract handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise; } diff --git a/src/stores/room-list/algorithms/list_ordering/ChaoticAlgorithm.ts b/src/stores/room-list/algorithms/list_ordering/ChaoticAlgorithm.ts index 7c1a0b1acc..185fb606fb 100644 --- a/src/stores/room-list/algorithms/list_ordering/ChaoticAlgorithm.ts +++ b/src/stores/room-list/algorithms/list_ordering/ChaoticAlgorithm.ts @@ -15,7 +15,6 @@ limitations under the License. */ import { Algorithm } from "./Algorithm"; -import { DefaultTagID } from "../../models"; import { ITagMap } from "../models"; /** @@ -33,11 +32,7 @@ export class ChaoticAlgorithm extends Algorithm { return Promise.resolve(); } - protected async regenerateTag(tagId: string | DefaultTagID, rooms: []): Promise<[]> { - return Promise.resolve(rooms); - } - - public async handleRoomUpdate(room): Promise { + public async handleRoomUpdate(room, cause): Promise { return Promise.resolve(false); } } diff --git a/src/stores/room-list/algorithms/list_ordering/ImportanceAlgorithm.ts b/src/stores/room-list/algorithms/list_ordering/ImportanceAlgorithm.ts index d73fdee930..d66f7cdc37 100644 --- a/src/stores/room-list/algorithms/list_ordering/ImportanceAlgorithm.ts +++ b/src/stores/room-list/algorithms/list_ordering/ImportanceAlgorithm.ts @@ -17,9 +17,9 @@ limitations under the License. import { Algorithm } from "./Algorithm"; import { Room } from "matrix-js-sdk/src/models/room"; -import { DefaultTagID, TagID } from "../../models"; +import { DefaultTagID, RoomUpdateCause, TagID } from "../../models"; import { ITagMap, SortAlgorithm } from "../models"; -import { getSortingAlgorithmInstance, sortRoomsWithAlgorithm } from "../tag_sorting"; +import { sortRoomsWithAlgorithm } from "../tag_sorting"; import * as Unread from '../../../../Unread'; /** @@ -194,11 +194,12 @@ export class ImportanceAlgorithm extends Algorithm { } } - protected async regenerateTag(tagId: string | DefaultTagID, rooms: []): Promise<[]> { - return Promise.resolve(rooms); - } - - public async handleRoomUpdate(room): Promise { - return Promise.resolve(false); + public async handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise { + const tags = this.roomIdsToTags[room.roomId]; + if (!tags) { + console.warn(`No tags known for "${room.name}" (${room.roomId})`); + return false; + } + return false; } } diff --git a/src/stores/room-list/models.ts b/src/stores/room-list/models.ts index d1c915e035..5294680773 100644 --- a/src/stores/room-list/models.ts +++ b/src/stores/room-list/models.ts @@ -34,3 +34,8 @@ export const OrderedDefaultTagIDs = [ ]; export type TagID = string | DefaultTagID; + +export enum RoomUpdateCause { + Timeline = "TIMELINE", + RoomRead = "ROOM_READ", +}