Merge branch 'develop' into travis/room-list/showmore2

This commit is contained in:
Travis Ralston 2020-07-10 10:57:17 -06:00
commit 477472c178
17 changed files with 363 additions and 157 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,7 +24,7 @@ limitations under the License.
right: 0; right: 0;
} }
.mx_NotificationBadge { .mx_NotificationBadge, .mx_RoomTile2_badgeContainer {
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;

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

@ -89,7 +89,6 @@ limitations under the License.
height: 16px; height: 16px;
// don't set width so that it takes no space when there is no badge to show // don't set width so that it takes no space when there is no badge to show
margin: auto 0; // vertically align margin: auto 0; // vertically align
position: relative; // fixes badge alignment in some scenarios
// Create a flexbox to make aligning dot badges easier // Create a flexbox to make aligning dot badges easier
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

@ -105,7 +105,7 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
ev.target.select(); ev.target.select();
}; };
private onBlur = () => { private onBlur = (ev: React.FocusEvent<HTMLInputElement>) => {
this.setState({focused: false}); this.setState({focused: false});
}; };

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;
console.log("new lists", newLists); if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log("new lists", newLists);
}
this.setState({sublists: newLists}, () => { this.setState({sublists: newLists}, () => {
this.props.onResize(); this.props.onResize();
@ -293,6 +296,7 @@ export default class RoomList2 extends React.Component<IProps, IState> {
isMinimized={this.props.isMinimized} isMinimized={this.props.isMinimized}
onResize={this.props.onResize} onResize={this.props.onResize}
extraBadTilesThatShouldntExist={extraTiles} extraBadTilesThatShouldntExist={extraTiles}
isFiltered={!!this.searchFilter.search}
/> />
); );
} }

View file

@ -78,6 +78,7 @@ interface IProps {
isMinimized: boolean; isMinimized: boolean;
tagId: TagID; tagId: TagID;
onResize: () => void; onResize: () => void;
isFiltered: boolean;
// TODO: Don't use this. It's for community invites, and community invites shouldn't be here. // TODO: Don't use this. It's for community invites, and community invites shouldn't be here.
// You should feel bad if you use this. // You should feel bad if you use this.
@ -98,6 +99,7 @@ interface IState {
notificationState: ListNotificationState; notificationState: ListNotificationState;
contextMenuPosition: PartialDOMRect; contextMenuPosition: PartialDOMRect;
isResizing: boolean; isResizing: boolean;
isExpanded: boolean; // used for the for expand of the sublist when the room list is being filtered
height: number; height: number;
} }
@ -118,6 +120,7 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
notificationState: RoomNotificationStateStore.instance.getListState(this.props.tagId), notificationState: RoomNotificationStateStore.instance.getListState(this.props.tagId),
contextMenuPosition: null, contextMenuPosition: null,
isResizing: false, isResizing: false,
isExpanded: this.props.isFiltered ? this.props.isFiltered : !this.layout.isCollapsed
height, height,
}; };
this.state.notificationState.setRooms(this.props.rooms); this.state.notificationState.setRooms(this.props.rooms);
@ -156,8 +159,15 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
return Math.min(nVisible, this.numTiles); return Math.min(nVisible, this.numTiles);
} }
public componentDidUpdate(prevProps) { public componentDidUpdate(prevProps: Readonly<IProps>) {
this.state.notificationState.setRooms(this.props.rooms); this.state.notificationState.setRooms(this.props.rooms);
if (prevProps.isFiltered !== this.props.isFiltered) {
if (this.props.isFiltered) {
this.setState({isExpanded: true});
} else {
this.setState({isExpanded: !this.layout.isCollapsed});
}
}
// as the rooms can come in one by one we need to reevaluate // as the rooms can come in one by one we need to reevaluate
// the amount of available rooms to cap the amount of requested visible rooms by the layout // the amount of available rooms to cap the amount of requested visible rooms by the layout
if (RoomSublist2.calcNumTiles(prevProps) !== this.numTiles) { if (RoomSublist2.calcNumTiles(prevProps) !== this.numTiles) {
@ -175,10 +185,9 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
// XXX: we have to do this a tick later because we have incorrect intermediate props during a room change // XXX: we have to do this a tick later because we have incorrect intermediate props during a room change
// where we lose the room we are changing from temporarily and then it comes back in an update right after. // where we lose the room we are changing from temporarily and then it comes back in an update right after.
setImmediate(() => { setImmediate(() => {
const isCollapsed = this.layout.isCollapsed;
const roomIndex = this.props.rooms.findIndex((r) => r.roomId === payload.room_id); const roomIndex = this.props.rooms.findIndex((r) => r.roomId === payload.room_id);
if (isCollapsed && roomIndex > -1) { if (!this.state.isExpanded && roomIndex > -1) {
this.toggleCollapsed(); this.toggleCollapsed();
} }
// extend the visible section to include the room if it is entirely invisible // extend the visible section to include the room if it is entirely invisible
@ -336,24 +345,23 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
}; };
private toggleCollapsed = () => { private toggleCollapsed = () => {
this.layout.isCollapsed = !this.layout.isCollapsed; this.layout.isCollapsed = this.state.isExpanded;
this.forceUpdate(); // because the layout doesn't trigger an update this.setState({isExpanded: !this.layout.isCollapsed});
setImmediate(() => this.props.onResize()); // needs to happen when the DOM is updated setImmediate(() => this.props.onResize()); // needs to happen when the DOM is updated
}; };
private onHeaderKeyDown = (ev: React.KeyboardEvent) => { private onHeaderKeyDown = (ev: React.KeyboardEvent) => {
const isCollapsed = this.layout && this.layout.isCollapsed;
switch (ev.key) { switch (ev.key) {
case Key.ARROW_LEFT: case Key.ARROW_LEFT:
ev.stopPropagation(); ev.stopPropagation();
if (!isCollapsed) { if (this.state.isExpanded) {
// On ARROW_LEFT collapse the room sublist if it isn't already // On ARROW_LEFT collapse the room sublist if it isn't already
this.toggleCollapsed(); this.toggleCollapsed();
} }
break; break;
case Key.ARROW_RIGHT: { case Key.ARROW_RIGHT: {
ev.stopPropagation(); ev.stopPropagation();
if (isCollapsed) { if (!this.state.isExpanded) {
// On ARROW_RIGHT expand the room sublist if it isn't already // On ARROW_RIGHT expand the room sublist if it isn't already
this.toggleCollapsed(); this.toggleCollapsed();
} else if (this.sublistRef.current) { } else if (this.sublistRef.current) {
@ -382,7 +390,7 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
}; };
private renderVisibleTiles(): React.ReactElement[] { private renderVisibleTiles(): React.ReactElement[] {
if (this.layout && this.layout.isCollapsed) { if (!this.state.isExpanded) {
// don't waste time on rendering // don't waste time on rendering
return []; return [];
} }
@ -539,7 +547,7 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
const collapseClasses = classNames({ const collapseClasses = classNames({
'mx_RoomSublist2_collapseBtn': true, 'mx_RoomSublist2_collapseBtn': true,
'mx_RoomSublist2_collapseBtn_collapsed': this.layout && this.layout.isCollapsed, 'mx_RoomSublist2_collapseBtn_collapsed': !this.state.isExpanded,
}); });
const classes = classNames({ const classes = classNames({
@ -567,7 +575,7 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
tabIndex={tabIndex} tabIndex={tabIndex}
className="mx_RoomSublist2_headerText" className="mx_RoomSublist2_headerText"
role="treeitem" role="treeitem"
aria-expanded={!this.layout.isCollapsed} aria-expanded={this.state.isExpanded}
aria-level={1} aria-level={1}
onClick={this.onHeaderClick} onClick={this.onHeaderClick}
onContextMenu={this.onContextMenu} onContextMenu={this.onContextMenu}

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);
} }
@ -91,27 +92,42 @@ 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/14231 if (!this.enabled) return; // TODO: Remove with https://github.com/vector-im/riot-web/issues/14231
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) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 if (!window.mx_QuietRoomListLogging) {
console.log(`Changing sticky room to ${activeRoomId}`); // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
this.algorithm.stickyRoom = activeRoom; console.log(`Changing sticky room to ${activeRoomId}`);
}
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')) {
@ -127,8 +143,12 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
// Update any settings here, as some may have happened before we were logically ready. // Update any settings here, as some may have happened before we were logically ready.
console.log("Regenerating room lists: Startup"); console.log("Regenerating room lists: Startup");
await this.readAndCacheSettingsFromStore(); await this.readAndCacheSettingsFromStore();
await this.regenerateAllLists(); await this.regenerateAllLists({trigger: false});
this.onRVSUpdate(); // fake an RVS update to adjust sticky room, if needed 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
@ -137,7 +157,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
} }
@ -151,7 +171,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();
} }
} }
@ -169,16 +190,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;
} }
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 if (!window.mx_QuietRoomListLogging) {
console.log(`[RoomListDebug] Got own read receipt in ${room.roomId}`); // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
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
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 if (!window.mx_QuietRoomListLogging) {
console.log(`[RoomListDebug] Got tag change in ${roomPayload.room.roomId}`); // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
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
@ -188,12 +215,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) => {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 if (!window.mx_QuietRoomListLogging) {
console.log(`[RoomListDebug] Live timeline event ${eventPayload.event.getId()}` +
` in ${updatedRoom.roomId}`);
if (eventPayload.event.getType() === 'm.room.tombstone' && eventPayload.event.getStateKey() === '') {
// 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] Live timeline event ${eventPayload.event.getId()}` +
` in ${updatedRoom.roomId}`);
}
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
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
@ -202,6 +233,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`);
@ -222,13 +254,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;
} }
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 if (!window.mx_QuietRoomListLogging) {
console.log(`[RoomListDebug] Decrypted timeline event ${eventPayload.event.getId()} in ${roomId}`); // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
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
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 if (!window.mx_QuietRoomListLogging) {
console.log(`[RoomListDebug] Received updated DM map`); // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
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];
@ -246,51 +283,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) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 if (!window.mx_QuietRoomListLogging) {
console.log(`[RoomListDebug] Handling new room ${membershipPayload.room.roomId}`); // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
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']) {
console.log(`[RoomListDebug] Room has a predecessor`); if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
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) {
console.log(`[RoomListDebug] Clearing sticky room due to room upgrade`); if (!window.mx_QuietRoomListLogging) {
await this.algorithm.setStickyRoomAsync(null); // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log(`[RoomListDebug] Clearing sticky room due to room upgrade`);
}
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.
console.log(`[RoomListDebug] Removing previous room from room list`); 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`);
}
await this.algorithm.handleRoomUpdate(prevRoom, RoomUpdateCause.RoomRemoved); await this.algorithm.handleRoomUpdate(prevRoom, RoomUpdateCause.RoomRemoved);
} }
} }
console.log(`[RoomListDebug] Adding new room to room list`); 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`);
}
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) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 if (!window.mx_QuietRoomListLogging) {
console.log(`[RoomListDebug] Handling invite to ${membershipPayload.room.roomId}`); // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
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) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 if (!window.mx_QuietRoomListLogging) {
console.log(`[RoomListDebug] Handling membership change in ${membershipPayload.room.roomId}`); // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
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;
} }
} }
@ -299,9 +358,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) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 if (!window.mx_QuietRoomListLogging) {
console.log(`[DEBUG] Room "${room.name}" (${room.roomId}) triggered by ${cause} requires list update`); // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
this.emit(LISTS_UPDATE_EVENT, this); console.log(`[DEBUG] Room "${room.name}" (${room.roomId}) triggered by ${cause} requires list update`);
}
this.updateFn.mark();
} }
} }
@ -309,6 +370,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 {
@ -347,6 +409,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 {
@ -382,6 +445,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);
@ -405,12 +472,19 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
} }
private onAlgorithmListUpdated = () => { private onAlgorithmListUpdated = () => {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 if (!window.mx_QuietRoomListLogging) {
console.log("Underlying algorithm has triggered a list update - refiring"); // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
this.emit(LISTS_UPDATE_EVENT, this); console.log("Underlying algorithm has triggered a list update - marking");
}
this.updateFn.mark();
}; };
private 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 = {};
@ -435,21 +509,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 {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 if (!window.mx_QuietRoomListLogging) {
console.log("Adding filter condition:", filter); // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
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 {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 if (!window.mx_QuietRoomListLogging) {
console.log("Removing filter condition:", filter); // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
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);
@ -458,6 +537,7 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
this.algorithm.removeFilterCondition(filter); this.algorithm.removeFilterCondition(filter);
} }
} }
this.updateFn.trigger();
} }
/** /**

View file

@ -41,6 +41,17 @@ import { getListAlgorithmInstance } from "./list-ordering";
*/ */
export const LIST_UPDATED_EVENT = "list_updated_event"; export const LIST_UPDATED_EVENT = "list_updated_event";
// These are the causes which require a room to be known in order for us to handle them. If
// a cause in this list is raised and we don't know about the room, we don't handle the update.
//
// Note: these typically happen when a new room is coming in, such as the user creating or
// joining the room. For these cases, we need to know about the room prior to handling it otherwise
// we'll make bad assumptions.
const CAUSES_REQUIRING_ROOM = [
RoomUpdateCause.Timeline,
RoomUpdateCause.ReadReceipt,
];
interface IStickyRoom { interface IStickyRoom {
room: Room; room: Room;
position: number; position: number;
@ -76,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;
} }
@ -104,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);
} }
@ -310,8 +315,10 @@ export class Algorithm extends EventEmitter {
} }
newMap[tagId] = allowedRoomsInThisTag; newMap[tagId] = allowedRoomsInThisTag;
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 if (!window.mx_QuietRoomListLogging) {
console.log(`[DEBUG] ${newMap[tagId].length}/${rooms.length} rooms filtered into ${tagId}`); // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
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[]>[]);
@ -320,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
console.log(`Recalculating filtered rooms for ${tagId}`); if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
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);
@ -348,8 +342,10 @@ export class Algorithm extends EventEmitter {
this.filteredRooms[tagId] = filteredRooms; this.filteredRooms[tagId] = filteredRooms;
} }
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 if (!window.mx_QuietRoomListLogging) {
console.log(`[DEBUG] ${filteredRooms.length}/${rooms.length} rooms filtered into ${tagId}`); // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log(`[DEBUG] ${filteredRooms.length}/${rooms.length} rooms filtered into ${tagId}`);
}
} }
protected tryInsertStickyRoomToFilterSet(rooms: Room[], tagId: TagID) { protected tryInsertStickyRoomToFilterSet(rooms: Room[], tagId: TagID) {
@ -388,8 +384,10 @@ export class Algorithm extends EventEmitter {
} }
if (!this._cachedStickyRooms || !updatedTag) { if (!this._cachedStickyRooms || !updatedTag) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 if (!window.mx_QuietRoomListLogging) {
console.log(`Generating clone of cached rooms for sticky room handling`); // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
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
@ -400,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.
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 if (!window.mx_QuietRoomListLogging) {
console.log(`Replacing cached sticky rooms for ${updatedTag}`); // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
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
} }
@ -410,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) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 if (!window.mx_QuietRoomListLogging) {
console.log(`Inserting sticky room ${sticky.room.roomId} at position ${sticky.position} in ${sticky.tag}`); // 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}`);
}
this._cachedStickyRooms[sticky.tag].splice(sticky.position, 0, sticky.room); this._cachedStickyRooms[sticky.tag].splice(sticky.position, 0, sticky.room);
} }
@ -636,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> {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 if (!window.mx_QuietRoomListLogging) {
console.log(`Handle room update for ${room.roomId} called with cause ${cause}`); // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
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
@ -666,18 +670,6 @@ export class Algorithm extends EventEmitter {
} }
} }
if (hasTags && isForLastSticky && !knownRoomRef) {
// we have a fairly good chance at losing a room right now. Under some circumstances,
// we can end up with a room which transitions references and tag changes, then gets
// lost when the sticky room changes. To counter this, we try and add the room to the
// list manually as the condition below to update the reference will fail.
//
// Other conditions *should* result in the room being sorted into the right place.
console.warn(`${room.roomId} was about to be lost - inserting at end of room list`);
this.rooms.push(room);
knownRoomRef = true;
}
// If we have tags for a room and don't have the room referenced, something went horribly // If we have tags for a room and don't have the room referenced, something went horribly
// wrong - the reference should have been updated above. // wrong - the reference should have been updated above.
if (hasTags && !knownRoomRef && !isSticky) { if (hasTags && !knownRoomRef && !isSticky) {
@ -690,6 +682,13 @@ export class Algorithm extends EventEmitter {
// to trigger a sticky room update ourselves. // to trigger a sticky room update ourselves.
this._stickyRoom.room = room; this._stickyRoom.room = room;
} }
// If after all that we're still a NewRoom update, add the room if applicable.
// We don't do this for the sticky room (because it causes duplication issues)
// or if we know about the reference (as it should be replaced).
if (cause === RoomUpdateCause.NewRoom && !isSticky && !knownRoomRef) {
this.rooms.push(room);
}
} }
if (cause === RoomUpdateCause.PossibleTagChange) { if (cause === RoomUpdateCause.PossibleTagChange) {
@ -699,30 +698,40 @@ 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) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 if (!window.mx_QuietRoomListLogging) {
console.log(`Removing ${room.roomId} from ${rmTag}`); // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
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;
} }
for (const addTag of diff.added) { for (const addTag of diff.added) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 if (!window.mx_QuietRoomListLogging) {
console.log(`Adding ${room.roomId} to ${addTag}`); // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
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);
this.cachedRooms[addTag] = algorithm.orderedRooms;
} }
// 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;
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 if (!window.mx_QuietRoomListLogging) {
console.log(`Changing update cause for ${room.roomId} to Timeline to sort rooms`); // 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`);
}
cause = RoomUpdateCause.Timeline; cause = RoomUpdateCause.Timeline;
didTagChange = true; didTagChange = true;
} else { } else {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 if (!window.mx_QuietRoomListLogging) {
console.warn(`Received no-op update for ${room.roomId} - changing to Timeline update`); // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log(`Received no-op update for ${room.roomId} - changing to Timeline update`);
}
cause = RoomUpdateCause.Timeline; cause = RoomUpdateCause.Timeline;
} }
@ -738,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);
} }
} }
} }
@ -748,15 +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) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 if (!window.mx_QuietRoomListLogging) {
console.warn(`[RoomListDebug] Received ${cause} update for sticky room ${room.roomId} - ignoring`); // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
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]) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 if (CAUSES_REQUIRING_ROOM.includes(cause)) {
console.log(`[RoomListDebug] Updating tags for room ${room.roomId} (${room.name})`); 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`);
}
return false;
}
if (!window.mx_QuietRoomListLogging) {
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
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]));
@ -767,12 +788,16 @@ export class Algorithm extends EventEmitter {
this.roomIdsToTags[room.roomId] = roomTags; this.roomIdsToTags[room.roomId] = roomTags;
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 if (!window.mx_QuietRoomListLogging) {
console.log(`[RoomListDebug] Updated tags for ${room.roomId}:`, roomTags); // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
console.log(`[RoomListDebug] Updated tags for ${room.roomId}:`, roomTags);
}
} }
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 if (!window.mx_QuietRoomListLogging) {
console.log(`[RoomListDebug] Reached algorithmic handling for ${room.roomId} and cause ${cause}`); // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
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) {
@ -794,8 +819,10 @@ export class Algorithm extends EventEmitter {
changed = true; changed = true;
} }
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 if (!window.mx_QuietRoomListLogging) {
console.log(`[RoomListDebug] Finished handling ${room.roomId} with cause ${cause} (changed=${changed})`); // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
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
@ -160,7 +157,10 @@ export class ImportanceAlgorithm extends OrderingAlgorithm {
this.cachedOrderedRooms.splice(this.indices[category], 0, room); // splice in the new room (pre-adjusted) this.cachedOrderedRooms.splice(this.indices[category], 0, room); // splice in the new room (pre-adjusted)
} else if (cause === RoomUpdateCause.RoomRemoved) { } else if (cause === RoomUpdateCause.RoomRemoved) {
const roomIdx = this.getRoomIndex(room); const roomIdx = this.getRoomIndex(room);
if (roomIdx === -1) return false; // no change if (roomIdx === -1) {
console.warn(`Tried to remove unknown room from ${this.tagId}: ${room.roomId}`);
return false; // no change
}
const oldCategory = this.getCategoryFromIndices(roomIdx, this.indices); const oldCategory = this.getCategoryFromIndices(roomIdx, this.indices);
this.alterCategoryPositionBy(oldCategory, -1, this.indices); this.alterCategoryPositionBy(oldCategory, -1, this.indices);
this.cachedOrderedRooms.splice(roomIdx, 1); // remove the room this.cachedOrderedRooms.splice(roomIdx, 1); // remove the room
@ -169,15 +169,6 @@ export class ImportanceAlgorithm extends OrderingAlgorithm {
} }
} }
private getRoomIndex(room: Room): number {
let roomIdx = this.cachedOrderedRooms.indexOf(room);
if (roomIdx === -1) { // can only happen if the js-sdk's store goes sideways.
console.warn(`Degrading performance to find missing room in "${this.tagId}": ${room.roomId}`);
roomIdx = this.cachedOrderedRooms.findIndex(r => r.roomId === room.roomId);
}
return roomIdx;
}
public async handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise<boolean> { public async handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise<boolean> {
try { try {
await this.updateLock.acquireAsync(); await this.updateLock.acquireAsync();

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> {
@ -50,8 +47,12 @@ export class NaturalAlgorithm extends OrderingAlgorithm {
if (cause === RoomUpdateCause.NewRoom) { if (cause === RoomUpdateCause.NewRoom) {
this.cachedOrderedRooms.push(room); this.cachedOrderedRooms.push(room);
} else if (cause === RoomUpdateCause.RoomRemoved) { } else if (cause === RoomUpdateCause.RoomRemoved) {
const idx = this.cachedOrderedRooms.indexOf(room); const idx = this.getRoomIndex(room);
if (idx >= 0) this.cachedOrderedRooms.splice(idx, 1); if (idx >= 0) {
this.cachedOrderedRooms.splice(idx, 1);
} else {
console.warn(`Tried to remove unknown room from ${this.tagId}: ${room.roomId}`);
}
} }
// TODO: Optimize this to avoid useless operations: https://github.com/vector-im/riot-web/issues/14035 // TODO: Optimize this to avoid useless operations: https://github.com/vector-im/riot-web/issues/14035

View file

@ -70,4 +70,13 @@ export abstract class OrderingAlgorithm {
* @returns True if the update requires the Algorithm to update the presentation layers. * @returns True if the update requires the Algorithm to update the presentation layers.
*/ */
public abstract handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise<boolean>; public abstract handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise<boolean>;
protected getRoomIndex(room: Room): number {
let roomIdx = this.cachedOrderedRooms.indexOf(room);
if (roomIdx === -1) { // can only happen if the js-sdk's store goes sideways.
console.warn(`Degrading performance to find missing room in "${this.tagId}": ${room.roomId}`);
roomIdx = this.cachedOrderedRooms.findIndex(r => r.roomId === room.roomId);
}
return roomIdx;
}
} }

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();
}
}
}