From 91a997da1435134f1f42065b3a589a5d49b2ba07 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 14 May 2020 14:06:48 -0600 Subject: [PATCH] Replace ChaoticAlgorithm for tag sorting with deterministic behaviour aka: implement the algorithms. --- ...ticAlgorithm.ts => AlphabeticAlgorithm.ts} | 11 ++- .../algorithms/tag_sorting/RecentAlgorithm.ts | 81 +++++++++++++++++++ .../room-list/algorithms/tag_sorting/index.ts | 7 +- 3 files changed, 92 insertions(+), 7 deletions(-) rename src/stores/room-list/algorithms/tag_sorting/{ChaoticAlgorithm.ts => AlphabeticAlgorithm.ts} (70%) create mode 100644 src/stores/room-list/algorithms/tag_sorting/RecentAlgorithm.ts diff --git a/src/stores/room-list/algorithms/tag_sorting/ChaoticAlgorithm.ts b/src/stores/room-list/algorithms/tag_sorting/AlphabeticAlgorithm.ts similarity index 70% rename from src/stores/room-list/algorithms/tag_sorting/ChaoticAlgorithm.ts rename to src/stores/room-list/algorithms/tag_sorting/AlphabeticAlgorithm.ts index 31846d084a..8d74ebd11e 100644 --- a/src/stores/room-list/algorithms/tag_sorting/ChaoticAlgorithm.ts +++ b/src/stores/room-list/algorithms/tag_sorting/AlphabeticAlgorithm.ts @@ -17,13 +17,16 @@ limitations under the License. import { Room } from "matrix-js-sdk/src/models/room"; import { TagID } from "../../models"; import { IAlgorithm } from "./IAlgorithm"; +import { MatrixClientPeg } from "../../../../MatrixClientPeg"; +import * as Unread from "../../../../Unread"; /** - * A demonstration to test the API surface. - * TODO: Remove this before landing + * Sorts rooms according to the browser's determination of alphabetic. */ -export class ChaoticAlgorithm implements IAlgorithm { +export class AlphabeticAlgorithm implements IAlgorithm { public async sortRooms(rooms: Room[], tagId: TagID): Promise { - return rooms; + return rooms.sort((a, b) => { + return a.name.localeCompare(b.name); + }); } } diff --git a/src/stores/room-list/algorithms/tag_sorting/RecentAlgorithm.ts b/src/stores/room-list/algorithms/tag_sorting/RecentAlgorithm.ts new file mode 100644 index 0000000000..df84c051f0 --- /dev/null +++ b/src/stores/room-list/algorithms/tag_sorting/RecentAlgorithm.ts @@ -0,0 +1,81 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { Room } from "matrix-js-sdk/src/models/room"; +import { TagID } from "../../models"; +import { IAlgorithm } from "./IAlgorithm"; +import { MatrixClientPeg } from "../../../../MatrixClientPeg"; +import * as Unread from "../../../../Unread"; + +/** + * 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 { + // 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. + // 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. + + 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; + } + + 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?) + + // TODO: Don't assume we're using the same client as the peg + if (ev.getSender() === MatrixClientPeg.get().getUserId() + || 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(a) - getLastTs(b); + }); + } +} diff --git a/src/stores/room-list/algorithms/tag_sorting/index.ts b/src/stores/room-list/algorithms/tag_sorting/index.ts index 155c0f0118..c22865f5ba 100644 --- a/src/stores/room-list/algorithms/tag_sorting/index.ts +++ b/src/stores/room-list/algorithms/tag_sorting/index.ts @@ -14,16 +14,17 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { ChaoticAlgorithm } from "./ChaoticAlgorithm"; import { SortAlgorithm } from "../models"; import { ManualAlgorithm } from "./ManualAlgorithm"; import { IAlgorithm } from "./IAlgorithm"; import { TagID } from "../../models"; import { Room } from "matrix-js-sdk/src/models/room"; +import { RecentAlgorithm } from "./RecentAlgorithm"; +import { AlphabeticAlgorithm } from "./AlphabeticAlgorithm"; const ALGORITHM_INSTANCES: { [algorithm in SortAlgorithm]: IAlgorithm } = { - [SortAlgorithm.Recent]: new ChaoticAlgorithm(), - [SortAlgorithm.Alphabetic]: new ChaoticAlgorithm(), + [SortAlgorithm.Recent]: new RecentAlgorithm(), + [SortAlgorithm.Alphabetic]: new AlphabeticAlgorithm(), [SortAlgorithm.Manual]: new ManualAlgorithm(), };