[WITH CONFLICTS] Merge branch 'develop' into travis/room-list/enable

This commit is contained in:
Travis Ralston 2020-07-10 10:18:12 -06:00
commit bf0a503e44
12 changed files with 313 additions and 117 deletions

View file

@ -54,7 +54,11 @@ $tagPanelWidth: 70px; // only applies in this file, used for calculations
flex-direction: column; flex-direction: column;
.mx_LeftPanel2_userHeader { .mx_LeftPanel2_userHeader {
padding: 12px 12px 20px; // 12px top, 12px sides, 20px bottom /* 12px top, 12px sides, 20px bottom (using 13px bottom to account
* for internal whitespace in the breadcrumbs)
*/
padding: 12px 12px 13px;
flex-shrink: 0; // to convince safari's layout engine the flexbox is fine
// Create another flexbox column for the rows to stack within // Create another flexbox column for the rows to stack within
display: flex; display: flex;
@ -72,7 +76,20 @@ $tagPanelWidth: 70px; // only applies in this file, used for calculations
width: 100%; width: 100%;
overflow-y: hidden; overflow-y: hidden;
overflow-x: scroll; overflow-x: scroll;
margin-top: 8px; margin-top: 20px;
padding-bottom: 2px;
&.mx_IndicatorScrollbar_leftOverflow {
mask-image: linear-gradient(90deg, transparent, black 10%);
}
&.mx_IndicatorScrollbar_rightOverflow {
mask-image: linear-gradient(90deg, black, black 90%, transparent);
}
&.mx_IndicatorScrollbar_rightOverflow.mx_IndicatorScrollbar_leftOverflow {
mask-image: linear-gradient(90deg, transparent, black 10%, black 90%, transparent);
}
} }
} }
@ -80,6 +97,8 @@ $tagPanelWidth: 70px; // only applies in this file, used for calculations
margin-left: 12px; margin-left: 12px;
margin-right: 12px; margin-right: 12px;
flex-shrink: 0; // to convince safari's layout engine the flexbox is fine
// Create a flexbox to organize the inputs // Create a flexbox to organize the inputs
display: flex; display: flex;
align-items: center; align-items: center;

View file

@ -24,6 +24,8 @@ limitations under the License.
margin-left: 8px; margin-left: 8px;
width: 100%; width: 100%;
flex-shrink: 0; // to convince safari's layout engine the flexbox is fine
.mx_RoomSublist2_headerContainer { .mx_RoomSublist2_headerContainer {
// Create a flexbox to make alignment easy // Create a flexbox to make alignment easy
display: flex; display: flex;

View file

@ -37,6 +37,9 @@ declare global {
mx_RoomListStore2: RoomListStore2; mx_RoomListStore2: RoomListStore2;
mx_RoomListLayoutStore: RoomListLayoutStore; mx_RoomListLayoutStore: RoomListLayoutStore;
mxPlatformPeg: PlatformPeg; mxPlatformPeg: PlatformPeg;
// TODO: Remove flag before launch: https://github.com/vector-im/riot-web/issues/14231
mx_QuietRoomListLogging: boolean;
} }
// workaround for https://github.com/microsoft/TypeScript/issues/30933 // workaround for https://github.com/microsoft/TypeScript/issues/30933

View file

@ -219,7 +219,10 @@ export default class RoomList2 extends React.Component<IProps, IState> {
private updateLists = () => { private updateLists = () => {
const newLists = RoomListStore.instance.orderedLists; const newLists = RoomListStore.instance.orderedLists;
if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log("new lists", newLists); console.log("new lists", newLists);
}
this.setState({sublists: newLists}, () => { this.setState({sublists: newLists}, () => {
this.props.onResize(); this.props.onResize();

View file

@ -256,7 +256,7 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
0 0
)); ));
} else { } else {
console.log(`Unexpected tag ${tagId} applied to ${this.props.room.room_id}`); console.warn(`Unexpected tag ${tagId} applied to ${this.props.room.room_id}`);
} }
if ((ev as React.KeyboardEvent).key === Key.ENTER) { if ((ev as React.KeyboardEvent).key === Key.ENTER) {

View file

@ -33,6 +33,7 @@ import { EffectiveMembership, getEffectiveMembership } from "./membership";
import { ListLayout } from "./ListLayout"; import { ListLayout } from "./ListLayout";
import { isNullOrUndefined } from "matrix-js-sdk/src/utils"; import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
import RoomListLayoutStore from "./RoomListLayoutStore"; import RoomListLayoutStore from "./RoomListLayoutStore";
import { MarkedExecution } from "../../utils/MarkedExecution";
interface IState { interface IState {
tagsEnabled?: boolean; tagsEnabled?: boolean;
@ -51,7 +52,7 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
private algorithm = new Algorithm(); private algorithm = new Algorithm();
private filterConditions: IFilterCondition[] = []; private filterConditions: IFilterCondition[] = [];
private tagWatcher = new TagWatcher(this); private tagWatcher = new TagWatcher(this);
private layoutMap: Map<TagID, ListLayout> = new Map<TagID, ListLayout>(); private updateFn = new MarkedExecution(() => this.emit(LISTS_UPDATE_EVENT));
private readonly watchedSettings = [ private readonly watchedSettings = [
'feature_custom_tags', 'feature_custom_tags',
@ -62,7 +63,7 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
this.checkEnabled(); this.checkEnabled();
for (const settingName of this.watchedSettings) SettingsStore.monitorSetting(settingName, null); for (const settingName of this.watchedSettings) SettingsStore.monitorSetting(settingName, null);
RoomViewStore.addListener(this.onRVSUpdate); RoomViewStore.addListener(() => this.handleRVSUpdate({}));
this.algorithm.on(LIST_UPDATED_EVENT, this.onAlgorithmListUpdated); this.algorithm.on(LIST_UPDATED_EVENT, this.onAlgorithmListUpdated);
} }
@ -119,34 +120,67 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
await this.updateAlgorithmInstances(); await this.updateAlgorithmInstances();
} }
private onRVSUpdate = () => { /**
* Handles suspected RoomViewStore changes.
* @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}) {
if (!this.enabled) return; // TODO: Remove with https://github.com/vector-im/riot-web/issues/14367 if (!this.enabled) return; // TODO: Remove with https://github.com/vector-im/riot-web/issues/14367
if (!this.matrixClient) return; // We assume there won't be RVS updates without a client if (!this.matrixClient) return; // We assume there won't be RVS updates without a client
const activeRoomId = RoomViewStore.getRoomId(); const activeRoomId = RoomViewStore.getRoomId();
if (!activeRoomId && this.algorithm.stickyRoom) { if (!activeRoomId && this.algorithm.stickyRoom) {
this.algorithm.stickyRoom = null; await this.algorithm.setStickyRoom(null);
} else if (activeRoomId) { } else if (activeRoomId) {
const activeRoom = this.matrixClient.getRoom(activeRoomId); const activeRoom = this.matrixClient.getRoom(activeRoomId);
if (!activeRoom) { if (!activeRoom) {
console.warn(`${activeRoomId} is current in RVS but missing from client - clearing sticky room`); console.warn(`${activeRoomId} is current in RVS but missing from client - clearing sticky room`);
this.algorithm.stickyRoom = null; await this.algorithm.setStickyRoom(null);
} else if (activeRoom !== this.algorithm.stickyRoom) { } else if (activeRoom !== this.algorithm.stickyRoom) {
if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log(`Changing sticky room to ${activeRoomId}`); console.log(`Changing sticky room to ${activeRoomId}`);
this.algorithm.stickyRoom = activeRoom; }
await this.algorithm.setStickyRoom(activeRoom);
} }
} }
};
protected async onDispatch(payload: ActionPayload) { if (trigger) this.updateFn.trigger();
}
protected onDispatch(payload: ActionPayload) {
// We do this to intentionally break out of the current event loop task, allowing
// us to instead wait for a more convenient time to run our updates.
setImmediate(() => this.onDispatchAsync(payload));
}
protected async onDispatchAsync(payload: ActionPayload) {
if (payload.action === 'MatrixActions.sync') { if (payload.action === 'MatrixActions.sync') {
// Filter out anything that isn't the first PREPARED sync. // Filter out anything that isn't the first PREPARED sync.
if (!(payload.prevState === 'PREPARED' && payload.state !== 'PREPARED')) { if (!(payload.prevState === 'PREPARED' && payload.state !== 'PREPARED')) {
return; return;
} }
<<<<<<<
await this.makeReady(payload.matrixClient); await this.makeReady(payload.matrixClient);
=======
// TODO: Remove with https://github.com/vector-im/riot-web/issues/14231
this.checkEnabled();
if (!this.enabled) return;
this._matrixClient = payload.matrixClient;
// 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.updateFn.trigger();
return; // no point in running the next conditions - they won't match
>>>>>>>
} }
// TODO: Remove this once the RoomListStore becomes default // TODO: Remove this once the RoomListStore becomes default
@ -155,7 +189,7 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
if (payload.action === 'on_client_not_viable' || payload.action === 'on_logged_out') { if (payload.action === 'on_client_not_viable' || payload.action === 'on_logged_out') {
// Reset state without causing updates as the client will have been destroyed // Reset state without causing updates as the client will have been destroyed
// and downstream code will throw NPE errors. // and downstream code will throw NPE errors.
this.reset(null, true); await this.reset(null, true);
this._matrixClient = null; this._matrixClient = null;
this.initialListsGenerated = false; // we'll want to regenerate them this.initialListsGenerated = false; // we'll want to regenerate them
} }
@ -169,7 +203,8 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
console.log("Regenerating room lists: Settings changed"); console.log("Regenerating room lists: Settings changed");
await this.readAndCacheSettingsFromStore(); await this.readAndCacheSettingsFromStore();
await this.regenerateAllLists(); // regenerate the lists now await this.regenerateAllLists({trigger: false}); // regenerate the lists now
this.updateFn.trigger();
} }
} }
@ -187,16 +222,22 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
console.warn(`Own read receipt was in unknown room ${room.roomId}`); console.warn(`Own read receipt was in unknown room ${room.roomId}`);
return; return;
} }
if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log(`[RoomListDebug] Got own read receipt in ${room.roomId}`); console.log(`[RoomListDebug] Got own read receipt in ${room.roomId}`);
}
await this.handleRoomUpdate(room, RoomUpdateCause.ReadReceipt); await this.handleRoomUpdate(room, RoomUpdateCause.ReadReceipt);
this.updateFn.trigger();
return; return;
} }
} else if (payload.action === 'MatrixActions.Room.tags') { } else if (payload.action === 'MatrixActions.Room.tags') {
const roomPayload = (<any>payload); // TODO: Type out the dispatcher types const roomPayload = (<any>payload); // TODO: Type out the dispatcher types
if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log(`[RoomListDebug] Got tag change in ${roomPayload.room.roomId}`); console.log(`[RoomListDebug] Got tag change in ${roomPayload.room.roomId}`);
}
await this.handleRoomUpdate(roomPayload.room, RoomUpdateCause.PossibleTagChange); await this.handleRoomUpdate(roomPayload.room, RoomUpdateCause.PossibleTagChange);
this.updateFn.trigger();
} else if (payload.action === 'MatrixActions.Room.timeline') { } else if (payload.action === 'MatrixActions.Room.timeline') {
const eventPayload = (<any>payload); // TODO: Type out the dispatcher types const eventPayload = (<any>payload); // TODO: Type out the dispatcher types
@ -206,12 +247,16 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
const roomId = eventPayload.event.getRoomId(); const roomId = eventPayload.event.getRoomId();
const room = this.matrixClient.getRoom(roomId); const room = this.matrixClient.getRoom(roomId);
const tryUpdate = async (updatedRoom: Room) => { const tryUpdate = async (updatedRoom: Room) => {
if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log(`[RoomListDebug] Live timeline event ${eventPayload.event.getId()}` + console.log(`[RoomListDebug] Live timeline event ${eventPayload.event.getId()}` +
` in ${updatedRoom.roomId}`); ` in ${updatedRoom.roomId}`);
}
if (eventPayload.event.getType() === 'm.room.tombstone' && eventPayload.event.getStateKey() === '') { if (eventPayload.event.getType() === 'm.room.tombstone' && eventPayload.event.getStateKey() === '') {
if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log(`[RoomListDebug] Got tombstone event - trying to remove now-dead room`); console.log(`[RoomListDebug] Got tombstone event - trying to remove now-dead room`);
}
const newRoom = this.matrixClient.getRoom(eventPayload.event.getContent()['replacement_room']); const newRoom = this.matrixClient.getRoom(eventPayload.event.getContent()['replacement_room']);
if (newRoom) { if (newRoom) {
// If we have the new room, then the new room check will have seen the predecessor // If we have the new room, then the new room check will have seen the predecessor
@ -220,6 +265,7 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
} }
} }
await this.handleRoomUpdate(updatedRoom, RoomUpdateCause.Timeline); await this.handleRoomUpdate(updatedRoom, RoomUpdateCause.Timeline);
this.updateFn.trigger();
}; };
if (!room) { if (!room) {
console.warn(`Live timeline event ${eventPayload.event.getId()} received without associated room`); console.warn(`Live timeline event ${eventPayload.event.getId()} received without associated room`);
@ -240,13 +286,18 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
console.warn(`Event ${eventPayload.event.getId()} was decrypted in an unknown room ${roomId}`); console.warn(`Event ${eventPayload.event.getId()} was decrypted in an unknown room ${roomId}`);
return; return;
} }
if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log(`[RoomListDebug] Decrypted timeline event ${eventPayload.event.getId()} in ${roomId}`); console.log(`[RoomListDebug] Decrypted timeline event ${eventPayload.event.getId()} in ${roomId}`);
}
await this.handleRoomUpdate(room, RoomUpdateCause.Timeline); await this.handleRoomUpdate(room, RoomUpdateCause.Timeline);
this.updateFn.trigger();
} else if (payload.action === 'MatrixActions.accountData' && payload.event_type === 'm.direct') { } else if (payload.action === 'MatrixActions.accountData' && payload.event_type === 'm.direct') {
const eventPayload = (<any>payload); // TODO: Type out the dispatcher types const eventPayload = (<any>payload); // TODO: Type out the dispatcher types
if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log(`[RoomListDebug] Received updated DM map`); console.log(`[RoomListDebug] Received updated DM map`);
}
const dmMap = eventPayload.event.getContent(); const dmMap = eventPayload.event.getContent();
for (const userId of Object.keys(dmMap)) { for (const userId of Object.keys(dmMap)) {
const roomIds = dmMap[userId]; const roomIds = dmMap[userId];
@ -264,51 +315,73 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
await this.handleRoomUpdate(room, RoomUpdateCause.PossibleTagChange); await this.handleRoomUpdate(room, RoomUpdateCause.PossibleTagChange);
} }
} }
this.updateFn.trigger();
} else if (payload.action === 'MatrixActions.Room.myMembership') { } else if (payload.action === 'MatrixActions.Room.myMembership') {
const membershipPayload = (<any>payload); // TODO: Type out the dispatcher types const membershipPayload = (<any>payload); // TODO: Type out the dispatcher types
const oldMembership = getEffectiveMembership(membershipPayload.oldMembership); const oldMembership = getEffectiveMembership(membershipPayload.oldMembership);
const newMembership = getEffectiveMembership(membershipPayload.membership); const newMembership = getEffectiveMembership(membershipPayload.membership);
if (oldMembership !== EffectiveMembership.Join && newMembership === EffectiveMembership.Join) { if (oldMembership !== EffectiveMembership.Join && newMembership === EffectiveMembership.Join) {
if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log(`[RoomListDebug] Handling new room ${membershipPayload.room.roomId}`); console.log(`[RoomListDebug] Handling new room ${membershipPayload.room.roomId}`);
}
// If we're joining an upgraded room, we'll want to make sure we don't proliferate // If we're joining an upgraded room, we'll want to make sure we don't proliferate
// the dead room in the list. // the dead room in the list.
const createEvent = membershipPayload.room.currentState.getStateEvents("m.room.create", ""); const createEvent = membershipPayload.room.currentState.getStateEvents("m.room.create", "");
if (createEvent && createEvent.getContent()['predecessor']) { if (createEvent && createEvent.getContent()['predecessor']) {
if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log(`[RoomListDebug] Room has a predecessor`); console.log(`[RoomListDebug] Room has a predecessor`);
}
const prevRoom = this.matrixClient.getRoom(createEvent.getContent()['predecessor']['room_id']); const prevRoom = this.matrixClient.getRoom(createEvent.getContent()['predecessor']['room_id']);
if (prevRoom) { if (prevRoom) {
const isSticky = this.algorithm.stickyRoom === prevRoom; const isSticky = this.algorithm.stickyRoom === prevRoom;
if (isSticky) { if (isSticky) {
if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log(`[RoomListDebug] Clearing sticky room due to room upgrade`); console.log(`[RoomListDebug] Clearing sticky room due to room upgrade`);
await this.algorithm.setStickyRoomAsync(null); }
await this.algorithm.setStickyRoom(null);
} }
// Note: we hit the algorithm instead of our handleRoomUpdate() function to // Note: we hit the algorithm instead of our handleRoomUpdate() function to
// avoid redundant updates. // avoid redundant updates.
if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log(`[RoomListDebug] Removing previous room from room list`); console.log(`[RoomListDebug] Removing previous room from room list`);
}
await this.algorithm.handleRoomUpdate(prevRoom, RoomUpdateCause.RoomRemoved); await this.algorithm.handleRoomUpdate(prevRoom, RoomUpdateCause.RoomRemoved);
} }
} }
if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log(`[RoomListDebug] Adding new room to room list`); console.log(`[RoomListDebug] Adding new room to room list`);
}
await this.handleRoomUpdate(membershipPayload.room, RoomUpdateCause.NewRoom); await this.handleRoomUpdate(membershipPayload.room, RoomUpdateCause.NewRoom);
this.updateFn.trigger();
return; return;
} }
if (oldMembership !== EffectiveMembership.Invite && newMembership === EffectiveMembership.Invite) { if (oldMembership !== EffectiveMembership.Invite && newMembership === EffectiveMembership.Invite) {
if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log(`[RoomListDebug] Handling invite to ${membershipPayload.room.roomId}`); console.log(`[RoomListDebug] Handling invite to ${membershipPayload.room.roomId}`);
}
await this.handleRoomUpdate(membershipPayload.room, RoomUpdateCause.NewRoom); await this.handleRoomUpdate(membershipPayload.room, RoomUpdateCause.NewRoom);
this.updateFn.trigger();
return; return;
} }
// If it's not a join, it's transitioning into a different list (possibly historical) // If it's not a join, it's transitioning into a different list (possibly historical)
if (oldMembership !== newMembership) { if (oldMembership !== newMembership) {
if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log(`[RoomListDebug] Handling membership change in ${membershipPayload.room.roomId}`); console.log(`[RoomListDebug] Handling membership change in ${membershipPayload.room.roomId}`);
}
await this.handleRoomUpdate(membershipPayload.room, RoomUpdateCause.PossibleTagChange); await this.handleRoomUpdate(membershipPayload.room, RoomUpdateCause.PossibleTagChange);
this.updateFn.trigger();
return; return;
} }
} }
@ -317,9 +390,11 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
private async handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise<any> { private async handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise<any> {
const shouldUpdate = await this.algorithm.handleRoomUpdate(room, cause); const shouldUpdate = await this.algorithm.handleRoomUpdate(room, cause);
if (shouldUpdate) { if (shouldUpdate) {
if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log(`[DEBUG] Room "${room.name}" (${room.roomId}) triggered by ${cause} requires list update`); console.log(`[DEBUG] Room "${room.name}" (${room.roomId}) triggered by ${cause} requires list update`);
this.emit(LISTS_UPDATE_EVENT, this); }
this.updateFn.mark();
} }
} }
@ -327,6 +402,7 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
await this.algorithm.setTagSorting(tagId, sort); await this.algorithm.setTagSorting(tagId, sort);
// TODO: Per-account? https://github.com/vector-im/riot-web/issues/14114 // TODO: Per-account? https://github.com/vector-im/riot-web/issues/14114
localStorage.setItem(`mx_tagSort_${tagId}`, sort); localStorage.setItem(`mx_tagSort_${tagId}`, sort);
this.updateFn.triggerIfWillMark();
} }
public getTagSorting(tagId: TagID): SortAlgorithm { public getTagSorting(tagId: TagID): SortAlgorithm {
@ -365,6 +441,7 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
await this.algorithm.setListOrdering(tagId, order); await this.algorithm.setListOrdering(tagId, order);
// TODO: Per-account? https://github.com/vector-im/riot-web/issues/14114 // TODO: Per-account? https://github.com/vector-im/riot-web/issues/14114
localStorage.setItem(`mx_listOrder_${tagId}`, order); localStorage.setItem(`mx_listOrder_${tagId}`, order);
this.updateFn.triggerIfWillMark();
} }
public getListOrder(tagId: TagID): ListAlgorithm { public getListOrder(tagId: TagID): ListAlgorithm {
@ -400,6 +477,10 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
} }
private async updateAlgorithmInstances() { private async 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();
for (const tag of Object.keys(this.orderedLists)) { for (const tag of Object.keys(this.orderedLists)) {
const definedSort = this.getTagSorting(tag); const definedSort = this.getTagSorting(tag);
const definedOrder = this.getListOrder(tag); const definedOrder = this.getListOrder(tag);
@ -423,13 +504,24 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
} }
private onAlgorithmListUpdated = () => { private onAlgorithmListUpdated = () => {
if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log("Underlying algorithm has triggered a list update - refiring"); console.log("Underlying algorithm has triggered a list update - marking");
this.emit(LISTS_UPDATE_EVENT, this); }
this.updateFn.mark();
}; };
<<<<<<<
// This is only exposed externally for the tests. Do not call this within the app. // This is only exposed externally for the tests. Do not call this within the app.
public async regenerateAllLists() { public async regenerateAllLists() {
=======
/**
* Regenerates the room whole room list, discarding any previous results.
* @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 regenerateAllLists({trigger = true}) {
>>>>>>>
console.warn("Regenerating all room lists"); console.warn("Regenerating all room lists");
const sorts: ITagSortingMap = {}; const sorts: ITagSortingMap = {};
@ -454,21 +546,26 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
this.initialListsGenerated = true; this.initialListsGenerated = true;
this.emit(LISTS_UPDATE_EVENT, this); if (trigger) this.updateFn.trigger();
} }
public addFilter(filter: IFilterCondition): void { public addFilter(filter: IFilterCondition): void {
if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log("Adding filter condition:", filter); console.log("Adding filter condition:", filter);
}
this.filterConditions.push(filter); this.filterConditions.push(filter);
if (this.algorithm) { if (this.algorithm) {
this.algorithm.addFilterCondition(filter); this.algorithm.addFilterCondition(filter);
} }
this.updateFn.trigger();
} }
public removeFilter(filter: IFilterCondition): void { public removeFilter(filter: IFilterCondition): void {
if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log("Removing filter condition:", filter); console.log("Removing filter condition:", filter);
}
const idx = this.filterConditions.indexOf(filter); const idx = this.filterConditions.indexOf(filter);
if (idx >= 0) { if (idx >= 0) {
this.filterConditions.splice(idx, 1); this.filterConditions.splice(idx, 1);
@ -477,6 +574,7 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
this.algorithm.removeFilterCondition(filter); this.algorithm.removeFilterCondition(filter);
} }
} }
this.updateFn.trigger();
} }
/** /**

View file

@ -87,12 +87,6 @@ export class Algorithm extends EventEmitter {
return this._stickyRoom ? this._stickyRoom.room : null; return this._stickyRoom ? this._stickyRoom.room : null;
} }
public set stickyRoom(val: Room) {
// setters can't be async, so we call a private function to do the work
// noinspection JSIgnoredPromiseFromCall
this.updateStickyRoom(val);
}
protected get hasFilters(): boolean { protected get hasFilters(): boolean {
return this.allowedByFilter.size > 0; return this.allowedByFilter.size > 0;
} }
@ -115,7 +109,7 @@ export class Algorithm extends EventEmitter {
* Awaitable version of the sticky room setter. * Awaitable version of the sticky room setter.
* @param val The new room to sticky. * @param val The new room to sticky.
*/ */
public async setStickyRoomAsync(val: Room) { public async setStickyRoom(val: Room) {
await this.updateStickyRoom(val); await this.updateStickyRoom(val);
} }
@ -321,9 +315,11 @@ export class Algorithm extends EventEmitter {
} }
newMap[tagId] = allowedRoomsInThisTag; newMap[tagId] = allowedRoomsInThisTag;
if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log(`[DEBUG] ${newMap[tagId].length}/${rooms.length} rooms filtered into ${tagId}`); console.log(`[DEBUG] ${newMap[tagId].length}/${rooms.length} rooms filtered into ${tagId}`);
} }
}
const allowedRooms = Object.values(newMap).reduce((rv, v) => { rv.push(...v); return rv; }, <Room[]>[]); const allowedRooms = Object.values(newMap).reduce((rv, v) => { rv.push(...v); return rv; }, <Room[]>[]);
this.allowedRoomsByFilters = new Set(allowedRooms); this.allowedRoomsByFilters = new Set(allowedRooms);
@ -331,26 +327,13 @@ export class Algorithm extends EventEmitter {
this.emit(LIST_UPDATED_EVENT); this.emit(LIST_UPDATED_EVENT);
} }
// TODO: Remove or use.
protected addPossiblyFilteredRoomsToTag(tagId: TagID, added: Room[]): void {
const filters = this.allowedByFilter.keys();
for (const room of added) {
for (const filter of filters) {
if (filter.isVisible(room)) {
this.allowedRoomsByFilters.add(room);
break;
}
}
}
// Now that we've updated the allowed rooms, recalculate the tag
this.recalculateFilteredRoomsForTag(tagId);
}
protected recalculateFilteredRoomsForTag(tagId: TagID): void { protected recalculateFilteredRoomsForTag(tagId: TagID): void {
if (!this.hasFilters) return; // don't bother doing work if there's nothing to do if (!this.hasFilters) return; // don't bother doing work if there's nothing to do
if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log(`Recalculating filtered rooms for ${tagId}`); console.log(`Recalculating filtered rooms for ${tagId}`);
}
delete this.filteredRooms[tagId]; delete this.filteredRooms[tagId];
const rooms = this.cachedRooms[tagId].map(r => r); // cheap clone const rooms = this.cachedRooms[tagId].map(r => r); // cheap clone
this.tryInsertStickyRoomToFilterSet(rooms, tagId); this.tryInsertStickyRoomToFilterSet(rooms, tagId);
@ -359,9 +342,11 @@ export class Algorithm extends EventEmitter {
this.filteredRooms[tagId] = filteredRooms; this.filteredRooms[tagId] = filteredRooms;
} }
if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log(`[DEBUG] ${filteredRooms.length}/${rooms.length} rooms filtered into ${tagId}`); console.log(`[DEBUG] ${filteredRooms.length}/${rooms.length} rooms filtered into ${tagId}`);
} }
}
protected tryInsertStickyRoomToFilterSet(rooms: Room[], tagId: TagID) { protected tryInsertStickyRoomToFilterSet(rooms: Room[], tagId: TagID) {
if (!this._stickyRoom || !this._stickyRoom.room || this._stickyRoom.tag !== tagId) return; if (!this._stickyRoom || !this._stickyRoom.room || this._stickyRoom.tag !== tagId) return;
@ -399,8 +384,10 @@ export class Algorithm extends EventEmitter {
} }
if (!this._cachedStickyRooms || !updatedTag) { if (!this._cachedStickyRooms || !updatedTag) {
if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log(`Generating clone of cached rooms for sticky room handling`); console.log(`Generating clone of cached rooms for sticky room handling`);
}
const stickiedTagMap: ITagMap = {}; const stickiedTagMap: ITagMap = {};
for (const tagId of Object.keys(this.cachedRooms)) { for (const tagId of Object.keys(this.cachedRooms)) {
stickiedTagMap[tagId] = this.cachedRooms[tagId].map(r => r); // shallow clone stickiedTagMap[tagId] = this.cachedRooms[tagId].map(r => r); // shallow clone
@ -411,8 +398,10 @@ export class Algorithm extends EventEmitter {
if (updatedTag) { if (updatedTag) {
// Update the tag indicated by the caller, if possible. This is mostly to ensure // Update the tag indicated by the caller, if possible. This is mostly to ensure
// our cache is up to date. // our cache is up to date.
if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log(`Replacing cached sticky rooms for ${updatedTag}`); console.log(`Replacing cached sticky rooms for ${updatedTag}`);
}
this._cachedStickyRooms[updatedTag] = this.cachedRooms[updatedTag].map(r => r); // shallow clone this._cachedStickyRooms[updatedTag] = this.cachedRooms[updatedTag].map(r => r); // shallow clone
} }
@ -421,8 +410,10 @@ export class Algorithm extends EventEmitter {
// we might have updated from the cache is also our sticky room. // we might have updated from the cache is also our sticky room.
const sticky = this._stickyRoom; const sticky = this._stickyRoom;
if (!updatedTag || updatedTag === sticky.tag) { if (!updatedTag || updatedTag === sticky.tag) {
if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log(`Inserting sticky room ${sticky.room.roomId} at position ${sticky.position} in ${sticky.tag}`); console.log(`Inserting sticky room ${sticky.room.roomId} at position ${sticky.position} in ${sticky.tag}`);
}
this._cachedStickyRooms[sticky.tag].splice(sticky.position, 0, sticky.room); this._cachedStickyRooms[sticky.tag].splice(sticky.position, 0, sticky.room);
} }
@ -647,8 +638,10 @@ export class Algorithm extends EventEmitter {
* processing. * processing.
*/ */
public async handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise<boolean> { public async handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise<boolean> {
if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log(`Handle room update for ${room.roomId} called with cause ${cause}`); console.log(`Handle room update for ${room.roomId} called with cause ${cause}`);
}
if (!this.algorithms) throw new Error("Not ready: no algorithms to determine tags from"); 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 // Note: check the isSticky against the room ID just in case the reference is wrong
@ -705,16 +698,20 @@ export class Algorithm extends EventEmitter {
const diff = arrayDiff(oldTags, newTags); const diff = arrayDiff(oldTags, newTags);
if (diff.removed.length > 0 || diff.added.length > 0) { if (diff.removed.length > 0 || diff.added.length > 0) {
for (const rmTag of diff.removed) { for (const rmTag of diff.removed) {
if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log(`Removing ${room.roomId} from ${rmTag}`); console.log(`Removing ${room.roomId} from ${rmTag}`);
}
const algorithm: OrderingAlgorithm = this.algorithms[rmTag]; const algorithm: OrderingAlgorithm = this.algorithms[rmTag];
if (!algorithm) throw new Error(`No algorithm for ${rmTag}`); if (!algorithm) throw new Error(`No algorithm for ${rmTag}`);
await algorithm.handleRoomUpdate(room, RoomUpdateCause.RoomRemoved); await algorithm.handleRoomUpdate(room, RoomUpdateCause.RoomRemoved);
this.cachedRooms[rmTag] = algorithm.orderedRooms; this.cachedRooms[rmTag] = algorithm.orderedRooms;
} }
for (const addTag of diff.added) { for (const addTag of diff.added) {
if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log(`Adding ${room.roomId} to ${addTag}`); console.log(`Adding ${room.roomId} to ${addTag}`);
}
const algorithm: OrderingAlgorithm = this.algorithms[addTag]; const algorithm: OrderingAlgorithm = this.algorithms[addTag];
if (!algorithm) throw new Error(`No algorithm for ${addTag}`); if (!algorithm) throw new Error(`No algorithm for ${addTag}`);
await algorithm.handleRoomUpdate(room, RoomUpdateCause.NewRoom); await algorithm.handleRoomUpdate(room, RoomUpdateCause.NewRoom);
@ -724,13 +721,17 @@ export class Algorithm extends EventEmitter {
// Update the tag map so we don't regen it in a moment // Update the tag map so we don't regen it in a moment
this.roomIdsToTags[room.roomId] = newTags; this.roomIdsToTags[room.roomId] = newTags;
if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log(`Changing update cause for ${room.roomId} to Timeline to sort rooms`); console.log(`Changing update cause for ${room.roomId} to Timeline to sort rooms`);
}
cause = RoomUpdateCause.Timeline; cause = RoomUpdateCause.Timeline;
didTagChange = true; didTagChange = true;
} else { } else {
if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.warn(`Received no-op update for ${room.roomId} - changing to Timeline update`); console.log(`Received no-op update for ${room.roomId} - changing to Timeline update`);
}
cause = RoomUpdateCause.Timeline; cause = RoomUpdateCause.Timeline;
} }
@ -746,7 +747,7 @@ export class Algorithm extends EventEmitter {
}; };
} else { } else {
// We have to clear the lock as the sticky room change will trigger updates. // We have to clear the lock as the sticky room change will trigger updates.
await this.setStickyRoomAsync(room); await this.setStickyRoom(room);
} }
} }
} }
@ -756,20 +757,27 @@ export class Algorithm extends EventEmitter {
// as the sticky room relies on this. // as the sticky room relies on this.
if (cause !== RoomUpdateCause.NewRoom && cause !== RoomUpdateCause.RoomRemoved) { if (cause !== RoomUpdateCause.NewRoom && cause !== RoomUpdateCause.RoomRemoved) {
if (this.stickyRoom === room) { if (this.stickyRoom === room) {
if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.warn(`[RoomListDebug] Received ${cause} update for sticky room ${room.roomId} - ignoring`); console.warn(`[RoomListDebug] Received ${cause} update for sticky room ${room.roomId} - ignoring`);
}
return false; return false;
} }
} }
if (!this.roomIdsToTags[room.roomId]) { if (!this.roomIdsToTags[room.roomId]) {
if (CAUSES_REQUIRING_ROOM.includes(cause)) { if (CAUSES_REQUIRING_ROOM.includes(cause)) {
if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.warn(`Skipping tag update for ${room.roomId} because we don't know about the room`); console.warn(`Skipping tag update for ${room.roomId} because we don't know about the room`);
}
return false; return false;
} }
if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log(`[RoomListDebug] Updating tags for room ${room.roomId} (${room.name})`); console.log(`[RoomListDebug] Updating tags for room ${room.roomId} (${room.name})`);
}
// Get the tags for the room and populate the cache // Get the tags for the room and populate the cache
const roomTags = this.getTagsForRoom(room).filter(t => !isNullOrUndefined(this.cachedRooms[t])); const roomTags = this.getTagsForRoom(room).filter(t => !isNullOrUndefined(this.cachedRooms[t]));
@ -780,12 +788,16 @@ export class Algorithm extends EventEmitter {
this.roomIdsToTags[room.roomId] = roomTags; this.roomIdsToTags[room.roomId] = roomTags;
if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log(`[RoomListDebug] Updated tags for ${room.roomId}:`, roomTags); console.log(`[RoomListDebug] Updated tags for ${room.roomId}:`, roomTags);
} }
}
if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log(`[RoomListDebug] Reached algorithmic handling for ${room.roomId} and cause ${cause}`); console.log(`[RoomListDebug] Reached algorithmic handling for ${room.roomId} and cause ${cause}`);
}
const tags = this.roomIdsToTags[room.roomId]; const tags = this.roomIdsToTags[room.roomId];
if (!tags) { if (!tags) {
@ -807,8 +819,10 @@ export class Algorithm extends EventEmitter {
changed = true; changed = true;
} }
if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log(`[RoomListDebug] Finished handling ${room.roomId} with cause ${cause} (changed=${changed})`); console.log(`[RoomListDebug] Finished handling ${room.roomId} with cause ${cause} (changed=${changed})`);
}
return changed; return changed;
} }
} }

View file

@ -87,9 +87,6 @@ export class ImportanceAlgorithm extends OrderingAlgorithm {
public constructor(tagId: TagID, initialSortingAlgorithm: SortAlgorithm) { public constructor(tagId: TagID, initialSortingAlgorithm: SortAlgorithm) {
super(tagId, initialSortingAlgorithm); super(tagId, initialSortingAlgorithm);
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log(`[RoomListDebug] Constructed an ImportanceAlgorithm for ${tagId}`);
} }
// noinspection JSMethodCanBeStatic // noinspection JSMethodCanBeStatic

View file

@ -28,9 +28,6 @@ export class NaturalAlgorithm extends OrderingAlgorithm {
public constructor(tagId: TagID, initialSortingAlgorithm: SortAlgorithm) { public constructor(tagId: TagID, initialSortingAlgorithm: SortAlgorithm) {
super(tagId, initialSortingAlgorithm); super(tagId, initialSortingAlgorithm);
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log(`[RoomListDebug] Constructed a NaturalAlgorithm for ${tagId}`);
} }
public async setRooms(rooms: Room[]): Promise<any> { public async setRooms(rooms: Room[]): Promise<any> {

View file

@ -52,8 +52,6 @@ export class CommunityFilterCondition extends EventEmitter implements IFilterCon
const beforeRoomIds = this.roomIds; const beforeRoomIds = this.roomIds;
this.roomIds = (await GroupStore.getGroupRooms(this.community.groupId)).map(r => r.roomId); this.roomIds = (await GroupStore.getGroupRooms(this.community.groupId)).map(r => r.roomId);
if (arrayHasDiff(beforeRoomIds, this.roomIds)) { if (arrayHasDiff(beforeRoomIds, this.roomIds)) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log("Updating filter for group: ", this.community.groupId);
this.emit(FILTER_CHANGED); this.emit(FILTER_CHANGED);
} }
}; };

View file

@ -41,8 +41,6 @@ export class NameFilterCondition extends EventEmitter implements IFilterConditio
public set search(val: string) { public set search(val: string) {
this._search = val; this._search = val;
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log("Updating filter for room name search:", this._search);
this.emit(FILTER_CHANGED); this.emit(FILTER_CHANGED);
} }

View file

@ -0,0 +1,67 @@
/*
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.
*/
/**
* A utility to ensure that a function is only called once triggered with
* a mark applied. Multiple marks can be applied to the function, however
* the function will only be called once upon trigger().
*
* The function starts unmarked.
*/
export class MarkedExecution {
private marked = false;
/**
* Creates a MarkedExecution for the provided function.
* @param fn The function to be called upon trigger if marked.
*/
constructor(private fn: () => void) {
}
/**
* Resets the mark without calling the function.
*/
public reset() {
this.marked = false;
}
/**
* Marks the function to be called upon trigger().
*/
public mark() {
this.marked = true;
}
/**
* If marked, the function will be called, otherwise this does nothing.
*/
public trigger() {
if (!this.marked) return;
this.reset(); // reset first just in case the fn() causes a trigger()
this.fn();
}
/**
* Triggers the function if a mark() call would mark it. If the function
* has already been marked this will do nothing.
*/
public triggerIfWillMark() {
if (!this.marked) {
this.mark();
this.trigger();
}
}
}