From b5391f8ec8d01fca748c1771c10e5074c364d5bd Mon Sep 17 00:00:00 2001 From: Mike Pennisi Date: Fri, 3 Jul 2020 17:22:33 -0400 Subject: [PATCH 01/46] "ignore"/"unignore" commands: validate user ID Extend the accepted patterns so that users are alerted about invalid input. These patterns are approximations of the Matrix user ID grammer. Resolves https://github.com/vector-im/riot-web/issues/12743 --- src/SlashCommands.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index f667c47b3c..11c955749d 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -660,7 +660,7 @@ export const Commands = [ if (args) { const cli = MatrixClientPeg.get(); - const matches = args.match(/^(\S+)$/); + const matches = args.match(/^(@[^:]+:\S+)$/); if (matches) { const userId = matches[1]; const ignoredUsers = cli.getIgnoredUsers(); @@ -690,7 +690,7 @@ export const Commands = [ if (args) { const cli = MatrixClientPeg.get(); - const matches = args.match(/^(\S+)$/); + const matches = args.match(/(^@[^:]+:\S+$)/); if (matches) { const userId = matches[1]; const ignoredUsers = cli.getIgnoredUsers(); From 8ef4b1b2e73ec1f40347a009e678ac918321a241 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 7 Jul 2020 13:56:46 -0600 Subject: [PATCH 02/46] Replace labs flag with a real setting --- src/components/structures/LoggedInView.tsx | 3 +-- src/settings/Settings.js | 3 ++- src/stores/RoomListStore.js | 2 +- src/stores/room-list/RoomListStore2.ts | 4 ++-- src/stores/room-list/RoomListStoreTempProxy.ts | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 6354bb8739..e65c2bc606 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -667,8 +667,7 @@ class LoggedInView extends React.Component { disabled={this.props.leftDisabled} /> ); - if (SettingsStore.isFeatureEnabled("feature_new_room_list")) { - // TODO: Supply props like collapsed and disabled to LeftPanel2 + if (SettingsStore.getValue("feature_new_room_list")) { leftPanel = ( { return this._matrixClient; } - // TODO: Remove enabled flag with the old RoomListStore: https://github.com/vector-im/riot-web/issues/14231 + // TODO: Remove enabled flag with the old RoomListStore: https://github.com/vector-im/riot-web/issues/14367 private checkEnabled() { - this.enabled = SettingsStore.isFeatureEnabled("feature_new_room_list"); + this.enabled = SettingsStore.getValue("feature_new_room_list"); if (this.enabled) { console.log("âš¡ new room list store engaged"); } diff --git a/src/stores/room-list/RoomListStoreTempProxy.ts b/src/stores/room-list/RoomListStoreTempProxy.ts index 86aff178ee..9aae159e19 100644 --- a/src/stores/room-list/RoomListStoreTempProxy.ts +++ b/src/stores/room-list/RoomListStoreTempProxy.ts @@ -28,7 +28,7 @@ import { ITagMap } from "./algorithms/models"; */ export class RoomListStoreTempProxy { public static isUsingNewStore(): boolean { - return SettingsStore.isFeatureEnabled("feature_new_room_list"); + return SettingsStore.getValue("feature_new_room_list"); } public static addListener(handler: () => void): RoomListStoreTempToken { From a59a8b76a985cc9e7d03fa9ed6f9655365f3e58c Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 7 Jul 2020 14:51:43 -0600 Subject: [PATCH 03/46] Update TODO comments to point to new issue --- res/css/structures/_LeftPanel2.scss | 2 +- res/css/views/rooms/_RoomBreadcrumbs2.scss | 2 +- res/css/views/rooms/_RoomSublist2.scss | 2 +- res/css/views/rooms/_RoomTile2.scss | 2 +- src/components/structures/LeftPanel2.tsx | 4 ++-- src/components/structures/RoomSearch.tsx | 2 +- src/components/views/rooms/RoomBreadcrumbs2.tsx | 4 ++-- src/components/views/rooms/RoomList2.tsx | 4 ++-- src/components/views/rooms/RoomSublist2.tsx | 4 ++-- src/components/views/rooms/RoomTile2.tsx | 4 ++-- .../views/settings/tabs/user/PreferencesUserSettingsTab.js | 4 ++-- src/stores/BreadcrumbsStore.ts | 6 +++--- src/stores/room-list/MessagePreviewStore.ts | 2 +- src/stores/room-list/RoomListStore2.ts | 4 ++-- src/stores/room-list/RoomListStoreTempProxy.ts | 2 +- 15 files changed, 24 insertions(+), 24 deletions(-) diff --git a/res/css/structures/_LeftPanel2.scss b/res/css/structures/_LeftPanel2.scss index a73658d916..10eb9dd2e9 100644 --- a/res/css/structures/_LeftPanel2.scss +++ b/res/css/structures/_LeftPanel2.scss @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231 +// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367 $tagPanelWidth: 70px; // only applies in this file, used for calculations diff --git a/res/css/views/rooms/_RoomBreadcrumbs2.scss b/res/css/views/rooms/_RoomBreadcrumbs2.scss index 6e5a5fbb16..0c3c41622e 100644 --- a/res/css/views/rooms/_RoomBreadcrumbs2.scss +++ b/res/css/views/rooms/_RoomBreadcrumbs2.scss @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231 +// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367 .mx_RoomBreadcrumbs2 { width: 100%; diff --git a/res/css/views/rooms/_RoomSublist2.scss b/res/css/views/rooms/_RoomSublist2.scss index 0e76152f86..9c919023e6 100644 --- a/res/css/views/rooms/_RoomSublist2.scss +++ b/res/css/views/rooms/_RoomSublist2.scss @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231 +// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367 .mx_RoomSublist2 { // The sublist is a column of rows, essentially diff --git a/res/css/views/rooms/_RoomTile2.scss b/res/css/views/rooms/_RoomTile2.scss index 50d376a66f..d844c14443 100644 --- a/res/css/views/rooms/_RoomTile2.scss +++ b/res/css/views/rooms/_RoomTile2.scss @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231 +// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367 // Note: the room tile expects to be in a flexbox column container .mx_RoomTile2 { diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index 7fac6cbff1..769f7daefd 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -34,8 +34,8 @@ import RoomListStore, { LISTS_UPDATE_EVENT } from "../../stores/room-list/RoomLi import {Key} from "../../Keyboard"; import IndicatorScrollbar from "../structures/IndicatorScrollbar"; -// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14231 -// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231 +// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14367 +// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367 /******************************************************************* * CAUTION * diff --git a/src/components/structures/RoomSearch.tsx b/src/components/structures/RoomSearch.tsx index 15f3bd5b54..d152a2b030 100644 --- a/src/components/structures/RoomSearch.tsx +++ b/src/components/structures/RoomSearch.tsx @@ -25,7 +25,7 @@ import { Key } from "../../Keyboard"; import AccessibleButton from "../views/elements/AccessibleButton"; import { Action } from "../../dispatcher/actions"; -// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14231 +// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14367 /******************************************************************* * CAUTION * diff --git a/src/components/views/rooms/RoomBreadcrumbs2.tsx b/src/components/views/rooms/RoomBreadcrumbs2.tsx index 687f4dd73e..fce8c6ee3a 100644 --- a/src/components/views/rooms/RoomBreadcrumbs2.tsx +++ b/src/components/views/rooms/RoomBreadcrumbs2.tsx @@ -28,8 +28,8 @@ import RoomListStore from "../../../stores/room-list/RoomListStore2"; import { DefaultTagID } from "../../../stores/room-list/models"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; -// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14231 -// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231 +// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14367 +// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367 /******************************************************************* * CAUTION * diff --git a/src/components/views/rooms/RoomList2.tsx b/src/components/views/rooms/RoomList2.tsx index fb0136fb29..db246b182d 100644 --- a/src/components/views/rooms/RoomList2.tsx +++ b/src/components/views/rooms/RoomList2.tsx @@ -42,8 +42,8 @@ import { TagSpecificNotificationState } from "../../../stores/notifications/TagS import { Action } from "../../../dispatcher/actions"; import { ViewRoomDeltaPayload } from "../../../dispatcher/payloads/ViewRoomDeltaPayload"; -// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14231 -// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231 +// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14367 +// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367 /******************************************************************* * CAUTION * diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index eefd29f0b7..60d4e307a1 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -42,8 +42,8 @@ import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import { Key } from "../../../Keyboard"; import StyledCheckbox from "../elements/StyledCheckbox"; -// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14231 -// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231 +// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14367 +// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367 /******************************************************************* * CAUTION * diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index abb31a6f71..3b15d7b27a 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -51,8 +51,8 @@ import NotificationBadge from "./NotificationBadge"; import { NotificationColor } from "../../../stores/notifications/NotificationColor"; import { Volume } from "../../../RoomNotifsTypes"; -// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14231 -// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231 +// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14367 +// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367 /******************************************************************* * CAUTION * diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index 40b622cf37..abe6b48712 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -32,12 +32,12 @@ export default class PreferencesUserSettingsTab extends React.Component { 'breadcrumbs', ]; - // TODO: Remove temp structures: https://github.com/vector-im/riot-web/issues/14231 + // TODO: Remove temp structures: https://github.com/vector-im/riot-web/issues/14367 static ROOM_LIST_2_SETTINGS = [ 'breadcrumbs', ]; - // TODO: Remove temp structures: https://github.com/vector-im/riot-web/issues/14231 + // TODO: Remove temp structures: https://github.com/vector-im/riot-web/issues/14367 static eligibleRoomListSettings = () => { if (RoomListStoreTempProxy.isUsingNewStore()) { return PreferencesUserSettingsTab.ROOM_LIST_2_SETTINGS; diff --git a/src/stores/BreadcrumbsStore.ts b/src/stores/BreadcrumbsStore.ts index c78f15c3b4..70d4e19a32 100644 --- a/src/stores/BreadcrumbsStore.ts +++ b/src/stores/BreadcrumbsStore.ts @@ -57,7 +57,7 @@ export class BreadcrumbsStore extends AsyncStoreWithClient { protected async onAction(payload: ActionPayload) { if (!this.matrixClient) return; - // TODO: Remove when new room list is made the default: https://github.com/vector-im/riot-web/issues/14231 + // TODO: Remove when new room list is made the default: https://github.com/vector-im/riot-web/issues/14367 if (!RoomListStoreTempProxy.isUsingNewStore()) return; if (payload.action === 'setting_updated') { @@ -80,7 +80,7 @@ export class BreadcrumbsStore extends AsyncStoreWithClient { } protected async onReady() { - // TODO: Remove when new room list is made the default: https://github.com/vector-im/riot-web/issues/14231 + // TODO: Remove when new room list is made the default: https://github.com/vector-im/riot-web/issues/14367 if (!RoomListStoreTempProxy.isUsingNewStore()) return; await this.updateRooms(); @@ -91,7 +91,7 @@ export class BreadcrumbsStore extends AsyncStoreWithClient { } protected async onNotReady() { - // TODO: Remove when new room list is made the default: https://github.com/vector-im/riot-web/issues/14231 + // TODO: Remove when new room list is made the default: https://github.com/vector-im/riot-web/issues/14367 if (!RoomListStoreTempProxy.isUsingNewStore()) return; this.matrixClient.removeListener("Room.myMembership", this.onMyMembership); diff --git a/src/stores/room-list/MessagePreviewStore.ts b/src/stores/room-list/MessagePreviewStore.ts index 01ddde2e17..ea7fa830cd 100644 --- a/src/stores/room-list/MessagePreviewStore.ts +++ b/src/stores/room-list/MessagePreviewStore.ts @@ -192,7 +192,7 @@ export class MessagePreviewStore extends AsyncStoreWithClient { protected async onAction(payload: ActionPayload) { if (!this.matrixClient) return; - // TODO: Remove when new room list is made the default: https://github.com/vector-im/riot-web/issues/14231 + // TODO: Remove when new room list is made the default: https://github.com/vector-im/riot-web/issues/14367 if (!RoomListStoreTempProxy.isUsingNewStore()) return; if (payload.action === 'MatrixActions.Room.timeline' || payload.action === 'MatrixActions.Event.decrypted') { diff --git a/src/stores/room-list/RoomListStore2.ts b/src/stores/room-list/RoomListStore2.ts index 1e5b4f302f..1acff9d3fd 100644 --- a/src/stores/room-list/RoomListStore2.ts +++ b/src/stores/room-list/RoomListStore2.ts @@ -89,7 +89,7 @@ export class RoomListStore2 extends AsyncStore { } private onRVSUpdate = () => { - 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/14367 if (!this.matrixClient) return; // We assume there won't be RVS updates without a client const activeRoomId = RoomViewStore.getRoomId(); @@ -115,7 +115,7 @@ export class RoomListStore2 extends AsyncStore { return; } - // TODO: Remove with https://github.com/vector-im/riot-web/issues/14231 + // TODO: Remove with https://github.com/vector-im/riot-web/issues/14367 this.checkEnabled(); if (!this.enabled) return; diff --git a/src/stores/room-list/RoomListStoreTempProxy.ts b/src/stores/room-list/RoomListStoreTempProxy.ts index 9aae159e19..2a5348ab6e 100644 --- a/src/stores/room-list/RoomListStoreTempProxy.ts +++ b/src/stores/room-list/RoomListStoreTempProxy.ts @@ -24,7 +24,7 @@ import { ITagMap } from "./algorithms/models"; * Temporary RoomListStore proxy. Should be replaced with RoomListStore2 when * it is available to everyone. * - * TODO: Delete this: https://github.com/vector-im/riot-web/issues/14231 + * TODO: Delete this: https://github.com/vector-im/riot-web/issues/14367 */ export class RoomListStoreTempProxy { public static isUsingNewStore(): boolean { From c774b88bdabd0914109277ebc35c5fe5a18eb182 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 7 Jul 2020 15:06:06 -0600 Subject: [PATCH 04/46] Initial pass of fixing tests * Use new components * Use new tagId prop on sublists * Define onResize for the room list so it doesn't crash --- test/components/views/rooms/RoomList-test.js | 30 ++++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/test/components/views/rooms/RoomList-test.js b/test/components/views/rooms/RoomList-test.js index d0694a8437..681f5ccb6b 100644 --- a/test/components/views/rooms/RoomList-test.js +++ b/test/components/views/rooms/RoomList-test.js @@ -57,11 +57,11 @@ describe('RoomList', () => { parentDiv = document.createElement('div'); document.body.appendChild(parentDiv); - const RoomList = sdk.getComponent('views.rooms.RoomList'); + const RoomList = sdk.getComponent('views.rooms.RoomList2'); const WrappedRoomList = TestUtils.wrapInMatrixClientContext(RoomList); root = ReactDOM.render( - + {}} /> , parentDiv); ReactTestUtils.findRenderedComponentWithType(root, RoomList); @@ -117,8 +117,8 @@ describe('RoomList', () => { }); function expectRoomInSubList(room, subListTest) { - const RoomSubList = sdk.getComponent('structures.RoomSubList'); - const RoomTile = sdk.getComponent('views.rooms.RoomTile'); + const RoomSubList = sdk.getComponent('views.rooms.RoomSublist2'); + const RoomTile = sdk.getComponent('views.rooms.RoomTile2'); const subLists = ReactTestUtils.scryRenderedComponentsWithType(root, RoomSubList); const containingSubList = subLists.find(subListTest); @@ -140,20 +140,20 @@ describe('RoomList', () => { expect(expectedRoomTile.props.room).toBe(room); } - function expectCorrectMove(oldTag, newTag) { - const getTagSubListTest = (tag) => { - if (tag === undefined) return (s) => s.props.label.endsWith('Rooms'); - return (s) => s.props.tagName === tag; + function expectCorrectMove(oldTagId, newTagId) { + const getTagSubListTest = (tagId) => { + return (s) => s.props.tagId === tagId; }; // Default to finding the destination sublist with newTag - const destSubListTest = getTagSubListTest(newTag); - const srcSubListTest = getTagSubListTest(oldTag); + const destSubListTest = getTagSubListTest(newTagId); + const srcSubListTest = getTagSubListTest(oldTagId); // Set up the room that will be moved such that it has the correct state for a room in - // the section for oldTag - if (['m.favourite', 'm.lowpriority'].includes(oldTag)) movingRoom.tags = {[oldTag]: {}}; - if (oldTag === DefaultTagID.DM) { + // the section for oldTagId + if (oldTagId === DefaultTagID.Favourite || oldTagId === DefaultTagID.LowPriority) { + movingRoom.tags = {[oldTagId]: {}}; + } else if (oldTagId === DefaultTagID.DM) { // Mock inverse m.direct DMRoomMap.shared().roomToUser = { [movingRoom.roomId]: '@someotheruser:domain', @@ -167,7 +167,7 @@ describe('RoomList', () => { expectRoomInSubList(movingRoom, srcSubListTest); dis.dispatch({action: 'RoomListActions.tagRoom.pending', request: { - oldTag, newTag, room: movingRoom, + oldTagId, newTagId, room: movingRoom, }}); // Run all setTimeouts for dispatches and room list rate limiting @@ -287,7 +287,7 @@ describe('RoomList', () => { clock.runAll(); // By default, the test will - expectRoomInSubList(otherRoom, (s) => s.props.label.endsWith('Rooms')); + expectRoomInSubList(otherRoom, (s) => s.props.tagId === DefaultTagID.Untagged); }); itDoesCorrectOptimisticUpdatesForDraggedRoomTiles(); From f89fcd1fe9c3df9d44add57f160bab408b3e5ec6 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 7 Jul 2020 15:45:59 -0600 Subject: [PATCH 05/46] Fix tests and add general safety We don't need the fake clock anymore, but we do have to wait for async actions to complete before moving forward. This also exposes a number of functions for the store to be puppetted with. --- src/stores/room-list/RoomListStore2.ts | 43 ++++++++++++++------ test/components/views/rooms/RoomList-test.js | 40 ++++++++++-------- 2 files changed, 55 insertions(+), 28 deletions(-) diff --git a/src/stores/room-list/RoomListStore2.ts b/src/stores/room-list/RoomListStore2.ts index 1acff9d3fd..63c3abdf09 100644 --- a/src/stores/room-list/RoomListStore2.ts +++ b/src/stores/room-list/RoomListStore2.ts @@ -72,6 +72,34 @@ export class RoomListStore2 extends AsyncStore { return this._matrixClient; } + // Intended for test usage + public async resetStore() { + await this.reset(); + this.tagWatcher = new TagWatcher(this); + this.filterConditions = []; + this.initialListsGenerated = false; + this._matrixClient = null; + + this.algorithm.off(LIST_UPDATED_EVENT, this.onAlgorithmListUpdated); + this.algorithm = new Algorithm(); + this.algorithm.on(LIST_UPDATED_EVENT, this.onAlgorithmListUpdated); + } + + // Public for test usage. Do not call this. + public async makeReady(client: MatrixClient) { + // TODO: Remove with https://github.com/vector-im/riot-web/issues/14367 + this.checkEnabled(); + if (!this.enabled) return; + + this._matrixClient = client; + + // 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(); + this.onRVSUpdate(); // fake an RVS update to adjust sticky room, if needed + } + // TODO: Remove enabled flag with the old RoomListStore: https://github.com/vector-im/riot-web/issues/14367 private checkEnabled() { this.enabled = SettingsStore.getValue("feature_new_room_list"); @@ -115,17 +143,7 @@ export class RoomListStore2 extends AsyncStore { return; } - // TODO: Remove with https://github.com/vector-im/riot-web/issues/14367 - 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(); - this.onRVSUpdate(); // fake an RVS update to adjust sticky room, if needed + await this.makeReady(payload.matrixClient); } // TODO: Remove this once the RoomListStore becomes default @@ -372,7 +390,8 @@ export class RoomListStore2 extends AsyncStore { this.emit(LISTS_UPDATE_EVENT, this); }; - private async regenerateAllLists() { + // This is only exposed externally for the tests. Do not call this within the app. + public async regenerateAllLists() { console.warn("Regenerating all room lists"); const sorts: ITagSortingMap = {}; diff --git a/test/components/views/rooms/RoomList-test.js b/test/components/views/rooms/RoomList-test.js index 681f5ccb6b..ba53676fc1 100644 --- a/test/components/views/rooms/RoomList-test.js +++ b/test/components/views/rooms/RoomList-test.js @@ -15,11 +15,17 @@ import GroupStore from '../../../../src/stores/GroupStore.js'; import { MatrixClient, Room, RoomMember } from 'matrix-js-sdk'; import {DefaultTagID} from "../../../../src/stores/room-list/models"; +import RoomListStore, {LISTS_UPDATE_EVENT} from "../../../../src/stores/room-list/RoomListStore2"; function generateRoomId() { return '!' + Math.random().toString().slice(2, 10) + ':domain'; } +function waitForRoomListStoreUpdate() { + return new Promise((resolve) => { + RoomListStore.instance.once(LISTS_UPDATE_EVENT, () => resolve()); + }); +} describe('RoomList', () => { function createRoom(opts) { @@ -34,7 +40,6 @@ describe('RoomList', () => { let client = null; let root = null; const myUserId = '@me:domain'; - let clock = null; const movingRoomId = '!someroomid'; let movingRoom; @@ -43,15 +48,13 @@ describe('RoomList', () => { let myMember; let myOtherMember; - beforeEach(function() { + beforeEach(async function(done) { TestUtils.stubClient(); client = MatrixClientPeg.get(); client.credentials = {userId: myUserId}; //revert this to prototype method as the test-utils monkey-patches this to return a hardcoded value client.getUserId = MatrixClient.prototype.getUserId; - clock = lolex.install(); - DMRoomMap.makeShared(); parentDiv = document.createElement('div'); @@ -102,16 +105,22 @@ describe('RoomList', () => { }); client.getRoom = (roomId) => roomMap[roomId]; + + // Now that everything has been set up, prepare and update the store + await RoomListStore.instance.makeReady(client); + + done(); }); - afterEach((done) => { + afterEach(async (done) => { if (parentDiv) { ReactDOM.unmountComponentAtNode(parentDiv); parentDiv.remove(); parentDiv = null; } - clock.uninstall(); + await RoomListStore.instance.resetLayouts(); + await RoomListStore.instance.resetStore(); done(); }); @@ -127,6 +136,7 @@ describe('RoomList', () => { try { const roomTiles = ReactTestUtils.scryRenderedComponentsWithType(containingSubList, RoomTile); console.info({roomTiles: roomTiles.length}); + console.log("IS SAME?", room === roomTiles[0].props.room, room, roomTiles[0].props.room); expectedRoomTile = roomTiles.find((tile) => tile.props.room === room); } catch (err) { // truncate the error message because it's spammy @@ -162,17 +172,12 @@ describe('RoomList', () => { dis.dispatch({action: 'MatrixActions.sync', prevState: null, state: 'PREPARED', matrixClient: client}); - clock.runAll(); - expectRoomInSubList(movingRoom, srcSubListTest); dis.dispatch({action: 'RoomListActions.tagRoom.pending', request: { oldTagId, newTagId, room: movingRoom, }}); - // Run all setTimeouts for dispatches and room list rate limiting - clock.runAll(); - expectRoomInSubList(movingRoom, destSubListTest); } @@ -269,6 +274,12 @@ describe('RoomList', () => { }; GroupStore._notifyListeners(); + // We also have to mock the client's getGroup function for the room list to filter it. + // It's not smart enough to tell the difference between a real group and a template though. + client.getGroup = (groupId) => { + return {groupId}; + }; + // Select tag dis.dispatch({action: 'select_tag', tag: '+group:domain'}, true); } @@ -277,16 +288,13 @@ describe('RoomList', () => { setupSelectedTag(); }); - it('displays the correct rooms when the groups rooms are changed', () => { + it('displays the correct rooms when the groups rooms are changed', async () => { GroupStore.getGroupRooms = (groupId) => { return [movingRoom, otherRoom]; }; GroupStore._notifyListeners(); - // Run through RoomList debouncing - clock.runAll(); - - // By default, the test will + await waitForRoomListStoreUpdate(); expectRoomInSubList(otherRoom, (s) => s.props.tagId === DefaultTagID.Untagged); }); From 767db73853f58d116d2007bdaba31338c46c4ed5 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 7 Jul 2020 15:50:04 -0600 Subject: [PATCH 06/46] Appease the linter --- test/components/views/rooms/RoomList-test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/components/views/rooms/RoomList-test.js b/test/components/views/rooms/RoomList-test.js index ba53676fc1..ade74dfe44 100644 --- a/test/components/views/rooms/RoomList-test.js +++ b/test/components/views/rooms/RoomList-test.js @@ -1,7 +1,6 @@ import React from 'react'; import ReactTestUtils from 'react-dom/test-utils'; import ReactDOM from 'react-dom'; -import lolex from 'lolex'; import * as TestUtils from '../../../test-utils'; From 044c2238990dfb6ecd164dcbed89680d7f17bf9b Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 7 Jul 2020 15:53:12 -0600 Subject: [PATCH 07/46] Remove debug --- test/components/views/rooms/RoomList-test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/components/views/rooms/RoomList-test.js b/test/components/views/rooms/RoomList-test.js index ade74dfe44..7876ad0f18 100644 --- a/test/components/views/rooms/RoomList-test.js +++ b/test/components/views/rooms/RoomList-test.js @@ -135,7 +135,6 @@ describe('RoomList', () => { try { const roomTiles = ReactTestUtils.scryRenderedComponentsWithType(containingSubList, RoomTile); console.info({roomTiles: roomTiles.length}); - console.log("IS SAME?", room === roomTiles[0].props.room, room, roomTiles[0].props.room); expectedRoomTile = roomTiles.find((tile) => tile.props.room === room); } catch (err) { // truncate the error message because it's spammy From 85af3ebcc0b3548a4fc3915bb721fd27b0783600 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 7 Jul 2020 15:58:15 -0600 Subject: [PATCH 08/46] Lie about DMs in tests --- test/components/views/rooms/RoomList-test.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/components/views/rooms/RoomList-test.js b/test/components/views/rooms/RoomList-test.js index 7876ad0f18..91e5e77937 100644 --- a/test/components/views/rooms/RoomList-test.js +++ b/test/components/views/rooms/RoomList-test.js @@ -56,6 +56,11 @@ describe('RoomList', () => { DMRoomMap.makeShared(); + // Lie to the room list store about DMs not existing + DMRoomMap.getUserIdForRoomId = () => { + return null; + } + parentDiv = document.createElement('div'); document.body.appendChild(parentDiv); From 5ace405062cf150c9c9672c593bbed78abae688c Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 7 Jul 2020 16:01:42 -0600 Subject: [PATCH 09/46] The linter will never be appeased --- test/components/views/rooms/RoomList-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/components/views/rooms/RoomList-test.js b/test/components/views/rooms/RoomList-test.js index 91e5e77937..3521ec0705 100644 --- a/test/components/views/rooms/RoomList-test.js +++ b/test/components/views/rooms/RoomList-test.js @@ -59,7 +59,7 @@ describe('RoomList', () => { // Lie to the room list store about DMs not existing DMRoomMap.getUserIdForRoomId = () => { return null; - } + }; parentDiv = document.createElement('div'); document.body.appendChild(parentDiv); From 121e41d20b4bc9baa73d7a2ea36f90a1b8b6804f Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 7 Jul 2020 16:20:53 -0600 Subject: [PATCH 10/46] Remove irrelevant function --- test/components/views/rooms/RoomList-test.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test/components/views/rooms/RoomList-test.js b/test/components/views/rooms/RoomList-test.js index 3521ec0705..7876ad0f18 100644 --- a/test/components/views/rooms/RoomList-test.js +++ b/test/components/views/rooms/RoomList-test.js @@ -56,11 +56,6 @@ describe('RoomList', () => { DMRoomMap.makeShared(); - // Lie to the room list store about DMs not existing - DMRoomMap.getUserIdForRoomId = () => { - return null; - }; - parentDiv = document.createElement('div'); document.body.appendChild(parentDiv); From f12d9512098dbd4e56dc1f985f042a6d79e6ff5f Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 7 Jul 2020 16:34:42 -0600 Subject: [PATCH 11/46] Update end-to-end tests for new room list --- .../src/usecases/accept-invite.js | 6 ++-- .../src/usecases/create-room.js | 36 +++++++++---------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/test/end-to-end-tests/src/usecases/accept-invite.js b/test/end-to-end-tests/src/usecases/accept-invite.js index 3f208cc1fc..a61aaec64c 100644 --- a/test/end-to-end-tests/src/usecases/accept-invite.js +++ b/test/end-to-end-tests/src/usecases/accept-invite.js @@ -15,10 +15,12 @@ See the License for the specific language governing permissions and limitations under the License. */ +const {findSublist} = require("./create-room"); + module.exports = async function acceptInvite(session, name) { session.log.step(`accepts "${name}" invite`); - //TODO: brittle selector - const invitesHandles = await session.queryAll('.mx_RoomTile_name.mx_RoomTile_invite'); + const inviteSublist = await findSublist("invites"); + const invitesHandles = await inviteSublist.$(".mx_RoomTile2_name"); const invitesWithText = await Promise.all(invitesHandles.map(async (inviteHandle) => { const text = await session.innerText(inviteHandle); return {inviteHandle, text}; diff --git a/test/end-to-end-tests/src/usecases/create-room.js b/test/end-to-end-tests/src/usecases/create-room.js index 7e219fd159..50cb1e02f3 100644 --- a/test/end-to-end-tests/src/usecases/create-room.js +++ b/test/end-to-end-tests/src/usecases/create-room.js @@ -16,21 +16,27 @@ limitations under the License. */ async function openRoomDirectory(session) { - const roomDirectoryButton = await session.query('.mx_LeftPanel_explore .mx_AccessibleButton'); + const roomDirectoryButton = await session.query('.mx_LeftPanel2_exploreButton'); await roomDirectoryButton.click(); } +async function findSublist(name) { + const sublists = await session.queryAll('.mx_RoomSublist2'); + for (const sublist of sublists) { + const header = await sublist.$('.mx_RoomSublist2_headerText'); + const headerText = await session.innerText(header); + if (headerText.toLowerCase().includes(name.toLowerCase())) { + return sublist; + } + } + throw new Error(`could not find room list section that contains '${name}' in header`); +} + async function createRoom(session, roomName, encrypted=false) { session.log.step(`creates room "${roomName}"`); - const roomListHeaders = await session.queryAll('.mx_RoomSubList_labelContainer'); - const roomListHeaderLabels = await Promise.all(roomListHeaders.map(h => session.innerText(h))); - const roomsIndex = roomListHeaderLabels.findIndex(l => l.toLowerCase().includes("rooms")); - if (roomsIndex === -1) { - throw new Error("could not find room list section that contains 'rooms' in header"); - } - const roomsHeader = roomListHeaders[roomsIndex]; - const addRoomButton = await roomsHeader.$(".mx_RoomSubList_addRoom"); + const roomsSublist = await findSublist("rooms"); + const addRoomButton = await roomsSublist.$(".mx_RoomSublist2_auxButton"); await addRoomButton.click(); const roomNameInput = await session.query('.mx_CreateRoomDialog_name input'); @@ -51,14 +57,8 @@ async function createRoom(session, roomName, encrypted=false) { async function createDm(session, invitees) { session.log.step(`creates DM with ${JSON.stringify(invitees)}`); - const roomListHeaders = await session.queryAll('.mx_RoomSubList_labelContainer'); - const roomListHeaderLabels = await Promise.all(roomListHeaders.map(h => session.innerText(h))); - const dmsIndex = roomListHeaderLabels.findIndex(l => l.toLowerCase().includes('direct messages')); - if (dmsIndex === -1) { - throw new Error("could not find room list section that contains 'direct messages' in header"); - } - const dmsHeader = roomListHeaders[dmsIndex]; - const startChatButton = await dmsHeader.$(".mx_RoomSubList_addRoom"); + const dmsSublist = await findSublist("people"); + const startChatButton = await dmsSublist.$(".mx_RoomSublist2_auxButton"); await startChatButton.click(); const inviteesEditor = await session.query('.mx_InviteDialog_editor textarea'); @@ -83,4 +83,4 @@ async function createDm(session, invitees) { session.log.done(); } -module.exports = {openRoomDirectory, createRoom, createDm}; +module.exports = {openRoomDirectory, findSublist, createRoom, createDm}; From 9000888013f27f75b106719832eeb88f571414dc Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 7 Jul 2020 16:38:24 -0600 Subject: [PATCH 12/46] Pass the session through --- test/end-to-end-tests/src/usecases/accept-invite.js | 2 +- test/end-to-end-tests/src/usecases/create-room.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/end-to-end-tests/src/usecases/accept-invite.js b/test/end-to-end-tests/src/usecases/accept-invite.js index a61aaec64c..27992e16df 100644 --- a/test/end-to-end-tests/src/usecases/accept-invite.js +++ b/test/end-to-end-tests/src/usecases/accept-invite.js @@ -19,7 +19,7 @@ const {findSublist} = require("./create-room"); module.exports = async function acceptInvite(session, name) { session.log.step(`accepts "${name}" invite`); - const inviteSublist = await findSublist("invites"); + const inviteSublist = await findSublist(session, "invites"); const invitesHandles = await inviteSublist.$(".mx_RoomTile2_name"); const invitesWithText = await Promise.all(invitesHandles.map(async (inviteHandle) => { const text = await session.innerText(inviteHandle); diff --git a/test/end-to-end-tests/src/usecases/create-room.js b/test/end-to-end-tests/src/usecases/create-room.js index 50cb1e02f3..24e42b92dd 100644 --- a/test/end-to-end-tests/src/usecases/create-room.js +++ b/test/end-to-end-tests/src/usecases/create-room.js @@ -20,7 +20,7 @@ async function openRoomDirectory(session) { await roomDirectoryButton.click(); } -async function findSublist(name) { +async function findSublist(session, name) { const sublists = await session.queryAll('.mx_RoomSublist2'); for (const sublist of sublists) { const header = await sublist.$('.mx_RoomSublist2_headerText'); @@ -35,7 +35,7 @@ async function findSublist(name) { async function createRoom(session, roomName, encrypted=false) { session.log.step(`creates room "${roomName}"`); - const roomsSublist = await findSublist("rooms"); + const roomsSublist = await findSublist(session, "rooms"); const addRoomButton = await roomsSublist.$(".mx_RoomSublist2_auxButton"); await addRoomButton.click(); @@ -57,7 +57,7 @@ async function createRoom(session, roomName, encrypted=false) { async function createDm(session, invitees) { session.log.step(`creates DM with ${JSON.stringify(invitees)}`); - const dmsSublist = await findSublist("people"); + const dmsSublist = await findSublist(session, "people"); const startChatButton = await dmsSublist.$(".mx_RoomSublist2_auxButton"); await startChatButton.click(); From 9bf2505e51db82afe2ef5d8e8b4356e9f25b79fc Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 7 Jul 2020 16:48:03 -0600 Subject: [PATCH 13/46] queryAll, not just query --- test/end-to-end-tests/src/usecases/accept-invite.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/end-to-end-tests/src/usecases/accept-invite.js b/test/end-to-end-tests/src/usecases/accept-invite.js index 27992e16df..d38fdcd0db 100644 --- a/test/end-to-end-tests/src/usecases/accept-invite.js +++ b/test/end-to-end-tests/src/usecases/accept-invite.js @@ -20,7 +20,7 @@ const {findSublist} = require("./create-room"); module.exports = async function acceptInvite(session, name) { session.log.step(`accepts "${name}" invite`); const inviteSublist = await findSublist(session, "invites"); - const invitesHandles = await inviteSublist.$(".mx_RoomTile2_name"); + const invitesHandles = await inviteSublist.$$(".mx_RoomTile2_name"); const invitesWithText = await Promise.all(invitesHandles.map(async (inviteHandle) => { const text = await session.innerText(inviteHandle); return {inviteHandle, text}; From bba819759215326d417f6ea5d3de8ee1074d06cd Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 9 Jul 2020 22:40:34 -0600 Subject: [PATCH 14/46] Use the new layout store --- test/components/views/rooms/RoomList-test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/components/views/rooms/RoomList-test.js b/test/components/views/rooms/RoomList-test.js index 7876ad0f18..81ccf7d7f8 100644 --- a/test/components/views/rooms/RoomList-test.js +++ b/test/components/views/rooms/RoomList-test.js @@ -15,6 +15,7 @@ import GroupStore from '../../../../src/stores/GroupStore.js'; import { MatrixClient, Room, RoomMember } from 'matrix-js-sdk'; import {DefaultTagID} from "../../../../src/stores/room-list/models"; import RoomListStore, {LISTS_UPDATE_EVENT} from "../../../../src/stores/room-list/RoomListStore2"; +import RoomListLayoutStore from "../../../../src/stores/room-list/RoomListLayoutStore"; function generateRoomId() { return '!' + Math.random().toString().slice(2, 10) + ':domain'; @@ -118,7 +119,7 @@ describe('RoomList', () => { parentDiv = null; } - await RoomListStore.instance.resetLayouts(); + await RoomListLayoutStore.instance.resetLayouts(); await RoomListStore.instance.resetStore(); done(); From f8db0a4637ada26406cfd475d287f61d523381c5 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 10 Jul 2020 10:21:00 -0600 Subject: [PATCH 15/46] Resolve complex merge conflicts --- src/stores/room-list/RoomListStore2.ts | 33 +++++++------------------- 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/src/stores/room-list/RoomListStore2.ts b/src/stores/room-list/RoomListStore2.ts index bbc2e7f478..d53514dd7a 100644 --- a/src/stores/room-list/RoomListStore2.ts +++ b/src/stores/room-list/RoomListStore2.ts @@ -97,11 +97,14 @@ export class RoomListStore2 extends AsyncStore { this._matrixClient = client; + // 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"); await this.readAndCacheSettingsFromStore(); - await this.regenerateAllLists(); - this.onRVSUpdate(); // fake an RVS update to adjust sticky room, if needed + await this.regenerateAllLists({trigger: false}); + await this.handleRVSUpdate({trigger: false}); // fake an RVS update to adjust sticky room, if needed + + this.updateFn.trigger(); } // TODO: Remove enabled flag with the old RoomListStore: https://github.com/vector-im/riot-web/issues/14367 @@ -162,25 +165,9 @@ export class RoomListStore2 extends AsyncStore { return; } -<<<<<<< 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 @@ -511,17 +498,15 @@ export class RoomListStore2 extends AsyncStore { this.updateFn.mark(); }; -<<<<<<< - // This is only exposed externally for the tests. Do not call this within the app. - public async regenerateAllLists() { -======= /** * Regenerates the room whole room list, discarding any previous results. + * + * Note: This is only exposed externally for the tests. Do not call this from within + * the app. * @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}) { ->>>>>>> + public async regenerateAllLists({trigger = true}) { console.warn("Regenerating all room lists"); const sorts: ITagSortingMap = {}; From 314250a6e4234aa56c0c967de983000377801f5c Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 10 Jul 2020 10:38:07 -0600 Subject: [PATCH 16/46] Add a test mode flag to the store --- src/stores/room-list/RoomListStore2.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/stores/room-list/RoomListStore2.ts b/src/stores/room-list/RoomListStore2.ts index d53514dd7a..acbb72f1d8 100644 --- a/src/stores/room-list/RoomListStore2.ts +++ b/src/stores/room-list/RoomListStore2.ts @@ -46,6 +46,12 @@ interface IState { export const LISTS_UPDATE_EVENT = "lists_update"; export class RoomListStore2 extends AsyncStore { + /** + * Set to true if you're running tests on the store. Should not be touched in + * any other environment. + */ + public static TEST_MODE = false; + private _matrixClient: MatrixClient; private initialListsGenerated = false; private enabled = false; @@ -104,6 +110,7 @@ export class RoomListStore2 extends AsyncStore { await this.regenerateAllLists({trigger: false}); await this.handleRVSUpdate({trigger: false}); // fake an RVS update to adjust sticky room, if needed + this.updateFn.mark(); // we almost certainly want to trigger an update. this.updateFn.trigger(); } @@ -152,7 +159,14 @@ export class RoomListStore2 extends AsyncStore { if (trigger) this.updateFn.trigger(); } - protected onDispatch(payload: ActionPayload) { + protected async onDispatch(payload: ActionPayload) { + // When we're running tests we can't reliably use setImmediate out of timing concerns. + // As such, we use a more synchronous model. + if (RoomListStore2.TEST_MODE) { + await this.onDispatchAsync(payload); + return; + } + // 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)); From 3826d8135838f494cba4d6c834b279a8b54394b3 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 10 Jul 2020 11:05:56 -0600 Subject: [PATCH 17/46] Enable test mode --- test/components/views/rooms/RoomList-test.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/components/views/rooms/RoomList-test.js b/test/components/views/rooms/RoomList-test.js index 81ccf7d7f8..e84f943708 100644 --- a/test/components/views/rooms/RoomList-test.js +++ b/test/components/views/rooms/RoomList-test.js @@ -14,7 +14,7 @@ import GroupStore from '../../../../src/stores/GroupStore.js'; import { MatrixClient, Room, RoomMember } from 'matrix-js-sdk'; import {DefaultTagID} from "../../../../src/stores/room-list/models"; -import RoomListStore, {LISTS_UPDATE_EVENT} from "../../../../src/stores/room-list/RoomListStore2"; +import RoomListStore, {LISTS_UPDATE_EVENT, RoomListStore2} from "../../../../src/stores/room-list/RoomListStore2"; import RoomListLayoutStore from "../../../../src/stores/room-list/RoomListLayoutStore"; function generateRoomId() { @@ -49,6 +49,8 @@ describe('RoomList', () => { let myOtherMember; beforeEach(async function(done) { + RoomListStore2.TEST_MODE = true; + TestUtils.stubClient(); client = MatrixClientPeg.get(); client.credentials = {userId: myUserId}; From bb6d46f92666b302523b3d849ed3e5b7f9f7c61f Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 10 Jul 2020 15:57:05 -0600 Subject: [PATCH 18/46] When the algorithm changes, re-add the filter listener --- src/stores/room-list/RoomListStore2.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/stores/room-list/RoomListStore2.ts b/src/stores/room-list/RoomListStore2.ts index 0d1ddfc4ca..75e43b3b57 100644 --- a/src/stores/room-list/RoomListStore2.ts +++ b/src/stores/room-list/RoomListStore2.ts @@ -92,8 +92,10 @@ export class RoomListStore2 extends AsyncStore { this._matrixClient = null; this.algorithm.off(LIST_UPDATED_EVENT, this.onAlgorithmListUpdated); + this.algorithm.off(FILTER_CHANGED, this.onAlgorithmListUpdated); this.algorithm = new Algorithm(); this.algorithm.on(LIST_UPDATED_EVENT, this.onAlgorithmListUpdated); + this.algorithm.on(FILTER_CHANGED, this.onAlgorithmListUpdated); } // Public for test usage. Do not call this. From 3062d14a783f31211b0d836eeea0847cce7f69ba Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 10 Jul 2020 16:07:24 -0600 Subject: [PATCH 19/46] Convert ImportanceAlgorithm over to using NotificationColor instead Fixes https://github.com/vector-im/riot-web/issues/14362 implicitly By re-using constructs we already have, we don't need to invent code which figures it out. --- .../list-ordering/ImportanceAlgorithm.ts | 78 ++++++------------- 1 file changed, 23 insertions(+), 55 deletions(-) diff --git a/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts b/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts index 88789d3a50..b3f1c2b146 100644 --- a/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts +++ b/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts @@ -19,47 +19,29 @@ import { Room } from "matrix-js-sdk/src/models/room"; import { RoomUpdateCause, TagID } from "../../models"; import { SortAlgorithm } from "../models"; import { sortRoomsWithAlgorithm } from "../tag-sorting"; -import * as Unread from '../../../../Unread'; import { OrderingAlgorithm } from "./OrderingAlgorithm"; - -/** - * The determined category of a room. - */ -export enum Category { - /** - * The room has unread mentions within. - */ - Red = "RED", - /** - * The room has unread notifications within. Note that these are not unread - * mentions - they are simply messages which the user has asked to cause a - * badge count update or push notification. - */ - Grey = "GREY", - /** - * The room has unread messages within (grey without the badge). - */ - Bold = "BOLD", - /** - * The room has no relevant unread messages within. - */ - Idle = "IDLE", -} +import { NotificationColor } from "../../../notifications/NotificationColor"; +import { RoomNotificationStateStore } from "../../../notifications/RoomNotificationStateStore"; interface ICategorizedRoomMap { // @ts-ignore - TS wants this to be a string, but we know better - [category: Category]: Room[]; + [category: NotificationColor]: Room[]; } interface ICategoryIndex { // @ts-ignore - TS wants this to be a string, but we know better - [category: Category]: number; // integer + [category: NotificationColor]: number; // integer } // Caution: changing this means you'll need to update a bunch of assumptions and // comments! Check the usage of Category carefully to figure out what needs changing // if you're going to change this array's order. -const CATEGORY_ORDER = [Category.Red, Category.Grey, Category.Bold, Category.Idle]; +const CATEGORY_ORDER = [ + NotificationColor.Red, + NotificationColor.Grey, + NotificationColor.Bold, + NotificationColor.None, // idle +]; /** * An implementation of the "importance" algorithm for room list sorting. Where @@ -92,10 +74,10 @@ export class ImportanceAlgorithm extends OrderingAlgorithm { // noinspection JSMethodCanBeStatic private categorizeRooms(rooms: Room[]): ICategorizedRoomMap { const map: ICategorizedRoomMap = { - [Category.Red]: [], - [Category.Grey]: [], - [Category.Bold]: [], - [Category.Idle]: [], + [NotificationColor.Red]: [], + [NotificationColor.Grey]: [], + [NotificationColor.Bold]: [], + [NotificationColor.None]: [], }; for (const room of rooms) { const category = this.getRoomCategory(room); @@ -105,25 +87,11 @@ export class ImportanceAlgorithm extends OrderingAlgorithm { } // noinspection JSMethodCanBeStatic - private getRoomCategory(room: Room): Category { - // Function implementation borrowed from old RoomListStore - - const mentions = room.getUnreadNotificationCount('highlight') > 0; - if (mentions) { - return Category.Red; - } - - let unread = room.getUnreadNotificationCount() > 0; - if (unread) { - return Category.Grey; - } - - unread = Unread.doesRoomHaveUnreadMessages(room); - if (unread) { - return Category.Bold; - } - - return Category.Idle; + private getRoomCategory(room: Room): NotificationColor { + // It's fine for us to call this a lot because it's cached, and we shouldn't be + // wasting anything by doing so as the store holds single references + const state = RoomNotificationStateStore.instance.getRoomState(room, this.tagId); + return state.color; } public async setRooms(rooms: Room[]): Promise { @@ -217,7 +185,7 @@ export class ImportanceAlgorithm extends OrderingAlgorithm { } } - private async sortCategory(category: Category) { + private async sortCategory(category: NotificationColor) { // This should be relatively quick because the room is usually inserted at the top of the // category, and most popular sorting algorithms will deal with trying to keep the active // room at the top/start of the category. For the few algorithms that will have to move the @@ -234,7 +202,7 @@ export class ImportanceAlgorithm extends OrderingAlgorithm { } // noinspection JSMethodCanBeStatic - private getCategoryFromIndices(index: number, indices: ICategoryIndex): Category { + private getCategoryFromIndices(index: number, indices: ICategoryIndex): NotificationColor { for (let i = 0; i < CATEGORY_ORDER.length; i++) { const category = CATEGORY_ORDER[i]; const isLast = i === (CATEGORY_ORDER.length - 1); @@ -250,7 +218,7 @@ export class ImportanceAlgorithm extends OrderingAlgorithm { } // noinspection JSMethodCanBeStatic - private moveRoomIndexes(nRooms: number, fromCategory: Category, toCategory: Category, indices: ICategoryIndex) { + private moveRoomIndexes(nRooms: number, fromCategory: NotificationColor, toCategory: NotificationColor, indices: ICategoryIndex) { // We have to update the index of the category *after* the from/toCategory variables // in order to update the indices correctly. Because the room is moving from/to those // categories, the next category's index will change - not the category we're modifying. @@ -261,7 +229,7 @@ export class ImportanceAlgorithm extends OrderingAlgorithm { this.alterCategoryPositionBy(toCategory, +nRooms, indices); } - private alterCategoryPositionBy(category: Category, n: number, indices: ICategoryIndex) { + private alterCategoryPositionBy(category: NotificationColor, n: number, indices: ICategoryIndex) { // Note: when we alter a category's index, we actually have to modify the ones following // the target and not the target itself. From 213e2df9fced040e4e143dfc32259abecfb9cc33 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 12 Jul 2020 08:49:04 +0100 Subject: [PATCH 20/46] Remove redundant scroll-margins and fix RoomTile wrongly scrolling Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/views/rooms/_RoomTile2.scss | 9 --------- src/components/views/rooms/RoomSublist2.tsx | 10 ++++++++-- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/res/css/views/rooms/_RoomTile2.scss b/res/css/views/rooms/_RoomTile2.scss index d2d394e266..8297d1952c 100644 --- a/res/css/views/rooms/_RoomTile2.scss +++ b/res/css/views/rooms/_RoomTile2.scss @@ -21,10 +21,6 @@ limitations under the License. margin-bottom: 4px; padding: 4px; - // allow scrollIntoView to ignore the sticky headers, must match combined height of .mx_RoomSublist2_headerContainer - scroll-margin-top: 32px; - scroll-margin-bottom: 32px; - // The tile is also a flexbox row itself display: flex; @@ -168,11 +164,6 @@ limitations under the License. } } -// do not apply scroll-margin-bottom to the sublist which will not have a sticky header below it -.mx_RoomSublist2:last-child .mx_RoomTile2 { - scroll-margin-bottom: 0; -} - // We use these both in context menus and the room tiles .mx_RoomTile2_iconBell::before { mask-image: url('$(res)/img/feather-customised/bell.svg'); diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index c351384be4..5cb86d4cba 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -17,7 +17,7 @@ limitations under the License. */ import * as React from "react"; -import { createRef } from "react"; +import {createRef, UIEventHandler} from "react"; import { Room } from "matrix-js-sdk/src/models/room"; import classNames from 'classnames'; import { RovingAccessibleButton, RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex"; @@ -595,6 +595,12 @@ export default class RoomSublist2 extends React.Component { ); } + private onScrollPrevent(e: React.UIEvent) { + // the RoomTile calls scrollIntoView and the browser may scroll a div we do not wish to be scrollable + // this fixes https://github.com/vector-im/riot-web/issues/14413 + (e.target as HTMLDivElement).scrollTop = 0; + } + public render(): React.ReactElement { // TODO: Error boundary: https://github.com/vector-im/riot-web/issues/14185 @@ -704,7 +710,7 @@ export default class RoomSublist2 extends React.Component { className="mx_RoomSublist2_resizeBox" enable={handles} > -
+
{visibleTiles}
{showNButton} From 3f51bb84e128370ac002d54b28ab3ed63d374cb1 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 12 Jul 2020 18:24:28 +0100 Subject: [PATCH 21/46] Fix RoomAvatar viewAvatarOnClick to work on actual avatars instead of default ones Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/avatars/RoomAvatar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/avatars/RoomAvatar.tsx b/src/components/views/avatars/RoomAvatar.tsx index 0947157652..907cc9f318 100644 --- a/src/components/views/avatars/RoomAvatar.tsx +++ b/src/components/views/avatars/RoomAvatar.tsx @@ -135,7 +135,7 @@ export default class RoomAvatar extends React.Component { ); } From 71ecd5dc85f794ff7798ee70b32690b199bd0406 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 12 Jul 2020 18:40:24 +0100 Subject: [PATCH 22/46] clean-up Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/avatars/RoomAvatar.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/views/avatars/RoomAvatar.tsx b/src/components/views/avatars/RoomAvatar.tsx index 907cc9f318..3317ed3a60 100644 --- a/src/components/views/avatars/RoomAvatar.tsx +++ b/src/components/views/avatars/RoomAvatar.tsx @@ -126,16 +126,16 @@ export default class RoomAvatar extends React.Component { }; public render() { - /*eslint no-unused-vars: ["error", { "ignoreRestSiblings": true }]*/ const {room, oobData, viewAvatarOnClick, ...otherProps} = this.props; const roomName = room ? room.name : oobData.name; return ( - ); } From d253c5883075f0e842e0dca730b4d95ab25f0a96 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 12 Jul 2020 19:06:47 +0100 Subject: [PATCH 23/46] Room List v2 Enter in the filter field should select the first result Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/LeftPanel2.tsx | 9 +++++++++ src/components/structures/RoomSearch.tsx | 3 +++ 2 files changed, 12 insertions(+) diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index f1f1ffd01f..8ec19ee26b 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -274,6 +274,14 @@ export default class LeftPanel2 extends React.Component { } }; + private onEnter = () => { + const firstRoom = this.listContainerRef.current.querySelector(".mx_RoomTile2"); + if (firstRoom) { + firstRoom.click(); + this.onSearch(""); // clear the search field + } + }; + private onMoveFocus = (up: boolean) => { let element = this.focusedElement; @@ -346,6 +354,7 @@ export default class LeftPanel2 extends React.Component { onQueryUpdate={this.onSearch} isMinimized={this.props.isMinimized} onVerticalArrow={this.onKeyDown} + onEnter={this.onEnter} /> void; isMinimized: boolean; onVerticalArrow(ev: React.KeyboardEvent); + onEnter(ev: React.KeyboardEvent); } interface IState { @@ -115,6 +116,8 @@ export default class RoomSearch extends React.PureComponent { defaultDispatcher.fire(Action.FocusComposer); } else if (ev.key === Key.ARROW_UP || ev.key === Key.ARROW_DOWN) { this.props.onVerticalArrow(ev); + } else if (ev.key === Key.ENTER) { + this.props.onEnter(ev); } }; From c3789245b869e3b9a44273e5da1002cfb5a4ead3 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 12 Jul 2020 19:25:43 +0100 Subject: [PATCH 24/46] Be consistent with the at-room pill avatar configurability Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/utils/pillify.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/pillify.js b/src/utils/pillify.js index f708ab7770..cb140c61a4 100644 --- a/src/utils/pillify.js +++ b/src/utils/pillify.js @@ -111,7 +111,7 @@ export function pillifyLinks(nodes, mxEvent, pills) { type={Pill.TYPE_AT_ROOM_MENTION} inMessage={true} room={room} - shouldShowPillAvatar={true} + shouldShowPillAvatar={shouldShowPillAvatar} />; ReactDOM.render(pill, pillContainer); From 3e2280a6f46d1af601b069f4f551db6c256e195a Mon Sep 17 00:00:00 2001 From: Jorik Schellekens Date: Mon, 13 Jul 2020 15:43:34 +0100 Subject: [PATCH 25/46] Stop classname from overwritting baseavatar's --- src/components/views/avatars/BaseAvatar.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/views/avatars/BaseAvatar.tsx b/src/components/views/avatars/BaseAvatar.tsx index aa2c0ea954..7f30a7a377 100644 --- a/src/components/views/avatars/BaseAvatar.tsx +++ b/src/components/views/avatars/BaseAvatar.tsx @@ -99,6 +99,7 @@ const BaseAvatar = (props: IProps) => { defaultToInitialLetter = true, onClick, inputRef, + className, ...otherProps } = props; @@ -138,7 +139,7 @@ const BaseAvatar = (props: IProps) => { @@ -149,7 +150,7 @@ const BaseAvatar = (props: IProps) => { } else { return ( { if (onClick !== null) { return ( { } else { return ( Date: Mon, 13 Jul 2020 16:11:21 +0100 Subject: [PATCH 26/46] Fix badges for font size 20 --- res/css/views/rooms/_NotificationBadge.scss | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/res/css/views/rooms/_NotificationBadge.scss b/res/css/views/rooms/_NotificationBadge.scss index 521f1dfc20..0e6d442cc1 100644 --- a/res/css/views/rooms/_NotificationBadge.scss +++ b/res/css/views/rooms/_NotificationBadge.scss @@ -48,15 +48,15 @@ limitations under the License. } &.mx_NotificationBadge_2char { - width: 16px; - height: 16px; - border-radius: 16px; + width: $font-16px; + height: $font-16px; + border-radius: $font-16px; } &.mx_NotificationBadge_3char { - width: 26px; - height: 16px; - border-radius: 16px; + width: $font-26px; + height: $font-16px; + border-radius: $font-16px; } // The following is the floating badge From 5bee9487176f2a1804413b94cf70c98749b301e1 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 13 Jul 2020 16:35:03 +0100 Subject: [PATCH 27/46] Fix room tile context menu for Historical rooms Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/MatrixChat.tsx | 21 +++++++++++++++++- src/components/structures/RoomView.js | 12 +++------- src/components/views/rooms/RoomTile2.tsx | 28 ++++++++++++++++++++++-- 3 files changed, 49 insertions(+), 12 deletions(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 89ee1bc22d..7b439d2303 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -50,7 +50,7 @@ import PageTypes from '../../PageTypes'; import { getHomePageUrl } from '../../utils/pages'; import createRoom from "../../createRoom"; -import { _t, getCurrentLanguage } from '../../languageHandler'; +import {_t, _td, getCurrentLanguage} from '../../languageHandler'; import SettingsStore, { SettingLevel } from "../../settings/SettingsStore"; import ThemeController from "../../settings/controllers/ThemeController"; import { startAnyRegistrationFlow } from "../../Registration.js"; @@ -554,6 +554,9 @@ export default class MatrixChat extends React.PureComponent { case 'leave_room': this.leaveRoom(payload.room_id); break; + case 'forget_room': + this.forgetRoom(payload.room_id); + break; case 'reject_invite': Modal.createTrackedDialog('Reject invitation', '', QuestionDialog, { title: _t('Reject invitation'), @@ -1124,6 +1127,22 @@ export default class MatrixChat extends React.PureComponent { }); } + private forgetRoom(roomId: string) { + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + MatrixClientPeg.get().forget(roomId).then(() => { + // Switch to another room view if we're currently viewing the historical room + if (this.state.currentRoomId === roomId) { + dis.dispatch({ action: "view_next_room" }); + } + }, function(err) { + const errCode = err.errcode || _td("unknown error code"); + Modal.createTrackedDialog("Failed to forget room", '', ErrorDialog, { + title: _t("Failed to forget room %(errCode)s", {errCode}), + description: ((err && err.message) ? err.message : _t("Operation failed")), + }); + }); + } + /** * Starts a chat with the welcome user, if the user doesn't already have one * @returns {string} The room ID of the new room, or null if no room was created diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index a9f75ce632..197acca599 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -1380,15 +1380,9 @@ export default createReactClass({ }, onForgetClick: function() { - this.context.forget(this.state.room.roomId).then(function() { - dis.dispatch({ action: 'view_next_room' }); - }, function(err) { - const errCode = err.errcode || _t("unknown error code"); - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createTrackedDialog('Failed to forget room', '', ErrorDialog, { - title: _t("Error"), - description: _t("Failed to forget room %(errCode)s", { errCode: errCode }), - }); + dis.dispatch({ + action: 'forget_room', + room_id: this.state.room.roomId, }); }, diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index ed188e996b..ca2f8865f9 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -276,6 +276,17 @@ export default class RoomTile2 extends React.Component { this.setState({generalMenuPosition: null}); // hide the menu }; + private onForgetRoomClick = (ev: ButtonEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + + dis.dispatch({ + action: 'forget_room', + room_id: this.props.room.roomId, + }); + this.setState({generalMenuPosition: null}); // hide the menu + }; + private onOpenRoomSettings = (ev: ButtonEvent) => { ev.preventDefault(); ev.stopPropagation(); @@ -315,7 +326,7 @@ export default class RoomTile2 extends React.Component { private onClickMute = ev => this.saveNotifState(ev, MUTE); private renderNotificationsMenu(isActive: boolean): React.ReactElement { - if (MatrixClientPeg.get().isGuest() || !this.showContextMenu) { + if (MatrixClientPeg.get().isGuest() || this.props.tag === DefaultTagID.Archived || !this.showContextMenu) { // the menu makes no sense in these cases so do not show one return null; } @@ -397,7 +408,20 @@ export default class RoomTile2 extends React.Component { const favouriteLabel = isFavorite ? _t("Favourited") : _t("Favourite"); let contextMenu = null; - if (this.state.generalMenuPosition) { + if (this.state.generalMenuPosition && this.props.tag === DefaultTagID.Archived) { + contextMenu = ( + +
+
+ + + {_t("Forget Room")} + +
+
+
+ ); + } else if (this.state.generalMenuPosition) { contextMenu = (
From b3c3ef594edc9f2047e4caeb23e34f2a43f877cd Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 13 Jul 2020 16:39:59 +0100 Subject: [PATCH 28/46] i18n Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/i18n/strings/en_EN.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index fb97bfa26c..c8d311f690 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1233,6 +1233,7 @@ "Favourited": "Favourited", "Favourite": "Favourite", "Leave Room": "Leave Room", + "Forget Room": "Forget Room", "Room options": "Room options", "Add a topic": "Add a topic", "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.": "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.", From 9a3744ebb25af6f0cb5af007112ba851c33ea51c Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 13 Jul 2020 09:54:15 -0600 Subject: [PATCH 29/46] Fix default sorting mechanics for new room list Fixes https://github.com/vector-im/riot-web/issues/14445 --- src/stores/room-list/RoomListStore2.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/stores/room-list/RoomListStore2.ts b/src/stores/room-list/RoomListStore2.ts index 8686a3a054..f3a77e765b 100644 --- a/src/stores/room-list/RoomListStore2.ts +++ b/src/stores/room-list/RoomListStore2.ts @@ -30,7 +30,6 @@ import { TagWatcher } from "./TagWatcher"; import RoomViewStore from "../RoomViewStore"; import { Algorithm, LIST_UPDATED_EVENT } from "./algorithms/Algorithm"; import { EffectiveMembership, getEffectiveMembership } from "./membership"; -import { ListLayout } from "./ListLayout"; import { isNullOrUndefined } from "matrix-js-sdk/src/utils"; import RoomListLayoutStore from "./RoomListLayoutStore"; import { MarkedExecution } from "../../utils/MarkedExecution"; @@ -425,7 +424,8 @@ export class RoomListStore2 extends AsyncStore { // logic must match calculateListOrder private calculateTagSorting(tagId: TagID): SortAlgorithm { - const defaultSort = SortAlgorithm.Alphabetic; + const isDefaultRecent = tagId === DefaultTagID.Invite || tagId === DefaultTagID.DM; + const defaultSort = isDefaultRecent ? SortAlgorithm.Recent : SortAlgorithm.Alphabetic; const settingAlphabetical = SettingsStore.getValue("RoomList.orderAlphabetically", null, true); const definedSort = this.getTagSorting(tagId); const storedSort = this.getStoredTagSorting(tagId); From 3060cdf9345f6ed47fe41aa78e7a79b9fe98e95a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 13 Jul 2020 17:01:50 +0100 Subject: [PATCH 30/46] Iterate PR Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/MatrixChat.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 7b439d2303..fc918521c2 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -74,6 +74,7 @@ import { } from "../../toasts/AnalyticsToast"; import {showToast as showNotificationsToast} from "../../toasts/DesktopNotificationsToast"; import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload"; +import ErrorDialog from "../views/dialogs/ErrorDialog"; /** constants for MatrixChat.state.view */ export enum Views { @@ -460,7 +461,6 @@ export default class MatrixChat extends React.PureComponent { onAction = (payload) => { // console.log(`MatrixClientPeg.onAction: ${payload.action}`); - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); // Start the onboarding process for certain actions @@ -1063,7 +1063,6 @@ export default class MatrixChat extends React.PureComponent { private leaveRoom(roomId: string) { const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const roomToLeave = MatrixClientPeg.get().getRoom(roomId); const warnings = this.leaveRoomWarnings(roomId); @@ -1128,13 +1127,12 @@ export default class MatrixChat extends React.PureComponent { } private forgetRoom(roomId: string) { - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); MatrixClientPeg.get().forget(roomId).then(() => { // Switch to another room view if we're currently viewing the historical room if (this.state.currentRoomId === roomId) { dis.dispatch({ action: "view_next_room" }); } - }, function(err) { + }).catch((err) => { const errCode = err.errcode || _td("unknown error code"); Modal.createTrackedDialog("Failed to forget room", '', ErrorDialog, { title: _t("Failed to forget room %(errCode)s", {errCode}), @@ -1391,7 +1389,6 @@ export default class MatrixChat extends React.PureComponent { return; } - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createTrackedDialog('Signed out', '', ErrorDialog, { title: _t('Signed Out'), description: _t('For security, this session has been signed out. Please sign in again.'), @@ -1461,7 +1458,6 @@ export default class MatrixChat extends React.PureComponent { } }); cli.on("crypto.warning", (type) => { - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); switch (type) { case 'CRYPTO_WARNING_OLD_VERSION_DETECTED': Modal.createTrackedDialog('Crypto migrated', '', ErrorDialog, { From 40ab3e23c6eff9921fbec197f13c3cb9c8d51d87 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 13 Jul 2020 10:09:46 -0600 Subject: [PATCH 31/46] Add a null guard on the client --- .../room-list/algorithms/tag-sorting/RecentAlgorithm.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm.ts b/src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm.ts index e7ca94ed95..154fd40b69 100644 --- a/src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm.ts +++ b/src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm.ts @@ -38,7 +38,11 @@ export class RecentAlgorithm implements IAlgorithm { // actually changed (probably needs to be done higher up?) then we could do an // insertion sort or similar on the limited set of changes. - const myUserId = MatrixClientPeg.get().getUserId(); + // TODO: Don't assume we're using the same client as the peg + let myUserId = ''; + if (MatrixClientPeg.get()) { + myUserId = MatrixClientPeg.get().getUserId(); + } const tsCache: { [roomId: string]: number } = {}; const getLastTs = (r: Room) => { @@ -68,7 +72,6 @@ export class RecentAlgorithm implements IAlgorithm { const ev = r.timeline[i]; if (!ev.getTs()) continue; // skip events that don't have timestamps (tests only?) - // TODO: Don't assume we're using the same client as the peg if (ev.getSender() === myUserId || Unread.eventTriggersUnreadCount(ev)) { return ev.getTs(); } From fd8f43e245269f16c86d73039a6b3e57608c8d68 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 13 Jul 2020 17:17:05 +0100 Subject: [PATCH 32/46] Fix room sub list header collapse/jump interactions on bottom-most sublist Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/rooms/RoomSublist2.tsx | 26 ++++++++++++--------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index 3623b8d48d..c22e6cd807 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -321,25 +321,29 @@ export default class RoomSublist2 extends React.Component { } }; - private onHeaderClick = (ev: React.MouseEvent) => { - let target = ev.target as HTMLDivElement; - if (!target.classList.contains('mx_RoomSublist2_headerText')) { - // If we don't have the headerText class, the user clicked the span in the headerText. - target = target.parentElement as HTMLDivElement; - } - - const possibleSticky = target.parentElement; + private onHeaderClick = () => { + const possibleSticky = this.headerButton.current.parentElement; const sublist = possibleSticky.parentElement.parentElement; const list = sublist.parentElement.parentElement; - // the scrollTop is capped at the height of the header in LeftPanel2 + // the scrollTop is capped at the height of the header in LeftPanel2, the top header is always sticky const isAtTop = list.scrollTop <= HEADER_HEIGHT; - const isSticky = possibleSticky.classList.contains('mx_RoomSublist2_headerContainer_sticky'); - if (isSticky && !isAtTop) { + const isAtBottom = list.scrollTop >= list.scrollHeight - list.offsetHeight; + const isStickyTop = possibleSticky.classList.contains('mx_RoomSublist2_headerContainer_stickyTop'); + const isStickyBottom = possibleSticky.classList.contains('mx_RoomSublist2_headerContainer_stickyBottom'); + + if ((isStickyBottom && !isAtBottom) || (isStickyTop && !isAtTop)) { // is sticky - jump to list sublist.scrollIntoView({behavior: 'smooth'}); } else { // on screen - toggle collapse + const isExpanded = this.state.isExpanded; this.toggleCollapsed(); + // if the bottom list is collapsed then scroll it in so it doesn't expand off screen + if (!isExpanded && isStickyBottom) { + setImmediate(() => { + sublist.scrollIntoView({behavior: 'smooth'}); + }); + } } }; From d2c7a55fa0d0cac3fcd7d68fb14588a24d0bfa30 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 13 Jul 2020 12:00:56 -0600 Subject: [PATCH 33/46] Ensure tag changes (leaving rooms) causes rooms to move between lists Fixes https://github.com/vector-im/riot-web/issues/14442 Turns out that we are so good at moving a room that when it flows through as a TIMELINE update the algorithm no-ops and does nothing, so don't do that. --- src/stores/room-list/algorithms/Algorithm.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/stores/room-list/algorithms/Algorithm.ts b/src/stores/room-list/algorithms/Algorithm.ts index 17e8283c74..a8adaed4d7 100644 --- a/src/stores/room-list/algorithms/Algorithm.ts +++ b/src/stores/room-list/algorithms/Algorithm.ts @@ -698,8 +698,8 @@ export class Algorithm extends EventEmitter { } } + let didTagChange = false; if (cause === RoomUpdateCause.PossibleTagChange) { - let didTagChange = false; const oldTags = this.roomIdsToTags[room.roomId] || []; const newTags = this.getTagsForRoom(room); const diff = arrayDiff(oldTags, newTags); @@ -713,6 +713,11 @@ export class Algorithm extends EventEmitter { if (!algorithm) throw new Error(`No algorithm for ${rmTag}`); await algorithm.handleRoomUpdate(room, RoomUpdateCause.RoomRemoved); this.cachedRooms[rmTag] = algorithm.orderedRooms; + + // Later on we won't update the filtered rooms or sticky room for removed + // tags, so do so now. + this.recalculateFilteredRoomsForTag(rmTag); + this.recalculateStickyRoom(rmTag); } for (const addTag of diff.added) { if (!window.mx_QuietRoomListLogging) { @@ -812,7 +817,7 @@ export class Algorithm extends EventEmitter { return false; } - let changed = false; + let changed = didTagChange; for (const tag of tags) { const algorithm: OrderingAlgorithm = this.algorithms[tag]; if (!algorithm) throw new Error(`No algorithm for ${tag}`); From c9e231c3eb9d8662b4e2fa551408a5a22a527b57 Mon Sep 17 00:00:00 2001 From: Jorik Schellekens Date: Mon, 13 Jul 2020 19:03:31 +0100 Subject: [PATCH 34/46] Add fad --- res/css/views/rooms/_RoomSublist2.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/res/css/views/rooms/_RoomSublist2.scss b/res/css/views/rooms/_RoomSublist2.scss index 633c33feea..20f836159b 100644 --- a/res/css/views/rooms/_RoomSublist2.scss +++ b/res/css/views/rooms/_RoomSublist2.scss @@ -196,6 +196,8 @@ limitations under the License. // as the box model should be top aligned. Happens in both FF and Chromium display: flex; flex-direction: column; + + mask-image: linear-gradient(0deg, transparent, black 3px); } .mx_RoomSublist2_resizerHandles_showNButton { From 8e982f52ff834e90464d944cf7a22131ee681936 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 13 Jul 2020 12:59:09 -0600 Subject: [PATCH 35/46] Fix extra room tiles being rendered on smaller sublists Fixes https://github.com/vector-im/riot-web/issues/14426 The issue only applies to lists which won't have a 'show less' button, as the lists with the button would have the button's height considered when determining visible tiles. For lists that were under that (1-4 rooms), the show more button wasn't being considered and thus leading to the padding being added rather than subtracted, causing an extra tile to render. By ensuring we include the padding for both show more and show less, we ensure that no extra tiles get rendered and that the cutoff semantics are still present. --- src/components/views/rooms/RoomSublist2.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index c22e6cd807..2887b7fa55 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -137,9 +137,10 @@ export default class RoomSublist2 extends React.Component { let padding = RESIZE_HANDLE_HEIGHT; // this is used for calculating the max height of the whole container, // and takes into account whether there should be room reserved for the show less button - // when fully expanded. Note that the show more button might still be shown when not fully expanded, - // but in this case it will take the space of a tile and we don't need to reserve space for it. - if (this.numTiles > this.layout.defaultVisibleTiles) { + // when fully expanded. We cannot check against the layout's defaultVisible tile count + // because there are conditions in which we need to know that the 'show more' button + // is present while well under the default tile limit. + if (this.numTiles > this.numVisibleTiles) { padding += SHOW_N_BUTTON_HEIGHT; } return padding; From a8829f09d0ed23dc33148009322cb4019148e35d Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 13 Jul 2020 13:15:44 -0600 Subject: [PATCH 36/46] Ensure RoomListStore2 gets reset when the client becomes invalidated Fixes https://github.com/vector-im/riot-web/issues/14384 We also make use of the new AsyncStore type to handle this more safely. --- src/stores/room-list/RoomListStore2.ts | 48 +++++++------------- test/components/views/rooms/RoomList-test.js | 2 +- 2 files changed, 18 insertions(+), 32 deletions(-) diff --git a/src/stores/room-list/RoomListStore2.ts b/src/stores/room-list/RoomListStore2.ts index f3a77e765b..1be2bae6be 100644 --- a/src/stores/room-list/RoomListStore2.ts +++ b/src/stores/room-list/RoomListStore2.ts @@ -33,6 +33,7 @@ import { EffectiveMembership, getEffectiveMembership } from "./membership"; import { isNullOrUndefined } from "matrix-js-sdk/src/utils"; import RoomListLayoutStore from "./RoomListLayoutStore"; import { MarkedExecution } from "../../utils/MarkedExecution"; +import { AsyncStoreWithClient } from "../AsyncStoreWithClient"; interface IState { tagsEnabled?: boolean; @@ -44,14 +45,13 @@ interface IState { */ export const LISTS_UPDATE_EVENT = "lists_update"; -export class RoomListStore2 extends AsyncStore { +export class RoomListStore2 extends AsyncStoreWithClient { /** * Set to true if you're running tests on the store. Should not be touched in * any other environment. */ public static TEST_MODE = false; - private _matrixClient: MatrixClient; private initialListsGenerated = false; private enabled = false; private algorithm = new Algorithm(); @@ -78,33 +78,30 @@ export class RoomListStore2 extends AsyncStore { return this.algorithm.getOrderedRooms(); } - public get matrixClient(): MatrixClient { - return this._matrixClient; - } - // Intended for test usage public async resetStore() { await this.reset(); this.tagWatcher = new TagWatcher(this); this.filterConditions = []; this.initialListsGenerated = false; - this._matrixClient = null; this.algorithm.off(LIST_UPDATED_EVENT, this.onAlgorithmListUpdated); this.algorithm.off(FILTER_CHANGED, this.onAlgorithmListUpdated); this.algorithm = new Algorithm(); this.algorithm.on(LIST_UPDATED_EVENT, this.onAlgorithmListUpdated); this.algorithm.on(FILTER_CHANGED, this.onAlgorithmListUpdated); + + // Reset state without causing updates as the client will have been destroyed + // and downstream code will throw NPE errors. + await this.reset(null, true); } // Public for test usage. Do not call this. - public async makeReady(client: MatrixClient) { + public async makeReady() { // TODO: Remove with https://github.com/vector-im/riot-web/issues/14367 this.checkEnabled(); if (!this.enabled) return; - this._matrixClient = client; - // 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"); @@ -161,7 +158,15 @@ export class RoomListStore2 extends AsyncStore { if (trigger) this.updateFn.trigger(); } - protected async onDispatch(payload: ActionPayload) { + protected async onReady(): Promise { + await this.makeReady(); + } + + protected async onNotReady(): Promise { + await this.resetStore(); + } + + protected async onAction(payload: ActionPayload) { // When we're running tests we can't reliably use setImmediate out of timing concerns. // As such, we use a more synchronous model. if (RoomListStore2.TEST_MODE) { @@ -175,29 +180,10 @@ export class RoomListStore2 extends AsyncStore { } protected async onDispatchAsync(payload: ActionPayload) { - if (payload.action === 'MatrixActions.sync') { - // Filter out anything that isn't the first PREPARED sync. - if (!(payload.prevState === 'PREPARED' && payload.state !== 'PREPARED')) { - return; - } - - await this.makeReady(payload.matrixClient); - - return; // no point in running the next conditions - they won't match - } - // TODO: Remove this once the RoomListStore becomes default if (!this.enabled) return; - if (payload.action === 'on_client_not_viable' || payload.action === 'on_logged_out') { - // Reset state without causing updates as the client will have been destroyed - // and downstream code will throw NPE errors. - await this.reset(null, true); - this._matrixClient = null; - this.initialListsGenerated = false; // we'll want to regenerate them - } - - // Everything below here requires a MatrixClient or some sort of logical readiness. + // Everything here requires a MatrixClient or some sort of logical readiness. const logicallyReady = this.matrixClient && this.initialListsGenerated; if (!logicallyReady) return; diff --git a/test/components/views/rooms/RoomList-test.js b/test/components/views/rooms/RoomList-test.js index e84f943708..313bd4b77e 100644 --- a/test/components/views/rooms/RoomList-test.js +++ b/test/components/views/rooms/RoomList-test.js @@ -109,7 +109,7 @@ describe('RoomList', () => { client.getRoom = (roomId) => roomMap[roomId]; // Now that everything has been set up, prepare and update the store - await RoomListStore.instance.makeReady(client); + await RoomListStore.instance.makeReady(); done(); }); From eb78b1b328a01fc7a6e2013c10a31e0feba80e6c Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 13 Jul 2020 13:18:01 -0600 Subject: [PATCH 37/46] Export the matrix client from the store --- src/stores/room-list/RoomListStore2.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/stores/room-list/RoomListStore2.ts b/src/stores/room-list/RoomListStore2.ts index 1be2bae6be..a3311ce90d 100644 --- a/src/stores/room-list/RoomListStore2.ts +++ b/src/stores/room-list/RoomListStore2.ts @@ -78,6 +78,10 @@ export class RoomListStore2 extends AsyncStoreWithClient { return this.algorithm.getOrderedRooms(); } + public get matrixClient(): MatrixClient { + return super.matrixClient; + } + // Intended for test usage public async resetStore() { await this.reset(); From 19500cf96abd23f761c5e6426da96af7329149ff Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 13 Jul 2020 13:24:02 -0600 Subject: [PATCH 38/46] Allow the tests to force a MatrixClient --- src/stores/room-list/RoomListStore2.ts | 6 +++++- test/components/views/rooms/RoomList-test.js | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/stores/room-list/RoomListStore2.ts b/src/stores/room-list/RoomListStore2.ts index a3311ce90d..d67c728bf0 100644 --- a/src/stores/room-list/RoomListStore2.ts +++ b/src/stores/room-list/RoomListStore2.ts @@ -101,7 +101,11 @@ export class RoomListStore2 extends AsyncStoreWithClient { } // Public for test usage. Do not call this. - public async makeReady() { + public async makeReady(forcedClient?: MatrixClient) { + if (forcedClient) { + super.matrixClient = forcedClient; + } + // TODO: Remove with https://github.com/vector-im/riot-web/issues/14367 this.checkEnabled(); if (!this.enabled) return; diff --git a/test/components/views/rooms/RoomList-test.js b/test/components/views/rooms/RoomList-test.js index 313bd4b77e..e84f943708 100644 --- a/test/components/views/rooms/RoomList-test.js +++ b/test/components/views/rooms/RoomList-test.js @@ -109,7 +109,7 @@ describe('RoomList', () => { client.getRoom = (roomId) => roomMap[roomId]; // Now that everything has been set up, prepare and update the store - await RoomListStore.instance.makeReady(); + await RoomListStore.instance.makeReady(client); done(); }); From 4b5faf814896445eec68de013c1fcb89a0b268a4 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 13 Jul 2020 21:24:43 +0100 Subject: [PATCH 39/46] Update top vs. bottom sticky styles separately If a sublist changes from sticky top to sticky bottom in a single run of the logic (without passing through the default state), we were leaving the previous top position set. This handles them independently to resolve this. Fixes https://github.com/vector-im/riot-web/issues/14390 Maybe helps with https://github.com/vector-im/riot-web/issues/14443 For vector-im/riot-web#13635 --- src/components/structures/LeftPanel2.tsx | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index 3c8994b1c0..58cf665430 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -187,10 +187,23 @@ export default class LeftPanel2 extends React.Component { if (header.style.top !== newTop) { header.style.top = newTop; } - } else if (style.stickyBottom) { + } else { + if (header.classList.contains("mx_RoomSublist2_headerContainer_stickyTop")) { + header.classList.remove("mx_RoomSublist2_headerContainer_stickyTop"); + } + if (header.style.top) { + header.style.removeProperty('top'); + } + } + + if (style.stickyBottom) { if (!header.classList.contains("mx_RoomSublist2_headerContainer_stickyBottom")) { header.classList.add("mx_RoomSublist2_headerContainer_stickyBottom"); } + } else { + if (header.classList.contains("mx_RoomSublist2_headerContainer_stickyBottom")) { + header.classList.remove("mx_RoomSublist2_headerContainer_stickyBottom"); + } } if (style.stickyTop || style.stickyBottom) { @@ -209,21 +222,12 @@ export default class LeftPanel2 extends React.Component { if (header.classList.contains("mx_RoomSublist2_headerContainer_sticky")) { header.classList.remove("mx_RoomSublist2_headerContainer_sticky"); } - if (header.classList.contains("mx_RoomSublist2_headerContainer_stickyTop")) { - header.classList.remove("mx_RoomSublist2_headerContainer_stickyTop"); - } - if (header.classList.contains("mx_RoomSublist2_headerContainer_stickyBottom")) { - header.classList.remove("mx_RoomSublist2_headerContainer_stickyBottom"); - } if (headerContainer.classList.contains("mx_RoomSublist2_headerContainer_hasSticky")) { headerContainer.classList.remove("mx_RoomSublist2_headerContainer_hasSticky"); } if (header.style.width) { header.style.removeProperty('width'); } - if (header.style.top) { - header.style.removeProperty('top'); - } } } From a3a1e2e01fa261698686fe8a1984b1dd5fc2197e Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 13 Jul 2020 16:38:13 -0600 Subject: [PATCH 40/46] Fix show less button occluding the last tile Fixes https://github.com/vector-im/riot-web/issues/14450 People may have to click various combinations of 'show more' and 'show less' until it fixes itself, as their layout could be a bit weird now. --- src/components/views/rooms/RoomSublist2.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index 2887b7fa55..e06bbb6ff2 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -624,11 +624,15 @@ export default class RoomSublist2 extends React.Component { const showMoreAtMinHeight = minTiles < this.numTiles; const minHeightPadding = RESIZE_HANDLE_HEIGHT + (showMoreAtMinHeight ? SHOW_N_BUTTON_HEIGHT : 0); const minTilesPx = layout.tilesToPixelsWithPadding(minTiles, minHeightPadding); - const maxTilesPx = layout.tilesToPixelsWithPadding(this.numTiles, this.padding); + let maxTilesPx = layout.tilesToPixelsWithPadding(this.numTiles, this.padding); const showMoreBtnClasses = classNames({ 'mx_RoomSublist2_showNButton': true, }); + if (this.numTiles > this.layout.defaultVisibleTiles) { + maxTilesPx += SHOW_N_BUTTON_HEIGHT; + } + // If we're hiding rooms, show a 'show more' button to the user. This button // floats above the resize handle, if we have one present. If the user has all // tiles visible, it becomes 'show less'. From 80cf2839d9c90e5e41116d56c7149d8f08143430 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 13 Jul 2020 18:46:17 -0600 Subject: [PATCH 41/46] Ensure breadcrumbs don't keep turning themselves back on Fixes https://github.com/vector-im/riot-web/issues/14452 --- src/stores/BreadcrumbsStore.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/stores/BreadcrumbsStore.ts b/src/stores/BreadcrumbsStore.ts index 48ef75cb59..5639a9104c 100644 --- a/src/stores/BreadcrumbsStore.ts +++ b/src/stores/BreadcrumbsStore.ts @@ -21,6 +21,7 @@ import { AsyncStoreWithClient } from "./AsyncStoreWithClient"; import defaultDispatcher from "../dispatcher/dispatcher"; import { arrayHasDiff } from "../utils/arrays"; import { RoomListStoreTempProxy } from "./room-list/RoomListStoreTempProxy"; +import { isNullOrUndefined } from "matrix-js-sdk/src/utils"; const MAX_ROOMS = 20; // arbitrary const AUTOJOIN_WAIT_THRESHOLD_MS = 90000; // 90s, the time we wait for an autojoined room to show up @@ -51,7 +52,11 @@ export class BreadcrumbsStore extends AsyncStoreWithClient { } public get visible(): boolean { - return this.state.enabled && this.matrixClient.getVisibleRooms().length >= 20; + return this.state.enabled && this.meetsRoomRequirement; + } + + private get meetsRoomRequirement(): boolean { + return this.matrixClient.getVisibleRooms().length >= 20; } protected async onAction(payload: ActionPayload) { @@ -99,8 +104,9 @@ export class BreadcrumbsStore extends AsyncStoreWithClient { } private onMyMembership = async (room: Room) => { - // We turn on breadcrumbs by default once the user has at least 1 room to show. - if (!this.state.enabled) { + // Only turn on breadcrumbs is the user hasn't explicitly turned it off again. + const settingValueRaw = SettingsStore.getValue("breadcrumbs", null, /*excludeDefault=*/true); + if (this.meetsRoomRequirement && isNullOrUndefined(settingValueRaw)) { await SettingsStore.setValue("breadcrumbs", null, SettingLevel.ACCOUNT, true); } }; From 917c41dfa03779b8207aa81f975b0395be645929 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 13 Jul 2020 20:08:12 -0600 Subject: [PATCH 42/46] Update sticky headers when breadcrumbs pop in or out Fixes https://github.com/vector-im/riot-web/issues/14455 --- src/components/structures/LeftPanel2.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index 58cf665430..9d836ddec3 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -111,6 +111,10 @@ export default class LeftPanel2 extends React.Component { const newVal = BreadcrumbsStore.instance.visible; if (newVal !== this.state.showBreadcrumbs) { this.setState({showBreadcrumbs: newVal}); + + // Update the sticky headers too as the breadcrumbs will be popping in or out. + if (!this.listContainerRef.current) return; // ignore: no headers to sticky + this.handleStickyHeaders(this.listContainerRef.current); } }; From bdb136e24ee6a5f2964b7b1b2780dd299e8c288e Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 13 Jul 2020 20:25:02 -0600 Subject: [PATCH 43/46] Clean up TODOs, comments, and imports in the new room list Fixes https://github.com/vector-im/riot-web/issues/14412 --- src/components/structures/LeftPanel2.tsx | 12 ------------ src/components/structures/RoomSearch.tsx | 10 ---------- src/components/structures/UserMenu.tsx | 1 + .../views/rooms/NotificationBadge.tsx | 2 -- src/components/views/rooms/RoomBreadcrumbs2.tsx | 9 --------- src/components/views/rooms/RoomList2.tsx | 10 +--------- src/components/views/rooms/RoomSublist2.tsx | 13 +------------ src/components/views/rooms/RoomTile2.tsx | 17 +---------------- src/components/views/rooms/TemporaryTile.tsx | 1 + src/stores/room-list/RoomListStore2.ts | 1 - src/stores/room-list/algorithms/Algorithm.ts | 2 -- .../list-ordering/NaturalAlgorithm.ts | 2 +- .../tag-sorting/AlphabeticAlgorithm.ts | 2 -- .../algorithms/tag-sorting/RecentAlgorithm.ts | 3 ++- 14 files changed, 8 insertions(+), 77 deletions(-) diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index 58cf665430..93270a5780 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -35,17 +35,8 @@ import RoomListStore, { LISTS_UPDATE_EVENT } from "../../stores/room-list/RoomLi import {Key} from "../../Keyboard"; import IndicatorScrollbar from "../structures/IndicatorScrollbar"; -// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14367 // TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367 -/******************************************************************* - * CAUTION * - ******************************************************************* - * This is a work in progress implementation and isn't complete or * - * even useful as a component. Please avoid using it until this * - * warning disappears. * - *******************************************************************/ - interface IProps { isMinimized: boolean; resizeNotifier: ResizeNotifier; @@ -246,7 +237,6 @@ export default class LeftPanel2 extends React.Component { } } - // TODO: Improve header reliability: https://github.com/vector-im/riot-web/issues/14232 private onScroll = (ev: React.MouseEvent) => { const list = ev.target as HTMLDivElement; this.handleStickyHeaders(list); @@ -387,8 +377,6 @@ export default class LeftPanel2 extends React.Component { onResize={this.onResize} />; - // TODO: Conference handling / calls: https://github.com/vector-im/riot-web/issues/14177 - const containerClasses = classNames({ "mx_LeftPanel2": true, "mx_LeftPanel2_hasTagPanel": !!tagPanel, diff --git a/src/components/structures/RoomSearch.tsx b/src/components/structures/RoomSearch.tsx index 231bd92ddf..517a5f2580 100644 --- a/src/components/structures/RoomSearch.tsx +++ b/src/components/structures/RoomSearch.tsx @@ -25,16 +25,6 @@ import { Key } from "../../Keyboard"; import AccessibleButton from "../views/elements/AccessibleButton"; import { Action } from "../../dispatcher/actions"; -// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14367 - -/******************************************************************* - * CAUTION * - ******************************************************************* - * This is a work in progress implementation and isn't complete or * - * even useful as a component. Please avoid using it until this * - * warning disappears. * - *******************************************************************/ - interface IProps { onQueryUpdate: (newQuery: string) => void; isMinimized: boolean; diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index a6eabe25f7..842e11b516 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -170,6 +170,7 @@ export default class UserMenu extends React.Component { ev.stopPropagation(); // TODO: Archived room view: https://github.com/vector-im/riot-web/issues/14038 + // Note: You'll need to uncomment the button too. console.log("TODO: Show archived rooms"); }; diff --git a/src/components/views/rooms/NotificationBadge.tsx b/src/components/views/rooms/NotificationBadge.tsx index 941a057927..d215df9126 100644 --- a/src/components/views/rooms/NotificationBadge.tsx +++ b/src/components/views/rooms/NotificationBadge.tsx @@ -18,8 +18,6 @@ import React from "react"; import classNames from "classnames"; import { formatMinimalBadgeCount } from "../../../utils/FormattingUtils"; import SettingsStore from "../../../settings/SettingsStore"; -import { DefaultTagID, TagID } from "../../../stores/room-list/models"; -import { readReceiptChangeIsFor } from "../../../utils/read-receipts"; import AccessibleButton from "../elements/AccessibleButton"; import { XOR } from "../../../@types/common"; import { NOTIFICATION_STATE_UPDATE, NotificationState } from "../../../stores/notifications/NotificationState"; diff --git a/src/components/views/rooms/RoomBreadcrumbs2.tsx b/src/components/views/rooms/RoomBreadcrumbs2.tsx index 7d0584ef66..619ad6da4d 100644 --- a/src/components/views/rooms/RoomBreadcrumbs2.tsx +++ b/src/components/views/rooms/RoomBreadcrumbs2.tsx @@ -27,17 +27,8 @@ import RoomListStore from "../../../stores/room-list/RoomListStore2"; import { DefaultTagID } from "../../../stores/room-list/models"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; -// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14367 // TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367 -/******************************************************************* - * CAUTION * - ******************************************************************* - * This is a work in progress implementation and isn't complete or * - * even useful as a component. Please avoid using it until this * - * warning disappears. * - *******************************************************************/ - interface IProps { } diff --git a/src/components/views/rooms/RoomList2.tsx b/src/components/views/rooms/RoomList2.tsx index 67787963a3..61f0f5c0c8 100644 --- a/src/components/views/rooms/RoomList2.tsx +++ b/src/components/views/rooms/RoomList2.tsx @@ -41,17 +41,8 @@ import { Action } from "../../../dispatcher/actions"; import { ViewRoomDeltaPayload } from "../../../dispatcher/payloads/ViewRoomDeltaPayload"; import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore"; -// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14367 // TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367 -/******************************************************************* - * CAUTION * - ******************************************************************* - * This is a work in progress implementation and isn't complete or * - * even useful as a component. Please avoid using it until this * - * warning disappears. * - *******************************************************************/ - interface IProps { onKeyDown: (ev: React.KeyboardEvent) => void; onFocus: (ev: React.FocusEvent) => void; @@ -231,6 +222,7 @@ export default class RoomList2 extends React.Component { private renderCommunityInvites(): React.ReactElement[] { // TODO: Put community invites in a more sensible place (not in the room list) + // See https://github.com/vector-im/riot-web/issues/14456 return MatrixClientPeg.get().getGroups().filter(g => { if (g.myMembership !== 'invite') return false; return !this.searchFilter || this.searchFilter.matches(g.name || ""); diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index e06bbb6ff2..252de622a1 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -17,7 +17,7 @@ limitations under the License. */ import * as React from "react"; -import {createRef, UIEventHandler} from "react"; +import {createRef} from "react"; import { Room } from "matrix-js-sdk/src/models/room"; import classNames from 'classnames'; import { RovingAccessibleButton, RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex"; @@ -48,17 +48,8 @@ import { polyfillTouchEvent } from "../../../@types/polyfill"; import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore"; import RoomListLayoutStore from "../../../stores/room-list/RoomListLayoutStore"; -// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14367 // TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367 -/******************************************************************* - * CAUTION * - ******************************************************************* - * This is a work in progress implementation and isn't complete or * - * even useful as a component. Please avoid using it until this * - * warning disappears. * - *******************************************************************/ - const SHOW_N_BUTTON_HEIGHT = 28; // As defined by CSS const RESIZE_HANDLE_HEIGHT = 4; // As defined by CSS export const HEADER_HEIGHT = 32; // As defined by CSS @@ -607,8 +598,6 @@ export default class RoomSublist2 extends React.Component { } public render(): React.ReactElement { - // TODO: Error boundary: https://github.com/vector-im/riot-web/issues/14185 - const visibleTiles = this.renderVisibleTiles(); const classes = classNames({ 'mx_RoomSublist2': true, diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index ca2f8865f9..fe6a19f2ed 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -55,17 +55,8 @@ import {ActionPayload} from "../../../dispatcher/payloads"; import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore"; import { NotificationState } from "../../../stores/notifications/NotificationState"; -// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14367 // TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367 -/******************************************************************* - * CAUTION * - ******************************************************************* - * This is a work in progress implementation and isn't complete or * - * even useful as a component. Please avoid using it until this * - * warning disappears. * - *******************************************************************/ - interface IProps { room: Room; showMessagePreview: boolean; @@ -124,7 +115,6 @@ const NotifOption: React.FC = ({active, onClick, iconClassNam export default class RoomTile2 extends React.Component { private dispatcherRef: string; private roomTileRef = createRef(); - // TODO: a11y: https://github.com/vector-im/riot-web/issues/14180 constructor(props: IProps) { super(props); @@ -310,7 +300,7 @@ export default class RoomTile2 extends React.Component { await setRoomNotifsState(this.props.room.roomId, newState); } catch (error) { // TODO: some form of error notification to the user to inform them that their state change failed. - // https://github.com/vector-im/riot-web/issues/14281 + // See https://github.com/vector-im/riot-web/issues/14281 console.error(error); } @@ -398,8 +388,6 @@ export default class RoomTile2 extends React.Component { private renderGeneralMenu(): React.ReactElement { if (!this.showContextMenu) return null; // no menu to show - // TODO: We could do with a proper invite context menu, unlike what showContextMenu suggests - const roomTags = RoomListStore.instance.getTagsForRoom(this.props.room); const isFavorite = roomTags.includes(DefaultTagID.Favourite); @@ -465,8 +453,6 @@ export default class RoomTile2 extends React.Component { } public render(): React.ReactElement { - // TODO: Invites: https://github.com/vector-im/riot-web/issues/14198 - const classes = classNames({ 'mx_RoomTile2': true, 'mx_RoomTile2_selected': this.state.selected, @@ -495,7 +481,6 @@ export default class RoomTile2 extends React.Component { ); } - // TODO: the original RoomTile uses state for the room name. Do we need to? let name = this.props.room.name; if (typeof name !== 'string') name = ''; name = name.replace(":", ":\u200b"); // add a zero-width space to allow linewrapping after the colon diff --git a/src/components/views/rooms/TemporaryTile.tsx b/src/components/views/rooms/TemporaryTile.tsx index a3ee7eb5bd..9baaba817d 100644 --- a/src/components/views/rooms/TemporaryTile.tsx +++ b/src/components/views/rooms/TemporaryTile.tsx @@ -34,6 +34,7 @@ interface IState { hover: boolean; } +// TODO: Remove with community invites in the room list: https://github.com/vector-im/riot-web/issues/14456 export default class TemporaryTile extends React.Component { constructor(props: IProps) { super(props); diff --git a/src/stores/room-list/RoomListStore2.ts b/src/stores/room-list/RoomListStore2.ts index d67c728bf0..9b17069d90 100644 --- a/src/stores/room-list/RoomListStore2.ts +++ b/src/stores/room-list/RoomListStore2.ts @@ -19,7 +19,6 @@ import { MatrixClient } from "matrix-js-sdk/src/client"; import SettingsStore from "../../settings/SettingsStore"; import { DefaultTagID, OrderedDefaultTagIDs, RoomUpdateCause, TagID } from "./models"; import TagOrderStore from "../TagOrderStore"; -import { AsyncStore } from "../AsyncStore"; import { Room } from "matrix-js-sdk/src/models/room"; import { IListOrderingMap, ITagMap, ITagSortingMap, ListAlgorithm, SortAlgorithm } from "./algorithms/models"; import { ActionPayload } from "../../dispatcher/payloads"; diff --git a/src/stores/room-list/algorithms/Algorithm.ts b/src/stores/room-list/algorithms/Algorithm.ts index a8adaed4d7..e0692bbb30 100644 --- a/src/stores/room-list/algorithms/Algorithm.ts +++ b/src/stores/room-list/algorithms/Algorithm.ts @@ -34,8 +34,6 @@ import { EffectiveMembership, getEffectiveMembership, splitRoomsByMembership } f import { OrderingAlgorithm } from "./list-ordering/OrderingAlgorithm"; import { getListAlgorithmInstance } from "./list-ordering"; -// TODO: Add locking support to avoid concurrent writes? https://github.com/vector-im/riot-web/issues/14235 - /** * Fired when the Algorithm has determined a list has been updated. */ diff --git a/src/stores/room-list/algorithms/list-ordering/NaturalAlgorithm.ts b/src/stores/room-list/algorithms/list-ordering/NaturalAlgorithm.ts index ae1a2c98f6..a1c9e4eb85 100644 --- a/src/stores/room-list/algorithms/list-ordering/NaturalAlgorithm.ts +++ b/src/stores/room-list/algorithms/list-ordering/NaturalAlgorithm.ts @@ -55,7 +55,7 @@ export class NaturalAlgorithm extends OrderingAlgorithm { } } - // 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/14457 // For example, we can skip updates to alphabetic (sometimes) and manually ordered tags this.cachedOrderedRooms = await sortRoomsWithAlgorithm(this.cachedOrderedRooms, this.tagId, this.sortingAlgorithm); diff --git a/src/stores/room-list/algorithms/tag-sorting/AlphabeticAlgorithm.ts b/src/stores/room-list/algorithms/tag-sorting/AlphabeticAlgorithm.ts index 8d74ebd11e..d909fb6288 100644 --- a/src/stores/room-list/algorithms/tag-sorting/AlphabeticAlgorithm.ts +++ b/src/stores/room-list/algorithms/tag-sorting/AlphabeticAlgorithm.ts @@ -17,8 +17,6 @@ limitations under the License. import { Room } from "matrix-js-sdk/src/models/room"; import { TagID } from "../../models"; import { IAlgorithm } from "./IAlgorithm"; -import { MatrixClientPeg } from "../../../../MatrixClientPeg"; -import * as Unread from "../../../../Unread"; /** * Sorts rooms according to the browser's determination of alphabetic. diff --git a/src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm.ts b/src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm.ts index 154fd40b69..0a735762a9 100644 --- a/src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm.ts +++ b/src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm.ts @@ -33,12 +33,13 @@ export class RecentAlgorithm implements IAlgorithm { // of the rooms to each other. // TODO: We could probably improve the sorting algorithm here by finding changes. - // See https://github.com/vector-im/riot-web/issues/14035 + // See https://github.com/vector-im/riot-web/issues/14459 // For example, if we spent a little bit of time to determine which elements have // actually changed (probably needs to be done higher up?) then we could do an // insertion sort or similar on the limited set of changes. // TODO: Don't assume we're using the same client as the peg + // See https://github.com/vector-im/riot-web/issues/14458 let myUserId = ''; if (MatrixClientPeg.get()) { myUserId = MatrixClientPeg.get().getUserId(); From 4a8a59c578e442e3cd410a2a6db0859494ed516e Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 13 Jul 2020 20:29:46 -0600 Subject: [PATCH 44/46] Make EffectiveMembership utils generic Fixes https://github.com/vector-im/riot-web/issues/14460 Just have to move them to utils. --- src/stores/notifications/RoomNotificationState.ts | 2 +- src/stores/room-list/RoomListStore2.ts | 2 +- src/stores/room-list/algorithms/Algorithm.ts | 2 +- src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm.ts | 2 +- src/{stores/room-list => utils}/membership.ts | 0 5 files changed, 4 insertions(+), 4 deletions(-) rename src/{stores/room-list => utils}/membership.ts (100%) diff --git a/src/stores/notifications/RoomNotificationState.ts b/src/stores/notifications/RoomNotificationState.ts index ab354c0e93..dc38f8bf0f 100644 --- a/src/stores/notifications/RoomNotificationState.ts +++ b/src/stores/notifications/RoomNotificationState.ts @@ -17,7 +17,7 @@ limitations under the License. import { NotificationColor } from "./NotificationColor"; import { IDestroyable } from "../../utils/IDestroyable"; import { MatrixClientPeg } from "../../MatrixClientPeg"; -import { EffectiveMembership, getEffectiveMembership } from "../room-list/membership"; +import { EffectiveMembership, getEffectiveMembership } from "../../utils/membership"; import { readReceiptChangeIsFor } from "../../utils/read-receipts"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { Room } from "matrix-js-sdk/src/models/room"; diff --git a/src/stores/room-list/RoomListStore2.ts b/src/stores/room-list/RoomListStore2.ts index d67c728bf0..80ad05cfbe 100644 --- a/src/stores/room-list/RoomListStore2.ts +++ b/src/stores/room-list/RoomListStore2.ts @@ -29,7 +29,7 @@ import { FILTER_CHANGED, IFilterCondition } from "./filters/IFilterCondition"; import { TagWatcher } from "./TagWatcher"; import RoomViewStore from "../RoomViewStore"; import { Algorithm, LIST_UPDATED_EVENT } from "./algorithms/Algorithm"; -import { EffectiveMembership, getEffectiveMembership } from "./membership"; +import { EffectiveMembership, getEffectiveMembership } from "../../utils/membership"; import { isNullOrUndefined } from "matrix-js-sdk/src/utils"; import RoomListLayoutStore from "./RoomListLayoutStore"; import { MarkedExecution } from "../../utils/MarkedExecution"; diff --git a/src/stores/room-list/algorithms/Algorithm.ts b/src/stores/room-list/algorithms/Algorithm.ts index a8adaed4d7..faa5b672d8 100644 --- a/src/stores/room-list/algorithms/Algorithm.ts +++ b/src/stores/room-list/algorithms/Algorithm.ts @@ -30,7 +30,7 @@ import { SortAlgorithm } from "./models"; import { FILTER_CHANGED, FilterPriority, IFilterCondition } from "../filters/IFilterCondition"; -import { EffectiveMembership, getEffectiveMembership, splitRoomsByMembership } from "../membership"; +import { EffectiveMembership, getEffectiveMembership, splitRoomsByMembership } from "../../../utils/membership"; import { OrderingAlgorithm } from "./list-ordering/OrderingAlgorithm"; import { getListAlgorithmInstance } from "./list-ordering"; diff --git a/src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm.ts b/src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm.ts index 154fd40b69..82a216c0aa 100644 --- a/src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm.ts +++ b/src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm.ts @@ -19,7 +19,7 @@ import { TagID } from "../../models"; import { IAlgorithm } from "./IAlgorithm"; import { MatrixClientPeg } from "../../../../MatrixClientPeg"; import * as Unread from "../../../../Unread"; -import { EffectiveMembership, getEffectiveMembership } from "../../membership"; +import { EffectiveMembership, getEffectiveMembership } from "../../../../utils/membership"; /** * Sorts rooms according to the last event's timestamp in each room that seems diff --git a/src/stores/room-list/membership.ts b/src/utils/membership.ts similarity index 100% rename from src/stores/room-list/membership.ts rename to src/utils/membership.ts From 03f94779f16d12801a951da8efd8647f61d9dcdc Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 14 Jul 2020 09:38:31 +0100 Subject: [PATCH 45/46] Fix show-all keyboard focus regression Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/rooms/RoomSublist2.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index e06bbb6ff2..d24b28bb60 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -237,10 +237,13 @@ export default class RoomSublist2 extends React.Component { }; private onShowAllClick = () => { + // read number of visible tiles before we mutate it + const numVisibleTiles = this.numVisibleTiles; const newHeight = this.layout.tilesToPixelsWithPadding(this.numTiles, this.padding); this.applyHeightChange(newHeight); this.setState({height: newHeight}, () => { - this.focusRoomTile(this.numTiles - 1); + // focus the top-most new room + this.focusRoomTile(numVisibleTiles); }); }; From a09f773edd2864c408bedd80474dcab5e62b301a Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 14 Jul 2020 12:13:04 +0100 Subject: [PATCH 46/46] Tweak sticky header hiding to avoid pop When transitioning between sublists, there can be a visibly observable jump in the positioning of list items when the header container is restored to normal size outside of sticky mode. To avoid this problem, this leaves all headers at normal size. This creates a new issue of a permanent gap at the top of the list for the first header, but this can be solved by always hiding (since it can only ever appear stuck to top). Fixes https://github.com/vector-im/riot-web/issues/14429 --- res/css/views/rooms/_RoomSublist2.scss | 15 +++++++++------ src/components/structures/LeftPanel2.tsx | 7 ------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/res/css/views/rooms/_RoomSublist2.scss b/res/css/views/rooms/_RoomSublist2.scss index 20f836159b..47de95dfc7 100644 --- a/res/css/views/rooms/_RoomSublist2.scss +++ b/res/css/views/rooms/_RoomSublist2.scss @@ -47,12 +47,6 @@ limitations under the License. padding-bottom: 8px; height: 24px; - // Hide the header container if the contained element is stickied. - // We don't use display:none as that causes the header to go away too. - &.mx_RoomSublist2_headerContainer_hasSticky { - height: 0; - } - .mx_RoomSublist2_stickable { flex: 1; max-width: 100%; @@ -180,6 +174,15 @@ limitations under the License. } } + // In the general case, we leave height of headers alone even if sticky, so + // that the sublists below them do not jump. However, that leaves a gap + // when scrolled to the top above the first sublist (whose header can only + // ever stick to top), so we force height to 0 for only that first header. + // See also https://github.com/vector-im/riot-web/issues/14429. + &:first-child .mx_RoomSublist2_headerContainer { + height: 0; + } + .mx_RoomSublist2_resizeBox { position: relative; diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index 870080c811..d41a7278d9 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -165,7 +165,6 @@ export default class LeftPanel2 extends React.Component { // layout updates. for (const header of targetStyles.keys()) { const style = targetStyles.get(header); - const headerContainer = header.parentElement; // .mx_RoomSublist2_headerContainer if (style.makeInvisible) { // we will have already removed the 'display: none', so add it back. @@ -205,9 +204,6 @@ export default class LeftPanel2 extends React.Component { if (!header.classList.contains("mx_RoomSublist2_headerContainer_sticky")) { header.classList.add("mx_RoomSublist2_headerContainer_sticky"); } - if (!headerContainer.classList.contains("mx_RoomSublist2_headerContainer_hasSticky")) { - headerContainer.classList.add("mx_RoomSublist2_headerContainer_hasSticky"); - } const newWidth = `${headerStickyWidth}px`; if (header.style.width !== newWidth) { @@ -217,9 +213,6 @@ export default class LeftPanel2 extends React.Component { if (header.classList.contains("mx_RoomSublist2_headerContainer_sticky")) { header.classList.remove("mx_RoomSublist2_headerContainer_sticky"); } - if (headerContainer.classList.contains("mx_RoomSublist2_headerContainer_hasSticky")) { - headerContainer.classList.remove("mx_RoomSublist2_headerContainer_hasSticky"); - } if (header.style.width) { header.style.removeProperty('width'); }