From 06336a88b3ca534a1a99f0ce79ebfadcaf832eef Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 17 Jul 2020 14:02:51 -0600 Subject: [PATCH 01/35] Remove setting for old room list --- src/components/structures/LoggedInView.tsx | 16 ++------ src/components/views/voip/CallPreview2.tsx | 40 +++++++------------ src/i18n/strings/en_EN.json | 1 - src/settings/Settings.js | 8 ---- src/stores/RoomListStore.js | 10 +---- src/stores/room-list/RoomListStore2.ts | 7 +--- .../room-list/RoomListStoreTempProxy.ts | 2 +- 7 files changed, 21 insertions(+), 63 deletions(-) diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index b65f176089..d4b0f7902a 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -607,7 +607,6 @@ class LoggedInView extends React.Component { }; render() { - const LeftPanel = sdk.getComponent('structures.LeftPanel'); const RoomView = sdk.getComponent('structures.RoomView'); const UserView = sdk.getComponent('structures.UserView'); const GroupView = sdk.getComponent('structures.GroupView'); @@ -661,21 +660,12 @@ class LoggedInView extends React.Component { bodyClasses += ' mx_MatrixChat_useCompactLayout'; } - let leftPanel = ( - ); - if (SettingsStore.getValue("feature_new_room_list")) { - leftPanel = ( - - ); - } return ( diff --git a/src/components/views/voip/CallPreview2.tsx b/src/components/views/voip/CallPreview2.tsx index 1f2caf5ef8..31b67a01ad 100644 --- a/src/components/views/voip/CallPreview2.tsx +++ b/src/components/views/voip/CallPreview2.tsx @@ -37,7 +37,6 @@ interface IProps { interface IState { roomId: string; activeCall: any; - newRoomListActive: boolean; } export default class CallPreview extends React.Component { @@ -51,12 +50,7 @@ export default class CallPreview extends React.Component { this.state = { roomId: RoomViewStore.getRoomId(), activeCall: CallHandler.getAnyActiveCall(), - newRoomListActive: SettingsStore.getValue("feature_new_room_list"), }; - - this.settingsWatcherRef = SettingsStore.watchSetting("feature_new_room_list", null, (name, roomId, level, valAtLevel, newVal) => this.setState({ - newRoomListActive: newVal, - })); } public componentDidMount() { @@ -102,28 +96,24 @@ export default class CallPreview extends React.Component { }; public render() { - if (this.state.newRoomListActive) { - const callForRoom = CallHandler.getCallForRoom(this.state.roomId); - const showCall = ( - this.state.activeCall && - this.state.activeCall.call_state === 'connected' && - !callForRoom + const callForRoom = CallHandler.getCallForRoom(this.state.roomId); + const showCall = ( + this.state.activeCall && + this.state.activeCall.call_state === 'connected' && + !callForRoom + ); + + if (showCall) { + return ( + ); - - if (showCall) { - return ( - - ); - } - - return ; } - return null; + return ; } } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index aae035b90b..360a29dc16 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -488,7 +488,6 @@ "Render simple counters in room header": "Render simple counters in room header", "Multiple integration managers": "Multiple integration managers", "Try out new ways to ignore people (experimental)": "Try out new ways to ignore people (experimental)", - "Use the improved room list (will refresh to apply changes)": "Use the improved room list (will refresh to apply changes)", "Support adding custom themes": "Support adding custom themes", "Enable advanced debugging for the room list": "Enable advanced debugging for the room list", "Show info about bridges in room settings": "Show info about bridges in room settings", diff --git a/src/settings/Settings.js b/src/settings/Settings.js index 81ca0382cf..a8b6310b90 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -140,14 +140,6 @@ export const SETTINGS = { supportedLevels: LEVELS_FEATURE, default: false, }, - "feature_new_room_list": { - // TODO: Remove setting: https://github.com/vector-im/riot-web/issues/14367 - // XXX: We shouldn't have non-features appear like features. - displayName: _td("Use the improved room list (will refresh to apply changes)"), - supportedLevels: LEVELS_FEATURE, - default: true, - controller: new ReloadOnChangeController(), - }, "feature_custom_themes": { isFeature: true, displayName: _td("Support adding custom themes"), diff --git a/src/stores/RoomListStore.js b/src/stores/RoomListStore.js index 1861085a27..6c18aa83ad 100644 --- a/src/stores/RoomListStore.js +++ b/src/stores/RoomListStore.js @@ -92,19 +92,12 @@ class RoomListStore extends Store { constructor() { super(dis); - this._checkDisabled(); + this.disabled = true; this._init(); this._getManualComparator = this._getManualComparator.bind(this); this._recentsComparator = this._recentsComparator.bind(this); } - _checkDisabled() { - this.disabled = SettingsStore.getValue("feature_new_room_list"); - if (this.disabled) { - console.warn("👋 legacy room list store has been disabled"); - } - } - /** * Changes the sorting algorithm used by the RoomListStore. * @param {string} algorithm The new algorithm to use. Should be one of the ALGO_* constants. @@ -196,7 +189,6 @@ class RoomListStore extends Store { break; } - this._checkDisabled(); if (this.disabled) return; // Always ensure that we set any state needed for settings here. It is possible that diff --git a/src/stores/room-list/RoomListStore2.ts b/src/stores/room-list/RoomListStore2.ts index 6f0fbb5afa..9576ae8ed6 100644 --- a/src/stores/room-list/RoomListStore2.ts +++ b/src/stores/room-list/RoomListStore2.ts @@ -52,7 +52,7 @@ export class RoomListStore2 extends AsyncStoreWithClient { public static TEST_MODE = false; private initialListsGenerated = false; - private enabled = false; + private enabled = true; private algorithm = new Algorithm(); private filterConditions: IFilterCondition[] = []; private tagWatcher = new TagWatcher(this); @@ -121,12 +121,7 @@ export class RoomListStore2 extends AsyncStoreWithClient { this.updateFn.trigger(); } - // 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"); - if (this.enabled) { - console.log("⚡ new room list store engaged"); - } if (SettingsStore.getValue("advancedRoomListLogging")) { console.warn("Advanced room list logging is enabled"); } diff --git a/src/stores/room-list/RoomListStoreTempProxy.ts b/src/stores/room-list/RoomListStoreTempProxy.ts index 2a5348ab6e..55d710004e 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.getValue("feature_new_room_list"); + return true; } public static addListener(handler: () => void): RoomListStoreTempToken { From 3c047cecfd7f269e83bf7903c1d5f48ed8cb7773 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 17 Jul 2020 14:09:05 -0600 Subject: [PATCH 02/35] Remove core structures for the old room list --- res/css/_components.scss | 9 - res/css/structures/_LeftPanel.scss | 178 ---- res/css/structures/_RoomSubList.scss | 187 ---- res/css/structures/_TopLeftMenuButton.scss | 49 - res/css/views/rooms/_InviteOnlyIcon.scss | 58 -- res/css/views/rooms/_RoomBreadcrumbs.scss | 112 --- res/css/views/rooms/_RoomDropTarget.scss | 55 -- res/css/views/rooms/_RoomList.scss | 70 -- res/css/views/rooms/_RoomTile.scss | 228 ----- res/css/views/rooms/_UserOnlineDot.scss | 23 - src/components/structures/LeftPanel.js | 305 ------- src/components/structures/RoomSubList.js | 496 ----------- .../structures/TopLeftMenuButton.js | 158 ---- .../views/create_room/CreateRoomButton.js | 44 - .../views/elements/CreateRoomButton.js | 40 - src/components/views/rooms/InviteOnlyIcon.js | 53 -- src/components/views/rooms/RoomBreadcrumbs.js | 394 -------- src/components/views/rooms/RoomDropTarget.js | 35 - src/components/views/rooms/RoomList.js | 838 ------------------ src/components/views/rooms/RoomTile.js | 565 ------------ src/components/views/rooms/UserOnlineDot.js | 48 - src/i18n/strings/en_EN.json | 25 +- 22 files changed, 9 insertions(+), 3961 deletions(-) delete mode 100644 res/css/structures/_LeftPanel.scss delete mode 100644 res/css/structures/_RoomSubList.scss delete mode 100644 res/css/structures/_TopLeftMenuButton.scss delete mode 100644 res/css/views/rooms/_InviteOnlyIcon.scss delete mode 100644 res/css/views/rooms/_RoomBreadcrumbs.scss delete mode 100644 res/css/views/rooms/_RoomDropTarget.scss delete mode 100644 res/css/views/rooms/_RoomList.scss delete mode 100644 res/css/views/rooms/_RoomTile.scss delete mode 100644 res/css/views/rooms/_UserOnlineDot.scss delete mode 100644 src/components/structures/LeftPanel.js delete mode 100644 src/components/structures/RoomSubList.js delete mode 100644 src/components/structures/TopLeftMenuButton.js delete mode 100644 src/components/views/create_room/CreateRoomButton.js delete mode 100644 src/components/views/elements/CreateRoomButton.js delete mode 100644 src/components/views/rooms/InviteOnlyIcon.js delete mode 100644 src/components/views/rooms/RoomBreadcrumbs.js delete mode 100644 src/components/views/rooms/RoomDropTarget.js delete mode 100644 src/components/views/rooms/RoomList.js delete mode 100644 src/components/views/rooms/RoomTile.js delete mode 100644 src/components/views/rooms/UserOnlineDot.js diff --git a/res/css/_components.scss b/res/css/_components.scss index 27eebf0ea9..77462ad4c1 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -11,7 +11,6 @@ @import "./structures/_GroupView.scss"; @import "./structures/_HeaderButtons.scss"; @import "./structures/_HomePage.scss"; -@import "./structures/_LeftPanel.scss"; @import "./structures/_LeftPanel2.scss"; @import "./structures/_MainSplit.scss"; @import "./structures/_MatrixChat.scss"; @@ -21,14 +20,12 @@ @import "./structures/_RoomDirectory.scss"; @import "./structures/_RoomSearch.scss"; @import "./structures/_RoomStatusBar.scss"; -@import "./structures/_RoomSubList.scss"; @import "./structures/_RoomView.scss"; @import "./structures/_ScrollPanel.scss"; @import "./structures/_SearchBox.scss"; @import "./structures/_TabbedView.scss"; @import "./structures/_TagPanel.scss"; @import "./structures/_ToastContainer.scss"; -@import "./structures/_TopLeftMenuButton.scss"; @import "./structures/_UploadBar.scss"; @import "./structures/_UserMenu.scss"; @import "./structures/_ViewSource.scss"; @@ -167,7 +164,6 @@ @import "./views/rooms/_EventTile.scss"; @import "./views/rooms/_GroupLayout.scss"; @import "./views/rooms/_IRCLayout.scss"; -@import "./views/rooms/_InviteOnlyIcon.scss"; @import "./views/rooms/_JumpToBottomButton.scss"; @import "./views/rooms/_LinkPreviewWidget.scss"; @import "./views/rooms/_MemberInfo.scss"; @@ -179,16 +175,12 @@ @import "./views/rooms/_PinnedEventsPanel.scss"; @import "./views/rooms/_PresenceLabel.scss"; @import "./views/rooms/_ReplyPreview.scss"; -@import "./views/rooms/_RoomBreadcrumbs.scss"; @import "./views/rooms/_RoomBreadcrumbs2.scss"; -@import "./views/rooms/_RoomDropTarget.scss"; @import "./views/rooms/_RoomHeader.scss"; -@import "./views/rooms/_RoomList.scss"; @import "./views/rooms/_RoomList2.scss"; @import "./views/rooms/_RoomPreviewBar.scss"; @import "./views/rooms/_RoomRecoveryReminder.scss"; @import "./views/rooms/_RoomSublist2.scss"; -@import "./views/rooms/_RoomTile.scss"; @import "./views/rooms/_RoomTile2.scss"; @import "./views/rooms/_RoomTileIcon.scss"; @import "./views/rooms/_RoomUpgradeWarningBar.scss"; @@ -196,7 +188,6 @@ @import "./views/rooms/_SendMessageComposer.scss"; @import "./views/rooms/_Stickers.scss"; @import "./views/rooms/_TopUnreadMessagesBar.scss"; -@import "./views/rooms/_UserOnlineDot.scss"; @import "./views/rooms/_WhoIsTypingTile.scss"; @import "./views/settings/_AvatarSetting.scss"; @import "./views/settings/_CrossSigningPanel.scss"; diff --git a/res/css/structures/_LeftPanel.scss b/res/css/structures/_LeftPanel.scss deleted file mode 100644 index 35d9f0e7da..0000000000 --- a/res/css/structures/_LeftPanel.scss +++ /dev/null @@ -1,178 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2018 New Vector Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -.mx_LeftPanel_container { - display: flex; - /* LeftPanel 260px */ - min-width: 260px; - max-width: 50%; - flex: 0 0 auto; -} - -.mx_LeftPanel_container.collapsed { - min-width: unset; - /* Collapsed LeftPanel 50px */ - flex: 0 0 50px; -} - -.mx_LeftPanel_container.collapsed.mx_LeftPanel_container_hasTagPanel { - /* TagPanel 70px + Collapsed LeftPanel 50px */ - flex: 0 0 120px; -} - -.mx_LeftPanel_tagPanelContainer { - flex: 0 0 70px; - height: 100%; -} - -.mx_LeftPanel_hideButton { - position: absolute; - top: 10px; - right: 0px; - padding: 8px; - cursor: pointer; -} - -.mx_LeftPanel { - flex: 1; - overflow-x: hidden; - display: flex; - flex-direction: column; - min-height: 0; -} - -.mx_LeftPanel .mx_AppTile_mini { - height: 132px; -} - -.mx_LeftPanel .mx_RoomList_scrollbar { - order: 1; - - flex: 1 1 0; - - overflow-y: auto; - z-index: 6; -} - -.mx_LeftPanel .mx_BottomLeftMenu { - order: 3; - - border-top: 1px solid $panel-divider-color; - margin-left: 16px; /* gutter */ - margin-right: 16px; /* gutter */ - flex: 0 0 60px; - z-index: 1; -} - -.mx_LeftPanel_container.collapsed .mx_BottomLeftMenu { - flex: 0 0 160px; - margin-bottom: 9px; -} - -.mx_LeftPanel .mx_BottomLeftMenu_options { - margin-top: 18px; -} - -.mx_BottomLeftMenu_options object { - pointer-events: none; -} - -.mx_BottomLeftMenu_options > div { - display: inline-block; -} - -.mx_BottomLeftMenu_options .mx_RoleButton { - margin-left: 0px; - margin-right: 10px; - height: 30px; -} - -.mx_BottomLeftMenu_options .mx_BottomLeftMenu_settings { - float: right; -} - -.mx_BottomLeftMenu_options .mx_BottomLeftMenu_settings .mx_RoleButton { - margin-right: 0px; -} - -.mx_LeftPanel_container.collapsed .mx_BottomLeftMenu_settings { - float: none; -} - -.mx_MatrixChat_useCompactLayout { - .mx_LeftPanel .mx_BottomLeftMenu { - flex: 0 0 50px; - } - - .mx_LeftPanel_container.collapsed .mx_BottomLeftMenu { - flex: 0 0 160px; - } - - .mx_LeftPanel .mx_BottomLeftMenu_options { - margin-top: 12px; - } -} - -.mx_LeftPanel_exploreAndFilterRow { - display: flex; - - .mx_SearchBox { - flex: 1 1 0; - min-width: 0; - margin: 4px 9px 1px 9px; - } -} - -.mx_LeftPanel_explore { - flex: 0 0 50%; - overflow: hidden; - transition: flex-basis 0.2s; - box-sizing: border-box; - - &.mx_LeftPanel_explore_hidden { - flex-basis: 0; - } - - .mx_AccessibleButton { - font-size: $font-14px; - margin: 4px 0 1px 9px; - padding: 9px; - padding-left: 42px; - font-weight: 600; - color: $notice-secondary-color; - position: relative; - border-radius: 4px; - - &:hover { - background-color: $primary-bg-color; - } - - &::before { - cursor: pointer; - mask: url('$(res)/img/explore.svg'); - mask-repeat: no-repeat; - mask-position: center center; - content: ""; - left: 14px; - top: 10px; - width: 16px; - height: 16px; - background-color: $notice-secondary-color; - position: absolute; - } - } -} diff --git a/res/css/structures/_RoomSubList.scss b/res/css/structures/_RoomSubList.scss deleted file mode 100644 index 2c53258b08..0000000000 --- a/res/css/structures/_RoomSubList.scss +++ /dev/null @@ -1,187 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -/* a word of explanation about the flex-shrink values employed here: - there are 3 priotized categories of screen real-estate grabbing, - each with a flex-shrink difference of 4 order of magnitude, - so they ideally wouldn't affect each other. - lowest category: .mx_RoomSubList - flex-shrink: 10000000 - distribute size of items within the same category by their size - middle category: .mx_RoomSubList.resized-sized - flex-shrink: 1000 - applied when using the resizer, will have a max-height set to it, - to limit the size - highest category: .mx_RoomSubList.resized-all - flex-shrink: 1 - small flex-shrink value (1), is only added if you can drag the resizer so far - so in practice you can only assign this category if there is enough space. -*/ - -.mx_RoomSubList { - display: flex; - flex-direction: column; -} - - -.mx_RoomSubList_nonEmpty .mx_AutoHideScrollbar_offset { - padding-bottom: 4px; -} - -.mx_RoomSubList_labelContainer { - display: flex; - flex-direction: row; - align-items: center; - flex: 0 0 auto; - margin: 0 8px; - padding: 0 8px; - height: 36px; -} - -.mx_RoomSubList_labelContainer.focus-visible:focus-within { - background-color: $roomtile-focused-bg-color; -} - -.mx_RoomSubList_label { - flex: 1; - cursor: pointer; - display: flex; - align-items: center; - padding: 0 6px; -} - -.mx_RoomSubList_label > span { - flex: 1 1 auto; - text-transform: uppercase; - color: $roomsublist-label-fg-color; - font-weight: 700; - font-size: $font-12px; - margin-left: 8px; -} - -.mx_RoomSubList_badge > div { - flex: 0 0 auto; - border-radius: $font-16px; - font-weight: 600; - font-size: $font-12px; - padding: 0 5px; - color: $roomtile-badge-fg-color; - background-color: $roomtile-name-color; - cursor: pointer; -} - -.mx_RoomSubList_addRoom, .mx_RoomSubList_badge { - margin-left: 7px; -} - -.mx_RoomSubList_addRoom { - background-color: $roomheader-addroom-bg-color; - border-radius: 10px; // 16/2 + 2 padding - height: 16px; - flex: 0 0 16px; - position: relative; - - &::before { - background-color: $roomheader-addroom-fg-color; - mask: url('$(res)/img/icons-room-add.svg'); - mask-repeat: no-repeat; - mask-position: center; - content: ''; - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - } -} - -.mx_RoomSubList_badgeHighlight > div { - color: $accent-fg-color; - background-color: $warning-color; -} - -.mx_RoomSubList_chevron { - pointer-events: none; - mask: url('$(res)/img/feather-customised/dropdown-arrow.svg'); - mask-repeat: no-repeat; - transition: transform 0.2s ease-in; - width: 10px; - height: 6px; - margin-left: 2px; - background-color: $roomsublist-label-fg-color; -} - -.mx_RoomSubList_chevronDown { - transform: rotateZ(0deg); -} - -.mx_RoomSubList_chevronUp { - transform: rotateZ(180deg); -} - -.mx_RoomSubList_chevronRight { - transform: rotateZ(-90deg); -} - -.mx_RoomSubList_scroll { - /* let rooms list grab as much space as it needs (auto), - potentially overflowing and showing a scrollbar */ - flex: 0 1 auto; - padding: 0 8px; -} - -.collapsed { - .mx_RoomSubList_scroll { - padding: 0; - } - - .mx_RoomSubList_labelContainer { - margin-right: 8px; - margin-left: 2px; - padding: 0; - } - - .mx_RoomSubList_addRoom { - margin-left: 3px; - margin-right: 10px; - } - - .mx_RoomSubList_label > span { - display: none; - } -} - -// overflow indicators -.mx_RoomSubList:not(.resized-all) > .mx_RoomSubList_scroll { - &.mx_IndicatorScrollbar_topOverflow::before { - position: sticky; - content: ""; - top: 0; - left: 0; - right: 0; - height: 8px; - z-index: 100; - display: block; - pointer-events: none; - transition: background-image 0.1s ease-in; - background: linear-gradient(to top, $panel-gradient); - } - - - &.mx_IndicatorScrollbar_topOverflow { - margin-top: -8px; - } -} diff --git a/res/css/structures/_TopLeftMenuButton.scss b/res/css/structures/_TopLeftMenuButton.scss deleted file mode 100644 index 8d2e36bcd6..0000000000 --- a/res/css/structures/_TopLeftMenuButton.scss +++ /dev/null @@ -1,49 +0,0 @@ -/* -Copyright 2018 New Vector Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -.mx_TopLeftMenuButton { - flex: 0 0 52px; - border-bottom: 1px solid $panel-divider-color; - color: $topleftmenu-color; - background-color: $primary-bg-color; - display: flex; - align-items: center; - min-width: 0; - padding: 0 4px; - overflow: hidden; -} - -.mx_TopLeftMenuButton .mx_BaseAvatar { - margin: 0 7px; -} - -.mx_TopLeftMenuButton_name { - margin: 0 7px; - font-size: $font-18px; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - font-weight: 600; -} - -.mx_TopLeftMenuButton_chevron { - margin: 0 7px; - mask: url('$(res)/img/feather-customised/dropdown-arrow.svg'); - mask-repeat: no-repeat; - width: $font-22px; - height: 6px; - background-color: $roomsublist-label-fg-color; -} diff --git a/res/css/views/rooms/_InviteOnlyIcon.scss b/res/css/views/rooms/_InviteOnlyIcon.scss deleted file mode 100644 index b71fd6348d..0000000000 --- a/res/css/views/rooms/_InviteOnlyIcon.scss +++ /dev/null @@ -1,58 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -@define-mixin mx_InviteOnlyIcon { - width: 12px; - height: 12px; - position: relative; - display: block !important; -} - -@define-mixin mx_InviteOnlyIcon_padlock { - background-color: $roomtile-name-color; - mask-image: url("$(res)/img/feather-customised/lock-solid.svg"); - mask-position: center; - mask-repeat: no-repeat; - mask-size: contain; - content: ""; - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; -} - -.mx_InviteOnlyIcon_large { - @mixin mx_InviteOnlyIcon; - margin: 0 4px; - - &::before { - @mixin mx_InviteOnlyIcon_padlock; - width: 12px; - height: 12px; - } -} - -.mx_InviteOnlyIcon_small { - @mixin mx_InviteOnlyIcon; - left: -2px; - - &::before { - @mixin mx_InviteOnlyIcon_padlock; - width: 10px; - height: 10px; - } -} diff --git a/res/css/views/rooms/_RoomBreadcrumbs.scss b/res/css/views/rooms/_RoomBreadcrumbs.scss deleted file mode 100644 index 3858d836e6..0000000000 --- a/res/css/views/rooms/_RoomBreadcrumbs.scss +++ /dev/null @@ -1,112 +0,0 @@ -/* -Copyright 2019 New Vector Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -.mx_RoomBreadcrumbs { - position: relative; - height: 42px; - padding: 8px; - padding-bottom: 0; - display: flex; - flex-direction: row; - - // repeating circles as empty placeholders - background: - radial-gradient( - circle at center, - $breadcrumb-placeholder-bg-color, - $breadcrumb-placeholder-bg-color 15px, - transparent 16px - ); - background-size: 36px; - background-position: 6px -1px; - background-repeat: repeat-x; - - - // Autohide the scrollbar - overflow-x: hidden; - &:hover { - overflow-x: visible; - } - - .mx_AutoHideScrollbar { - display: flex; - flex-direction: row; - height: 100%; - } - - .mx_RoomBreadcrumbs_crumb { - margin-left: 4px; - height: 32px; - display: inline-block; - transition: transform 0.3s, width 0.3s; - position: relative; - - .mx_RoomTile_badge { - position: absolute; - top: -3px; - right: -4px; - } - - .mx_RoomBreadcrumbs_dmIndicator { - position: absolute; - bottom: 0; - right: -4px; - } - } - - .mx_RoomBreadcrumbs_animate { - margin-left: 0; - width: 32px; - transform: scale(1); - } - - .mx_RoomBreadcrumbs_preAnimate { - width: 0; - transform: scale(0); - } - - .mx_RoomBreadcrumbs_left { - opacity: 0.5; - } - - // Note: we have to manually control the gradient and stuff, but the IndicatorScrollbar - // will deal with left/right positioning for us. Normally we'd use position:sticky on - // a few key elements, however that doesn't work in horizontal scrolling scenarios. - - .mx_IndicatorScrollbar_leftOverflowIndicator, - .mx_IndicatorScrollbar_rightOverflowIndicator { - display: none; - } - - .mx_IndicatorScrollbar_leftOverflowIndicator { - background: linear-gradient(to left, $panel-gradient); - } - - .mx_IndicatorScrollbar_rightOverflowIndicator { - background: linear-gradient(to right, $panel-gradient); - } - - &.mx_IndicatorScrollbar_leftOverflow .mx_IndicatorScrollbar_leftOverflowIndicator, - &.mx_IndicatorScrollbar_rightOverflow .mx_IndicatorScrollbar_rightOverflowIndicator { - position: absolute; - top: 0; - bottom: 0; - width: 15px; - display: block; - pointer-events: none; - z-index: 100; - } -} diff --git a/res/css/views/rooms/_RoomDropTarget.scss b/res/css/views/rooms/_RoomDropTarget.scss deleted file mode 100644 index 2e8145c2c9..0000000000 --- a/res/css/views/rooms/_RoomDropTarget.scss +++ /dev/null @@ -1,55 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -.mx_RoomDropTarget_container { - background-color: $secondary-accent-color; - padding-left: 18px; - padding-right: 18px; - padding-top: 8px; - padding-bottom: 7px; -} - -.collapsed .mx_RoomDropTarget_container { - padding-right: 10px; - padding-left: 10px; -} - -.mx_RoomDropTarget { - font-size: $font-13px; - padding-top: 5px; - padding-bottom: 5px; - border: 1px dashed $accent-color; - color: $primary-fg-color; - background-color: $droptarget-bg-color; - border-radius: 4px; -} - - -.mx_RoomDropTarget_label { - position: relative; - margin-top: 3px; - line-height: $font-21px; - z-index: 1; - text-align: center; -} - -.collapsed .mx_RoomDropTarget_avatar { - float: none; -} - -.collapsed .mx_RoomDropTarget_label { - display: none; -} diff --git a/res/css/views/rooms/_RoomList.scss b/res/css/views/rooms/_RoomList.scss deleted file mode 100644 index c23c19699d..0000000000 --- a/res/css/views/rooms/_RoomList.scss +++ /dev/null @@ -1,70 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2017 Vector Creations Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -.mx_RoomList.mx_RoomList2 { - overflow-y: auto; -} - -.mx_RoomList { - /* take up remaining space below TopLeftMenu */ - flex: 1; - min-height: 0; - overflow-y: hidden; -} - -.mx_RoomList .mx_ResizeHandle { - // needed so the z-index takes effect - position: relative; -} - -/* hide resize handles next to collapsed / empty sublists */ -.mx_RoomList .mx_RoomSubList:not(.mx_RoomSubList_nonEmpty) + .mx_ResizeHandle { - display: none; -} - -.mx_RoomList_expandButton { - margin-left: 8px; - cursor: pointer; - padding-left: 12px; - padding-right: 12px; -} - -.mx_RoomList_emptySubListTip_container { - padding-left: 18px; - padding-right: 18px; - padding-top: 8px; - padding-bottom: 7px; -} - -.mx_RoomList_emptySubListTip { - font-size: $font-13px; - padding: 5px; - border: 1px dashed $accent-color; - color: $primary-fg-color; - background-color: $droptarget-bg-color; - border-radius: 4px; - line-height: $font-16px; -} - -.mx_RoomList_emptySubListTip .mx_RoleButton { - vertical-align: -2px; -} - -.mx_RoomList_headerButtons { - position: absolute; - right: 60px; -} diff --git a/res/css/views/rooms/_RoomTile.scss b/res/css/views/rooms/_RoomTile.scss deleted file mode 100644 index 7f93da0bbf..0000000000 --- a/res/css/views/rooms/_RoomTile.scss +++ /dev/null @@ -1,228 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -.mx_RoomTile { - display: flex; - flex-direction: row; - align-items: center; - cursor: pointer; - height: 34px; - margin: 0; - padding: 0 8px 0 10px; - position: relative; - - .mx_RoomTile_menuButton { - display: none; - flex: 0 0 16px; - height: 16px; - background-image: url('$(res)/img/icon_context.svg'); - background-repeat: no-repeat; - background-position: center; - } - - .mx_UserOnlineDot { - display: block; - margin-right: 5px; - } -} - -.mx_RoomTile:focus { - filter: none !important; - background-color: $roomtile-focused-bg-color; -} - -.mx_RoomTile_tooltip { - display: inline-block; - position: relative; - top: -54px; - left: -12px; -} - -.mx_RoomTile_nameContainer { - display: flex; - align-items: center; - flex: 1; - vertical-align: middle; - min-width: 0; -} - -.mx_RoomTile_labelContainer { - display: flex; - flex-direction: column; - flex: 1; - min-width: 0; -} - -.mx_RoomTile_subtext { - display: inline-block; - font-size: $font-11px; - padding: 0 0 0 7px; - margin: 0; - overflow: hidden; - white-space: nowrap; - text-overflow: clip; - position: relative; - bottom: 4px; -} - -.mx_RoomTile_avatar_container { - position: relative; - display: flex; -} - -.mx_RoomTile_avatar { - flex: 0; - padding: 4px; - width: 24px; - vertical-align: middle; -} - -.mx_RoomTile_hasSubtext .mx_RoomTile_avatar { - padding-top: 0; - vertical-align: super; -} - -.mx_RoomTile_dm { - display: block; - position: absolute; - bottom: 0; - right: -5px; - z-index: 2; -} - -// Note we match .mx_E2EIcon to make sure this matches more tightly than just -// .mx_E2EIcon on its own -.mx_RoomTile_e2eIcon.mx_E2EIcon { - height: 14px; - width: 14px; - display: block; - position: absolute; - bottom: -2px; - right: -5px; - z-index: 1; - margin: 0; -} - -.mx_RoomTile_name { - font-size: $font-14px; - padding: 0 4px; - color: $roomtile-name-color; - white-space: nowrap; - overflow-x: hidden; - text-overflow: ellipsis; -} - -.mx_RoomTile_badge { - flex: 0 1 content; - border-radius: 0.8em; - padding: 0 0.4em; - color: $roomtile-badge-fg-color; - font-weight: 600; - font-size: $font-12px; -} - -.collapsed { - .mx_RoomTile { - margin: 0 6px; - padding: 0 2px; - position: relative; - justify-content: center; - } - - .mx_RoomTile_name { - display: none; - } - - .mx_RoomTile_badge { - position: absolute; - right: 6px; - top: 0px; - border-radius: 16px; - z-index: 3; - border: 0.18em solid $secondary-accent-color; - } - - .mx_RoomTile_menuButton { - display: none; // no design for this for now - } - .mx_UserOnlineDot { - display: none; // no design for this for now - } -} - -// toggle menuButton and badge on menu displayed -.mx_RoomTile_menuDisplayed, -// or on keyboard focus of room tile -.mx_LeftPanel_container:not(.collapsed) .mx_RoomTile:focus-within, -// or on pointer hover -.mx_LeftPanel_container:not(.collapsed) .mx_RoomTile:hover { - .mx_RoomTile_menuButton { - display: block; - } - .mx_UserOnlineDot { - display: none; - } -} - -.mx_RoomTile_unreadNotify .mx_RoomTile_badge, -.mx_RoomTile_badge.mx_RoomTile_badgeUnread { - background-color: $roomtile-name-color; -} - -.mx_RoomTile_highlight .mx_RoomTile_badge, -.mx_RoomTile_badge.mx_RoomTile_badgeRed { - color: $accent-fg-color; - background-color: $warning-color; -} - -.mx_RoomTile_unread, .mx_RoomTile_highlight { - .mx_RoomTile_name { - font-weight: 600; - color: $roomtile-selected-color; - } -} - -.mx_RoomTile_selected { - border-radius: 4px; - background-color: $roomtile-selected-bg-color; -} - -.mx_DNDRoomTile { - transform: none; - transition: transform 0.2s; -} - -.mx_DNDRoomTile_dragging { - transform: scale(1.05, 1.05); -} - -.mx_RoomTile_arrow { - position: absolute; - right: 0px; -} - -.mx_RoomTile.mx_RoomTile_transparent { - background-color: transparent; -} - -.mx_RoomTile.mx_RoomTile_transparent:focus { - background-color: $roomtile-transparent-focused-color; -} - -.mx_GroupInviteTile .mx_RoomTile_name { - flex: 1; -} diff --git a/res/css/views/rooms/_UserOnlineDot.scss b/res/css/views/rooms/_UserOnlineDot.scss deleted file mode 100644 index f9da8648ed..0000000000 --- a/res/css/views/rooms/_UserOnlineDot.scss +++ /dev/null @@ -1,23 +0,0 @@ -/* -Copyright 2019 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -.mx_UserOnlineDot { - border-radius: 50%; - background-color: $accent-color; - height: 6px; - width: 6px; - display: inline-block; -} diff --git a/src/components/structures/LeftPanel.js b/src/components/structures/LeftPanel.js deleted file mode 100644 index bae69b5631..0000000000 --- a/src/components/structures/LeftPanel.js +++ /dev/null @@ -1,305 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import createReactClass from 'create-react-class'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; -import { Key } from '../../Keyboard'; -import * as sdk from '../../index'; -import dis from '../../dispatcher/dispatcher'; -import * as VectorConferenceHandler from '../../VectorConferenceHandler'; -import SettingsStore from '../../settings/SettingsStore'; -import {_t} from "../../languageHandler"; -import Analytics from "../../Analytics"; -import {Action} from "../../dispatcher/actions"; - - -const LeftPanel = createReactClass({ - displayName: 'LeftPanel', - - // NB. If you add props, don't forget to update - // shouldComponentUpdate! - propTypes: { - collapsed: PropTypes.bool.isRequired, - }, - - getInitialState: function() { - return { - searchFilter: '', - breadcrumbs: false, - }; - }, - - // TODO: [REACT-WARNING] Move this to constructor - UNSAFE_componentWillMount: function() { - this.focusedElement = null; - - this._breadcrumbsWatcherRef = SettingsStore.watchSetting( - "breadcrumbs", null, this._onBreadcrumbsChanged); - this._tagPanelWatcherRef = SettingsStore.watchSetting( - "TagPanel.enableTagPanel", null, () => this.forceUpdate()); - - const useBreadcrumbs = !!SettingsStore.getValue("breadcrumbs"); - Analytics.setBreadcrumbs(useBreadcrumbs); - this.setState({breadcrumbs: useBreadcrumbs}); - }, - - componentWillUnmount: function() { - SettingsStore.unwatchSetting(this._breadcrumbsWatcherRef); - SettingsStore.unwatchSetting(this._tagPanelWatcherRef); - }, - - shouldComponentUpdate: function(nextProps, nextState) { - // MatrixChat will update whenever the user switches - // rooms, but propagating this change all the way down - // the react tree is quite slow, so we cut this off - // here. The RoomTiles listen for the room change - // events themselves to know when to update. - // We just need to update if any of these things change. - if ( - this.props.collapsed !== nextProps.collapsed || - this.props.disabled !== nextProps.disabled - ) { - return true; - } - - if (this.state.searchFilter !== nextState.searchFilter) { - return true; - } - if (this.state.searchExpanded !== nextState.searchExpanded) { - return true; - } - - return false; - }, - - componentDidUpdate(prevProps, prevState) { - if (prevState.breadcrumbs !== this.state.breadcrumbs) { - Analytics.setBreadcrumbs(this.state.breadcrumbs); - } - }, - - _onBreadcrumbsChanged: function(settingName, roomId, level, valueAtLevel, value) { - // Features are only possible at a single level, so we can get away with using valueAtLevel. - // The SettingsStore runs on the same tick as the update, so `value` will be wrong. - this.setState({breadcrumbs: valueAtLevel}); - - // For some reason the setState doesn't trigger a render of the component, so force one. - // Probably has to do with the change happening outside of a change detector cycle. - this.forceUpdate(); - }, - - _onFocus: function(ev) { - this.focusedElement = ev.target; - }, - - _onBlur: function(ev) { - this.focusedElement = null; - }, - - _onFilterKeyDown: function(ev) { - if (!this.focusedElement) return; - - switch (ev.key) { - // On enter of rooms filter select and activate first room if such one exists - case Key.ENTER: { - const firstRoom = ev.target.closest(".mx_LeftPanel").querySelector(".mx_RoomTile"); - if (firstRoom) { - firstRoom.click(); - } - break; - } - } - }, - - _onKeyDown: function(ev) { - if (!this.focusedElement) return; - - switch (ev.key) { - case Key.ARROW_UP: - this._onMoveFocus(ev, true, true); - break; - case Key.ARROW_DOWN: - this._onMoveFocus(ev, false, true); - break; - } - }, - - _onMoveFocus: function(ev, up, trap) { - let element = this.focusedElement; - - // unclear why this isn't needed - // var descending = (up == this.focusDirection) ? this.focusDescending : !this.focusDescending; - // this.focusDirection = up; - - let descending = false; // are we currently descending or ascending through the DOM tree? - let classes; - - do { - const child = up ? element.lastElementChild : element.firstElementChild; - const sibling = up ? element.previousElementSibling : element.nextElementSibling; - - if (descending) { - if (child) { - element = child; - } else if (sibling) { - element = sibling; - } else { - descending = false; - element = element.parentElement; - } - } else { - if (sibling) { - element = sibling; - descending = true; - } else { - element = element.parentElement; - } - } - - if (element) { - classes = element.classList; - } - } while (element && !( - classes.contains("mx_RoomTile") || - classes.contains("mx_RoomSubList_label") || - classes.contains("mx_LeftPanel_filterRooms"))); - - if (element) { - ev.stopPropagation(); - ev.preventDefault(); - element.focus(); - this.focusedElement = element; - } else if (trap) { - // if navigation is via up/down arrow-keys, trap in the widget so it doesn't send to composer - ev.stopPropagation(); - ev.preventDefault(); - } - }, - - onSearch: function(term) { - this.setState({ searchFilter: term }); - }, - - onSearchCleared: function(source) { - if (source === "keyboard") { - dis.fire(Action.FocusComposer); - } - this.setState({searchExpanded: false}); - }, - - collectRoomList: function(ref) { - this._roomList = ref; - }, - - _onSearchFocus: function() { - this.setState({searchExpanded: true}); - }, - - _onSearchBlur: function(event) { - if (event.target.value.length === 0) { - this.setState({searchExpanded: false}); - } - }, - - render: function() { - const RoomList = sdk.getComponent('rooms.RoomList'); - const RoomBreadcrumbs = sdk.getComponent('rooms.RoomBreadcrumbs'); - const TagPanel = sdk.getComponent('structures.TagPanel'); - const CustomRoomTagPanel = sdk.getComponent('structures.CustomRoomTagPanel'); - const TopLeftMenuButton = sdk.getComponent('structures.TopLeftMenuButton'); - const SearchBox = sdk.getComponent('structures.SearchBox'); - const CallPreview = sdk.getComponent('voip.CallPreview'); - const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); - - const tagPanelEnabled = SettingsStore.getValue("TagPanel.enableTagPanel"); - let tagPanelContainer; - - const isCustomTagsEnabled = SettingsStore.isFeatureEnabled("feature_custom_tags"); - - if (tagPanelEnabled) { - tagPanelContainer = (
- - { isCustomTagsEnabled ? : undefined } -
); - } - - const containerClasses = classNames( - "mx_LeftPanel_container", "mx_fadable", - { - "collapsed": this.props.collapsed, - "mx_LeftPanel_container_hasTagPanel": tagPanelEnabled, - "mx_fadable_faded": this.props.disabled, - }, - ); - - let exploreButton; - if (!this.props.collapsed) { - exploreButton = ( -
- dis.fire(Action.ViewRoomDirectory)}>{_t("Explore")} -
- ); - } - - const searchBox = (); - - let breadcrumbs; - if (this.state.breadcrumbs) { - breadcrumbs = (); - } - - const roomList = ; - - return ( -
- { tagPanelContainer } - -
- ); - }, -}); - -export default LeftPanel; diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js deleted file mode 100644 index 090f3de22a..0000000000 --- a/src/components/structures/RoomSubList.js +++ /dev/null @@ -1,496 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2017 Vector Creations Ltd -Copyright 2018, 2019 New Vector Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React, {createRef} from 'react'; -import classNames from 'classnames'; -import * as sdk from '../../index'; -import dis from '../../dispatcher/dispatcher'; -import * as Unread from '../../Unread'; -import * as RoomNotifs from '../../RoomNotifs'; -import * as FormattingUtils from '../../utils/FormattingUtils'; -import IndicatorScrollbar from './IndicatorScrollbar'; -import {Key} from '../../Keyboard'; -import { Group } from 'matrix-js-sdk'; -import PropTypes from 'prop-types'; -import RoomTile from "../views/rooms/RoomTile"; -import LazyRenderList from "../views/elements/LazyRenderList"; -import {_t} from "../../languageHandler"; -import {RovingTabIndexWrapper} from "../../accessibility/RovingTabIndex"; -import {toPx} from "../../utils/units"; - -// turn this on for drop & drag console debugging galore -const debug = false; - -class RoomTileErrorBoundary extends React.PureComponent { - constructor(props) { - super(props); - - this.state = { - error: null, - }; - } - - static getDerivedStateFromError(error) { - // Side effects are not permitted here, so we only update the state so - // that the next render shows an error message. - return { error }; - } - - componentDidCatch(error, { componentStack }) { - // Browser consoles are better at formatting output when native errors are passed - // in their own `console.error` invocation. - console.error(error); - console.error( - "The above error occured while React was rendering the following components:", - componentStack, - ); - } - - render() { - if (this.state.error) { - return (
- {this.props.roomId} -
); - } else { - return this.props.children; - } - } -} - -export default class RoomSubList extends React.PureComponent { - static displayName = 'RoomSubList'; - static debug = debug; - - static propTypes = { - list: PropTypes.arrayOf(PropTypes.object).isRequired, - label: PropTypes.string.isRequired, - tagName: PropTypes.string, - addRoomLabel: PropTypes.string, - - // passed through to RoomTile and used to highlight room with `!` regardless of notifications count - isInvite: PropTypes.bool, - - startAsHidden: PropTypes.bool, - showSpinner: PropTypes.bool, // true to show a spinner if 0 elements when expanded - collapsed: PropTypes.bool.isRequired, // is LeftPanel collapsed? - onHeaderClick: PropTypes.func, - incomingCall: PropTypes.object, - extraTiles: PropTypes.arrayOf(PropTypes.node), // extra elements added beneath tiles - forceExpand: PropTypes.bool, - }; - - static defaultProps = { - onHeaderClick: function() { - }, // NOP - extraTiles: [], - isInvite: false, - }; - - static getDerivedStateFromProps(props, state) { - return { - listLength: props.list.length, - scrollTop: props.list.length === state.listLength ? state.scrollTop : 0, - }; - } - - constructor(props) { - super(props); - - this.state = { - hidden: this.props.startAsHidden || false, - // some values to get LazyRenderList starting - scrollerHeight: 800, - scrollTop: 0, - // React 16's getDerivedStateFromProps(props, state) doesn't give the previous props so - // we have to store the length of the list here so we can see if it's changed or not... - listLength: null, - }; - - this._header = createRef(); - this._subList = createRef(); - this._scroller = createRef(); - this._headerButton = createRef(); - } - - componentDidMount() { - this.dispatcherRef = dis.register(this.onAction); - } - - componentWillUnmount() { - dis.unregister(this.dispatcherRef); - } - - // The header is collapsible if it is hidden or not stuck - // The dataset elements are added in the RoomList _initAndPositionStickyHeaders method - isCollapsibleOnClick() { - const stuck = this._header.current.dataset.stuck; - if (!this.props.forceExpand && (this.state.hidden || stuck === undefined || stuck === "none")) { - return true; - } else { - return false; - } - } - - onAction = (payload) => { - switch (payload.action) { - case 'on_room_read': - // XXX: Previously RoomList would forceUpdate whenever on_room_read is dispatched, - // but this is no longer true, so we must do it here (and can apply the small - // optimisation of checking that we care about the room being read). - // - // Ultimately we need to transition to a state pushing flow where something - // explicitly notifies the components concerned that the notif count for a room - // has change (e.g. a Flux store). - if (this.props.list.some((r) => r.roomId === payload.roomId)) { - this.forceUpdate(); - } - break; - - case 'view_room': - if (this.state.hidden && !this.props.forceExpand && payload.show_room_tile && - this.props.list.some((r) => r.roomId === payload.room_id) - ) { - this.toggle(); - } - } - }; - - toggle = () => { - if (this.isCollapsibleOnClick()) { - // The header isCollapsible, so the click is to be interpreted as collapse and truncation logic - const isHidden = !this.state.hidden; - this.setState({hidden: isHidden}, () => { - this.props.onHeaderClick(isHidden); - }); - } else { - // The header is stuck, so the click is to be interpreted as a scroll to the header - this.props.onHeaderClick(this.state.hidden, this._header.current.dataset.originalPosition); - } - }; - - onClick = (ev) => { - this.toggle(); - }; - - onHeaderKeyDown = (ev) => { - switch (ev.key) { - case Key.ARROW_LEFT: - // On ARROW_LEFT collapse the room sublist - if (!this.state.hidden && !this.props.forceExpand) { - this.onClick(); - } - ev.stopPropagation(); - break; - case Key.ARROW_RIGHT: { - ev.stopPropagation(); - if (this.state.hidden && !this.props.forceExpand) { - // sublist is collapsed, expand it - this.onClick(); - } else if (!this.props.forceExpand) { - // sublist is expanded, go to first room - const element = this._subList.current && this._subList.current.querySelector(".mx_RoomTile"); - if (element) { - element.focus(); - } - } - break; - } - } - }; - - onKeyDown = (ev) => { - switch (ev.key) { - // On ARROW_LEFT go to the sublist header - case Key.ARROW_LEFT: - ev.stopPropagation(); - this._headerButton.current.focus(); - break; - // Consume ARROW_RIGHT so it doesn't cause focus to get sent to composer - case Key.ARROW_RIGHT: - ev.stopPropagation(); - } - }; - - onRoomTileClick = (roomId, ev) => { - dis.dispatch({ - action: 'view_room', - show_room_tile: true, // to make sure the room gets scrolled into view - room_id: roomId, - clear_search: (ev && (ev.key === Key.ENTER || ev.key === Key.SPACE)), - }); - }; - - _updateSubListCount = () => { - // Force an update by setting the state to the current state - // Doing it this way rather than using forceUpdate(), so that the shouldComponentUpdate() - // method is honoured - this.setState(this.state); - }; - - makeRoomTile = (room) => { - return 0} - notificationCount={RoomNotifs.getUnreadNotificationCount(room)} - isInvite={this.props.isInvite} - refreshSubList={this._updateSubListCount} - incomingCall={null} - onClick={this.onRoomTileClick} - />; - }; - - _onNotifBadgeClick = (e) => { - // prevent the roomsublist collapsing - e.preventDefault(); - e.stopPropagation(); - const room = this.props.list.find(room => RoomNotifs.getRoomHasBadge(room)); - if (room) { - dis.dispatch({ - action: 'view_room', - room_id: room.roomId, - }); - } - }; - - _onInviteBadgeClick = (e) => { - // prevent the roomsublist collapsing - e.preventDefault(); - e.stopPropagation(); - // switch to first room in sortedList as that'll be the top of the list for the user - if (this.props.list && this.props.list.length > 0) { - dis.dispatch({ - action: 'view_room', - room_id: this.props.list[0].roomId, - }); - } else if (this.props.extraTiles && this.props.extraTiles.length > 0) { - // Group Invites are different in that they are all extra tiles and not rooms - // XXX: this is a horrible special case because Group Invite sublist is a hack - if (this.props.extraTiles[0].props && this.props.extraTiles[0].props.group instanceof Group) { - dis.dispatch({ - action: 'view_group', - group_id: this.props.extraTiles[0].props.group.groupId, - }); - } - } - }; - - onAddRoom = (e) => { - e.stopPropagation(); - if (this.props.onAddRoom) this.props.onAddRoom(); - }; - - _getHeaderJsx(isCollapsed) { - const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); - const AccessibleTooltipButton = sdk.getComponent('elements.AccessibleTooltipButton'); - const subListNotifications = !this.props.isInvite ? - RoomNotifs.aggregateNotificationCount(this.props.list) : - {count: 0, highlight: true}; - const subListNotifCount = subListNotifications.count; - const subListNotifHighlight = subListNotifications.highlight; - - // When collapsed, allow a long hover on the header to show user - // the full tag name and room count - let title; - if (this.props.collapsed) { - title = this.props.label; - } - - let incomingCall; - if (this.props.incomingCall) { - // We can assume that if we have an incoming call then it is for this list - const IncomingCallBox = sdk.getComponent("voip.IncomingCallBox"); - incomingCall = - ; - } - - const len = this.props.list.length + this.props.extraTiles.length; - let chevron; - if (len) { - const chevronClasses = classNames({ - 'mx_RoomSubList_chevron': true, - 'mx_RoomSubList_chevronRight': isCollapsed, - 'mx_RoomSubList_chevronDown': !isCollapsed, - }); - chevron = (
); - } - - return - {({onFocus, isActive, ref}) => { - const tabIndex = isActive ? 0 : -1; - - let badge; - if (!this.props.collapsed) { - const badgeClasses = classNames({ - 'mx_RoomSubList_badge': true, - 'mx_RoomSubList_badgeHighlight': subListNotifHighlight, - }); - // Wrap the contents in a div and apply styles to the child div so that the browser default outline works - if (subListNotifCount > 0) { - badge = ( - -
- { FormattingUtils.formatCount(subListNotifCount) } -
-
- ); - } else if (this.props.isInvite && this.props.list.length) { - // no notifications but highlight anyway because this is an invite badge - badge = ( - -
- { this.props.list.length } -
-
- ); - } - } - - let addRoomButton; - if (this.props.onAddRoom) { - addRoomButton = ( - - ); - } - - return ( -
- - { chevron } - {this.props.label} - { incomingCall } - - { badge } - { addRoomButton } -
- ); - } } -
; - } - - checkOverflow = () => { - if (this._scroller.current) { - this._scroller.current.checkOverflow(); - } - }; - - setHeight = (height) => { - if (this._subList.current) { - this._subList.current.style.height = toPx(height); - } - this._updateLazyRenderHeight(height); - }; - - _updateLazyRenderHeight(height) { - this.setState({scrollerHeight: height}); - } - - _onScroll = () => { - this.setState({scrollTop: this._scroller.current.getScrollTop()}); - }; - - _canUseLazyListRendering() { - // for now disable lazy rendering as they are already rendered tiles - // not rooms like props.list we pass to LazyRenderList - return !this.props.extraTiles || !this.props.extraTiles.length; - } - - render() { - const len = this.props.list.length + this.props.extraTiles.length; - const isCollapsed = this.state.hidden && !this.props.forceExpand; - - const subListClasses = classNames({ - "mx_RoomSubList": true, - "mx_RoomSubList_hidden": len && isCollapsed, - "mx_RoomSubList_nonEmpty": len && !isCollapsed, - }); - - let content; - if (len) { - if (isCollapsed) { - // no body - } else if (this._canUseLazyListRendering()) { - content = ( - - - - ); - } else { - const roomTiles = this.props.list.map(r => this.makeRoomTile(r)); - const tiles = roomTiles.concat(this.props.extraTiles); - content = ( - - { tiles } - - ); - } - } else { - if (this.props.showSpinner && !isCollapsed) { - const Loader = sdk.getComponent("elements.Spinner"); - content = ; - } - } - - return ( -
- { this._getHeaderJsx(isCollapsed) } - { content } -
- ); - } -} diff --git a/src/components/structures/TopLeftMenuButton.js b/src/components/structures/TopLeftMenuButton.js deleted file mode 100644 index 71e7e61406..0000000000 --- a/src/components/structures/TopLeftMenuButton.js +++ /dev/null @@ -1,158 +0,0 @@ -/* -Copyright 2018 New Vector Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import PropTypes from 'prop-types'; -import TopLeftMenu from '../views/context_menus/TopLeftMenu'; -import BaseAvatar from '../views/avatars/BaseAvatar'; -import {MatrixClientPeg} from '../../MatrixClientPeg'; -import * as Avatar from '../../Avatar'; -import { _t } from '../../languageHandler'; -import dis from "../../dispatcher/dispatcher"; -import {ContextMenu, ContextMenuButton} from "./ContextMenu"; -import {Action} from "../../dispatcher/actions"; - -const AVATAR_SIZE = 28; - -export default class TopLeftMenuButton extends React.Component { - static propTypes = { - collapsed: PropTypes.bool.isRequired, - }; - - static displayName = 'TopLeftMenuButton'; - - constructor() { - super(); - this.state = { - menuDisplayed: false, - profileInfo: null, - }; - } - - async _getProfileInfo() { - const cli = MatrixClientPeg.get(); - const userId = cli.getUserId(); - const profileInfo = await cli.getProfileInfo(userId); - const avatarUrl = Avatar.avatarUrlForUser( - {avatarUrl: profileInfo.avatar_url}, - AVATAR_SIZE, AVATAR_SIZE, "crop"); - - return { - userId, - name: profileInfo.displayname, - avatarUrl, - }; - } - - async componentDidMount() { - this._dispatcherRef = dis.register(this.onAction); - - try { - const profileInfo = await this._getProfileInfo(); - this.setState({profileInfo}); - } catch (ex) { - console.log("could not fetch profile"); - console.error(ex); - } - } - - componentWillUnmount() { - dis.unregister(this._dispatcherRef); - } - - onAction = (payload) => { - // For accessibility - if (payload.action === Action.ToggleUserMenu) { - if (this._buttonRef) this._buttonRef.click(); - } - }; - - _getDisplayName() { - if (MatrixClientPeg.get().isGuest()) { - return _t("Guest"); - } else if (this.state.profileInfo) { - return this.state.profileInfo.name; - } else { - return MatrixClientPeg.get().getUserId(); - } - } - - openMenu = (e) => { - e.preventDefault(); - e.stopPropagation(); - this.setState({ menuDisplayed: true }); - }; - - closeMenu = () => { - this.setState({ - menuDisplayed: false, - }); - }; - - render() { - const cli = MatrixClientPeg.get().getUserId(); - - const name = this._getDisplayName(); - let nameElement; - let chevronElement; - if (!this.props.collapsed) { - nameElement =
- { name } -
; - chevronElement = ; - } - - let contextMenu; - if (this.state.menuDisplayed) { - const elementRect = this._buttonRef.getBoundingClientRect(); - - contextMenu = ( - - - - ); - } - - return - this._buttonRef = r} - label={_t("Your profile")} - isExpanded={this.state.menuDisplayed} - > - - { nameElement } - { chevronElement } - - - { contextMenu } - ; - } -} diff --git a/src/components/views/create_room/CreateRoomButton.js b/src/components/views/create_room/CreateRoomButton.js deleted file mode 100644 index adf3972eff..0000000000 --- a/src/components/views/create_room/CreateRoomButton.js +++ /dev/null @@ -1,44 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; -import { _t } from '../../../languageHandler'; - -export default createReactClass({ - displayName: 'CreateRoomButton', - propTypes: { - onCreateRoom: PropTypes.func, - }, - - getDefaultProps: function() { - return { - onCreateRoom: function() {}, - }; - }, - - onClick: function() { - this.props.onCreateRoom(); - }, - - render: function() { - return ( - - ); - }, -}); diff --git a/src/components/views/elements/CreateRoomButton.js b/src/components/views/elements/CreateRoomButton.js deleted file mode 100644 index 1410bdabdb..0000000000 --- a/src/components/views/elements/CreateRoomButton.js +++ /dev/null @@ -1,40 +0,0 @@ -/* -Copyright 2017 Vector Creations Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import * as sdk from '../../../index'; -import PropTypes from 'prop-types'; -import { _t } from '../../../languageHandler'; - -const CreateRoomButton = function(props) { - const ActionButton = sdk.getComponent('elements.ActionButton'); - return ( - - ); -}; - -CreateRoomButton.propTypes = { - size: PropTypes.string, - tooltip: PropTypes.bool, -}; - -export default CreateRoomButton; diff --git a/src/components/views/rooms/InviteOnlyIcon.js b/src/components/views/rooms/InviteOnlyIcon.js deleted file mode 100644 index b02f9843d9..0000000000 --- a/src/components/views/rooms/InviteOnlyIcon.js +++ /dev/null @@ -1,53 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import { _t } from '../../../languageHandler'; -import * as sdk from '../../../index'; - -export default class InviteOnlyIcon extends React.Component { - constructor() { - super(); - - this.state = { - hover: false, - }; - } - - onHoverStart = () => { - this.setState({hover: true}); - }; - - onHoverEnd = () => { - this.setState({hover: false}); - }; - - render() { - const classes = this.props.collapsedPanel ? "mx_InviteOnlyIcon_small": "mx_InviteOnlyIcon_large"; - - const Tooltip = sdk.getComponent("elements.Tooltip"); - let tooltip; - if (this.state.hover) { - tooltip = ; - } - return (
- { tooltip } -
); - } -} diff --git a/src/components/views/rooms/RoomBreadcrumbs.js b/src/components/views/rooms/RoomBreadcrumbs.js deleted file mode 100644 index fe443d720f..0000000000 --- a/src/components/views/rooms/RoomBreadcrumbs.js +++ /dev/null @@ -1,394 +0,0 @@ -/* -Copyright 2019 New Vector Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React, {createRef} from "react"; -import dis from "../../../dispatcher/dispatcher"; -import {MatrixClientPeg} from "../../../MatrixClientPeg"; -import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore"; -import AccessibleButton from '../elements/AccessibleButton'; -import RoomAvatar from '../avatars/RoomAvatar'; -import classNames from 'classnames'; -import * as sdk from "../../../index"; -import Analytics from "../../../Analytics"; -import * as RoomNotifs from '../../../RoomNotifs'; -import * as FormattingUtils from "../../../utils/FormattingUtils"; -import DMRoomMap from "../../../utils/DMRoomMap"; -import {_t} from "../../../languageHandler"; - -const MAX_ROOMS = 20; -const MIN_ROOMS_BEFORE_ENABLED = 10; - -// The threshold time in milliseconds to wait for an autojoined room to show up. -const AUTOJOIN_WAIT_THRESHOLD_MS = 90000; // 90 seconds - -export default class RoomBreadcrumbs extends React.Component { - constructor(props) { - super(props); - this.state = {rooms: [], enabled: false}; - - this.onAction = this.onAction.bind(this); - this._dispatcherRef = null; - - // The room IDs we're waiting to come down the Room handler and when we - // started waiting for them. Used to track a room over an upgrade/autojoin. - this._waitingRoomQueue = [/* { roomId, addedTs } */]; - - this._scroller = createRef(); - } - - // TODO: [REACT-WARNING] Move this to constructor - UNSAFE_componentWillMount() { // eslint-disable-line camelcase - this._dispatcherRef = dis.register(this.onAction); - - const storedRooms = SettingsStore.getValue("breadcrumb_rooms"); - this._loadRoomIds(storedRooms || []); - - this._settingWatchRef = SettingsStore.watchSetting("breadcrumb_rooms", null, this.onBreadcrumbsChanged); - - this.setState({enabled: this._shouldEnable()}); - - MatrixClientPeg.get().on("Room.myMembership", this.onMyMembership); - MatrixClientPeg.get().on("Room.receipt", this.onRoomReceipt); - MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline); - MatrixClientPeg.get().on("Event.decrypted", this.onEventDecrypted); - MatrixClientPeg.get().on("Room", this.onRoom); - } - - componentWillUnmount() { - dis.unregister(this._dispatcherRef); - - SettingsStore.unwatchSetting(this._settingWatchRef); - - const client = MatrixClientPeg.get(); - if (client) { - client.removeListener("Room.myMembership", this.onMyMembership); - client.removeListener("Room.receipt", this.onRoomReceipt); - client.removeListener("Room.timeline", this.onRoomTimeline); - client.removeListener("Event.decrypted", this.onEventDecrypted); - client.removeListener("Room", this.onRoom); - } - } - - componentDidUpdate() { - const rooms = this.state.rooms.slice(); - - if (rooms.length) { - const roomModel = rooms[0]; - if (!roomModel.animated) { - roomModel.animated = true; - setTimeout(() => this.setState({rooms}), 0); - } - } - } - - onAction(payload) { - switch (payload.action) { - case 'view_room': - if (payload.auto_join && !MatrixClientPeg.get().getRoom(payload.room_id)) { - // Queue the room instead of pushing it immediately - we're probably just waiting - // for a join to complete (ie: joining the upgraded room). - this._waitingRoomQueue.push({roomId: payload.room_id, addedTs: (new Date).getTime()}); - break; - } - this._appendRoomId(payload.room_id); - break; - - // XXX: slight hack in order to zero the notification count when a room - // is read. Copied from RoomTile - case 'on_room_read': { - const room = MatrixClientPeg.get().getRoom(payload.roomId); - this._calculateRoomBadges(room, /*zero=*/true); - break; - } - } - } - - onMyMembership = (room, membership) => { - if (membership === "leave" || membership === "ban") { - const rooms = this.state.rooms.slice(); - const roomState = rooms.find((r) => r.room.roomId === room.roomId); - if (roomState) { - roomState.left = true; - this.setState({rooms}); - } - } - this.onRoomMembershipChanged(); - }; - - onRoomReceipt = (event, room) => { - if (this.state.rooms.map(r => r.room.roomId).includes(room.roomId)) { - this._calculateRoomBadges(room); - } - }; - - onRoomTimeline = (event, room) => { - if (!room) return; // Can be null for the notification timeline, etc. - if (this.state.rooms.map(r => r.room.roomId).includes(room.roomId)) { - this._calculateRoomBadges(room); - } - }; - - onEventDecrypted = (event) => { - if (this.state.rooms.map(r => r.room.roomId).includes(event.getRoomId())) { - this._calculateRoomBadges(MatrixClientPeg.get().getRoom(event.getRoomId())); - } - }; - - onBreadcrumbsChanged = (settingName, roomId, level, valueAtLevel, value) => { - if (!value) return; - - const currentState = this.state.rooms.map((r) => r.room.roomId); - if (currentState.length === value.length) { - let changed = false; - for (let i = 0; i < currentState.length; i++) { - if (currentState[i] !== value[i]) { - changed = true; - break; - } - } - if (!changed) return; - } - - this._loadRoomIds(value); - }; - - onRoomMembershipChanged = () => { - if (!this.state.enabled && this._shouldEnable()) { - this.setState({enabled: true}); - } - }; - - onRoom = (room) => { - // Always check for membership changes when we see new rooms - this.onRoomMembershipChanged(); - - const waitingRoom = this._waitingRoomQueue.find(r => r.roomId === room.roomId); - if (!waitingRoom) return; - this._waitingRoomQueue.splice(this._waitingRoomQueue.indexOf(waitingRoom), 1); - - const now = (new Date()).getTime(); - if ((now - waitingRoom.addedTs) > AUTOJOIN_WAIT_THRESHOLD_MS) return; // Too long ago. - this._appendRoomId(room.roomId); // add the room we've been waiting for - }; - - _shouldEnable() { - const client = MatrixClientPeg.get(); - const joinedRoomCount = client.getRooms().reduce((count, r) => { - return count + (r.getMyMembership() === "join" ? 1 : 0); - }, 0); - return joinedRoomCount >= MIN_ROOMS_BEFORE_ENABLED; - } - - _loadRoomIds(roomIds) { - if (!roomIds || roomIds.length <= 0) return; // Skip updates with no rooms - - // If we're here, the list changed. - const rooms = roomIds.map((r) => MatrixClientPeg.get().getRoom(r)).filter((r) => r).map((r) => { - const badges = this._calculateBadgesForRoom(r) || {}; - return { - room: r, - animated: false, - ...badges, - }; - }); - this.setState({ - rooms: rooms, - }); - } - - _calculateBadgesForRoom(room, zero=false) { - if (!room) return null; - - // Reset the notification variables for simplicity - const roomModel = { - redBadge: false, - formattedCount: "0", - showCount: false, - }; - - if (zero) return roomModel; - - const notifState = RoomNotifs.getRoomNotifsState(room.roomId); - if (RoomNotifs.MENTION_BADGE_STATES.includes(notifState)) { - const highlightNotifs = RoomNotifs.getUnreadNotificationCount(room, 'highlight'); - const unreadNotifs = RoomNotifs.getUnreadNotificationCount(room); - - const redBadge = highlightNotifs > 0; - const greyBadge = redBadge || (unreadNotifs > 0 && RoomNotifs.BADGE_STATES.includes(notifState)); - - if (redBadge || greyBadge) { - const notifCount = redBadge ? highlightNotifs : unreadNotifs; - const limitedCount = FormattingUtils.formatCount(notifCount); - - roomModel.redBadge = redBadge; - roomModel.formattedCount = limitedCount; - roomModel.showCount = true; - } - } - - return roomModel; - } - - _calculateRoomBadges(room, zero=false) { - if (!room) return; - - const rooms = this.state.rooms.slice(); - const roomModel = rooms.find((r) => r.room.roomId === room.roomId); - if (!roomModel) return; // No applicable room, so don't do math on it - - const badges = this._calculateBadgesForRoom(room, zero); - if (!badges) return; // No badges for some reason - - Object.assign(roomModel, badges); - this.setState({rooms}); - } - - _appendRoomId(roomId) { - let room = MatrixClientPeg.get().getRoom(roomId); - if (!room) return; - - const rooms = this.state.rooms.slice(); - - // If the room is upgraded, use that room instead. We'll also splice out - // any children of the room. - const history = MatrixClientPeg.get().getRoomUpgradeHistory(roomId); - if (history.length > 1) { - room = history[history.length - 1]; // Last room is most recent - - // Take out any room that isn't the most recent room - for (let i = 0; i < history.length - 1; i++) { - const idx = rooms.findIndex((r) => r.room.roomId === history[i].roomId); - if (idx !== -1) rooms.splice(idx, 1); - } - } - - const existingIdx = rooms.findIndex((r) => r.room.roomId === room.roomId); - if (existingIdx !== -1) { - rooms.splice(existingIdx, 1); - } - - rooms.splice(0, 0, {room, animated: false}); - - if (rooms.length > MAX_ROOMS) { - rooms.splice(MAX_ROOMS, rooms.length - MAX_ROOMS); - } - this.setState({rooms}); - - if (this._scroller.current) { - this._scroller.current.moveToOrigin(); - } - - // We don't track room aesthetics (badges, membership, etc) over the wire so we - // don't need to do this elsewhere in the file. Just where we alter the room IDs - // and their order. - const roomIds = rooms.map((r) => r.room.roomId); - if (roomIds.length > 0) { - SettingsStore.setValue("breadcrumb_rooms", null, SettingLevel.ACCOUNT, roomIds); - } - } - - _viewRoom(room, index) { - Analytics.trackEvent("Breadcrumbs", "click_node", index); - dis.dispatch({action: "view_room", room_id: room.roomId}); - } - - _onMouseEnter(room) { - this._onHover(room); - } - - _onMouseLeave(room) { - this._onHover(null); // clear hover states - } - - _onHover(room) { - const rooms = this.state.rooms.slice(); - for (const r of rooms) { - r.hover = room && r.room.roomId === room.roomId; - } - this.setState({rooms}); - } - - _isDmRoom(room) { - const dmRooms = DMRoomMap.shared().getUserIdForRoomId(room.roomId); - return Boolean(dmRooms); - } - - render() { - const Tooltip = sdk.getComponent('elements.Tooltip'); - const IndicatorScrollbar = sdk.getComponent('structures.IndicatorScrollbar'); - - // check for collapsed here and not at parent so we keep rooms in our state - // when collapsing and expanding - if (this.props.collapsed || !this.state.enabled) { - return null; - } - - const rooms = this.state.rooms; - const avatars = rooms.map((r, i) => { - const isFirst = i === 0; - const classes = classNames({ - "mx_RoomBreadcrumbs_crumb": true, - "mx_RoomBreadcrumbs_preAnimate": isFirst && !r.animated, - "mx_RoomBreadcrumbs_animate": isFirst, - "mx_RoomBreadcrumbs_left": r.left, - }); - - let tooltip = null; - if (r.hover) { - tooltip = ; - } - - let badge; - if (r.showCount) { - const badgeClasses = classNames({ - 'mx_RoomTile_badge': true, - 'mx_RoomTile_badgeButton': true, - 'mx_RoomTile_badgeRed': r.redBadge, - 'mx_RoomTile_badgeUnread': !r.redBadge, - }); - - badge =
{r.formattedCount}
; - } - - return ( - this._viewRoom(r.room, i)} - onMouseEnter={() => this._onMouseEnter(r.room)} - onMouseLeave={() => this._onMouseLeave(r.room)} - aria-label={_t("Room %(name)s", {name: r.room.name})} - > - - {badge} - {tooltip} - - ); - }); - return ( -
- - { avatars } - -
- ); - } -} diff --git a/src/components/views/rooms/RoomDropTarget.js b/src/components/views/rooms/RoomDropTarget.js deleted file mode 100644 index 61b7ca6d59..0000000000 --- a/src/components/views/rooms/RoomDropTarget.js +++ /dev/null @@ -1,35 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import createReactClass from 'create-react-class'; - -export default createReactClass({ - displayName: 'RoomDropTarget', - - render: function() { - return ( -
-
-
- { this.props.label } -
-
-
- ); - }, -}); diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js deleted file mode 100644 index dee4015003..0000000000 --- a/src/components/views/rooms/RoomList.js +++ /dev/null @@ -1,838 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2017, 2018 Vector Creations Ltd -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import SettingsStore from "../../../settings/SettingsStore"; -import Timer from "../../../utils/Timer"; -import React from "react"; -import ReactDOM from "react-dom"; -import createReactClass from 'create-react-class'; -import PropTypes from 'prop-types'; -import * as utils from "matrix-js-sdk/src/utils"; -import { _t } from '../../../languageHandler'; -import {MatrixClientPeg} from "../../../MatrixClientPeg"; -import rate_limited_func from "../../../ratelimitedfunc"; -import * as Rooms from '../../../Rooms'; -import DMRoomMap from '../../../utils/DMRoomMap'; -import TagOrderStore from '../../../stores/TagOrderStore'; -import CustomRoomTagStore from '../../../stores/CustomRoomTagStore'; -import GroupStore from '../../../stores/GroupStore'; -import RoomSubList from '../../structures/RoomSubList'; -import ResizeHandle from '../elements/ResizeHandle'; -import CallHandler from "../../../CallHandler"; -import dis from "../../../dispatcher/dispatcher"; -import * as sdk from "../../../index"; -import * as Receipt from "../../../utils/Receipt"; -import {Resizer} from '../../../resizer'; -import {Layout, Distributor} from '../../../resizer/distributors/roomsublist2'; -import {RovingTabIndexProvider} from "../../../accessibility/RovingTabIndex"; -import {RoomListStoreTempProxy} from "../../../stores/room-list/RoomListStoreTempProxy"; -import {DefaultTagID} from "../../../stores/room-list/models"; -import * as Unread from "../../../Unread"; -import RoomViewStore from "../../../stores/RoomViewStore"; -import {TAG_DM} from "../../../stores/RoomListStore"; - -const HIDE_CONFERENCE_CHANS = true; -const STANDARD_TAGS_REGEX = /^(m\.(favourite|lowpriority|server_notice)|im\.vector\.fake\.(invite|recent|direct|archived))$/; -const HOVER_MOVE_TIMEOUT = 1000; - -function labelForTagName(tagName) { - if (tagName.startsWith('u.')) return tagName.slice(2); - return tagName; -} - -export default createReactClass({ - displayName: 'RoomList', - - propTypes: { - ConferenceHandler: PropTypes.any, - collapsed: PropTypes.bool.isRequired, - searchFilter: PropTypes.string, - }, - - getInitialState: function() { - - this._hoverClearTimer = null; - this._subListRefs = { - // key => RoomSubList ref - }; - - const sizesJson = window.localStorage.getItem("mx_roomlist_sizes"); - const collapsedJson = window.localStorage.getItem("mx_roomlist_collapsed"); - this.subListSizes = sizesJson ? JSON.parse(sizesJson) : {}; - this.collapsedState = collapsedJson ? JSON.parse(collapsedJson) : {}; - this._layoutSections = []; - - const unfilteredOptions = { - allowWhitespace: false, - handleHeight: 1, - }; - this._unfilteredlayout = new Layout((key, size) => { - const subList = this._subListRefs[key]; - if (subList) { - subList.setHeight(size); - } - // update overflow indicators - this._checkSubListsOverflow(); - // don't store height for collapsed sublists - if (!this.collapsedState[key]) { - this.subListSizes[key] = size; - window.localStorage.setItem("mx_roomlist_sizes", - JSON.stringify(this.subListSizes)); - } - }, this.subListSizes, this.collapsedState, unfilteredOptions); - - this._filteredLayout = new Layout((key, size) => { - const subList = this._subListRefs[key]; - if (subList) { - subList.setHeight(size); - } - }, null, null, { - allowWhitespace: false, - handleHeight: 0, - }); - - this._layout = this._unfilteredlayout; - - return { - isLoadingLeftRooms: false, - totalRoomCount: null, - lists: {}, - incomingCallTag: null, - incomingCall: null, - selectedTags: [], - hover: false, - customTags: CustomRoomTagStore.getTags(), - }; - }, - - // TODO: [REACT-WARNING] Replace component with real class, put this in the constructor. - UNSAFE_componentWillMount: function() { - this.mounted = false; - - const cli = MatrixClientPeg.get(); - - cli.on("Room", this.onRoom); - cli.on("deleteRoom", this.onDeleteRoom); - cli.on("Room.receipt", this.onRoomReceipt); - cli.on("RoomMember.name", this.onRoomMemberName); - cli.on("Event.decrypted", this.onEventDecrypted); - cli.on("accountData", this.onAccountData); - cli.on("Group.myMembership", this._onGroupMyMembership); - cli.on("RoomState.events", this.onRoomStateEvents); - - const dmRoomMap = DMRoomMap.shared(); - // A map between tags which are group IDs and the room IDs of rooms that should be kept - // in the room list when filtering by that tag. - this._visibleRoomsForGroup = { - // $groupId: [$roomId1, $roomId2, ...], - }; - // All rooms that should be kept in the room list when filtering. - // By default, show all rooms. - this._visibleRooms = MatrixClientPeg.get().getVisibleRooms(); - - // Listen to updates to group data. RoomList cares about members and rooms in order - // to filter the room list when group tags are selected. - this._groupStoreToken = GroupStore.registerListener(null, () => { - (TagOrderStore.getOrderedTags() || []).forEach((tag) => { - if (tag[0] !== '+') { - return; - } - // This group's rooms or members may have updated, update rooms for its tag - this.updateVisibleRoomsForTag(dmRoomMap, tag); - this.updateVisibleRooms(); - }); - }); - - this._tagStoreToken = TagOrderStore.addListener(() => { - // Filters themselves have changed - this.updateVisibleRooms(); - }); - - this._roomListStoreToken = RoomListStoreTempProxy.addListener(() => { - this._delayedRefreshRoomList(); - }); - - - if (SettingsStore.isFeatureEnabled("feature_custom_tags")) { - this._customTagStoreToken = CustomRoomTagStore.addListener(() => { - this.setState({ - customTags: CustomRoomTagStore.getTags(), - }); - }); - } - - this.refreshRoomList(); - - // order of the sublists - //this.listOrder = []; - - // loop count to stop a stack overflow if the user keeps waggling the - // mouse for >30s in a row, or if running under mocha - this._delayedRefreshRoomListLoopCount = 0; - }, - - componentDidMount: function() { - this.dispatcherRef = dis.register(this.onAction); - const cfg = { - getLayout: () => this._layout, - }; - this.resizer = new Resizer(this.resizeContainer, Distributor, cfg); - this.resizer.setClassNames({ - handle: "mx_ResizeHandle", - vertical: "mx_ResizeHandle_vertical", - reverse: "mx_ResizeHandle_reverse", - }); - this._layout.update( - this._layoutSections, - this.resizeContainer && this.resizeContainer.offsetHeight, - ); - this._checkSubListsOverflow(); - - this.resizer.attach(); - if (this.props.resizeNotifier) { - this.props.resizeNotifier.on("leftPanelResized", this.onResize); - } - this.mounted = true; - }, - - componentDidUpdate: function(prevProps) { - let forceLayoutUpdate = false; - this._repositionIncomingCallBox(undefined, false); - if (!this.props.searchFilter && prevProps.searchFilter) { - this._layout = this._unfilteredlayout; - forceLayoutUpdate = true; - } else if (this.props.searchFilter && !prevProps.searchFilter) { - this._layout = this._filteredLayout; - forceLayoutUpdate = true; - } - this._layout.update( - this._layoutSections, - this.resizeContainer && this.resizeContainer.clientHeight, - forceLayoutUpdate, - ); - this._checkSubListsOverflow(); - }, - - onAction: function(payload) { - switch (payload.action) { - case 'call_state': - var call = CallHandler.getCall(payload.room_id); - if (call && call.call_state === 'ringing') { - this.setState({ - incomingCall: call, - incomingCallTag: this.getTagNameForRoomId(payload.room_id), - }); - this._repositionIncomingCallBox(undefined, true); - } else { - this.setState({ - incomingCall: null, - incomingCallTag: null, - }); - } - break; - case 'view_room_delta': { - const currentRoomId = RoomViewStore.getRoomId(); - const { - "im.vector.fake.invite": inviteRooms, - "m.favourite": favouriteRooms, - [TAG_DM]: dmRooms, - "im.vector.fake.recent": recentRooms, - "m.lowpriority": lowPriorityRooms, - "im.vector.fake.archived": historicalRooms, - "m.server_notice": serverNoticeRooms, - ...tags - } = this.state.lists; - - const shownCustomTagRooms = Object.keys(tags).filter(tagName => { - return (!this.state.customTags || this.state.customTags[tagName]) && - !tagName.match(STANDARD_TAGS_REGEX); - }).map(tagName => tags[tagName]); - - // this order matches the one when generating the room sublists below. - let rooms = this._applySearchFilter([ - ...inviteRooms, - ...favouriteRooms, - ...dmRooms, - ...recentRooms, - ...[].concat.apply([], shownCustomTagRooms), // eslint-disable-line prefer-spread - ...lowPriorityRooms, - ...historicalRooms, - ...serverNoticeRooms, - ], this.props.searchFilter); - - if (payload.unread) { - // filter to only notification rooms (and our current active room so we can index properly) - rooms = rooms.filter(room => { - return room.roomId === currentRoomId || Unread.doesRoomHaveUnreadMessages(room); - }); - } - - const currentIndex = rooms.findIndex(room => room.roomId === currentRoomId); - // use slice to account for looping around the start - const [room] = rooms.slice((currentIndex + payload.delta) % rooms.length); - if (room) { - dis.dispatch({ - action: 'view_room', - room_id: room.roomId, - show_room_tile: true, // to make sure the room gets scrolled into view - }); - } - break; - } - } - }, - - componentWillUnmount: function() { - this.mounted = false; - - dis.unregister(this.dispatcherRef); - if (MatrixClientPeg.get()) { - MatrixClientPeg.get().removeListener("Room", this.onRoom); - MatrixClientPeg.get().removeListener("deleteRoom", this.onDeleteRoom); - MatrixClientPeg.get().removeListener("Room.receipt", this.onRoomReceipt); - MatrixClientPeg.get().removeListener("RoomMember.name", this.onRoomMemberName); - MatrixClientPeg.get().removeListener("Event.decrypted", this.onEventDecrypted); - MatrixClientPeg.get().removeListener("accountData", this.onAccountData); - MatrixClientPeg.get().removeListener("Group.myMembership", this._onGroupMyMembership); - MatrixClientPeg.get().removeListener("RoomState.events", this.onRoomStateEvents); - } - - if (this.props.resizeNotifier) { - this.props.resizeNotifier.removeListener("leftPanelResized", this.onResize); - } - - - if (this._tagStoreToken) { - this._tagStoreToken.remove(); - } - - if (this._roomListStoreToken) { - this._roomListStoreToken.remove(); - } - if (this._customTagStoreToken) { - this._customTagStoreToken.remove(); - } - - // NB: GroupStore is not a Flux.Store - if (this._groupStoreToken) { - this._groupStoreToken.unregister(); - } - - // cancel any pending calls to the rate_limited_funcs - this._delayedRefreshRoomList.cancelPendingCall(); - }, - - - onResize: function() { - if (this.mounted && this._layout && this.resizeContainer && - Array.isArray(this._layoutSections) - ) { - this._layout.update( - this._layoutSections, - this.resizeContainer.offsetHeight, - ); - } - }, - - onRoom: function(room) { - this.updateVisibleRooms(); - }, - - onRoomStateEvents: function(ev, state) { - if (ev.getType() === "m.room.create" || ev.getType() === "m.room.tombstone") { - this.updateVisibleRooms(); - } - }, - - onDeleteRoom: function(roomId) { - this.updateVisibleRooms(); - }, - - onArchivedHeaderClick: function(isHidden, scrollToPosition) { - if (!isHidden) { - const self = this; - this.setState({ isLoadingLeftRooms: true }); - // we don't care about the response since it comes down via "Room" - // events. - MatrixClientPeg.get().syncLeftRooms().catch(function(err) { - console.error("Failed to sync left rooms: %s", err); - console.error(err); - }).finally(function() { - self.setState({ isLoadingLeftRooms: false }); - }); - } - }, - - onRoomReceipt: function(receiptEvent, room) { - // because if we read a notification, it will affect notification count - // only bother updating if there's a receipt from us - if (Receipt.findReadReceiptFromUserId(receiptEvent, MatrixClientPeg.get().credentials.userId)) { - this._delayedRefreshRoomList(); - } - }, - - onRoomMemberName: function(ev, member) { - this._delayedRefreshRoomList(); - }, - - onEventDecrypted: function(ev) { - // An event being decrypted may mean we need to re-order the room list - this._delayedRefreshRoomList(); - }, - - onAccountData: function(ev) { - if (ev.getType() == 'm.direct') { - this._delayedRefreshRoomList(); - } - }, - - _onGroupMyMembership: function(group) { - this.forceUpdate(); - }, - - onMouseMove: async function(ev) { - if (!this._hoverClearTimer) { - this.setState({hover: true}); - this._hoverClearTimer = new Timer(HOVER_MOVE_TIMEOUT); - this._hoverClearTimer.start(); - let finished = true; - try { - await this._hoverClearTimer.finished(); - } catch (err) { - finished = false; - } - this._hoverClearTimer = null; - if (finished) { - this.setState({hover: false}); - this._delayedRefreshRoomList(); - } - } else { - this._hoverClearTimer.restart(); - } - }, - - onMouseLeave: function(ev) { - if (this._hoverClearTimer) { - this._hoverClearTimer.abort(); - this._hoverClearTimer = null; - } - this.setState({hover: false}); - - // Refresh the room list just in case the user missed something. - this._delayedRefreshRoomList(); - }, - - _delayedRefreshRoomList: rate_limited_func(function() { - this.refreshRoomList(); - }, 500), - - // Update which rooms and users should appear in RoomList for a given group tag - updateVisibleRoomsForTag: function(dmRoomMap, tag) { - if (!this.mounted) return; - // For now, only handle group tags - if (tag[0] !== '+') return; - - this._visibleRoomsForGroup[tag] = []; - GroupStore.getGroupRooms(tag).forEach((room) => this._visibleRoomsForGroup[tag].push(room.roomId)); - GroupStore.getGroupMembers(tag).forEach((member) => { - if (member.userId === MatrixClientPeg.get().credentials.userId) return; - dmRoomMap.getDMRoomsForUserId(member.userId).forEach( - (roomId) => this._visibleRoomsForGroup[tag].push(roomId), - ); - }); - // TODO: Check if room has been tagged to the group by the user - }, - - // Update which rooms and users should appear according to which tags are selected - updateVisibleRooms: function() { - const selectedTags = TagOrderStore.getSelectedTags(); - const visibleGroupRooms = []; - selectedTags.forEach((tag) => { - (this._visibleRoomsForGroup[tag] || []).forEach( - (roomId) => visibleGroupRooms.push(roomId), - ); - }); - - // If there are any tags selected, constrain the rooms listed to the - // visible rooms as determined by visibleGroupRooms. Here, we - // de-duplicate and filter out rooms that the client doesn't know - // about (hence the Set and the null-guard on `room`). - if (selectedTags.length > 0) { - const roomSet = new Set(); - visibleGroupRooms.forEach((roomId) => { - const room = MatrixClientPeg.get().getRoom(roomId); - if (room) { - roomSet.add(room); - } - }); - this._visibleRooms = Array.from(roomSet); - } else { - // Show all rooms - this._visibleRooms = MatrixClientPeg.get().getVisibleRooms(); - } - this._delayedRefreshRoomList(); - }, - - refreshRoomList: function() { - if (this.state.hover) { - // Don't re-sort the list if we're hovering over the list - return; - } - - // TODO: ideally we'd calculate this once at start, and then maintain - // any changes to it incrementally, updating the appropriate sublists - // as needed. - // Alternatively we'd do something magical with Immutable.js or similar. - const lists = this.getRoomLists(); - let totalRooms = 0; - for (const l of Object.values(lists)) { - totalRooms += l.length; - } - this.setState({ - lists, - totalRoomCount: totalRooms, - // Do this here so as to not render every time the selected tags - // themselves change. - selectedTags: TagOrderStore.getSelectedTags(), - }, () => { - // we don't need to restore any size here, do we? - // i guess we could have triggered a new group to appear - // that already an explicit size the last time it appeared ... - this._checkSubListsOverflow(); - }); - - // this._lastRefreshRoomListTs = Date.now(); - }, - - getTagNameForRoomId: function(roomId) { - const lists = RoomListStoreTempProxy.getRoomLists(); - for (const tagName of Object.keys(lists)) { - for (const room of lists[tagName]) { - // Should be impossible, but guard anyways. - if (!room) { - continue; - } - const myUserId = MatrixClientPeg.get().getUserId(); - if (HIDE_CONFERENCE_CHANS && Rooms.isConfCallRoom(room, myUserId, this.props.ConferenceHandler)) { - continue; - } - - if (room.roomId === roomId) return tagName; - } - } - - return null; - }, - - getRoomLists: function() { - const lists = RoomListStoreTempProxy.getRoomLists(); - - const filteredLists = {}; - - const isRoomVisible = { - // $roomId: true, - }; - - this._visibleRooms.forEach((r) => { - isRoomVisible[r.roomId] = true; - }); - - Object.keys(lists).forEach((tagName) => { - const filteredRooms = lists[tagName].filter((taggedRoom) => { - // Somewhat impossible, but guard against it anyway - if (!taggedRoom) { - return; - } - const myUserId = MatrixClientPeg.get().getUserId(); - if (HIDE_CONFERENCE_CHANS && Rooms.isConfCallRoom(taggedRoom, myUserId, this.props.ConferenceHandler)) { - return; - } - - return Boolean(isRoomVisible[taggedRoom.roomId]); - }); - - if (filteredRooms.length > 0 || tagName.match(STANDARD_TAGS_REGEX)) { - filteredLists[tagName] = filteredRooms; - } - }); - - return filteredLists; - }, - - _getScrollNode: function() { - if (!this.mounted) return null; - const panel = ReactDOM.findDOMNode(this); - if (!panel) return null; - - if (panel.classList.contains('gm-prevented')) { - return panel; - } else { - return panel.children[2]; // XXX: Fragile! - } - }, - - _repositionIncomingCallBox: function(e, firstTime) { - const incomingCallBox = document.getElementById("incomingCallBox"); - if (incomingCallBox && incomingCallBox.parentElement) { - const scrollArea = this._getScrollNode(); - if (!scrollArea) return; - // Use the offset of the top of the scroll area from the window - // as this is used to calculate the CSS fixed top position for the stickies - const scrollAreaOffset = scrollArea.getBoundingClientRect().top + window.pageYOffset; - // Use the offset of the top of the component from the window - // as this is used to calculate the CSS fixed top position for the stickies - const scrollAreaHeight = ReactDOM.findDOMNode(this).getBoundingClientRect().height; - - let top = (incomingCallBox.parentElement.getBoundingClientRect().top + window.pageYOffset); - // Make sure we don't go too far up, if the headers aren't sticky - top = (top < scrollAreaOffset) ? scrollAreaOffset : top; - // make sure we don't go too far down, if the headers aren't sticky - const bottomMargin = scrollAreaOffset + (scrollAreaHeight - 45); - top = (top > bottomMargin) ? bottomMargin : top; - - incomingCallBox.style.top = top + "px"; - incomingCallBox.style.left = scrollArea.offsetLeft + scrollArea.offsetWidth + 12 + "px"; - } - }, - - _makeGroupInviteTiles(filter) { - const ret = []; - const lcFilter = filter && filter.toLowerCase(); - - const GroupInviteTile = sdk.getComponent('groups.GroupInviteTile'); - for (const group of MatrixClientPeg.get().getGroups()) { - const {groupId, name, myMembership} = group; - // filter to only groups in invite state and group_id starts with filter or group name includes it - if (myMembership !== 'invite') continue; - if (lcFilter && !groupId.toLowerCase().startsWith(lcFilter) && - !(name && name.toLowerCase().includes(lcFilter))) continue; - ret.push(); - } - - return ret; - }, - - _applySearchFilter: function(list, filter) { - if (filter === "") return list; - const lcFilter = filter.toLowerCase(); - // apply toLowerCase before and after removeHiddenChars because different rules get applied - // e.g M -> M but m -> n, yet some unicode homoglyphs come out as uppercase, e.g 𝚮 -> H - const fuzzyFilter = utils.removeHiddenChars(lcFilter).toLowerCase(); - // case insensitive if room name includes filter, - // or if starts with `#` and one of room's aliases starts with filter - return list.filter((room) => { - if (filter[0] === "#") { - if (room.getCanonicalAlias() && room.getCanonicalAlias().toLowerCase().startsWith(lcFilter)) { - return true; - } - if (room.getAltAliases().some((alias) => alias.toLowerCase().startsWith(lcFilter))) { - return true; - } - } - return room.name && utils.removeHiddenChars(room.name.toLowerCase()).toLowerCase().includes(fuzzyFilter); - }); - }, - - _handleCollapsedState: function(key, collapsed) { - // persist collapsed state - this.collapsedState[key] = collapsed; - window.localStorage.setItem("mx_roomlist_collapsed", JSON.stringify(this.collapsedState)); - // load the persisted size configuration of the expanded sub list - if (collapsed) { - this._layout.collapseSection(key); - } else { - this._layout.expandSection(key, this.subListSizes[key]); - } - // check overflow, as sub lists sizes have changed - // important this happens after calling resize above - this._checkSubListsOverflow(); - }, - - // check overflow for scroll indicator gradient - _checkSubListsOverflow() { - Object.values(this._subListRefs).forEach(l => l.checkOverflow()); - }, - - _subListRef: function(key, ref) { - if (!ref) { - delete this._subListRefs[key]; - } else { - this._subListRefs[key] = ref; - } - }, - - _mapSubListProps: function(subListsProps) { - this._layoutSections = []; - const defaultProps = { - collapsed: this.props.collapsed, - isFiltered: !!this.props.searchFilter, - }; - - subListsProps.forEach((p) => { - p.list = this._applySearchFilter(p.list, this.props.searchFilter); - }); - - subListsProps = subListsProps.filter((props => { - const len = props.list.length + (props.extraTiles ? props.extraTiles.length : 0); - return len !== 0 || props.onAddRoom; - })); - - return subListsProps.reduce((components, props, i) => { - props = {...defaultProps, ...props}; - const isLast = i === subListsProps.length - 1; - const len = props.list.length + (props.extraTiles ? props.extraTiles.length : 0); - const {key, label, onHeaderClick, ...otherProps} = props; - const chosenKey = key || label; - const onSubListHeaderClick = (collapsed) => { - this._handleCollapsedState(chosenKey, collapsed); - if (onHeaderClick) { - onHeaderClick(collapsed); - } - }; - const startAsHidden = props.startAsHidden || this.collapsedState[chosenKey]; - this._layoutSections.push({ - id: chosenKey, - count: len, - }); - const subList = (); - - if (!isLast) { - return components.concat( - subList, - - ); - } else { - return components.concat(subList); - } - }, []); - }, - - _collectResizeContainer: function(el) { - this.resizeContainer = el; - }, - - render: function() { - const incomingCallIfTaggedAs = (tagName) => { - if (!this.state.incomingCall) return null; - if (this.state.incomingCallTag !== tagName) return null; - return this.state.incomingCall; - }; - - let subLists = [ - { - list: [], - extraTiles: this._makeGroupInviteTiles(this.props.searchFilter), - label: _t('Community Invites'), - isInvite: true, - }, - { - list: this.state.lists['im.vector.fake.invite'], - label: _t('Invites'), - incomingCall: incomingCallIfTaggedAs('im.vector.fake.invite'), - isInvite: true, - }, - { - list: this.state.lists['m.favourite'], - label: _t('Favourites'), - tagName: "m.favourite", - incomingCall: incomingCallIfTaggedAs('m.favourite'), - }, - { - list: this.state.lists[DefaultTagID.DM], - label: _t('Direct Messages'), - tagName: DefaultTagID.DM, - incomingCall: incomingCallIfTaggedAs(DefaultTagID.DM), - onAddRoom: () => {dis.dispatch({action: 'view_create_chat'});}, - addRoomLabel: _t("Start chat"), - }, - { - list: this.state.lists['im.vector.fake.recent'], - label: _t('Rooms'), - incomingCall: incomingCallIfTaggedAs('im.vector.fake.recent'), - onAddRoom: () => {dis.dispatch({action: 'view_create_room'});}, - addRoomLabel: _t("Create room"), - }, - ]; - const tagSubLists = Object.keys(this.state.lists) - .filter((tagName) => { - return (!this.state.customTags || this.state.customTags[tagName]) && - !tagName.match(STANDARD_TAGS_REGEX); - }).map((tagName) => { - return { - list: this.state.lists[tagName], - key: tagName, - label: labelForTagName(tagName), - tagName: tagName, - incomingCall: incomingCallIfTaggedAs(tagName), - }; - }); - subLists = subLists.concat(tagSubLists); - subLists = subLists.concat([ - { - list: this.state.lists['m.lowpriority'], - label: _t('Low priority'), - tagName: "m.lowpriority", - incomingCall: incomingCallIfTaggedAs('m.lowpriority'), - }, - { - list: this.state.lists['im.vector.fake.archived'], - label: _t('Historical'), - incomingCall: incomingCallIfTaggedAs('im.vector.fake.archived'), - startAsHidden: true, - showSpinner: this.state.isLoadingLeftRooms, - onHeaderClick: this.onArchivedHeaderClick, - }, - { - list: this.state.lists['m.server_notice'], - label: _t('System Alerts'), - tagName: "m.lowpriority", - incomingCall: incomingCallIfTaggedAs('m.server_notice'), - }, - ]); - - const subListComponents = this._mapSubListProps(subLists); - - const {resizeNotifier, collapsed, searchFilter, ConferenceHandler, onKeyDown, ...props} = this.props; // eslint-disable-line - return ( - - {({onKeyDownHandler}) =>
- { subListComponents } -
} -
- ); - }, -}); diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js deleted file mode 100644 index 5917f2ae77..0000000000 --- a/src/components/views/rooms/RoomTile.js +++ /dev/null @@ -1,565 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2017 New Vector Ltd -Copyright 2018 Michael Telatynski <7t3chguy@gmail.com> -Copyright 2019 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React, {createRef} from 'react'; -import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; -import classNames from 'classnames'; -import dis from '../../../dispatcher/dispatcher'; -import {MatrixClientPeg} from '../../../MatrixClientPeg'; -import DMRoomMap from '../../../utils/DMRoomMap'; -import * as sdk from '../../../index'; -import {ContextMenu, ContextMenuButton, toRightOf} from '../../structures/ContextMenu'; -import * as RoomNotifs from '../../../RoomNotifs'; -import * as FormattingUtils from '../../../utils/FormattingUtils'; -import ActiveRoomObserver from '../../../ActiveRoomObserver'; -import RoomViewStore from '../../../stores/RoomViewStore'; -import SettingsStore from "../../../settings/SettingsStore"; -import {_t} from "../../../languageHandler"; -import {RovingTabIndexWrapper} from "../../../accessibility/RovingTabIndex"; -import E2EIcon from './E2EIcon'; -import InviteOnlyIcon from './InviteOnlyIcon'; -// eslint-disable-next-line camelcase -import rate_limited_func from '../../../ratelimitedfunc'; -import { shieldStatusForRoom } from '../../../utils/ShieldUtils'; - -export default createReactClass({ - displayName: 'RoomTile', - - propTypes: { - onClick: PropTypes.func, - - room: PropTypes.object.isRequired, - collapsed: PropTypes.bool.isRequired, - unread: PropTypes.bool.isRequired, - highlight: PropTypes.bool.isRequired, - // If true, apply mx_RoomTile_transparent class - transparent: PropTypes.bool, - isInvite: PropTypes.bool.isRequired, - incomingCall: PropTypes.object, - }, - - getDefaultProps: function() { - return { - isDragging: false, - }; - }, - - getInitialState: function() { - const joinRules = this.props.room.currentState.getStateEvents("m.room.join_rules", ""); - const joinRule = joinRules && joinRules.getContent().join_rule; - - return ({ - joinRule, - hover: false, - badgeHover: false, - contextMenuPosition: null, // DOM bounding box, null if non-shown - roomName: this.props.room.name, - notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId), - notificationCount: this.props.room.getUnreadNotificationCount(), - selected: this.props.room.roomId === RoomViewStore.getRoomId(), - statusMessage: this._getStatusMessage(), - e2eStatus: null, - }); - }, - - _shouldShowStatusMessage() { - if (!SettingsStore.isFeatureEnabled("feature_custom_status")) { - return false; - } - const isInvite = this.props.room.getMyMembership() === "invite"; - const isJoined = this.props.room.getMyMembership() === "join"; - const looksLikeDm = this.props.room.getInvitedAndJoinedMemberCount() === 2; - return !isInvite && isJoined && looksLikeDm; - }, - - _getStatusMessageUser() { - if (!MatrixClientPeg.get()) return null; // We've probably been logged out - - const selfId = MatrixClientPeg.get().getUserId(); - const otherMember = this.props.room.currentState.getMembersExcept([selfId])[0]; - if (!otherMember) { - return null; - } - return otherMember.user; - }, - - _getStatusMessage() { - const statusUser = this._getStatusMessageUser(); - if (!statusUser) { - return ""; - } - return statusUser._unstable_statusMessage; - }, - - onRoomStateMember: function(ev, state, member) { - // we only care about leaving users - // because trust state will change if someone joins a megolm session anyway - if (member.membership !== "leave") { - return; - } - // ignore members in other rooms - if (member.roomId !== this.props.room.roomId) { - return; - } - - this._updateE2eStatus(); - }, - - onUserVerificationChanged: function(userId, _trustStatus) { - if (!this.props.room.getMember(userId)) { - // Not in this room - return; - } - this._updateE2eStatus(); - }, - - onCrossSigningKeysChanged: function() { - this._updateE2eStatus(); - }, - - onRoomTimeline: function(ev, room) { - if (!room) return; - if (room.roomId != this.props.room.roomId) return; - if (ev.getType() !== "m.room.encryption") return; - MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline); - this.onFindingRoomToBeEncrypted(); - }, - - onFindingRoomToBeEncrypted: function() { - const cli = MatrixClientPeg.get(); - cli.on("RoomState.members", this.onRoomStateMember); - cli.on("userTrustStatusChanged", this.onUserVerificationChanged); - cli.on("crossSigning.keysChanged", this.onCrossSigningKeysChanged); - this._updateE2eStatus(); - }, - - _updateE2eStatus: async function() { - const cli = MatrixClientPeg.get(); - if (!cli.isRoomEncrypted(this.props.room.roomId)) { - return; - } - - /* At this point, the user has encryption on and cross-signing on */ - this.setState({ - e2eStatus: await shieldStatusForRoom(cli, this.props.room), - }); - }, - - onRoomName: function(room) { - if (room !== this.props.room) return; - this.setState({ - roomName: this.props.room.name, - }); - }, - - onJoinRule: function(ev) { - if (ev.getType() !== "m.room.join_rules") return; - if (ev.getRoomId() !== this.props.room.roomId) return; - this.setState({ joinRule: ev.getContent().join_rule }); - }, - - onAccountData: function(accountDataEvent) { - if (accountDataEvent.getType() === 'm.push_rules') { - this.setState({ - notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId), - }); - } - }, - - onAction: function(payload) { - switch (payload.action) { - // XXX: slight hack in order to zero the notification count when a room - // is read. Ideally this state would be given to this via props (as we - // do with `unread`). This is still better than forceUpdating the entire - // RoomList when a room is read. - case 'on_room_read': - if (payload.roomId !== this.props.room.roomId) break; - this.setState({ - notificationCount: this.props.room.getUnreadNotificationCount(), - }); - break; - // RoomTiles are one of the few components that may show custom status and - // also remain on screen while in Settings toggling the feature. This ensures - // you can clearly see the status hide and show when toggling the feature. - case 'feature_custom_status_changed': - this.forceUpdate(); - break; - - case 'view_room': - // when the room is selected make sure its tile is visible, for breadcrumbs/keyboard shortcut access - if (payload.room_id === this.props.room.roomId && payload.show_room_tile) { - this._scrollIntoView(); - } - break; - } - }, - - _scrollIntoView: function() { - if (!this._roomTile.current) return; - this._roomTile.current.scrollIntoView({ - block: "nearest", - behavior: "auto", - }); - }, - - _onActiveRoomChange: function() { - this.setState({ - selected: this.props.room.roomId === RoomViewStore.getRoomId(), - }); - }, - - // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs - UNSAFE_componentWillMount: function() { - this._roomTile = createRef(); - }, - - componentDidMount: function() { - /* We bind here rather than in the definition because otherwise we wind up with the - method only being callable once every 500ms across all instances, which would be wrong */ - this._updateE2eStatus = rate_limited_func(this._updateE2eStatus, 500); - - const cli = MatrixClientPeg.get(); - cli.on("accountData", this.onAccountData); - cli.on("Room.name", this.onRoomName); - cli.on("RoomState.events", this.onJoinRule); - if (cli.isRoomEncrypted(this.props.room.roomId)) { - this.onFindingRoomToBeEncrypted(); - } else { - cli.on("Room.timeline", this.onRoomTimeline); - } - ActiveRoomObserver.addListener(this.props.room.roomId, this._onActiveRoomChange); - this.dispatcherRef = dis.register(this.onAction); - - if (this._shouldShowStatusMessage()) { - const statusUser = this._getStatusMessageUser(); - if (statusUser) { - statusUser.on("User._unstable_statusMessage", this._onStatusMessageCommitted); - } - } - - // when we're first rendered (or our sublist is expanded) make sure we are visible if we're active - if (this.state.selected) { - this._scrollIntoView(); - } - }, - - componentWillUnmount: function() { - const cli = MatrixClientPeg.get(); - if (cli) { - MatrixClientPeg.get().removeListener("accountData", this.onAccountData); - MatrixClientPeg.get().removeListener("Room.name", this.onRoomName); - cli.removeListener("RoomState.events", this.onJoinRule); - cli.removeListener("RoomState.members", this.onRoomStateMember); - cli.removeListener("userTrustStatusChanged", this.onUserVerificationChanged); - cli.removeListener("crossSigning.keysChanged", this.onCrossSigningKeysChanged); - cli.removeListener("Room.timeline", this.onRoomTimeline); - } - ActiveRoomObserver.removeListener(this.props.room.roomId, this._onActiveRoomChange); - dis.unregister(this.dispatcherRef); - - if (this._shouldShowStatusMessage()) { - const statusUser = this._getStatusMessageUser(); - if (statusUser) { - statusUser.removeListener( - "User._unstable_statusMessage", - this._onStatusMessageCommitted, - ); - } - } - }, - - // TODO: [REACT-WARNING] Replace with appropriate lifecycle event - UNSAFE_componentWillReceiveProps: function(props) { - // XXX: This could be a lot better - this makes the assumption that - // the notification count may have changed when the properties of - // the room tile change. - this.setState({ - notificationCount: this.props.room.getUnreadNotificationCount(), - }); - }, - - // Do a simple shallow comparison of props and state to avoid unnecessary - // renders. The assumption made here is that only state and props are used - // in rendering this component and children. - // - // RoomList is frequently made to forceUpdate, so this decreases number of - // RoomTile renderings. - shouldComponentUpdate: function(newProps, newState) { - if (Object.keys(newProps).some((k) => newProps[k] !== this.props[k])) { - return true; - } - if (Object.keys(newState).some((k) => newState[k] !== this.state[k])) { - return true; - } - return false; - }, - - _onStatusMessageCommitted() { - // The status message `User` object has observed a message change. - this.setState({ - statusMessage: this._getStatusMessage(), - }); - }, - - onClick: function(ev) { - if (this.props.onClick) { - this.props.onClick(this.props.room.roomId, ev); - } - }, - - onMouseEnter: function() { - this.setState( { hover: true }); - this.badgeOnMouseEnter(); - }, - - onMouseLeave: function() { - this.setState( { hover: false }); - this.badgeOnMouseLeave(); - }, - - badgeOnMouseEnter: function() { - // Only allow non-guests to access the context menu - // and only change it if it needs to change - if (!MatrixClientPeg.get().isGuest() && !this.state.badgeHover) { - this.setState( { badgeHover: true } ); - } - }, - - badgeOnMouseLeave: function() { - this.setState( { badgeHover: false } ); - }, - - _showContextMenu: function(boundingClientRect) { - // Only allow non-guests to access the context menu - if (MatrixClientPeg.get().isGuest()) return; - - const state = { - contextMenuPosition: boundingClientRect, - }; - - // If the badge is clicked, then no longer show tooltip - if (this.props.collapsed) { - state.hover = false; - } - - this.setState(state); - }, - - onContextMenuButtonClick: function(e) { - // Prevent the RoomTile onClick event firing as well - e.stopPropagation(); - e.preventDefault(); - - this._showContextMenu(e.target.getBoundingClientRect()); - }, - - onContextMenu: function(e) { - // Prevent the native context menu - e.preventDefault(); - - this._showContextMenu({ - right: e.clientX, - top: e.clientY, - height: 0, - }); - }, - - closeMenu: function() { - this.setState({ - contextMenuPosition: null, - }); - this.props.refreshSubList(); - }, - - render: function() { - const isInvite = this.props.room.getMyMembership() === "invite"; - const notificationCount = this.props.notificationCount; - // var highlightCount = this.props.room.getUnreadNotificationCount("highlight"); - - const notifBadges = notificationCount > 0 && RoomNotifs.shouldShowNotifBadge(this.state.notifState); - const mentionBadges = this.props.highlight && RoomNotifs.shouldShowMentionBadge(this.state.notifState); - const badges = notifBadges || mentionBadges; - - let subtext = null; - if (this._shouldShowStatusMessage()) { - subtext = this.state.statusMessage; - } - - const isMenuDisplayed = Boolean(this.state.contextMenuPosition); - - const dmUserId = DMRoomMap.shared().getUserIdForRoomId(this.props.room.roomId); - - const classes = classNames({ - 'mx_RoomTile': true, - 'mx_RoomTile_selected': this.state.selected, - 'mx_RoomTile_unread': this.props.unread, - 'mx_RoomTile_unreadNotify': notifBadges, - 'mx_RoomTile_highlight': mentionBadges, - 'mx_RoomTile_invited': isInvite, - 'mx_RoomTile_menuDisplayed': isMenuDisplayed, - 'mx_RoomTile_noBadges': !badges, - 'mx_RoomTile_transparent': this.props.transparent, - 'mx_RoomTile_hasSubtext': subtext && !this.props.collapsed, - }); - - const avatarClasses = classNames({ - 'mx_RoomTile_avatar': true, - }); - - const badgeClasses = classNames({ - 'mx_RoomTile_badge': true, - 'mx_RoomTile_badgeButton': this.state.badgeHover || isMenuDisplayed, - }); - - let name = this.state.roomName; - if (typeof name !== 'string') name = ''; - name = name.replace(":", ":\u200b"); // add a zero-width space to allow linewrapping after the colon - - let badge; - if (badges) { - const limitedCount = FormattingUtils.formatCount(notificationCount); - const badgeContent = notificationCount ? limitedCount : '!'; - badge =
{ badgeContent }
; - } - - let label; - let subtextLabel; - let tooltip; - if (!this.props.collapsed) { - const nameClasses = classNames({ - 'mx_RoomTile_name': true, - 'mx_RoomTile_invite': this.props.isInvite, - 'mx_RoomTile_badgeShown': badges || this.state.badgeHover || isMenuDisplayed, - }); - - subtextLabel = subtext ? { subtext } : null; - // XXX: this is a workaround for Firefox giving this div a tabstop :( [tabIndex] - label =
{ name }
; - } else if (this.state.hover) { - const Tooltip = sdk.getComponent("elements.Tooltip"); - tooltip = ; - } - - //var incomingCallBox; - //if (this.props.incomingCall) { - // var IncomingCallBox = sdk.getComponent("voip.IncomingCallBox"); - // incomingCallBox = ; - //} - - const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); - - let contextMenuButton; - if (!MatrixClientPeg.get().isGuest()) { - contextMenuButton = ( - - ); - } - - const RoomAvatar = sdk.getComponent('avatars.RoomAvatar'); - - let ariaLabel = name; - - let dmOnline; - const { room } = this.props; - const member = room.getMember(dmUserId); - if (member && member.membership === "join" && room.getJoinedMemberCount() === 2) { - const UserOnlineDot = sdk.getComponent('rooms.UserOnlineDot'); - dmOnline = ; - } - - // The following labels are written in such a fashion to increase screen reader efficiency (speed). - if (notifBadges && mentionBadges && !isInvite) { - ariaLabel += " " + _t("%(count)s unread messages including mentions.", { - count: notificationCount, - }); - } else if (notifBadges) { - ariaLabel += " " + _t("%(count)s unread messages.", { count: notificationCount }); - } else if (mentionBadges && !isInvite) { - ariaLabel += " " + _t("Unread mentions."); - } else if (this.props.unread) { - ariaLabel += " " + _t("Unread messages."); - } - - let contextMenu; - if (isMenuDisplayed) { - const RoomTileContextMenu = sdk.getComponent('context_menus.RoomTileContextMenu'); - contextMenu = ( - - - - ); - } - - let privateIcon = null; - if (this.state.joinRule === "invite" && !dmUserId) { - privateIcon = ; - } - - let e2eIcon = null; - if (this.state.e2eStatus) { - e2eIcon = ; - } - - return - - {({onFocus, isActive, ref}) => - -
-
- - { e2eIcon } -
-
- { privateIcon } -
-
- { label } - { subtextLabel } -
- { dmOnline } - { contextMenuButton } - { badge } -
- { /* { incomingCallBox } */ } - { tooltip } -
- } -
- - { contextMenu } -
; - }, -}); diff --git a/src/components/views/rooms/UserOnlineDot.js b/src/components/views/rooms/UserOnlineDot.js deleted file mode 100644 index 426dd1bf64..0000000000 --- a/src/components/views/rooms/UserOnlineDot.js +++ /dev/null @@ -1,48 +0,0 @@ -/* -Copyright 2019 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React, {useContext, useEffect, useMemo, useState, useCallback} from "react"; -import PropTypes from "prop-types"; - -import {useEventEmitter} from "../../../hooks/useEventEmitter"; -import MatrixClientContext from "../../../contexts/MatrixClientContext"; - -const UserOnlineDot = ({userId}) => { - const cli = useContext(MatrixClientContext); - const user = useMemo(() => cli.getUser(userId), [cli, userId]); - - const [isOnline, setIsOnline] = useState(false); - - // Recheck if the user or client changes - useEffect(() => { - setIsOnline(user && (user.currentlyActive || user.presence === "online")); - }, [cli, user]); - // Recheck also if we receive a User.currentlyActive event - const currentlyActiveHandler = useCallback((ev) => { - const content = ev.getContent(); - setIsOnline(content.currently_active || content.presence === "online"); - }, []); - useEventEmitter(user, "User.currentlyActive", currentlyActiveHandler); - useEventEmitter(user, "User.presence", currentlyActiveHandler); - - return isOnline ? : null; -}; - -UserOnlineDot.propTypes = { - userId: PropTypes.string.isRequired, -}; - -export default UserOnlineDot; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 360a29dc16..60ff5d8c05 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1141,7 +1141,6 @@ "Seen by %(displayName)s (%(userName)s) at %(dateTime)s": "Seen by %(displayName)s (%(userName)s) at %(dateTime)s", "Replying": "Replying", "Room %(name)s": "Room %(name)s", - "Recent rooms": "Recent rooms", "No recently visited rooms": "No recently visited rooms", "No rooms to show": "No rooms to show", "Unnamed room": "Unnamed room", @@ -1154,17 +1153,15 @@ "Forget room": "Forget room", "Search": "Search", "Share room": "Share room", - "Community Invites": "Community Invites", "Invites": "Invites", "Favourites": "Favourites", - "Direct Messages": "Direct Messages", + "People": "People", "Start chat": "Start chat", "Rooms": "Rooms", "Create room": "Create room", "Low priority": "Low priority", - "Historical": "Historical", "System Alerts": "System Alerts", - "People": "People", + "Historical": "Historical", "This room": "This room", "Joining room …": "Joining room …", "Loading …": "Loading …", @@ -1220,13 +1217,6 @@ "Add room": "Add room", "Show %(count)s more|other": "Show %(count)s more", "Show %(count)s more|one": "Show %(count)s more", - "Options": "Options", - "%(count)s unread messages including mentions.|other": "%(count)s unread messages including mentions.", - "%(count)s unread messages including mentions.|one": "1 unread mention.", - "%(count)s unread messages.|other": "%(count)s unread messages.", - "%(count)s unread messages.|one": "1 unread message.", - "Unread mentions.": "Unread mentions.", - "Unread messages.": "Unread messages.", "Use default": "Use default", "All messages": "All messages", "Mentions & Keywords": "Mentions & Keywords", @@ -1236,6 +1226,11 @@ "Leave Room": "Leave Room", "Forget Room": "Forget Room", "Room options": "Room options", + "%(count)s unread messages including mentions.|other": "%(count)s unread messages including mentions.", + "%(count)s unread messages including mentions.|one": "1 unread mention.", + "%(count)s unread messages.|other": "%(count)s unread messages.", + "%(count)s unread messages.|one": "1 unread message.", + "Unread messages.": "Unread messages.", "This room is public": "This room is public", "Away": "Away", "Add a topic": "Add a topic", @@ -1333,6 +1328,7 @@ "Invite": "Invite", "Share Link to User": "Share Link to User", "Direct message": "Direct message", + "Options": "Options", "Demote yourself?": "Demote yourself?", "You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.": "You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.", "Demote": "Demote", @@ -1724,6 +1720,7 @@ "Recent Conversations": "Recent Conversations", "Suggestions": "Suggestions", "Recently Direct Messaged": "Recently Direct Messaged", + "Direct Messages": "Direct Messages", "Start a conversation with someone using their name, username (like ) or email address.": "Start a conversation with someone using their name, username (like ) or email address.", "Go": "Go", "Invite someone using their name, username (like ), email address or share this room.": "Invite someone using their name, username (like ), email address or share this room.", @@ -2057,9 +2054,6 @@ "Send a Direct Message": "Send a Direct Message", "Explore Public Rooms": "Explore Public Rooms", "Create a Group Chat": "Create a Group Chat", - "Explore": "Explore", - "Filter": "Filter", - "Filter rooms…": "Filter rooms…", "Explore rooms": "Explore rooms", "Failed to reject invitation": "Failed to reject invitation", "This room is not public. You will not be able to rejoin without an invite.": "This room is not public. You will not be able to rejoin without an invite.", @@ -2135,7 +2129,6 @@ "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.", "Tried to load a specific point in this room's timeline, but was unable to find it.": "Tried to load a specific point in this room's timeline, but was unable to find it.", "Failed to load timeline position": "Failed to load timeline position", - "Your profile": "Your profile", "Uploading %(filename)s and %(count)s others|other": "Uploading %(filename)s and %(count)s others", "Uploading %(filename)s and %(count)s others|zero": "Uploading %(filename)s", "Uploading %(filename)s and %(count)s others|one": "Uploading %(filename)s and %(count)s other", From 1810711380a87d420394dc7b3457f9752936520d Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 17 Jul 2020 14:25:09 -0600 Subject: [PATCH 03/35] Dismantle usage of the proxy store class --- src/actions/RoomListActions.ts | 9 +++++---- src/components/structures/LoggedInView.tsx | 12 ++++++------ src/components/views/dialogs/InviteDialog.js | 5 ++--- .../tabs/user/PreferencesUserSettingsTab.js | 18 +----------------- src/stores/BreadcrumbsStore.ts | 10 ---------- src/stores/CustomRoomTagStore.js | 19 +++++++++---------- src/stores/room-list/MessagePreviewStore.ts | 4 ---- 7 files changed, 23 insertions(+), 54 deletions(-) diff --git a/src/actions/RoomListActions.ts b/src/actions/RoomListActions.ts index e15e1b0c65..d8c6723d7b 100644 --- a/src/actions/RoomListActions.ts +++ b/src/actions/RoomListActions.ts @@ -24,7 +24,8 @@ import * as sdk from '../index'; import { MatrixClient } from "matrix-js-sdk/src/client"; import { Room } from "matrix-js-sdk/src/models/room"; import { AsyncActionPayload } from "../dispatcher/payloads"; -import { RoomListStoreTempProxy } from "../stores/room-list/RoomListStoreTempProxy"; +import RoomListStore from "../stores/room-list/RoomListStore2"; +import { SortAlgorithm } from "../stores/room-list/algorithms/models"; export default class RoomListActions { /** @@ -51,9 +52,9 @@ export default class RoomListActions { let metaData = null; // Is the tag ordered manually? - if (newTag && !newTag.match(/^(m\.lowpriority|im\.vector\.fake\.(invite|recent|direct|archived))$/)) { - const lists = RoomListStoreTempProxy.getRoomLists(); - const newList = [...lists[newTag]]; + const store = RoomListStore.instance; + if (newTag && store.getTagSorting(newTag) === SortAlgorithm.Manual) { + const newList = [...store.orderedLists[newTag]]; newList.sort((a, b) => a.tags[newTag].order - b.tags[newTag].order); diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index d4b0f7902a..9b7a87c1dc 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -40,7 +40,6 @@ import * as KeyboardShortcuts from "../../accessibility/KeyboardShortcuts"; import HomePage from "./HomePage"; import ResizeNotifier from "../../utils/ResizeNotifier"; import PlatformPeg from "../../PlatformPeg"; -import { RoomListStoreTempProxy } from "../../stores/room-list/RoomListStoreTempProxy"; import { DefaultTagID } from "../../stores/room-list/models"; import { showToast as showSetPasswordToast, @@ -54,6 +53,7 @@ import { Action } from "../../dispatcher/actions"; import LeftPanel2 from "./LeftPanel2"; import CallContainer from '../views/voip/CallContainer'; import { ViewRoomDeltaPayload } from "../../dispatcher/payloads/ViewRoomDeltaPayload"; +import RoomListStore from "../../stores/room-list/RoomListStore2"; // We need to fetch each pinned message individually (if we don't already have it) // so each pinned message may trigger a request. Limit the number per room for sanity. @@ -308,8 +308,8 @@ class LoggedInView extends React.Component { }; onRoomStateEvents = (ev, state) => { - const roomLists = RoomListStoreTempProxy.getRoomLists(); - if (roomLists[DefaultTagID.ServerNotice] && roomLists[DefaultTagID.ServerNotice].some(r => r.roomId === ev.getRoomId())) { + const serverNoticeList = RoomListStore.instance.orderedLists[DefaultTagID.ServerNotice]; + if (serverNoticeList && serverNoticeList.some(r => r.roomId === ev.getRoomId())) { this._updateServerNoticeEvents(); } }; @@ -328,11 +328,11 @@ class LoggedInView extends React.Component { } _updateServerNoticeEvents = async () => { - const roomLists = RoomListStoreTempProxy.getRoomLists(); - if (!roomLists[DefaultTagID.ServerNotice]) return []; + const serverNoticeList = RoomListStore.instance.orderedLists[DefaultTagID.ServerNotice]; + if (!serverNoticeList) return []; const events = []; - for (const room of roomLists[DefaultTagID.ServerNotice]) { + for (const room of serverNoticeList) { const pinStateEvent = room.currentState.getStateEvents("m.room.pinned_events", ""); if (!pinStateEvent || !pinStateEvent.getContent().pinned) continue; diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index 7ac9e21518..0c1e0c5387 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -35,8 +35,8 @@ import createRoom, {canEncryptToAllUsers, privateShouldBeEncrypted} from "../../ import {inviteMultipleToRoom} from "../../../RoomInvite"; import {Key} from "../../../Keyboard"; import {Action} from "../../../dispatcher/actions"; -import {RoomListStoreTempProxy} from "../../../stores/room-list/RoomListStoreTempProxy"; import {DefaultTagID} from "../../../stores/room-list/models"; +import RoomListStore from "../../../stores/room-list/RoomListStore2"; export const KIND_DM = "dm"; export const KIND_INVITE = "invite"; @@ -346,8 +346,7 @@ export default class InviteDialog extends React.PureComponent { // Also pull in all the rooms tagged as DefaultTagID.DM so we don't miss anything. Sometimes the // room list doesn't tag the room for the DMRoomMap, but does for the room list. - const taggedRooms = RoomListStoreTempProxy.getRoomLists(); - const dmTaggedRooms = taggedRooms[DefaultTagID.DM]; + const dmTaggedRooms = RoomListStore.instance.orderedLists[DefaultTagID.DM]; const myUserId = MatrixClientPeg.get().getUserId(); for (const dmRoom of dmTaggedRooms) { const otherMembers = dmRoom.getJoinedMembers().filter(u => u.userId !== myUserId); diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index abe6b48712..fe60a4a179 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -23,28 +23,12 @@ import SettingsStore from "../../../../../settings/SettingsStore"; import Field from "../../../elements/Field"; import * as sdk from "../../../../.."; import PlatformPeg from "../../../../../PlatformPeg"; -import {RoomListStoreTempProxy} from "../../../../../stores/room-list/RoomListStoreTempProxy"; export default class PreferencesUserSettingsTab extends React.Component { static ROOM_LIST_SETTINGS = [ - 'RoomList.orderAlphabetically', - 'RoomList.orderByImportance', 'breadcrumbs', ]; - // 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/14367 - static eligibleRoomListSettings = () => { - if (RoomListStoreTempProxy.isUsingNewStore()) { - return PreferencesUserSettingsTab.ROOM_LIST_2_SETTINGS; - } - return PreferencesUserSettingsTab.ROOM_LIST_SETTINGS; - }; - static COMPOSER_SETTINGS = [ 'MessageComposerInput.autoReplaceEmoji', 'MessageComposerInput.suggestEmoji', @@ -189,7 +173,7 @@ export default class PreferencesUserSettingsTab extends React.Component {
{_t("Room list")} - {this._renderGroup(PreferencesUserSettingsTab.eligibleRoomListSettings())} + {this._renderGroup(PreferencesUserSettingsTab.ROOM_LIST_SETTINGS)}
diff --git a/src/stores/BreadcrumbsStore.ts b/src/stores/BreadcrumbsStore.ts index 5639a9104c..2c6fd320a6 100644 --- a/src/stores/BreadcrumbsStore.ts +++ b/src/stores/BreadcrumbsStore.ts @@ -20,7 +20,6 @@ import { ActionPayload } from "../dispatcher/payloads"; 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 @@ -62,9 +61,6 @@ 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/14367 - if (!RoomListStoreTempProxy.isUsingNewStore()) return; - if (payload.action === 'setting_updated') { if (payload.settingName === 'breadcrumb_rooms') { await this.updateRooms(); @@ -85,9 +81,6 @@ 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/14367 - if (!RoomListStoreTempProxy.isUsingNewStore()) return; - await this.updateRooms(); await this.updateState({enabled: SettingsStore.getValue("breadcrumbs", null)}); @@ -96,9 +89,6 @@ 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/14367 - if (!RoomListStoreTempProxy.isUsingNewStore()) return; - this.matrixClient.removeListener("Room.myMembership", this.onMyMembership); this.matrixClient.removeListener("Room", this.onRoom); } diff --git a/src/stores/CustomRoomTagStore.js b/src/stores/CustomRoomTagStore.js index 48c80294b4..ed96e40dfd 100644 --- a/src/stores/CustomRoomTagStore.js +++ b/src/stores/CustomRoomTagStore.js @@ -18,8 +18,9 @@ import * as RoomNotifs from '../RoomNotifs'; import EventEmitter from 'events'; import { throttle } from "lodash"; import SettingsStore from "../settings/SettingsStore"; -import {RoomListStoreTempProxy} from "./room-list/RoomListStoreTempProxy"; +import RoomListStore, {LISTS_UPDATE_EVENT} from "./room-list/RoomListStore2"; +// TODO: All of this needs updating for new custom tags: https://github.com/vector-im/riot-web/issues/14091 const STANDARD_TAGS_REGEX = /^(m\.(favourite|lowpriority|server_notice)|im\.vector\.fake\.(invite|recent|direct|archived))$/; function commonPrefix(a, b) { @@ -60,9 +61,7 @@ class CustomRoomTagStore extends EventEmitter { trailing: true, }, ); - this._roomListStoreToken = RoomListStoreTempProxy.addListener(() => { - this._setState({tags: this._getUpdatedTags()}); - }); + RoomListStore.instance.on(LISTS_UPDATE_EVENT,this._onListsUpdated); dis.register(payload => this._onDispatch(payload)); } @@ -85,7 +84,7 @@ class CustomRoomTagStore extends EventEmitter { } getSortedTags() { - const roomLists = RoomListStoreTempProxy.getRoomLists(); + const roomLists = RoomListStore.instance.orderedLists; const tagNames = Object.keys(this._state.tags).sort(); const prefixes = tagNames.map((name, i) => { @@ -109,6 +108,9 @@ class CustomRoomTagStore extends EventEmitter { }); } + _onListsUpdated = () => { + this._setState({tags: this._getUpdatedTags()}); + }; _onDispatch(payload) { switch (payload.action) { @@ -126,10 +128,7 @@ class CustomRoomTagStore extends EventEmitter { case 'on_logged_out': { // we assume to always have a tags object in the state this._state = {tags: {}}; - if (this._roomListStoreToken) { - this._roomListStoreToken.remove(); - this._roomListStoreToken = null; - } + RoomListStore.instance.off(LISTS_UPDATE_EVENT,this._onListsUpdated); } break; } @@ -140,7 +139,7 @@ class CustomRoomTagStore extends EventEmitter { return; } - const newTagNames = Object.keys(RoomListStoreTempProxy.getRoomLists()) + const newTagNames = Object.keys(RoomListStore.instance.orderedLists) .filter((tagName) => { return !tagName.match(STANDARD_TAGS_REGEX); }).sort(); diff --git a/src/stores/room-list/MessagePreviewStore.ts b/src/stores/room-list/MessagePreviewStore.ts index ea7fa830cd..e5ef735927 100644 --- a/src/stores/room-list/MessagePreviewStore.ts +++ b/src/stores/room-list/MessagePreviewStore.ts @@ -18,7 +18,6 @@ import { Room } from "matrix-js-sdk/src/models/room"; import { ActionPayload } from "../../dispatcher/payloads"; import { AsyncStoreWithClient } from "../AsyncStoreWithClient"; import defaultDispatcher from "../../dispatcher/dispatcher"; -import { RoomListStoreTempProxy } from "./RoomListStoreTempProxy"; import { MessageEventPreview } from "./previews/MessageEventPreview"; import { NameEventPreview } from "./previews/NameEventPreview"; import { TagID } from "./models"; @@ -192,9 +191,6 @@ 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/14367 - if (!RoomListStoreTempProxy.isUsingNewStore()) return; - if (payload.action === 'MatrixActions.Room.timeline' || payload.action === 'MatrixActions.Event.decrypted') { const event = payload.event; // TODO: Type out the dispatcher if (!Object.keys(this.state).includes(event.getRoomId())) return; // not important From 62b58e18e9f977dbda83c209aa1641ff4bc81175 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 17 Jul 2020 14:25:56 -0600 Subject: [PATCH 04/35] Remove the temporary room list store proxy --- .../room-list/RoomListStoreTempProxy.ts | 61 ------------------- 1 file changed, 61 deletions(-) delete mode 100644 src/stores/room-list/RoomListStoreTempProxy.ts diff --git a/src/stores/room-list/RoomListStoreTempProxy.ts b/src/stores/room-list/RoomListStoreTempProxy.ts deleted file mode 100644 index 55d710004e..0000000000 --- a/src/stores/room-list/RoomListStoreTempProxy.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import SettingsStore from "../../settings/SettingsStore"; -import RoomListStore from "./RoomListStore2"; -import OldRoomListStore from "../RoomListStore"; -import { UPDATE_EVENT } from "../AsyncStore"; -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/14367 - */ -export class RoomListStoreTempProxy { - public static isUsingNewStore(): boolean { - return true; - } - - public static addListener(handler: () => void): RoomListStoreTempToken { - if (RoomListStoreTempProxy.isUsingNewStore()) { - const offFn = () => RoomListStore.instance.off(UPDATE_EVENT, handler); - RoomListStore.instance.on(UPDATE_EVENT, handler); - return new RoomListStoreTempToken(offFn); - } else { - const token = OldRoomListStore.addListener(handler); - return new RoomListStoreTempToken(() => token.remove()); - } - } - - public static getRoomLists(): ITagMap { - if (RoomListStoreTempProxy.isUsingNewStore()) { - return RoomListStore.instance.orderedLists; - } else { - return OldRoomListStore.getRoomLists(); - } - } -} - -export class RoomListStoreTempToken { - constructor(private offFn: () => void) { - } - - public remove(): void { - this.offFn(); - } -} From 1f9c07861eb65970b535a6e65eb166798bec7ad0 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 17 Jul 2020 14:26:56 -0600 Subject: [PATCH 05/35] Remove the old room list store --- src/actions/RoomListActions.ts | 12 +- src/stores/RoomListStore.js | 805 --------------------------------- 2 files changed, 6 insertions(+), 811 deletions(-) delete mode 100644 src/stores/RoomListStore.js diff --git a/src/actions/RoomListActions.ts b/src/actions/RoomListActions.ts index d8c6723d7b..f12d4d3084 100644 --- a/src/actions/RoomListActions.ts +++ b/src/actions/RoomListActions.ts @@ -16,7 +16,6 @@ limitations under the License. */ import { asyncAction } from './actionCreators'; -import { TAG_DM } from '../stores/RoomListStore'; import Modal from '../Modal'; import * as Rooms from '../Rooms'; import { _t } from '../languageHandler'; @@ -26,6 +25,7 @@ import { Room } from "matrix-js-sdk/src/models/room"; import { AsyncActionPayload } from "../dispatcher/payloads"; import RoomListStore from "../stores/room-list/RoomListStore2"; import { SortAlgorithm } from "../stores/room-list/algorithms/models"; +import { DefaultTagID } from "../stores/room-list/models"; export default class RoomListActions { /** @@ -82,11 +82,11 @@ export default class RoomListActions { const roomId = room.roomId; // Evil hack to get DMs behaving - if ((oldTag === undefined && newTag === TAG_DM) || - (oldTag === TAG_DM && newTag === undefined) + if ((oldTag === undefined && newTag === DefaultTagID.DM) || + (oldTag === DefaultTagID.DM && newTag === undefined) ) { return Rooms.guessAndSetDMRoom( - room, newTag === TAG_DM, + room, newTag === DefaultTagID.DM, ).catch((err) => { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); console.error("Failed to set direct chat tag " + err); @@ -103,7 +103,7 @@ export default class RoomListActions { // but we avoid ever doing a request with TAG_DM. // // if we moved lists, remove the old tag - if (oldTag && oldTag !== TAG_DM && + if (oldTag && oldTag !== DefaultTagID.DM && hasChangedSubLists ) { const promiseToDelete = matrixClient.deleteRoomTag( @@ -121,7 +121,7 @@ export default class RoomListActions { } // if we moved lists or the ordering changed, add the new tag - if (newTag && newTag !== TAG_DM && + if (newTag && newTag !== DefaultTagID.DM && (hasChangedSubLists || metaData) ) { // metaData is the body of the PUT to set the tag, so it must diff --git a/src/stores/RoomListStore.js b/src/stores/RoomListStore.js deleted file mode 100644 index 6c18aa83ad..0000000000 --- a/src/stores/RoomListStore.js +++ /dev/null @@ -1,805 +0,0 @@ -/* -Copyright 2018, 2019 New Vector Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ -import {Store} from 'flux/utils'; -import dis from '../dispatcher/dispatcher'; -import DMRoomMap from '../utils/DMRoomMap'; -import * as Unread from '../Unread'; -import SettingsStore from "../settings/SettingsStore"; - -/* -Room sorting algorithm: -* Always prefer to have red > grey > bold > idle -* The room being viewed should be sticky (not jump down to the idle list) -* When switching to a new room, sort the last sticky room to the top of the idle list. - -The approach taken by the store is to generate an initial representation of all the -tagged lists (accepting that it'll take a little bit longer to calculate) and make -small changes to that over time. This results in quick changes to the room list while -also having update operations feel more like popping/pushing to a stack. - */ - -const CATEGORY_RED = "red"; // Mentions in the room -const CATEGORY_GREY = "grey"; // Unread notified messages (not mentions) -const CATEGORY_BOLD = "bold"; // Unread messages (not notified, 'Mentions Only' rooms) -const CATEGORY_IDLE = "idle"; // Nothing of interest - -export const TAG_DM = "im.vector.fake.direct"; - -/** - * Identifier for manual sorting behaviour: sort by the user defined order. - * @type {string} - */ -export const ALGO_MANUAL = "manual"; - -/** - * Identifier for alphabetic sorting behaviour: sort by the room name alphabetically first. - * @type {string} - */ -export const ALGO_ALPHABETIC = "alphabetic"; - -/** - * Identifier for classic sorting behaviour: sort by the most recent message first. - * @type {string} - */ -export const ALGO_RECENT = "recent"; - -const CATEGORY_ORDER = [CATEGORY_RED, CATEGORY_GREY, CATEGORY_BOLD, CATEGORY_IDLE]; - -const getListAlgorithm = (listKey, settingAlgorithm) => { - // apply manual sorting only to m.favourite, otherwise respect the global setting - // all the known tags are listed explicitly here to simplify future changes - switch (listKey) { - case "im.vector.fake.invite": - case "im.vector.fake.recent": - case "im.vector.fake.archived": - case "m.lowpriority": - case TAG_DM: - return settingAlgorithm; - - case "m.favourite": - default: // custom-tags - return ALGO_MANUAL; - } -}; - -const knownLists = new Set([ - "m.favourite", - "im.vector.fake.invite", - "im.vector.fake.recent", - "im.vector.fake.archived", - "m.lowpriority", - TAG_DM, -]); - -/** - * A class for storing application state for categorising rooms in - * the RoomList. - */ -class RoomListStore extends Store { - constructor() { - super(dis); - - this.disabled = true; - this._init(); - this._getManualComparator = this._getManualComparator.bind(this); - this._recentsComparator = this._recentsComparator.bind(this); - } - - /** - * Changes the sorting algorithm used by the RoomListStore. - * @param {string} algorithm The new algorithm to use. Should be one of the ALGO_* constants. - * @param {boolean} orderImportantFirst Whether to sort by categories of importance - */ - updateSortingAlgorithm(algorithm, orderImportantFirst) { - // Dev note: We only have two algorithms at the moment, but it isn't impossible that we want - // multiple in the future. Also constants make things slightly clearer. - console.log("Updating room sorting algorithm: ", {algorithm, orderImportantFirst}); - this._setState({algorithm, orderImportantFirst}); - - // Trigger a resort of the entire list to reflect the change in algorithm - this._generateInitialRoomLists(); - } - - _init() { - if (this.disabled) return; - - // Initialise state - const defaultLists = { - "m.server_notice": [/* { room: js-sdk room, category: string } */], - "im.vector.fake.invite": [], - "m.favourite": [], - "im.vector.fake.recent": [], - [TAG_DM]: [], - "m.lowpriority": [], - "im.vector.fake.archived": [], - }; - this._state = { - // The rooms in these arrays are ordered according to either the - // 'recents' behaviour or 'manual' behaviour. - lists: defaultLists, - presentationLists: defaultLists, // like `lists`, but with arrays of rooms instead - ready: false, - stickyRoomId: null, - algorithm: ALGO_RECENT, - orderImportantFirst: false, - }; - - SettingsStore.monitorSetting('RoomList.orderAlphabetically', null); - SettingsStore.monitorSetting('RoomList.orderByImportance', null); - SettingsStore.monitorSetting('feature_custom_tags', null); - } - - _setState(newState) { - if (this.disabled) return; - - // If we're changing the lists, transparently change the presentation lists (which - // is given to requesting components). This dramatically simplifies our code elsewhere - // while also ensuring we don't need to update all the calling components to support - // categories. - if (newState['lists']) { - const presentationLists = {}; - for (const key of Object.keys(newState['lists'])) { - presentationLists[key] = newState['lists'][key].map((e) => e.room); - } - newState['presentationLists'] = presentationLists; - } - this._state = Object.assign(this._state, newState); - this.__emitChange(); - } - - __onDispatch(payload) { - if (this.disabled) return; - - const logicallyReady = this._matrixClient && this._state.ready; - switch (payload.action) { - case 'setting_updated': { - if (!logicallyReady) break; - - switch (payload.settingName) { - case "RoomList.orderAlphabetically": - this.updateSortingAlgorithm(payload.newValue ? ALGO_ALPHABETIC : ALGO_RECENT, - this._state.orderImportantFirst); - break; - case "RoomList.orderByImportance": - this.updateSortingAlgorithm(this._state.algorithm, payload.newValue); - break; - case "feature_custom_tags": - this._setState({tagsEnabled: payload.newValue}); - this._generateInitialRoomLists(); // Tags means we have to start from scratch - break; - } - } - break; - // Initialise state after initial sync - case 'MatrixActions.sync': { - if (!(payload.prevState !== 'PREPARED' && payload.state === 'PREPARED')) { - break; - } - - if (this.disabled) return; - - // Always ensure that we set any state needed for settings here. It is possible that - // setting updates trigger on startup before we are ready to sync, so we want to make - // sure that the right state is in place before we actually react to those changes. - - this._setState({tagsEnabled: SettingsStore.isFeatureEnabled("feature_custom_tags")}); - - this._matrixClient = payload.matrixClient; - - const orderByImportance = SettingsStore.getValue("RoomList.orderByImportance"); - const orderAlphabetically = SettingsStore.getValue("RoomList.orderAlphabetically"); - this.updateSortingAlgorithm(orderAlphabetically ? ALGO_ALPHABETIC : ALGO_RECENT, orderByImportance); - } - break; - case 'MatrixActions.Room.receipt': { - if (!logicallyReady) break; - - // First see if the receipt event is for our own user. If it was, trigger - // a room update (we probably read the room on a different device). - const myUserId = this._matrixClient.getUserId(); - for (const eventId of Object.keys(payload.event.getContent())) { - const receiptUsers = Object.keys(payload.event.getContent()[eventId]['m.read'] || {}); - if (receiptUsers.includes(myUserId)) { - this._roomUpdateTriggered(payload.room.roomId); - return; - } - } - } - break; - case 'MatrixActions.Room.tags': { - if (!logicallyReady) break; - // TODO: Figure out which rooms changed in the tag and only change those. - // This is very blunt and wipes out the sticky room stuff - this._generateInitialRoomLists(); - } - break; - case 'MatrixActions.Room.timeline': { - if (!logicallyReady || - !payload.isLiveEvent || - !payload.isLiveUnfilteredRoomTimelineEvent || - !this._eventTriggersRecentReorder(payload.event) || - this._state.algorithm !== ALGO_RECENT - ) { - break; - } - - this._roomUpdateTriggered(payload.event.getRoomId()); - } - break; - // When an event is decrypted, it could mean we need to reorder the room - // list because we now know the type of the event. - case 'MatrixActions.Event.decrypted': { - if (!logicallyReady) break; - - const roomId = payload.event.getRoomId(); - - // We may have decrypted an event without a roomId (e.g to_device) - if (!roomId) break; - - const room = this._matrixClient.getRoom(roomId); - - // We somehow decrypted an event for a room our client is unaware of - if (!room) break; - - const liveTimeline = room.getLiveTimeline(); - const eventTimeline = room.getTimelineForEvent(payload.event.getId()); - - // Either this event was not added to the live timeline (e.g. pagination) - // or it doesn't affect the ordering of the room list. - if (liveTimeline !== eventTimeline || !this._eventTriggersRecentReorder(payload.event)) { - break; - } - - this._roomUpdateTriggered(roomId); - } - break; - case 'MatrixActions.accountData': { - if (!logicallyReady) break; - if (payload.event_type !== 'm.direct') break; - // TODO: Figure out which rooms changed in the direct chat and only change those. - // This is very blunt and wipes out the sticky room stuff - this._generateInitialRoomLists(); - } - break; - case 'MatrixActions.Room.myMembership': { - if (!logicallyReady) break; - this._roomUpdateTriggered(payload.room.roomId, true); - } - break; - // This could be a new room that we've been invited to, joined or created - // we won't get a RoomMember.membership for these cases if we're not already - // a member. - case 'MatrixActions.Room': { - if (!logicallyReady) break; - this._roomUpdateTriggered(payload.room.roomId, true); - } - break; - // TODO: Re-enable optimistic updates when we support dragging again - // case 'RoomListActions.tagRoom.pending': { - // if (!logicallyReady) break; - // // XXX: we only show one optimistic update at any one time. - // // Ideally we should be making a list of in-flight requests - // // that are backed by transaction IDs. Until the js-sdk - // // supports this, we're stuck with only being able to use - // // the most recent optimistic update. - // console.log("!! Optimistic tag: ", payload); - // } - // break; - // case 'RoomListActions.tagRoom.failure': { - // if (!logicallyReady) break; - // // Reset state according to js-sdk - // console.log("!! Optimistic tag failure: ", payload); - // } - // break; - case 'on_client_not_viable': - case 'on_logged_out': { - // Reset state without pushing an update to the view, which generally assumes that - // the matrix client isn't `null` and so causing a re-render will cause NPEs. - this._init(); - this._matrixClient = null; - } - break; - case 'view_room': { - if (!logicallyReady) break; - - // Note: it is important that we set a new stickyRoomId before setting the old room - // to IDLE. If we don't, the wrong room gets counted as sticky. - const currentStickyId = this._state.stickyRoomId; - this._setState({stickyRoomId: payload.room_id}); - if (currentStickyId) { - this._setRoomCategory(this._matrixClient.getRoom(currentStickyId), CATEGORY_IDLE); - } - } - break; - } - } - - _roomUpdateTriggered(roomId, ignoreSticky) { - // We don't calculate categories for sticky rooms because we have a moderate - // interest in trying to maintain the category that they were last in before - // being artificially flagged as IDLE. Also, this reduces the amount of time - // we spend in _setRoomCategory ever so slightly. - if (this._state.stickyRoomId !== roomId || ignoreSticky) { - // Micro optimization: Only look up the room if we're confident we'll need it. - const room = this._matrixClient.getRoom(roomId); - if (!room) return; - - const category = this._calculateCategory(room); - this._setRoomCategory(room, category); - } - } - - _filterTags(tags) { - tags = tags ? Object.keys(tags) : []; - if (this._state.tagsEnabled) return tags; - return tags.filter((t) => knownLists.has(t)); - } - - _getRecommendedTagsForRoom(room) { - const tags = []; - - const myMembership = room.getMyMembership(); - if (myMembership === 'join' || myMembership === 'invite') { - // Stack the user's tags on top - tags.push(...this._filterTags(room.tags)); - - // Order matters here: The DMRoomMap updates before invites - // are accepted, so we check to see if the room is an invite - // first, then if it is a direct chat, and finally default - // to the "recents" list. - const dmRoomMap = DMRoomMap.shared(); - if (myMembership === 'invite') { - tags.push("im.vector.fake.invite"); - } else if (dmRoomMap.getUserIdForRoomId(room.roomId) && tags.length === 0) { - // We intentionally don't duplicate rooms in other tags into the people list - // as a feature. - tags.push(TAG_DM); - } else if (tags.length === 0) { - tags.push("im.vector.fake.recent"); - } - } else if (myMembership) { // null-guard as null means it was peeked - tags.push("im.vector.fake.archived"); - } - - - return tags; - } - - _slotRoomIntoList(room, category, tag, existingEntries, newList, lastTimestampFn) { - const targetCategoryIndex = CATEGORY_ORDER.indexOf(category); - - let categoryComparator = (a, b) => lastTimestampFn(a.room) >= lastTimestampFn(b.room); - const sortAlgorithm = getListAlgorithm(tag, this._state.algorithm); - if (sortAlgorithm === ALGO_RECENT) { - categoryComparator = (a, b) => this._recentsComparator(a, b, lastTimestampFn); - } else if (sortAlgorithm === ALGO_ALPHABETIC) { - categoryComparator = (a, b) => this._lexicographicalComparator(a, b); - } - - // The slotting algorithm works by trying to position the room in the most relevant - // category of the list (red > grey > etc). To accomplish this, we need to consider - // a couple cases: the category existing in the list but having other rooms in it and - // the case of the category simply not existing and needing to be started. In order to - // do this efficiently, we only want to iterate over the list once and solve our sorting - // problem as we go. - // - // Firstly, we'll remove any existing entry that references the room we're trying to - // insert. We don't really want to consider the old entry and want to recreate it. We - // also exclude the sticky (currently active) room from the categorization logic and - // let it pass through wherever it resides in the list: it shouldn't be moving around - // the list too much, so we want to keep it where it is. - // - // The case of the category we want existing is easy to handle: once we hit the category, - // find the room that has a most recent event later than our own and insert just before - // that (making us the more recent room). If we end up hitting the next category before - // we can slot the room in, insert the room at the top of the category as a fallback. We - // do this to ensure that the room doesn't go too far down the list given it was previously - // considered important (in the case of going down in category) or is now more important - // (suddenly becoming red, for instance). The boundary tracking is how we end up achieving - // this, as described in the next paragraphs. - // - // The other case of the category not already existing is a bit more complicated. We track - // the boundaries of each category relative to the list we're currently building so that - // when we miss the category we can insert the room at the right spot. Most importantly, we - // can't assume that the end of the list being built is the right spot because of the last - // paragraph's requirement: the room should be put to the top of a category if the category - // runs out of places to put it. - // - // All told, our tracking looks something like this: - // - // ------ A <- Category boundary (start of red) - // RED - // RED - // RED - // ------ B <- In this example, we have a grey room we want to insert. - // BOLD - // BOLD - // ------ C - // IDLE - // IDLE - // ------ D <- End of list - // - // Given that example, and our desire to insert a GREY room into the list, this iterates - // over the room list until it realizes that BOLD comes after GREY and we're no longer - // in the RED section. Because there's no rooms there, we simply insert there which is - // also a "category boundary". If we change the example to wanting to insert a BOLD room - // which can't be ordered by timestamp with the existing couple rooms, we would still make - // use of the boundary flag to insert at B before changing the boundary indicator to C. - - let desiredCategoryBoundaryIndex = 0; - let foundBoundary = false; - let pushedEntry = false; - - for (const entry of existingEntries) { - // We insert our own record as needed, so don't let the old one through. - if (entry.room.roomId === room.roomId) { - continue; - } - - // if the list is a recent list, and the room appears in this list, and we're - // not looking at a sticky room (sticky rooms have unreliable categories), try - // to slot the new room in - if (entry.room.roomId !== this._state.stickyRoomId && !pushedEntry) { - const entryCategoryIndex = CATEGORY_ORDER.indexOf(entry.category); - - // As per above, check if we're meeting that boundary we wanted to locate. - if (entryCategoryIndex >= targetCategoryIndex && !foundBoundary) { - desiredCategoryBoundaryIndex = newList.length - 1; - foundBoundary = true; - } - - // If we've hit the top of a boundary beyond our target category, insert at the top of - // the grouping to ensure the room isn't slotted incorrectly. Otherwise, try to insert - // based on most recent timestamp. - const changedBoundary = entryCategoryIndex > targetCategoryIndex; - const currentCategory = entryCategoryIndex === targetCategoryIndex; - if (changedBoundary || (currentCategory && categoryComparator({room}, entry) <= 0)) { - if (changedBoundary) { - // If we changed a boundary, then we've gone too far - go to the top of the last - // section instead. - newList.splice(desiredCategoryBoundaryIndex, 0, {room, category}); - } else { - // If we're ordering by timestamp, just insert normally - newList.push({room, category}); - } - pushedEntry = true; - } - } - - // Fall through and clone the list. - newList.push(entry); - } - - if (!pushedEntry && desiredCategoryBoundaryIndex >= 0) { - console.warn(`!! Room ${room.roomId} nearly lost: Ran off the end of ${tag}`); - console.warn(`!! Inserting at position ${desiredCategoryBoundaryIndex} with category ${category}`); - newList.splice(desiredCategoryBoundaryIndex, 0, {room, category}); - pushedEntry = true; - } - - return pushedEntry; - } - - _setRoomCategory(room, category) { - if (!room) return; // This should only happen in tests - - const listsClone = {}; - - // Micro optimization: Support lazily loading the last timestamp in a room - const timestampCache = {}; // {roomId => ts} - const lastTimestamp = (room) => { - if (!timestampCache[room.roomId]) { - timestampCache[room.roomId] = this._tsOfNewestEvent(room); - } - return timestampCache[room.roomId]; - }; - const targetTags = this._getRecommendedTagsForRoom(room); - const insertedIntoTags = []; - - // We need to make sure all the tags (lists) are updated with the room's new position. We - // generally only get called here when there's a new room to insert or a room has potentially - // changed positions within the list. - // - // We do all our checks by iterating over the rooms in the existing lists, trying to insert - // our room where we can. As a guiding principle, we should be removing the room from all - // tags, and insert the room into targetTags. We should perform the deletion before the addition - // where possible to keep a consistent state. By the end of this, targetTags should be the - // same as insertedIntoTags. - - for (const key of Object.keys(this._state.lists)) { - const shouldHaveRoom = targetTags.includes(key); - - // Speed optimization: Don't do complicated math if we don't have to. - if (!shouldHaveRoom) { - listsClone[key] = this._state.lists[key].filter((e) => e.room.roomId !== room.roomId); - } else if (getListAlgorithm(key, this._state.algorithm) === ALGO_MANUAL) { - // Manually ordered tags are sorted later, so for now we'll just clone the tag - // and add our room if needed - listsClone[key] = this._state.lists[key].filter((e) => e.room.roomId !== room.roomId); - listsClone[key].push({room, category}); - insertedIntoTags.push(key); - } else { - listsClone[key] = []; - - const pushedEntry = this._slotRoomIntoList( - room, category, key, this._state.lists[key], listsClone[key], lastTimestamp); - - if (!pushedEntry) { - // This should rarely happen: _slotRoomIntoList has several checks which attempt - // to make sure that a room is not lost in the list. If we do lose the room though, - // we shouldn't throw it on the floor and forget about it. Instead, we should insert - // it somewhere. We'll insert it at the top for a couple reasons: 1) it is probably - // an important room for the user and 2) if this does happen, we'd want a bug report. - console.warn(`!! Room ${room.roomId} nearly lost: Failed to find a position`); - console.warn(`!! Inserting at position 0 in the list and flagging as inserted`); - console.warn("!! Additional info: ", { - category, - key, - upToIndex: listsClone[key].length, - expectedCount: this._state.lists[key].length, - }); - listsClone[key].splice(0, 0, {room, category}); - } - insertedIntoTags.push(key); - } - } - - // Double check that we inserted the room in the right places. - // There should never be a discrepancy. - for (const targetTag of targetTags) { - let count = 0; - for (const insertedTag of insertedIntoTags) { - if (insertedTag === targetTag) count++; - } - - if (count !== 1) { - console.warn(`!! Room ${room.roomId} inserted ${count} times to ${targetTag}`); - } - - // This is a workaround for https://github.com/vector-im/riot-web/issues/11303 - // The logging is to try and identify what happened exactly. - if (count === 0) { - // Something went very badly wrong - try to recover the room. - // We don't bother checking how the target list is ordered - we're expecting - // to just insert it. - console.warn(`!! Recovering ${room.roomId} for tag ${targetTag} at position 0`); - if (!listsClone[targetTag]) { - console.warn(`!! List for tag ${targetTag} does not exist - creating`); - listsClone[targetTag] = []; - } - listsClone[targetTag].splice(0, 0, {room, category}); - } - } - - // Sort the favourites before we set the clone - for (const tag of Object.keys(listsClone)) { - if (getListAlgorithm(tag, this._state.algorithm) !== ALGO_MANUAL) continue; // skip recents (pre-sorted) - listsClone[tag].sort(this._getManualComparator(tag)); - } - - this._setState({lists: listsClone}); - } - - _generateInitialRoomLists() { - // Log something to show that we're throwing away the old results. This is for the inevitable - // question of "why is 100% of my CPU going towards Riot?" - a quick look at the logs would reveal - // that something is wrong with the RoomListStore. - console.log("Generating initial room lists"); - - const lists = { - "m.server_notice": [], - "im.vector.fake.invite": [], - "m.favourite": [], - "im.vector.fake.recent": [], - [TAG_DM]: [], - "m.lowpriority": [], - "im.vector.fake.archived": [], - }; - - const dmRoomMap = DMRoomMap.shared(); - - this._matrixClient.getRooms().forEach((room) => { - const myUserId = this._matrixClient.getUserId(); - const membership = room.getMyMembership(); - const me = room.getMember(myUserId); - - if (membership === "invite") { - lists["im.vector.fake.invite"].push({room, category: CATEGORY_RED}); - } else if (membership === "join" || membership === "ban" || (me && me.isKicked())) { - // Used to split rooms via tags - let tagNames = Object.keys(room.tags); - - // ignore any m. tag names we don't know about - tagNames = tagNames.filter((t) => { - // Speed optimization: Avoid hitting the SettingsStore at all costs by making it the - // last condition possible. - return lists[t] !== undefined || (!t.startsWith('m.') && this._state.tagsEnabled); - }); - - if (tagNames.length) { - for (let i = 0; i < tagNames.length; i++) { - const tagName = tagNames[i]; - lists[tagName] = lists[tagName] || []; - - // Default to an arbitrary category for tags which aren't ordered by recents - let category = CATEGORY_IDLE; - if (getListAlgorithm(tagName, this._state.algorithm) !== ALGO_MANUAL) { - category = this._calculateCategory(room); - } - lists[tagName].push({room, category}); - } - } else if (dmRoomMap.getUserIdForRoomId(room.roomId)) { - // "Direct Message" rooms (that we're still in and that aren't otherwise tagged) - lists[TAG_DM].push({room, category: this._calculateCategory(room)}); - } else { - lists["im.vector.fake.recent"].push({room, category: this._calculateCategory(room)}); - } - } else if (membership === "leave") { - // The category of these rooms is not super important, so deprioritize it to the lowest - // possible value. - lists["im.vector.fake.archived"].push({room, category: CATEGORY_IDLE}); - } - }); - - // We use this cache in the recents comparator because _tsOfNewestEvent can take a while. This - // cache only needs to survive the sort operation below and should not be implemented outside - // of this function, otherwise the room lists will almost certainly be out of date and wrong. - const latestEventTsCache = {}; // roomId => timestamp - const tsOfNewestEventFn = (room) => { - if (!room) return Number.MAX_SAFE_INTEGER; // Should only happen in tests - - if (latestEventTsCache[room.roomId]) { - return latestEventTsCache[room.roomId]; - } - - const ts = this._tsOfNewestEvent(room); - latestEventTsCache[room.roomId] = ts; - return ts; - }; - - Object.keys(lists).forEach((listKey) => { - let comparator; - switch (getListAlgorithm(listKey, this._state.algorithm)) { - case ALGO_RECENT: - comparator = (entryA, entryB) => this._recentsComparator(entryA, entryB, tsOfNewestEventFn); - break; - case ALGO_ALPHABETIC: - comparator = this._lexicographicalComparator; - break; - case ALGO_MANUAL: - default: - comparator = this._getManualComparator(listKey); - break; - } - - if (this._state.orderImportantFirst) { - lists[listKey].sort((entryA, entryB) => { - if (entryA.category !== entryB.category) { - const idxA = CATEGORY_ORDER.indexOf(entryA.category); - const idxB = CATEGORY_ORDER.indexOf(entryB.category); - if (idxA > idxB) return 1; - if (idxA < idxB) return -1; - return 0; // Technically not possible - } - return comparator(entryA, entryB); - }); - } else { - // skip the category comparison even though it should no-op when orderImportantFirst disabled - lists[listKey].sort(comparator); - } - }); - - this._setState({ - lists, - ready: true, // Ready to receive updates to ordering - }); - } - - _eventTriggersRecentReorder(ev) { - return ev.getTs() && ( - Unread.eventTriggersUnreadCount(ev) || - ev.getSender() === this._matrixClient.credentials.userId - ); - } - - _tsOfNewestEvent(room) { - // Apparently we can have rooms without timelines, at least under testing - // environments. Just return MAX_INT when this happens. - if (!room || !room.timeline) return Number.MAX_SAFE_INTEGER; - - for (let i = room.timeline.length - 1; i >= 0; --i) { - const ev = room.timeline[i]; - if (this._eventTriggersRecentReorder(ev)) { - return ev.getTs(); - } - } - - // we might only have events that don't trigger the unread indicator, - // in which case use the oldest event even if normally it wouldn't count. - // This is better than just assuming the last event was forever ago. - if (room.timeline.length && room.timeline[0].getTs()) { - return room.timeline[0].getTs(); - } else { - return Number.MAX_SAFE_INTEGER; - } - } - - _calculateCategory(room) { - if (!this._state.orderImportantFirst) { - // Effectively disable the categorization of rooms if we're supposed to - // be sorting by more recent messages first. This triggers the timestamp - // comparison bit of _setRoomCategory and _recentsComparator instead of - // the category ordering. - return CATEGORY_IDLE; - } - - 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; - } - - _recentsComparator(entryA, entryB, tsOfNewestEventFn) { - const timestampA = tsOfNewestEventFn(entryA.room); - const timestampB = tsOfNewestEventFn(entryB.room); - return timestampB - timestampA; - } - - _lexicographicalComparator(entryA, entryB) { - return entryA.room.name.localeCompare(entryB.room.name); - } - - _getManualComparator(tagName, optimisticRequest) { - return (entryA, entryB) => { - const roomA = entryA.room; - const roomB = entryB.room; - - let metaA = roomA.tags[tagName]; - let metaB = roomB.tags[tagName]; - - if (optimisticRequest && roomA === optimisticRequest.room) metaA = optimisticRequest.metaData; - if (optimisticRequest && roomB === optimisticRequest.room) metaB = optimisticRequest.metaData; - - // Make sure the room tag has an order element, if not set it to be the bottom - const a = metaA ? Number(metaA.order) : undefined; - const b = metaB ? Number(metaB.order) : undefined; - - // Order undefined room tag orders to the bottom - if (a === undefined && b !== undefined) { - return 1; - } else if (a !== undefined && b === undefined) { - return -1; - } - - return a === b ? this._lexicographicalComparator(entryA, entryB) : (a > b ? 1 : -1); - }; - } - - getRoomLists() { - return this._state.presentationLists; - } -} - -if (global.singletonRoomListStore === undefined) { - global.singletonRoomListStore = new RoomListStore(); -} -export default global.singletonRoomListStore; From 209a5d222003c0e864c526d46dff05c04ceb4f89 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 17 Jul 2020 15:10:30 -0600 Subject: [PATCH 06/35] Rename RoomListStore2 class name We use `RoomListStore` as a singleton, and don't want the ugly `2` at the end of the actual store instance, so here we rename it to something half-decent. --- src/@types/global.d.ts | 4 ++-- src/stores/room-list/RoomListStore2.ts | 12 ++++++------ src/stores/room-list/TagWatcher.ts | 4 ++-- test/components/views/rooms/RoomList-test.js | 7 +++++-- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index 9424cdcd17..2d446e444a 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -20,7 +20,7 @@ import { IMatrixClientPeg } from "../MatrixClientPeg"; import ToastStore from "../stores/ToastStore"; import DeviceListener from "../DeviceListener"; import RebrandListener from "../RebrandListener"; -import { RoomListStore2 } from "../stores/room-list/RoomListStore2"; +import { RoomListStoreClass } from "../stores/room-list/RoomListStore2"; import { PlatformPeg } from "../PlatformPeg"; import RoomListLayoutStore from "../stores/room-list/RoomListLayoutStore"; import {IntegrationManagers} from "../integrations/IntegrationManagers"; @@ -37,7 +37,7 @@ declare global { mx_ToastStore: ToastStore; mx_DeviceListener: DeviceListener; mx_RebrandListener: RebrandListener; - mx_RoomListStore2: RoomListStore2; + mx_RoomListStore: RoomListStoreClass; mx_RoomListLayoutStore: RoomListLayoutStore; mxPlatformPeg: PlatformPeg; mxIntegrationManagers: typeof IntegrationManagers; diff --git a/src/stores/room-list/RoomListStore2.ts b/src/stores/room-list/RoomListStore2.ts index 9576ae8ed6..62e515c5b3 100644 --- a/src/stores/room-list/RoomListStore2.ts +++ b/src/stores/room-list/RoomListStore2.ts @@ -44,7 +44,7 @@ interface IState { */ export const LISTS_UPDATE_EVENT = "lists_update"; -export class RoomListStore2 extends AsyncStoreWithClient { +export class RoomListStoreClass extends AsyncStoreWithClient { /** * Set to true if you're running tests on the store. Should not be touched in * any other environment. @@ -175,7 +175,7 @@ export class RoomListStore2 extends AsyncStoreWithClient { 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) { + if (RoomListStoreClass.TEST_MODE) { await this.onDispatchAsync(payload); return; } @@ -608,15 +608,15 @@ export class RoomListStore2 extends AsyncStoreWithClient { } export default class RoomListStore { - private static internalInstance: RoomListStore2; + private static internalInstance: RoomListStoreClass; - public static get instance(): RoomListStore2 { + public static get instance(): RoomListStoreClass { if (!RoomListStore.internalInstance) { - RoomListStore.internalInstance = new RoomListStore2(); + RoomListStore.internalInstance = new RoomListStoreClass(); } return RoomListStore.internalInstance; } } -window.mx_RoomListStore2 = RoomListStore.instance; +window.mx_RoomListStore = RoomListStore.instance; diff --git a/src/stores/room-list/TagWatcher.ts b/src/stores/room-list/TagWatcher.ts index 56b6437524..6f011271d5 100644 --- a/src/stores/room-list/TagWatcher.ts +++ b/src/stores/room-list/TagWatcher.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { RoomListStore2 } from "./RoomListStore2"; +import { RoomListStoreClass } from "./RoomListStore2"; import TagOrderStore from "../TagOrderStore"; import { CommunityFilterCondition } from "./filters/CommunityFilterCondition"; import { arrayDiff, arrayHasDiff } from "../../utils/arrays"; @@ -26,7 +26,7 @@ export class TagWatcher { // TODO: Support custom tags, somehow: https://github.com/vector-im/riot-web/issues/14091 private filters = new Map(); - constructor(private store: RoomListStore2) { + constructor(private store: RoomListStoreClass) { TagOrderStore.addListener(this.onTagsUpdated); } diff --git a/test/components/views/rooms/RoomList-test.js b/test/components/views/rooms/RoomList-test.js index e84f943708..43aa3dc2f8 100644 --- a/test/components/views/rooms/RoomList-test.js +++ b/test/components/views/rooms/RoomList-test.js @@ -14,7 +14,10 @@ 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, RoomListStore2} from "../../../../src/stores/room-list/RoomListStore2"; +import RoomListStore, { + LISTS_UPDATE_EVENT, + RoomListStoreClass +} from "../../../../src/stores/room-list/RoomListStore2"; import RoomListLayoutStore from "../../../../src/stores/room-list/RoomListLayoutStore"; function generateRoomId() { @@ -49,7 +52,7 @@ describe('RoomList', () => { let myOtherMember; beforeEach(async function(done) { - RoomListStore2.TEST_MODE = true; + RoomListStoreClass.TEST_MODE = true; TestUtils.stubClient(); client = MatrixClientPeg.get(); From 2b15ba21ddc99a7545b92d516f982c2dbce7efe5 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 17 Jul 2020 15:11:34 -0600 Subject: [PATCH 07/35] Rename RoomListStore file --- src/@types/global.d.ts | 2 +- src/actions/RoomListActions.ts | 2 +- src/components/structures/LeftPanel2.tsx | 2 +- src/components/structures/LoggedInView.tsx | 2 +- src/components/views/dialogs/InviteDialog.js | 2 +- src/components/views/rooms/RoomBreadcrumbs2.tsx | 2 +- src/components/views/rooms/RoomList2.tsx | 2 +- src/components/views/rooms/RoomSublist2.tsx | 2 +- src/components/views/rooms/RoomTile2.tsx | 2 +- .../views/settings/tabs/room/AdvancedRoomSettingsTab.js | 2 +- src/stores/CustomRoomTagStore.js | 2 +- src/stores/room-list/{RoomListStore2.ts => RoomListStore.ts} | 0 src/stores/room-list/TagWatcher.ts | 2 +- test/components/views/rooms/RoomList-test.js | 2 +- 14 files changed, 13 insertions(+), 13 deletions(-) rename src/stores/room-list/{RoomListStore2.ts => RoomListStore.ts} (100%) diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index 2d446e444a..f556ff8b5c 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -20,7 +20,7 @@ import { IMatrixClientPeg } from "../MatrixClientPeg"; import ToastStore from "../stores/ToastStore"; import DeviceListener from "../DeviceListener"; import RebrandListener from "../RebrandListener"; -import { RoomListStoreClass } from "../stores/room-list/RoomListStore2"; +import { RoomListStoreClass } from "../stores/room-list/RoomListStore"; import { PlatformPeg } from "../PlatformPeg"; import RoomListLayoutStore from "../stores/room-list/RoomListLayoutStore"; import {IntegrationManagers} from "../integrations/IntegrationManagers"; diff --git a/src/actions/RoomListActions.ts b/src/actions/RoomListActions.ts index f12d4d3084..da07bcb169 100644 --- a/src/actions/RoomListActions.ts +++ b/src/actions/RoomListActions.ts @@ -23,7 +23,7 @@ import * as sdk from '../index'; import { MatrixClient } from "matrix-js-sdk/src/client"; import { Room } from "matrix-js-sdk/src/models/room"; import { AsyncActionPayload } from "../dispatcher/payloads"; -import RoomListStore from "../stores/room-list/RoomListStore2"; +import RoomListStore from "../stores/room-list/RoomListStore"; import { SortAlgorithm } from "../stores/room-list/algorithms/models"; import { DefaultTagID } from "../stores/room-list/models"; diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index 717ec240ac..c8ab37e014 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -31,7 +31,7 @@ import { BreadcrumbsStore } from "../../stores/BreadcrumbsStore"; import { UPDATE_EVENT } from "../../stores/AsyncStore"; import ResizeNotifier from "../../utils/ResizeNotifier"; import SettingsStore from "../../settings/SettingsStore"; -import RoomListStore, { LISTS_UPDATE_EVENT } from "../../stores/room-list/RoomListStore2"; +import RoomListStore, { LISTS_UPDATE_EVENT } from "../../stores/room-list/RoomListStore"; import {Key} from "../../Keyboard"; import IndicatorScrollbar from "../structures/IndicatorScrollbar"; import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton"; diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 9b7a87c1dc..7e47620b05 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -53,7 +53,7 @@ import { Action } from "../../dispatcher/actions"; import LeftPanel2 from "./LeftPanel2"; import CallContainer from '../views/voip/CallContainer'; import { ViewRoomDeltaPayload } from "../../dispatcher/payloads/ViewRoomDeltaPayload"; -import RoomListStore from "../../stores/room-list/RoomListStore2"; +import RoomListStore from "../../stores/room-list/RoomListStore"; // We need to fetch each pinned message individually (if we don't already have it) // so each pinned message may trigger a request. Limit the number per room for sanity. diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index 0c1e0c5387..68c71300fb 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -36,7 +36,7 @@ import {inviteMultipleToRoom} from "../../../RoomInvite"; import {Key} from "../../../Keyboard"; import {Action} from "../../../dispatcher/actions"; import {DefaultTagID} from "../../../stores/room-list/models"; -import RoomListStore from "../../../stores/room-list/RoomListStore2"; +import RoomListStore from "../../../stores/room-list/RoomListStore"; export const KIND_DM = "dm"; export const KIND_INVITE = "invite"; diff --git a/src/components/views/rooms/RoomBreadcrumbs2.tsx b/src/components/views/rooms/RoomBreadcrumbs2.tsx index 71e9d9d6e1..c857cd9968 100644 --- a/src/components/views/rooms/RoomBreadcrumbs2.tsx +++ b/src/components/views/rooms/RoomBreadcrumbs2.tsx @@ -23,7 +23,7 @@ import defaultDispatcher from "../../../dispatcher/dispatcher"; import Analytics from "../../../Analytics"; import { UPDATE_EVENT } from "../../../stores/AsyncStore"; import { CSSTransition } from "react-transition-group"; -import RoomListStore from "../../../stores/room-list/RoomListStore2"; +import RoomListStore from "../../../stores/room-list/RoomListStore"; import { DefaultTagID } from "../../../stores/room-list/models"; import { RovingAccessibleTooltipButton } from "../../../accessibility/RovingTabIndex"; import Toolbar from "../../../accessibility/Toolbar"; diff --git a/src/components/views/rooms/RoomList2.tsx b/src/components/views/rooms/RoomList2.tsx index 86becc2fca..1ef36c8a1f 100644 --- a/src/components/views/rooms/RoomList2.tsx +++ b/src/components/views/rooms/RoomList2.tsx @@ -23,7 +23,7 @@ import { Room } from "matrix-js-sdk/src/models/room"; import { _t, _td } from "../../../languageHandler"; import { RovingTabIndexProvider } from "../../../accessibility/RovingTabIndex"; import { ResizeNotifier } from "../../../utils/ResizeNotifier"; -import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore2"; +import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore"; import RoomViewStore from "../../../stores/RoomViewStore"; import { ITagMap } from "../../../stores/room-list/algorithms/models"; import { DefaultTagID, TagID } from "../../../stores/room-list/models"; diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index 4883d4f2a3..f2dffeeb1e 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -32,7 +32,7 @@ import { StyledMenuItemCheckbox, StyledMenuItemRadio, } from "../../structures/ContextMenu"; -import RoomListStore from "../../../stores/room-list/RoomListStore2"; +import RoomListStore from "../../../stores/room-list/RoomListStore"; import { ListAlgorithm, SortAlgorithm } from "../../../stores/room-list/algorithms/models"; import { DefaultTagID, TagID } from "../../../stores/room-list/models"; import dis from "../../../dispatcher/dispatcher"; diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index b19bb23160..a66a415c58 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -48,7 +48,7 @@ import { import { MatrixClientPeg } from "../../../MatrixClientPeg"; import NotificationBadge from "./NotificationBadge"; import { Volume } from "../../../RoomNotifsTypes"; -import RoomListStore from "../../../stores/room-list/RoomListStore2"; +import RoomListStore from "../../../stores/room-list/RoomListStore"; import RoomListActions from "../../../actions/RoomListActions"; import defaultDispatcher from "../../../dispatcher/dispatcher"; import {ActionPayload} from "../../../dispatcher/payloads"; diff --git a/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.js b/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.js index 2edf3021dc..391f4f7845 100644 --- a/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.js +++ b/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.js @@ -22,7 +22,7 @@ import * as sdk from "../../../../.."; import AccessibleButton from "../../../elements/AccessibleButton"; import Modal from "../../../../../Modal"; import dis from "../../../../../dispatcher/dispatcher"; -import RoomListStore from "../../../../../stores/room-list/RoomListStore2"; +import RoomListStore from "../../../../../stores/room-list/RoomListStore"; import RoomListActions from "../../../../../actions/RoomListActions"; import { DefaultTagID } from '../../../../../stores/room-list/models'; import LabelledToggleSwitch from '../../../elements/LabelledToggleSwitch'; diff --git a/src/stores/CustomRoomTagStore.js b/src/stores/CustomRoomTagStore.js index ed96e40dfd..b1f9ad6d36 100644 --- a/src/stores/CustomRoomTagStore.js +++ b/src/stores/CustomRoomTagStore.js @@ -18,7 +18,7 @@ import * as RoomNotifs from '../RoomNotifs'; import EventEmitter from 'events'; import { throttle } from "lodash"; import SettingsStore from "../settings/SettingsStore"; -import RoomListStore, {LISTS_UPDATE_EVENT} from "./room-list/RoomListStore2"; +import RoomListStore, {LISTS_UPDATE_EVENT} from "./room-list/RoomListStore"; // TODO: All of this needs updating for new custom tags: https://github.com/vector-im/riot-web/issues/14091 const STANDARD_TAGS_REGEX = /^(m\.(favourite|lowpriority|server_notice)|im\.vector\.fake\.(invite|recent|direct|archived))$/; diff --git a/src/stores/room-list/RoomListStore2.ts b/src/stores/room-list/RoomListStore.ts similarity index 100% rename from src/stores/room-list/RoomListStore2.ts rename to src/stores/room-list/RoomListStore.ts diff --git a/src/stores/room-list/TagWatcher.ts b/src/stores/room-list/TagWatcher.ts index 6f011271d5..1c16571e5b 100644 --- a/src/stores/room-list/TagWatcher.ts +++ b/src/stores/room-list/TagWatcher.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { RoomListStoreClass } from "./RoomListStore2"; +import { RoomListStoreClass } from "./RoomListStore"; import TagOrderStore from "../TagOrderStore"; import { CommunityFilterCondition } from "./filters/CommunityFilterCondition"; import { arrayDiff, arrayHasDiff } from "../../utils/arrays"; diff --git a/test/components/views/rooms/RoomList-test.js b/test/components/views/rooms/RoomList-test.js index 43aa3dc2f8..56a62472fe 100644 --- a/test/components/views/rooms/RoomList-test.js +++ b/test/components/views/rooms/RoomList-test.js @@ -17,7 +17,7 @@ import {DefaultTagID} from "../../../../src/stores/room-list/models"; import RoomListStore, { LISTS_UPDATE_EVENT, RoomListStoreClass -} from "../../../../src/stores/room-list/RoomListStore2"; +} from "../../../../src/stores/room-list/RoomListStore"; import RoomListLayoutStore from "../../../../src/stores/room-list/RoomListLayoutStore"; function generateRoomId() { From 1cce6e2e3282031bb1a444a34662fe44d43a042d Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 17 Jul 2020 15:14:58 -0600 Subject: [PATCH 08/35] Enable new room list store forever --- src/stores/AsyncStoreWithClient.ts | 1 - src/stores/room-list/RoomListStore.ts | 21 ++++----------------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/src/stores/AsyncStoreWithClient.ts b/src/stores/AsyncStoreWithClient.ts index ce7fd45eec..5b9f95f991 100644 --- a/src/stores/AsyncStoreWithClient.ts +++ b/src/stores/AsyncStoreWithClient.ts @@ -18,7 +18,6 @@ import { MatrixClient } from "matrix-js-sdk/src/client"; import { AsyncStore } from "./AsyncStore"; import { ActionPayload } from "../dispatcher/payloads"; - export abstract class AsyncStoreWithClient extends AsyncStore { protected matrixClient: MatrixClient; diff --git a/src/stores/room-list/RoomListStore.ts b/src/stores/room-list/RoomListStore.ts index 62e515c5b3..2bf238a84a 100644 --- a/src/stores/room-list/RoomListStore.ts +++ b/src/stores/room-list/RoomListStore.ts @@ -44,7 +44,7 @@ interface IState { */ export const LISTS_UPDATE_EVENT = "lists_update"; -export class RoomListStoreClass extends AsyncStoreWithClient { +export class RoomListStoreClass extends AsyncStoreWithClient { /** * Set to true if you're running tests on the store. Should not be touched in * any other environment. @@ -52,7 +52,6 @@ export class RoomListStoreClass extends AsyncStoreWithClient { public static TEST_MODE = false; private initialListsGenerated = false; - private enabled = true; private algorithm = new Algorithm(); private filterConditions: IFilterCondition[] = []; private tagWatcher = new TagWatcher(this); @@ -66,7 +65,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient { constructor() { super(defaultDispatcher); - this.checkEnabled(); + this.checkLoggingEnabled(); for (const settingName of this.watchedSettings) SettingsStore.monitorSetting(settingName, null); RoomViewStore.addListener(() => this.handleRVSUpdate({})); this.algorithm.on(LIST_UPDATED_EVENT, this.onAlgorithmListUpdated); @@ -106,9 +105,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient { super.matrixClient = forcedClient; } - // TODO: Remove with https://github.com/vector-im/riot-web/issues/14367 - this.checkEnabled(); - if (!this.enabled) return; + this.checkLoggingEnabled(); // 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. @@ -121,7 +118,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient { this.updateFn.trigger(); } - private checkEnabled() { + private checkLoggingEnabled() { if (SettingsStore.getValue("advancedRoomListLogging")) { console.warn("Advanced room list logging is enabled"); } @@ -141,7 +138,6 @@ export class RoomListStoreClass extends AsyncStoreWithClient { * be used if the calling code will manually trigger the update. */ private async handleRVSUpdate({trigger = true}) { - if (!this.enabled) return; // TODO: Remove with https://github.com/vector-im/riot-web/issues/14367 if (!this.matrixClient) return; // We assume there won't be RVS updates without a client const activeRoomId = RoomViewStore.getRoomId(); @@ -186,9 +182,6 @@ export class RoomListStoreClass extends AsyncStoreWithClient { } protected async onDispatchAsync(payload: ActionPayload) { - // TODO: Remove this once the RoomListStore becomes default - if (!this.enabled) return; - // Everything here requires a MatrixClient or some sort of logical readiness. const logicallyReady = this.matrixClient && this.initialListsGenerated; if (!logicallyReady) return; @@ -509,12 +502,6 @@ export class RoomListStoreClass extends AsyncStoreWithClient { } } - protected async updateState(newState: IState) { - if (!this.enabled) return; - - await super.updateState(newState); - } - private onAlgorithmListUpdated = () => { if (SettingsStore.getValue("advancedRoomListLogging")) { // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 From 52219a8341559ecf402132d739a122fab5e4f8bf Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 17 Jul 2020 15:18:42 -0600 Subject: [PATCH 09/35] Remove legacy resizing code --- src/resizer/distributors/roomsublist.js | 132 --------- src/resizer/distributors/roomsublist2.js | 332 ----------------------- src/resizer/index.js | 1 - 3 files changed, 465 deletions(-) delete mode 100644 src/resizer/distributors/roomsublist.js delete mode 100644 src/resizer/distributors/roomsublist2.js diff --git a/src/resizer/distributors/roomsublist.js b/src/resizer/distributors/roomsublist.js deleted file mode 100644 index cc7875bfb0..0000000000 --- a/src/resizer/distributors/roomsublist.js +++ /dev/null @@ -1,132 +0,0 @@ -/* -Copyright 2019 New Vector Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import Sizer from "../sizer"; -import ResizeItem from "../item"; - -class RoomSizer extends Sizer { - setItemSize(item, size) { - item.style.maxHeight = `${Math.round(size)}px`; - item.classList.add("resized-sized"); - } - - clearItemSize(item) { - item.style.maxHeight = null; - item.classList.remove("resized-sized"); - } -} - -class RoomSubListItem extends ResizeItem { - isCollapsed() { - return this.domNode.classList.contains("mx_RoomSubList_hidden"); - } - - maxSize() { - const header = this.domNode.querySelector(".mx_RoomSubList_labelContainer"); - const scrollItem = this.domNode.querySelector(".mx_RoomSubList_scroll"); - const headerHeight = this.sizer.getItemSize(header); - return headerHeight + (scrollItem ? scrollItem.scrollHeight : 0); - } - - minSize() { - const isNotEmpty = this.domNode.classList.contains("mx_RoomSubList_nonEmpty"); - return isNotEmpty ? 74 : 31; //size of header + 1? room tile (see room sub list css) - } - - isSized() { - return this.domNode.classList.contains("resized-sized"); - } -} - -export default class RoomSubListDistributor { - static createItem(resizeHandle, resizer, sizer) { - return new RoomSubListItem(resizeHandle, resizer, sizer); - } - - static createSizer(containerElement, vertical, reverse) { - return new RoomSizer(containerElement, vertical, reverse); - } - - constructor(item) { - this.item = item; - } - - _handleSize() { - return 1; - } - - resize(size) { - //console.log("*** starting resize session with size", size); - let item = this.item; - while (item) { - const minSize = item.minSize(); - if (item.isCollapsed()) { - item = item.previous(); - } else if (size <= minSize) { - //console.log(" - resizing", item.id, "to min size", minSize); - item.setSize(minSize); - const remainder = minSize - size; - item = item.previous(); - if (item) { - size = item.size() - remainder - this._handleSize(); - } - } else { - const maxSize = item.maxSize(); - if (size > maxSize) { - // console.log(" - resizing", item.id, "to maxSize", maxSize); - item.setSize(maxSize); - const remainder = size - maxSize; - item = item.previous(); - if (item) { - size = item.size() + remainder; // todo: handle size here? - } - } else { - //console.log(" - resizing", item.id, "to size", size); - item.setSize(size); - item = null; - size = 0; - } - } - } - //console.log("*** ending resize session"); - } - - resizeFromContainerOffset(containerOffset) { - this.resize(containerOffset - this.item.offset()); - } - - start() { - // set all max-height props to the actual height. - let item = this.item.first(); - const sizes = []; - while (item) { - if (!item.isCollapsed()) { - sizes.push(item.size()); - } else { - sizes.push(100); - } - item = item.next(); - } - item = this.item.first(); - sizes.forEach((size) => { - item.setSize(size); - item = item.next(); - }); - } - - finish() { - } -} diff --git a/src/resizer/distributors/roomsublist2.js b/src/resizer/distributors/roomsublist2.js deleted file mode 100644 index a715087630..0000000000 --- a/src/resizer/distributors/roomsublist2.js +++ /dev/null @@ -1,332 +0,0 @@ -/* -Copyright 2019 New Vector Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import FixedDistributor from "./fixed"; - -function clamp(height, min, max) { - if (height > max) return max; - if (height < min) return min; - return height; -} - -export class Layout { - constructor(applyHeight, initialSizes, collapsedState, options) { - // callback to set height of section - this._applyHeight = applyHeight; - // list of {id, count} objects, - // determines sections and order of them - this._sections = []; - // stores collapsed by id - this._collapsedState = Object.assign({}, collapsedState); - // total available height to the layout - // (including resize handles, ...) - this._availableHeight = 0; - // heights stored by section section id - this._sectionHeights = Object.assign({}, initialSizes); - // in-progress heights, while dragging. Committed on mouse-up. - this._heights = []; - // use while manually resizing to cancel - // the resize for a given mouse position - // when the previous resize made the layout - // constrained - this._clampedOffset = 0; - // used while manually resizing, to clear - // _clampedOffset when the direction of resizing changes - this._lastOffset = 0; - - this._allowWhitespace = options && options.allowWhitespace; - this._handleHeight = (options && options.handleHeight) || 0; - } - - setAvailableHeight(newSize) { - this._availableHeight = newSize; - // needs more work - this._applyNewSize(); - } - - expandSection(id, height) { - this._collapsedState[id] = false; - this._applyNewSize(); - this.openHandle(id).setHeight(height).finish(); - } - - collapseSection(id) { - this._collapsedState[id] = true; - this._applyNewSize(); - } - - update(sections, availableHeight, force = false) { - let heightChanged = false; - - if (Number.isFinite(availableHeight) && availableHeight !== this._availableHeight) { - heightChanged = true; - this._availableHeight = availableHeight; - } - - const sectionsChanged = - sections.length !== this._sections.length || - sections.some((a, i) => { - const b = this._sections[i]; - return a.id !== b.id || a.count !== b.count; - }); - - if (!heightChanged && !sectionsChanged && !force) { - return; - } - - this._sections = sections; - const totalHeight = this._getAvailableHeight(); - const defaultHeight = Math.floor(totalHeight / this._sections.length); - this._sections.forEach((section, i) => { - if (!this._sectionHeights[section.id]) { - this._sectionHeights[section.id] = clamp( - defaultHeight, - this._getMinHeight(i), - this._getMaxHeight(i), - ); - } - }); - this._applyNewSize(); - } - - openHandle(id) { - const index = this._getSectionIndex(id); - return new Handle(this, index, this._sectionHeights[id]); - } - - _getAvailableHeight() { - const nonCollapsedSectionCount = this._sections.reduce((count, section) => { - const collapsed = this._collapsedState[section.id]; - return count + (collapsed ? 0 : 1); - }, 0); - return this._availableHeight - ((nonCollapsedSectionCount - 1) * this._handleHeight); - } - - _applyNewSize() { - const newHeight = this._getAvailableHeight(); - const currHeight = this._sections.reduce((sum, section) => { - return sum + this._sectionHeights[section.id]; - }, 0); - const offset = newHeight - currHeight; - this._heights = this._sections.map((section) => this._sectionHeights[section.id]); - const sections = this._sections.map((_, i) => i); - this._applyOverflow(-offset, sections, true); - this._applyHeights(); - this._commitHeights(); - } - - _getSectionIndex(id) { - return this._sections.findIndex((s) => s.id === id); - } - - _getMaxHeight(i) { - const section = this._sections[i]; - const collapsed = this._collapsedState[section.id]; - - if (collapsed) { - return this._sectionHeight(0); - } else if (!this._allowWhitespace) { - return this._sectionHeight(section.count); - } else { - return 100000; - } - } - - _sectionHeight(count) { - return 36 + (count === 0 ? 0 : 4 + (count * 34)); - } - - _getMinHeight(i) { - const section = this._sections[i]; - const collapsed = this._collapsedState[section.id]; - const maxItems = collapsed ? 0 : 1; - return this._sectionHeight(Math.min(section.count, maxItems)); - } - - _applyOverflow(overflow, sections, blend) { - // take the given overflow amount, and applies it to the given sections. - // calls itself recursively until it has distributed all the overflow - // or run out of unclamped sections. - - const unclampedSections = []; - - let overflowPerSection = blend ? (overflow / sections.length) : overflow; - for (const i of sections) { - const newHeight = clamp( - this._heights[i] - overflowPerSection, - this._getMinHeight(i), - this._getMaxHeight(i), - ); - if (newHeight == this._heights[i] - overflowPerSection) { - unclampedSections.push(i); - } - // when section is growing, overflow increases? - // 100 -= 200 - 300 - // 100 -= -100 - // 200 - overflow -= this._heights[i] - newHeight; - this._heights[i] = newHeight; - if (!blend) { - overflowPerSection = overflow; - if (Math.abs(overflow) < 1.0) break; - } - } - - if (Math.abs(overflow) > 1.0 && unclampedSections.length > 0) { - // we weren't able to distribute all the overflow so recurse and try again - overflow = this._applyOverflow(overflow, unclampedSections, blend); - } - - return overflow; - } - - _rebalanceAbove(sectionIndex, overflowAbove) { - if (Math.abs(overflowAbove) > 1.0) { - const sections = []; - for (let i = sectionIndex - 1; i >= 0; i--) { - sections.push(i); - } - overflowAbove = this._applyOverflow(overflowAbove, sections); - } - return overflowAbove; - } - - _rebalanceBelow(sectionIndex, overflowBelow) { - if (Math.abs(overflowBelow) > 1.0) { - const sections = []; - for (let i = sectionIndex + 1; i < this._sections.length; i++) { - sections.push(i); - } - overflowBelow = this._applyOverflow(overflowBelow, sections); - } - return overflowBelow; - } - - // @param offset the amount the sectionIndex is moved from what is stored in _sectionHeights, positive if downwards - // if we're constrained, return the offset we should be constrained at. - _relayout(sectionIndex = 0, offset = 0, constrained = false) { - this._heights = this._sections.map((section) => this._sectionHeights[section.id]); - // are these the amounts the items above/below shrank/grew and need to be relayouted? - let overflowAbove; - let overflowBelow; - const maxHeight = this._getMaxHeight(sectionIndex); - const minHeight = this._getMinHeight(sectionIndex); - // new height > max ? - if (this._heights[sectionIndex] + offset > maxHeight) { - // we're pulling downwards and constrained - // overflowAbove = minus how much are we above max height - overflowAbove = (maxHeight - this._heights[sectionIndex]) - offset; - overflowBelow = offset; - } else if (this._heights[sectionIndex] + offset < minHeight) { // new height < min? - // we're pulling upwards and constrained - overflowAbove = (minHeight - this._heights[sectionIndex]) - offset; - overflowBelow = offset; - } else { - overflowAbove = 0; - overflowBelow = offset; - } - this._heights[sectionIndex] = clamp(this._heights[sectionIndex] + offset, minHeight, maxHeight); - - // these are reassigned the amount of overflow that could not be rebalanced - // meaning we dragged the handle too far and it can't follow the cursor anymore - overflowAbove = this._rebalanceAbove(sectionIndex, overflowAbove); - overflowBelow = this._rebalanceBelow(sectionIndex, overflowBelow); - - if (!constrained) { // to avoid risk of infinite recursion - // clamp to avoid overflowing or underflowing the page - if (Math.abs(overflowAbove) > 1.0) { - // here we do the layout again with offset - the amount of space we took too much - this._relayout(sectionIndex, offset + overflowAbove, true); - return offset + overflowAbove; - } - - if (Math.abs(overflowBelow) > 1.0) { - // here we do the layout again with offset - the amount of space we took too much - this._relayout(sectionIndex, offset - overflowBelow, true); - return offset - overflowBelow; - } - } - - this._applyHeights(); - return undefined; - } - - _applyHeights() { - // apply the heights - for (let i = 0; i < this._sections.length; i++) { - const section = this._sections[i]; - this._applyHeight(section.id, this._heights[i]); - } - } - - _commitHeights() { - this._sections.forEach((section, i) => { - this._sectionHeights[section.id] = this._heights[i]; - }); - } - - _setUncommittedSectionHeight(sectionIndex, offset) { - if (Math.sign(offset) != Math.sign(this._lastOffset)) { - this._clampedOffset = undefined; - } - if (this._clampedOffset !== undefined) { - if (offset < 0 && offset < this._clampedOffset) { - return; - } - if (offset > 0 && offset > this._clampedOffset) { - return; - } - } - this._clampedOffset = this._relayout(sectionIndex, offset); - this._lastOffset = offset; - } -} - -class Handle { - constructor(layout, sectionIndex, height) { - this._layout = layout; - this._sectionIndex = sectionIndex; - this._initialHeight = height; - } - - setHeight(height) { - this._layout._setUncommittedSectionHeight( - this._sectionIndex, - height - this._initialHeight, - ); - return this; - } - - finish() { - this._layout._commitHeights(); - return this; - } -} - -export class Distributor extends FixedDistributor { - constructor(item, cfg) { - super(item); - this._handle = cfg.getLayout().openHandle(item.id); - } - - finish() { - this._handle.finish(); - } - - resize(height) { - this._handle.setHeight(height); - } -} diff --git a/src/resizer/index.js b/src/resizer/index.js index 7c4b2bd493..1fd8f4da46 100644 --- a/src/resizer/index.js +++ b/src/resizer/index.js @@ -17,5 +17,4 @@ limitations under the License. export FixedDistributor from "./distributors/fixed"; export CollapseDistributor from "./distributors/collapse"; -export RoomSubListDistributor from "./distributors/roomsublist"; export Resizer from "./resizer"; From 2441cbc9ac301770c99e9d0ce8850535e8dd56ca Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 17 Jul 2020 15:22:18 -0600 Subject: [PATCH 10/35] LeftPanel2 -> LeftPanel --- res/css/_components.scss | 2 +- .../{_LeftPanel2.scss => _LeftPanel.scss} | 42 +++++++++---------- res/css/structures/_MatrixChat.scss | 2 +- res/css/views/rooms/_RoomSublist2.scss | 2 +- res/themes/light/css/_mods.scss | 4 +- .../{LeftPanel2.tsx => LeftPanel.tsx} | 36 ++++++++-------- src/components/structures/LoggedInView.tsx | 4 +- src/components/views/rooms/RoomSublist2.tsx | 2 +- .../src/usecases/create-room.js | 2 +- 9 files changed, 46 insertions(+), 50 deletions(-) rename res/css/structures/{_LeftPanel2.scss => _LeftPanel.scss} (85%) rename src/components/structures/{LeftPanel2.tsx => LeftPanel.tsx} (92%) diff --git a/res/css/_components.scss b/res/css/_components.scss index 77462ad4c1..eabdcd6843 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -11,7 +11,7 @@ @import "./structures/_GroupView.scss"; @import "./structures/_HeaderButtons.scss"; @import "./structures/_HomePage.scss"; -@import "./structures/_LeftPanel2.scss"; +@import "./structures/_LeftPanel.scss"; @import "./structures/_MainSplit.scss"; @import "./structures/_MatrixChat.scss"; @import "./structures/_MyGroups.scss"; diff --git a/res/css/structures/_LeftPanel2.scss b/res/css/structures/_LeftPanel.scss similarity index 85% rename from res/css/structures/_LeftPanel2.scss rename to res/css/structures/_LeftPanel.scss index 9603731dd5..b142d6ee3d 100644 --- a/res/css/structures/_LeftPanel2.scss +++ b/res/css/structures/_LeftPanel.scss @@ -14,11 +14,9 @@ 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/14367 - $tagPanelWidth: 56px; // only applies in this file, used for calculations -.mx_LeftPanel2 { +.mx_LeftPanel { background-color: $roomlist2-bg-color; min-width: 260px; max-width: 50%; @@ -26,7 +24,7 @@ $tagPanelWidth: 56px; // only applies in this file, used for calculations // Create a row-based flexbox for the TagPanel and the room list display: flex; - .mx_LeftPanel2_tagPanelContainer { + .mx_LeftPanel_tagPanelContainer { flex-grow: 0; flex-shrink: 0; flex-basis: $tagPanelWidth; @@ -38,15 +36,15 @@ $tagPanelWidth: 56px; // only applies in this file, used for calculations // TagPanel handles its own CSS } - &:not(.mx_LeftPanel2_hasTagPanel) { - .mx_LeftPanel2_roomListContainer { + &:not(.mx_LeftPanel_hasTagPanel) { + .mx_LeftPanel_roomListContainer { width: 100%; } } // Note: The 'room list' in this context is actually everything that isn't the tag // panel, such as the menu options, breadcrumbs, filtering, etc - .mx_LeftPanel2_roomListContainer { + .mx_LeftPanel_roomListContainer { width: calc(100% - $tagPanelWidth); background-color: $roomlist2-bg-color; @@ -54,7 +52,7 @@ $tagPanelWidth: 56px; // only applies in this file, used for calculations display: flex; flex-direction: column; - .mx_LeftPanel2_userHeader { + .mx_LeftPanel_userHeader { /* 12px top, 12px sides, 20px bottom (using 13px bottom to account * for internal whitespace in the breadcrumbs) */ @@ -66,7 +64,7 @@ $tagPanelWidth: 56px; // only applies in this file, used for calculations flex-direction: column; } - .mx_LeftPanel2_breadcrumbsContainer { + .mx_LeftPanel_breadcrumbsContainer { overflow-y: hidden; overflow-x: scroll; margin: 12px 12px 0 12px; @@ -89,7 +87,7 @@ $tagPanelWidth: 56px; // only applies in this file, used for calculations } } - .mx_LeftPanel2_filterContainer { + .mx_LeftPanel_filterContainer { margin-left: 12px; margin-right: 12px; @@ -99,7 +97,7 @@ $tagPanelWidth: 56px; // only applies in this file, used for calculations display: flex; align-items: center; - .mx_RoomSearch_expanded + .mx_LeftPanel2_exploreButton { + .mx_RoomSearch_expanded + .mx_LeftPanel_exploreButton { // Cheaty way to return the occupied space to the filter input flex-basis: 0; margin: 0; @@ -112,7 +110,7 @@ $tagPanelWidth: 56px; // only applies in this file, used for calculations } } - .mx_LeftPanel2_exploreButton { + .mx_LeftPanel_exploreButton { width: 28px; height: 28px; border-radius: 20px; @@ -136,7 +134,7 @@ $tagPanelWidth: 56px; // only applies in this file, used for calculations } } - .mx_LeftPanel2_roomListWrapper { + .mx_LeftPanel_roomListWrapper { // Create a flexbox to ensure the containing items cause appropriate overflow. display: flex; @@ -145,16 +143,16 @@ $tagPanelWidth: 56px; // only applies in this file, used for calculations min-height: 0; margin-top: 10px; // so we're not up against the search/filter - &.mx_LeftPanel2_roomListWrapper_stickyBottom { + &.mx_LeftPanel_roomListWrapper_stickyBottom { padding-bottom: 32px; } - &.mx_LeftPanel2_roomListWrapper_stickyTop { + &.mx_LeftPanel_roomListWrapper_stickyTop { padding-top: 32px; } } - .mx_LeftPanel2_actualRoomListContainer { + .mx_LeftPanel_actualRoomListContainer { flex-grow: 1; // fill the available space overflow-y: auto; width: 100%; @@ -167,26 +165,26 @@ $tagPanelWidth: 56px; // only applies in this file, used for calculations } // These styles override the defaults for the minimized (66px) layout - &.mx_LeftPanel2_minimized { + &.mx_LeftPanel_minimized { min-width: unset; // We have to forcefully set the width to override the resizer's style attribute. - &.mx_LeftPanel2_hasTagPanel { + &.mx_LeftPanel_hasTagPanel { width: calc(68px + $tagPanelWidth) !important; } - &:not(.mx_LeftPanel2_hasTagPanel) { + &:not(.mx_LeftPanel_hasTagPanel) { width: 68px !important; } - .mx_LeftPanel2_roomListContainer { + .mx_LeftPanel_roomListContainer { width: 68px; - .mx_LeftPanel2_filterContainer { + .mx_LeftPanel_filterContainer { // Organize the flexbox into a centered column layout flex-direction: column; justify-content: center; - .mx_LeftPanel2_exploreButton { + .mx_LeftPanel_exploreButton { margin-left: 0; margin-top: 8px; background-color: transparent; diff --git a/res/css/structures/_MatrixChat.scss b/res/css/structures/_MatrixChat.scss index 08ed9e5559..88b29a96e8 100644 --- a/res/css/structures/_MatrixChat.scss +++ b/res/css/structures/_MatrixChat.scss @@ -66,7 +66,7 @@ limitations under the License. } /* not the left panel, and not the resize handle, so the roomview/groupview/... */ -.mx_MatrixChat > :not(.mx_LeftPanel_container):not(.mx_LeftPanel2):not(.mx_ResizeHandle) { +.mx_MatrixChat > :not(.mx_LeftPanel):not(.mx_ResizeHandle) { background-color: $primary-bg-color; flex: 1 1 0; diff --git a/res/css/views/rooms/_RoomSublist2.scss b/res/css/views/rooms/_RoomSublist2.scss index 77a762b4d8..eac2aa838d 100644 --- a/res/css/views/rooms/_RoomSublist2.scss +++ b/res/css/views/rooms/_RoomSublist2.scss @@ -43,7 +43,7 @@ limitations under the License. // all works by ensuring the header text has a fixed height when sticky so the // fixed height of the container can maintain the scroll position. - // The combined height must be set in the LeftPanel2 component for sticky headers + // The combined height must be set in the LeftPanel component for sticky headers // to work correctly. padding-bottom: 8px; height: 24px; diff --git a/res/themes/light/css/_mods.scss b/res/themes/light/css/_mods.scss index 810e0375ba..54ba7795ee 100644 --- a/res/themes/light/css/_mods.scss +++ b/res/themes/light/css/_mods.scss @@ -5,7 +5,7 @@ // it can be blurred by the tag panel and room list @supports (backdrop-filter: none) { - .mx_LeftPanel2 { + .mx_LeftPanel { background-image: var(--avatar-url); background-repeat: no-repeat; background-size: cover; @@ -16,7 +16,7 @@ backdrop-filter: blur($tagpanel-background-blur-amount); } - .mx_LeftPanel2 .mx_LeftPanel2_roomListContainer { + .mx_LeftPanel .mx_LeftPanel_roomListContainer { backdrop-filter: blur($roomlist-background-blur-amount); } } diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel.tsx similarity index 92% rename from src/components/structures/LeftPanel2.tsx rename to src/components/structures/LeftPanel.tsx index c8ab37e014..a8e763c6ab 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel.tsx @@ -36,8 +36,6 @@ import {Key} from "../../Keyboard"; import IndicatorScrollbar from "../structures/IndicatorScrollbar"; import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton"; -// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367 - interface IProps { isMinimized: boolean; resizeNotifier: ResizeNotifier; @@ -58,7 +56,7 @@ const cssClasses = [ "mx_RoomSublist2_showNButton", ]; -export default class LeftPanel2 extends React.Component { +export default class LeftPanel extends React.Component { private listContainerRef: React.RefObject = createRef(); private tagPanelWatcherRef: string; private focusedElement = null; @@ -222,16 +220,16 @@ export default class LeftPanel2 extends React.Component { // add appropriate sticky classes to wrapper so it has // the necessary top/bottom padding to put the sticky header in - const listWrapper = list.parentElement; // .mx_LeftPanel2_roomListWrapper + const listWrapper = list.parentElement; // .mx_LeftPanel_roomListWrapper if (lastTopHeader) { - listWrapper.classList.add("mx_LeftPanel2_roomListWrapper_stickyTop"); + listWrapper.classList.add("mx_LeftPanel_roomListWrapper_stickyTop"); } else { - listWrapper.classList.remove("mx_LeftPanel2_roomListWrapper_stickyTop"); + listWrapper.classList.remove("mx_LeftPanel_roomListWrapper_stickyTop"); } if (firstBottomHeader) { - listWrapper.classList.add("mx_LeftPanel2_roomListWrapper_stickyBottom"); + listWrapper.classList.add("mx_LeftPanel_roomListWrapper_stickyBottom"); } else { - listWrapper.classList.remove("mx_LeftPanel2_roomListWrapper_stickyBottom"); + listWrapper.classList.remove("mx_LeftPanel_roomListWrapper_stickyBottom"); } } @@ -315,7 +313,7 @@ export default class LeftPanel2 extends React.Component { private renderHeader(): React.ReactNode { return ( -
+
); @@ -325,7 +323,7 @@ export default class LeftPanel2 extends React.Component { if (this.state.showBreadcrumbs && !this.props.isMinimized) { return ( @@ -337,7 +335,7 @@ export default class LeftPanel2 extends React.Component { private renderSearchExplore(): React.ReactNode { return (
{ onEnter={this.onEnter} /> @@ -359,7 +357,7 @@ export default class LeftPanel2 extends React.Component { public render(): React.ReactNode { const tagPanel = !this.state.showTagPanel ? null : ( -
+
); @@ -376,24 +374,24 @@ export default class LeftPanel2 extends React.Component { />; const containerClasses = classNames({ - "mx_LeftPanel2": true, - "mx_LeftPanel2_hasTagPanel": !!tagPanel, - "mx_LeftPanel2_minimized": this.props.isMinimized, + "mx_LeftPanel": true, + "mx_LeftPanel_hasTagPanel": !!tagPanel, + "mx_LeftPanel_minimized": this.props.isMinimized, }); const roomListClasses = classNames( - "mx_LeftPanel2_actualRoomListContainer", + "mx_LeftPanel_actualRoomListContainer", "mx_AutoHideScrollbar", ); return (
{tagPanel} -