Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 2fce69a

Browse files
committed
Remove the need for a tag manager
Instead putting the tag handling in the Algorithm class
1 parent bfc5077 commit 2fce69a

9 files changed

+212
-269
lines changed

src/stores/room-list/README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,10 @@ all kinds of filtering.
109109

110110
## Class breakdowns
111111

112-
The `RoomListStore` is the major coordinator of various `IAlgorithm` implementations, which take care
113-
of the various `ListAlgorithm` and `SortingAlgorithm` options. A `TagManager` is responsible for figuring
114-
out which tags get which rooms, as Matrix specifies them as a reverse map: tags are defined on rooms and
115-
are not defined as a collection of rooms (unlike how they are presented to the user). Various list-specific
116-
utilities are also included, though they are expected to move somewhere more general when needed. For
117-
example, the `membership` utilities could easily be moved elsewhere as needed.
112+
The `RoomListStore` is the major coordinator of various `Algorithm` implementations, which take care
113+
of the various `ListAlgorithm` and `SortingAlgorithm` options. The `Algorithm` superclass is also
114+
responsible for figuring out which tags get which rooms, as Matrix specifies them as a reverse map:
115+
tags are defined on rooms and are not defined as a collection of rooms (unlike how they are presented
116+
to the user). Various list-specific utilities are also included, though they are expected to move
117+
somewhere more general when needed. For example, the `membership` utilities could easily be moved
118+
elsewhere as needed.

src/stores/room-list/RoomListStore2.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { MatrixClient } from "matrix-js-sdk/src/client";
1919
import { ActionPayload, defaultDispatcher } from "../../dispatcher-types";
2020
import SettingsStore from "../../settings/SettingsStore";
2121
import { DefaultTagID, OrderedDefaultTagIDs, TagID } from "./models";
22-
import { IAlgorithm, ITagMap, ITagSortingMap, ListAlgorithm, SortAlgorithm } from "./algorithms/IAlgorithm";
22+
import { Algorithm, ITagMap, ITagSortingMap, ListAlgorithm, SortAlgorithm } from "./algorithms/Algorithm";
2323
import TagOrderStore from "../TagOrderStore";
2424
import { getAlgorithmInstance } from "./algorithms";
2525
import { AsyncStore } from "../AsyncStore";
@@ -41,7 +41,7 @@ class _RoomListStore extends AsyncStore<ActionPayload> {
4141
private matrixClient: MatrixClient;
4242
private initialListsGenerated = false;
4343
private enabled = false;
44-
private algorithm: IAlgorithm;
44+
private algorithm: Algorithm;
4545

4646
private readonly watchedSettings = [
4747
'RoomList.orderAlphabetically',

src/stores/room-list/RoomListStoreTempProxy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { Room } from "matrix-js-sdk/src/models/room";
1919
import SettingsStore from "../../settings/SettingsStore";
2020
import RoomListStore from "./RoomListStore2";
2121
import OldRoomListStore from "../RoomListStore";
22-
import { ITagMap } from "./algorithms/IAlgorithm";
22+
import { ITagMap } from "./algorithms/Algorithm";
2323
import { UPDATE_EVENT } from "../AsyncStore";
2424

2525
/**

src/stores/room-list/TagManager.ts

Lines changed: 0 additions & 32 deletions
This file was deleted.
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
/*
2+
Copyright 2020 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { DefaultTagID, TagID } from "../models";
18+
import { Room } from "matrix-js-sdk/src/models/room";
19+
import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
20+
import { EffectiveMembership, splitRoomsByMembership } from "../membership";
21+
22+
export enum SortAlgorithm {
23+
Manual = "MANUAL",
24+
Alphabetic = "ALPHABETIC",
25+
Recent = "RECENT",
26+
}
27+
28+
export enum ListAlgorithm {
29+
// Orders Red > Grey > Bold > Idle
30+
Importance = "IMPORTANCE",
31+
32+
// Orders however the SortAlgorithm decides
33+
Natural = "NATURAL",
34+
}
35+
36+
export interface ITagSortingMap {
37+
// @ts-ignore - TypeScript really wants this to be [tagId: string] but we know better.
38+
[tagId: TagID]: SortAlgorithm;
39+
}
40+
41+
export interface ITagMap {
42+
// @ts-ignore - TypeScript really wants this to be [tagId: string] but we know better.
43+
[tagId: TagID]: Room[];
44+
}
45+
46+
// TODO: Add locking support to avoid concurrent writes?
47+
// TODO: EventEmitter support? Might not be needed.
48+
49+
export abstract class Algorithm {
50+
protected cached: ITagMap = {};
51+
protected sortAlgorithms: ITagSortingMap;
52+
protected rooms: Room[] = [];
53+
protected roomsByTag: {
54+
// @ts-ignore - TS wants this to be a string but we know better
55+
[tagId: TagID]: Room[];
56+
} = {};
57+
58+
protected constructor() {
59+
}
60+
61+
/**
62+
* Asks the Algorithm to regenerate all lists, using the tags given
63+
* as reference for which lists to generate and which way to generate
64+
* them.
65+
* @param {ITagSortingMap} tagSortingMap The tags to generate.
66+
* @returns {Promise<*>} A promise which resolves when complete.
67+
*/
68+
public async populateTags(tagSortingMap: ITagSortingMap): Promise<any> {
69+
if (!tagSortingMap) throw new Error(`Map cannot be null or empty`);
70+
this.sortAlgorithms = tagSortingMap;
71+
return this.setKnownRooms(this.rooms);
72+
}
73+
74+
/**
75+
* Gets an ordered set of rooms for the all known tags.
76+
* @returns {ITagMap} The cached list of rooms, ordered,
77+
* for each tag. May be empty, but never null/undefined.
78+
*/
79+
public getOrderedRooms(): ITagMap {
80+
return this.cached;
81+
}
82+
83+
/**
84+
* Seeds the Algorithm with a set of rooms. The algorithm will discard all
85+
* previously known information and instead use these rooms instead.
86+
* @param {Room[]} rooms The rooms to force the algorithm to use.
87+
* @returns {Promise<*>} A promise which resolves when complete.
88+
*/
89+
public async setKnownRooms(rooms: Room[]): Promise<any> {
90+
if (isNullOrUndefined(rooms)) throw new Error(`Array of rooms cannot be null`);
91+
if (!this.sortAlgorithms) throw new Error(`Cannot set known rooms without a tag sorting map`);
92+
93+
this.rooms = rooms;
94+
95+
const newTags: ITagMap = {};
96+
for (const tagId in this.sortAlgorithms) {
97+
// noinspection JSUnfilteredForInLoop
98+
newTags[tagId] = [];
99+
}
100+
101+
// If we can avoid doing work, do so.
102+
if (!rooms.length) {
103+
await this.generateFreshTags(newTags); // just in case it wants to do something
104+
this.cached = newTags;
105+
return;
106+
}
107+
108+
// Split out the easy rooms first (leave and invite)
109+
const memberships = splitRoomsByMembership(rooms);
110+
for (const room of memberships[EffectiveMembership.Invite]) {
111+
console.log(`[DEBUG] "${room.name}" (${room.roomId}) is an Invite`);
112+
newTags[DefaultTagID.Invite].push(room);
113+
}
114+
for (const room of memberships[EffectiveMembership.Leave]) {
115+
console.log(`[DEBUG] "${room.name}" (${room.roomId}) is Historical`);
116+
newTags[DefaultTagID.Archived].push(room);
117+
}
118+
119+
// Now process all the joined rooms. This is a bit more complicated
120+
for (const room of memberships[EffectiveMembership.Join]) {
121+
const tags = Object.keys(room.tags || {});
122+
123+
let inTag = false;
124+
if (tags.length > 0) {
125+
for (const tag of tags) {
126+
if (!isNullOrUndefined(newTags[tag])) {
127+
console.log(`[DEBUG] "${room.name}" (${room.roomId}) is tagged as ${tag}`);
128+
newTags[tag].push(room);
129+
inTag = true;
130+
}
131+
}
132+
}
133+
134+
if (!inTag) {
135+
// TODO: Determine if DM and push there instead
136+
newTags[DefaultTagID.Untagged].push(room);
137+
console.log(`[DEBUG] "${room.name}" (${room.roomId}) is Untagged`);
138+
}
139+
}
140+
141+
await this.generateFreshTags(newTags);
142+
143+
this.cached = newTags;
144+
}
145+
146+
/**
147+
* Called when the Algorithm believes a complete regeneration of the existing
148+
* lists is needed.
149+
* @param {ITagMap} updatedTagMap The tag map which needs populating. Each tag
150+
* will already have the rooms which belong to it - they just need ordering. Must
151+
* be mutated in place.
152+
* @returns {Promise<*>} A promise which resolves when complete.
153+
*/
154+
protected abstract generateFreshTags(updatedTagMap: ITagMap): Promise<any>;
155+
156+
/**
157+
* Called when the Algorithm wants a whole tag to be reordered. Typically this will
158+
* be done whenever the tag's scope changes (added/removed rooms).
159+
* @param {TagID} tagId The tag ID which changed.
160+
* @param {Room[]} rooms The rooms within the tag, unordered.
161+
* @returns {Promise<Room[]>} Resolves to the ordered rooms in the tag.
162+
*/
163+
protected abstract regenerateTag(tagId: TagID, rooms: Room[]): Promise<Room[]>;
164+
165+
/**
166+
* Asks the Algorithm to update its knowledge of a room. For example, when
167+
* a user tags a room, joins/creates a room, or leaves a room the Algorithm
168+
* should be told that the room's info might have changed. The Algorithm
169+
* may no-op this request if no changes are required.
170+
* @param {Room} room The room which might have affected sorting.
171+
* @returns {Promise<boolean>} A promise which resolve to true or false
172+
* depending on whether or not getOrderedRooms() should be called after
173+
* processing.
174+
*/
175+
// TODO: Take a ReasonForChange to better predict the behaviour?
176+
// TODO: Intercept here and handle tag changes automatically
177+
public abstract handleRoomUpdate(room: Room): Promise<boolean>;
178+
}

src/stores/room-list/algorithms/ChaoticAlgorithm.ts

Lines changed: 10 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -14,89 +14,29 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import { IAlgorithm, ITagMap, ITagSortingMap, ListAlgorithm } from "./IAlgorithm";
18-
import { Room } from "matrix-js-sdk/src/models/room";
19-
import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
17+
import { Algorithm, ITagMap } from "./Algorithm";
2018
import { DefaultTagID } from "../models";
21-
import { splitRoomsByMembership } from "../membership";
2219

2320
/**
2421
* A demonstration/temporary algorithm to verify the API surface works.
2522
* TODO: Remove this before shipping
2623
*/
27-
export class ChaoticAlgorithm implements IAlgorithm {
24+
export class ChaoticAlgorithm extends Algorithm {
2825

29-
private cached: ITagMap = {};
30-
private sortAlgorithms: ITagSortingMap;
31-
private rooms: Room[] = [];
32-
33-
constructor(private representativeAlgorithm: ListAlgorithm) {
26+
constructor() {
27+
super();
3428
console.log("Constructed a ChaoticAlgorithm");
3529
}
3630

37-
getOrderedRooms(): ITagMap {
38-
return this.cached;
39-
}
40-
41-
async populateTags(tagSortingMap: ITagSortingMap): Promise<any> {
42-
if (!tagSortingMap) throw new Error(`Map cannot be null or empty`);
43-
this.sortAlgorithms = tagSortingMap;
44-
this.setKnownRooms(this.rooms); // regenerate the room lists
31+
protected async generateFreshTags(updatedTagMap: ITagMap): Promise<any> {
32+
return Promise.resolve();
4533
}
4634

47-
handleRoomUpdate(room): Promise<boolean> {
48-
return undefined;
35+
protected async regenerateTag(tagId: string | DefaultTagID, rooms: []): Promise<[]> {
36+
return Promise.resolve(rooms);
4937
}
5038

51-
setKnownRooms(rooms: Room[]): Promise<any> {
52-
if (isNullOrUndefined(rooms)) throw new Error(`Array of rooms cannot be null`);
53-
if (!this.sortAlgorithms) throw new Error(`Cannot set known rooms without a tag sorting map`);
54-
55-
this.rooms = rooms;
56-
57-
const newTags = {};
58-
for (const tagId in this.sortAlgorithms) {
59-
// noinspection JSUnfilteredForInLoop
60-
newTags[tagId] = [];
61-
}
62-
63-
// If we can avoid doing work, do so.
64-
if (!rooms.length) {
65-
this.cached = newTags;
66-
return;
67-
}
68-
69-
// TODO: Remove logging
70-
const memberships = splitRoomsByMembership(rooms);
71-
console.log({alg: this.representativeAlgorithm, memberships});
72-
73-
// Step through each room and determine which tags it should be in.
74-
// We don't care about ordering or sorting here - we're simply organizing things.
75-
for (const room of rooms) {
76-
const tags = room.tags;
77-
let inTag = false;
78-
for (const tagId in tags) {
79-
// noinspection JSUnfilteredForInLoop
80-
if (isNullOrUndefined(newTags[tagId])) {
81-
// skip the tag if we don't know about it
82-
continue;
83-
}
84-
85-
inTag = true;
86-
87-
// noinspection JSUnfilteredForInLoop
88-
newTags[tagId].push(room);
89-
}
90-
91-
// If the room wasn't pushed to a tag, push it to the untagged tag.
92-
if (!inTag) {
93-
newTags[DefaultTagID.Untagged].push(room);
94-
}
95-
}
96-
97-
// TODO: Do sorting
98-
99-
// Finally, assign the tags to our cache
100-
this.cached = newTags;
39+
public async handleRoomUpdate(room): Promise<boolean> {
40+
return Promise.resolve(false);
10141
}
10242
}

0 commit comments

Comments
 (0)