diff --git a/docs/labs.md b/docs/labs.md index 60f35dd4a4..700a0243c7 100644 --- a/docs/labs.md +++ b/docs/labs.md @@ -108,7 +108,3 @@ Unreliable in encrypted rooms. ## Knock rooms (`feature_ask_to_join`) [In Development] Enables knock feature for rooms. This allows users to ask to join a room. - -## New room list (`feature_new_room_list`) [In Development] - -Enable the new room list that is currently in development. diff --git a/playwright/e2e/left-panel/room-list-panel/room-list-filter-sort.spec.ts b/playwright/e2e/left-panel/room-list-panel/room-list-filter-sort.spec.ts index d1c0332cef..8fa70ab45d 100644 --- a/playwright/e2e/left-panel/room-list-panel/room-list-filter-sort.spec.ts +++ b/playwright/e2e/left-panel/room-list-panel/room-list-filter-sort.spec.ts @@ -18,7 +18,6 @@ test.describe("Room list filters and sort", () => { displayName: "BotBob", autoAcceptInvites: true, }, - labsFlags: ["feature_new_room_list"], }); function getPrimaryFilters(page: Page): Locator { diff --git a/playwright/e2e/left-panel/room-list-panel/room-list-header.spec.ts b/playwright/e2e/left-panel/room-list-panel/room-list-header.spec.ts index 96e0ca8597..00cd2806ed 100644 --- a/playwright/e2e/left-panel/room-list-panel/room-list-header.spec.ts +++ b/playwright/e2e/left-panel/room-list-panel/room-list-header.spec.ts @@ -9,10 +9,6 @@ import { test, expect } from "../../../element-web-test"; import type { Page } from "@playwright/test"; test.describe("Header section of the room list", () => { - test.use({ - labsFlags: ["feature_new_room_list"], - }); - /** * Get the header section of the room list * @param page diff --git a/playwright/e2e/left-panel/room-list-panel/room-list-panel.spec.ts b/playwright/e2e/left-panel/room-list-panel/room-list-panel.spec.ts index bc1387cbce..ef7aa1857f 100644 --- a/playwright/e2e/left-panel/room-list-panel/room-list-panel.spec.ts +++ b/playwright/e2e/left-panel/room-list-panel/room-list-panel.spec.ts @@ -10,10 +10,6 @@ import { type Page } from "@playwright/test"; import { test, expect } from "../../../element-web-test"; test.describe("Room list panel", () => { - test.use({ - labsFlags: ["feature_new_room_list"], - }); - /** * Get the room list view * @param page diff --git a/playwright/e2e/left-panel/room-list-panel/room-list-search.spec.ts b/playwright/e2e/left-panel/room-list-panel/room-list-search.spec.ts index 028503f622..15b97717da 100644 --- a/playwright/e2e/left-panel/room-list-panel/room-list-search.spec.ts +++ b/playwright/e2e/left-panel/room-list-panel/room-list-search.spec.ts @@ -10,10 +10,6 @@ import { type Page } from "@playwright/test"; import { test, expect } from "../../../element-web-test"; test.describe("Search section of the room list", () => { - test.use({ - labsFlags: ["feature_new_room_list"], - }); - /** * Get the search section of the room list * @param page diff --git a/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts b/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts index 4d09f228d0..dd95d1410f 100644 --- a/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts +++ b/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts @@ -12,7 +12,6 @@ import { expect, test } from "../../../element-web-test"; test.describe("Room list", () => { test.use({ displayName: "Alice", - labsFlags: ["feature_new_room_list"], botCreateOpts: { displayName: "BotBob", }, @@ -276,7 +275,7 @@ test.describe("Room list", () => { }); test.describe("Avatar decoration", () => { - test.use({ labsFlags: ["feature_video_rooms", "feature_new_room_list"] }); + test.use({ labsFlags: ["feature_video_rooms"] }); test("should be a public room", { tag: "@screenshot" }, async ({ page, app, user }) => { // @ts-ignore Visibility enum is not accessible diff --git a/playwright/e2e/release-announcement/releaseAnnouncement.spec.ts b/playwright/e2e/release-announcement/releaseAnnouncement.spec.ts index 34c90ddf03..32c953fd6a 100644 --- a/playwright/e2e/release-announcement/releaseAnnouncement.spec.ts +++ b/playwright/e2e/release-announcement/releaseAnnouncement.spec.ts @@ -22,7 +22,6 @@ test.describe("Release announcement", () => { await app.viewRoomById(roomId); await use({ roomId }); }, - labsFlags: ["feature_new_room_list"], }); test( diff --git a/playwright/e2e/room/room-header.spec.ts b/playwright/e2e/room/room-header.spec.ts index 2f681fe3d3..d3b96c650e 100644 --- a/playwright/e2e/room/room-header.spec.ts +++ b/playwright/e2e/room/room-header.spec.ts @@ -15,11 +15,6 @@ import { type ElementAppPage } from "../../pages/ElementAppPage"; test.describe("Room Header", () => { test.use({ displayName: "Sakura", - config: { - features: { - feature_new_room_list: false, - }, - }, }); test.describe("with feature_notifications enabled", () => { diff --git a/playwright/e2e/settings/preferences-user-settings-tab.spec.ts b/playwright/e2e/settings/preferences-user-settings-tab.spec.ts index 8adbc74cc5..bd3ef0701c 100644 --- a/playwright/e2e/settings/preferences-user-settings-tab.spec.ts +++ b/playwright/e2e/settings/preferences-user-settings-tab.spec.ts @@ -21,8 +21,6 @@ test.describe("Preferences user settings tab", () => { const locator = await app.settings.openUserSettings("Preferences"); await use(locator); }, - // display message preview settings - labsFlags: ["feature_new_room_list"], }); test("should be rendered properly", { tag: "@screenshot" }, async ({ app, page, user }) => { diff --git a/res/css/components/views/spaces/_QuickThemeSwitcher.pcss b/res/css/components/views/spaces/_QuickThemeSwitcher.pcss index b84bb636e6..387b8a56b4 100644 --- a/res/css/components/views/spaces/_QuickThemeSwitcher.pcss +++ b/res/css/components/views/spaces/_QuickThemeSwitcher.pcss @@ -9,6 +9,7 @@ Please see LICENSE files in the repository root for full details. .mx_QuickThemeSwitcher { display: flex; align-items: center; + margin-top: var(--cpd-space-2x); .mx_Dropdown { min-width: 100px; diff --git a/res/css/structures/_BackdropPanel.pcss b/res/css/structures/_BackdropPanel.pcss deleted file mode 100644 index ee12886880..0000000000 --- a/res/css/structures/_BackdropPanel.pcss +++ /dev/null @@ -1,29 +0,0 @@ -/* -Copyright 2021-2024 New Vector Ltd. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -.mx_BackdropPanel { - position: absolute; - left: 0; - top: 0; - height: 100vh; - width: 100%; - overflow: hidden; - filter: blur(var(--lp-background-blur)); - /* Force a new layer for the backdropPanel so it's better hardware supported */ - transform: translateZ(0); -} - -.mx_BackdropPanel--image { - position: absolute; - top: 0; - left: 0; - min-height: 100%; - z-index: 0; - pointer-events: none; - overflow: hidden; - user-select: none; -} diff --git a/res/css/structures/_LeftPanel.pcss b/res/css/structures/_LeftPanel.pcss index 14c9230a37..f33f2b4f0b 100644 --- a/res/css/structures/_LeftPanel.pcss +++ b/res/css/structures/_LeftPanel.pcss @@ -180,27 +180,6 @@ Please see LICENSE files in the repository root for full details. .mx_LegacyRoomListHeader:first-child { margin-top: 12px; } - - .mx_LeftPanel_roomListWrapper { - /* Make the y-scrollbar more responsive */ - padding-right: 2px; - overflow: hidden; - margin-top: 10px; /* so we're not up against the search/filter */ - flex: 1 0 0; /* needed in Safari to properly set flex-basis */ - - &.mx_LeftPanel_roomListWrapper_stickyBottom { - padding-bottom: 32px; - } - - &.mx_LeftPanel_roomListWrapper_stickyTop { - padding-top: 32px; - } - } - - .mx_LeftPanel_actualRoomListContainer { - position: relative; /* for sticky headers */ - height: 100%; /* ensure scrolling still works */ - } } /* These styles override the defaults for the minimized (66px) layout */ diff --git a/res/css/structures/_QuickSettingsButton.pcss b/res/css/structures/_QuickSettingsButton.pcss index 83d997f3a2..84fbdf9dd6 100644 --- a/res/css/structures/_QuickSettingsButton.pcss +++ b/res/css/structures/_QuickSettingsButton.pcss @@ -110,12 +110,6 @@ Please see LICENSE files in the repository root for full details. } } -.mx_QuickSettingsButton_ContextMenuWrapper_new_room_list { - .mx_QuickThemeSwitcher { - margin-top: var(--cpd-space-2x); - } -} - .mx_QuickSettingsButton_icon { margin-right: var(--cpd-space-1x); color: $secondary-content; diff --git a/res/css/structures/_RoomSearch.pcss b/res/css/structures/_RoomSearch.pcss deleted file mode 100644 index beb60e7ef9..0000000000 --- a/res/css/structures/_RoomSearch.pcss +++ /dev/null @@ -1,93 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2020 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -/* Note: this component expects to be contained within a flexbox */ -.mx_RoomSearch { - flex: 1; - min-width: 0; - border-radius: 8px; - background-color: $panel-actions; - /* keep border thickness consistent to prevent movement */ - border: 1px solid transparent; - height: 28px; - padding: 1px; - - /* Create a flexbox for the icons (easier to manage) */ - display: flex; - align-items: center; - - cursor: pointer; - - .mx_RoomSearch_icon { - width: 20px; - height: 20px; - color: $secondary-content; - margin-left: var(--cpd-space-2x); - flex-shrink: 0; - } - - .mx_RoomSearch_spotlightTriggerText { - color: var(--cpd-color-text-secondary); - flex: 1; - min-width: 0; - /* the following rules are to match that of a real input field */ - overflow: hidden; - margin: 9px; - font: var(--cpd-font-body-sm-semibold); - } - - .mx_RoomSearch_shortcutPrompt { - border-radius: 6px; - background-color: $panel-actions; - padding: 2px 4px; - user-select: none; - font-size: $font-12px; - line-height: $font-15px; - font-family: inherit; - font-weight: var(--cpd-font-weight-semibold); - color: $light-fg-color; - margin-right: 6px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - &.mx_RoomSearch_minimized { - height: 32px; - min-height: 32px; - width: 32px; - box-sizing: border-box; - - .mx_RoomSearch_icon { - margin: 0 auto; - padding: 1px; - align-self: center; - } - - .mx_RoomSearch_shortcutPrompt { - display: none; - } - } - - &:hover { - background-color: $tertiary-content; - - .mx_RoomSearch_spotlightTriggerText { - color: $background; - } - - .mx_RoomSearch_shortcutPrompt { - background-color: $background; - color: $secondary-content; - } - - .mx_RoomSearch_icon { - color: $background; - } - } -} diff --git a/res/css/structures/_SpacePanel.pcss b/res/css/structures/_SpacePanel.pcss index d64a40bf28..d4dc02749c 100644 --- a/res/css/structures/_SpacePanel.pcss +++ b/res/css/structures/_SpacePanel.pcss @@ -14,7 +14,8 @@ Please see LICENSE files in the repository root for full details. --height-nested: 24px; --height-topLevel: 32px; - background-color: $spacePanel-bg-color; + background-color: var(--cpd-color-bg-canvas-default); + border-right: 1px solid var(--cpd-color-bg-subtle-primary); flex: 0 0 auto; padding: 0; margin: 0; @@ -30,11 +31,6 @@ Please see LICENSE files in the repository root for full details. width: 68px; } - &.newUi { - background-color: var(--cpd-color-bg-canvas-default); - border-right: 1px solid var(--cpd-color-bg-subtle-primary); - } - .mx_SpacePanel_toggleCollapse { position: absolute; width: 18px; @@ -397,9 +393,10 @@ Please see LICENSE files in the repository root for full details. .mx_UserMenu { padding-bottom: 12px; border-bottom: 1px solid $separator; - margin: 12px 14px 4px 18px; + margin: var(--cpd-space-4x) 14px 4px 18px; width: min-content; max-width: 226px; + border-bottom: none; /* Display the container and img here as block elements so they don't take * up extra vertical space. @@ -408,11 +405,6 @@ Please see LICENSE files in the repository root for full details. display: block; } } - - &.newUi .mx_UserMenu { - margin-top: var(--cpd-space-4x); - border-bottom: none; - } } .mx_SpacePanel_contextMenu { diff --git a/res/css/views/rooms/_LegacyRoomList.pcss b/res/css/views/rooms/_LegacyRoomList.pcss deleted file mode 100644 index c2cdcc220b..0000000000 --- a/res/css/views/rooms/_LegacyRoomList.pcss +++ /dev/null @@ -1,33 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2020 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -.mx_LegacyRoomList { - padding-right: 7px; /* width of the scrollbar, to line things up */ -} - -.mx_LegacyRoomList_iconPlus::before { - mask-image: url("$(res)/img/element-icons/roomlist/plus-circle.svg"); -} -.mx_LegacyRoomList_iconNewRoom::before { - mask-image: url("$(res)/img/element-icons/roomlist/hash-plus.svg"); -} -.mx_LegacyRoomList_iconNewVideoRoom::before { - mask-image: url("$(res)/img/element-icons/roomlist/hash-video.svg"); -} -.mx_LegacyRoomList_iconAddExistingRoom::before { - mask-image: url("$(res)/img/element-icons/roomlist/hash.svg"); -} -.mx_LegacyRoomList_iconExplore::before { - mask-image: url("$(res)/img/element-icons/roomlist/hash-search.svg"); -} -.mx_LegacyRoomList_iconStartChat::before { - mask-image: url("@vector-im/compound-design-tokens/icons/user-add-solid.svg"); -} -.mx_LegacyRoomList_iconInvite::before { - mask-image: url("$(res)/img/element-icons/room/share.svg"); -} diff --git a/res/css/views/rooms/_LegacyRoomListHeader.pcss b/res/css/views/rooms/_LegacyRoomListHeader.pcss deleted file mode 100644 index c04b56d94a..0000000000 --- a/res/css/views/rooms/_LegacyRoomListHeader.pcss +++ /dev/null @@ -1,108 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2021 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -.mx_LegacyRoomListHeader { - display: flex; - align-items: center; - - .mx_LegacyRoomListHeader_contextLessTitle, - .mx_LegacyRoomListHeader_contextMenuButton { - font: var(--cpd-font-heading-sm-semibold); - font-weight: var(--cpd-font-weight-semibold); - padding: 1px 24px 1px 4px; - position: relative; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - margin-left: 8px; - margin-right: auto; - user-select: none; - } - - .mx_LegacyRoomListHeader_contextMenuButton { - border-radius: 6px; - - &:hover { - background-color: $quinary-content; - } - - &::before { - content: ""; - width: 20px; - height: 20px; - top: 3px; - right: 0; - position: absolute; - mask-position: center; - mask-size: contain; - mask-repeat: no-repeat; - background-color: $tertiary-content; - mask-image: url("@vector-im/compound-design-tokens/icons/chevron-down.svg"); - } - - &[aria-expanded="true"] { - background-color: $quinary-content; - - &::before { - transform: rotate(180deg); - } - } - } - - .mx_LegacyRoomListHeader_plusButton { - width: 32px; - height: 32px; - border-radius: 8px; - position: relative; - padding: 8px; - margin-left: 8px; - margin-right: 12px; - background-color: $panel-actions; - box-sizing: border-box; - flex-shrink: 0; - - &::before { - content: ""; - width: 16px; - height: 16px; - position: absolute; - mask-position: center; - mask-size: contain; - mask-repeat: no-repeat; - background-color: $secondary-content; - mask-image: url("@vector-im/compound-design-tokens/icons/plus.svg"); - } - - &:hover { - background-color: $tertiary-content; - - &::before { - background-color: $background; - } - } - } -} - -.mx_LegacyRoomListHeader_iconInvite::before { - mask-image: url("$(res)/img/element-icons/room/invite.svg"); -} -.mx_LegacyRoomListHeader_iconStartChat::before { - mask-image: url("@vector-im/compound-design-tokens/icons/user-add-solid.svg"); -} -.mx_LegacyRoomListHeader_iconNewRoom::before { - mask-image: url("$(res)/img/element-icons/roomlist/hash-plus.svg"); -} -.mx_LegacyRoomListHeader_iconNewVideoRoom::before { - mask-image: url("$(res)/img/element-icons/roomlist/hash-video.svg"); -} -.mx_LegacyRoomListHeader_iconExplore::before { - mask-image: url("$(res)/img/element-icons/roomlist/hash-search.svg"); -} -.mx_LegacyRoomListHeader_iconPlus::before { - mask-image: url("@vector-im/compound-design-tokens/icons/plus.svg"); -} diff --git a/res/css/views/rooms/_RoomBreadcrumbs.pcss b/res/css/views/rooms/_RoomBreadcrumbs.pcss deleted file mode 100644 index 7dd150cd27..0000000000 --- a/res/css/views/rooms/_RoomBreadcrumbs.pcss +++ /dev/null @@ -1,43 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2020 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -.mx_RoomBreadcrumbs { - width: 100%; - - /* Create a flexbox for the crumbs */ - display: flex; - flex-direction: row; - align-items: flex-start; - margin-bottom: 12px; - - .mx_RoomBreadcrumbs_crumb { - margin-right: 8px; - width: 32px; - } - - /* These classes come from the CSSTransition component. There's many more classes we */ - /* could care about, but this is all we worried about for now. The animation works by */ - /* first triggering the enter state with the newest breadcrumb off screen (-40px) then */ - /* sliding it into view. */ - &.mx_RoomBreadcrumbs-enter { - transform: translateX(-40px); /* 32px for the avatar, 8px for the margin */ - } - &.mx_RoomBreadcrumbs-enter-active { - transform: translateX(0); - - /* Timing function is as-requested by design. */ - /* NOTE: The transition time MUST match the value passed to CSSTransition! */ - transition: transform 640ms cubic-bezier(0.66, 0.02, 0.36, 1); - } - - .mx_RoomBreadcrumbs_placeholder { - font: var(--cpd-font-body-md-semibold); - line-height: 32px; /* specifically to match the height this is not scaled */ - height: 32px; - } -} diff --git a/res/css/views/rooms/_RoomSublist.pcss b/res/css/views/rooms/_RoomSublist.pcss deleted file mode 100644 index 3361bce4bb..0000000000 --- a/res/css/views/rooms/_RoomSublist.pcss +++ /dev/null @@ -1,422 +0,0 @@ -/* -Copyright 2024,2025 New Vector Ltd. -Copyright 2020 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -.mx_RoomSublist { - margin-left: 8px; - margin-bottom: 4px; - - &.mx_RoomSublist_hidden { - display: none; - } - - &:not(.mx_RoomSublist_minimized) { - .mx_RoomSublist_headerContainer { - height: auto; - } - } - - .mx_RoomSublist_headerContainer { - /* Create a flexbox to make alignment easy */ - display: flex; - align-items: center; - - /* *************************** */ - /* Sticky Headers Start */ - - /* Ideally we'd be able to use `position: sticky; top: 0; bottom: 0;` on the */ - /* headerContainer, however due to our layout concerns we actually have to */ - /* calculate it manually so we can sticky things in the right places. We also */ - /* target the headerText instead of the container to reduce jumps when scrolling, */ - /* and to help hide the badges/other buttons that could appear on hover. This */ - /* 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 LeftPanel component for sticky headers */ - /* to work correctly. */ - padding-bottom: 8px; - height: 24px; - color: $secondary-content; - - .mx_RoomSublist_stickableContainer { - width: 100%; - } - - .mx_RoomSublist_stickable { - flex: 1; - max-width: 100%; - - /* Create a flexbox to make ordering easy */ - display: flex; - align-items: center; - - /* We use a generic sticky class for 2 reasons: to reduce style duplication and */ - /* to identify when a header is sticky. If we didn't have a consistent sticky class, */ - /* we'd have to do the "is sticky" checks again on click, as clicking the header */ - /* when sticky scrolls instead of collapses the list. */ - &.mx_RoomSublist_headerContainer_sticky { - position: fixed; - height: 32px; /* to match the header container */ - /* width set by JS because of a compat issue between Firefox and Chrome */ - width: calc(100% - 15px); - } - - /* We don't have a top style because the top is dependent on the room list header's */ - /* height, and is therefore calculated in JS. */ - /* The class, mx_RoomSublist_headerContainer_stickyTop, is applied though. */ - } - - /* Sticky Headers End */ - /* *************************** */ - - .mx_RoomSublist_badgeContainer { - /* Create another flexbox row because it's super easy to position the badge this way. */ - display: flex; - align-items: center; - justify-content: center; - - /* Apply the width and margin to the badge so the container doesn't occupy dead space */ - .mx_NotificationBadge { - /* Do not set a width so the badges get properly sized */ - margin-left: 8px; /* same as menu+aux buttons */ - } - } - - &:not(.mx_RoomSublist_headerContainer_withAux) { - .mx_NotificationBadge { - margin-right: 4px; /* just to push it over a bit, aligning it with the other elements */ - } - } - - .mx_RoomSublist_auxButton, - .mx_RoomSublist_menuButton { - margin-left: 8px; /* should be the same as the notification badge */ - position: relative; - width: 24px; - height: 24px; - border-radius: 8px; - - &::before { - content: ""; - width: 16px; - height: 16px; - position: absolute; - top: 4px; - left: 4px; - mask-position: center; - mask-size: contain; - mask-repeat: no-repeat; - background: var(--cpd-color-icon-secondary); - } - } - - .mx_RoomSublist_auxButton:hover, - .mx_RoomSublist_menuButton:hover { - background: $panel-actions; - } - - /* Hide the menu button by default */ - .mx_RoomSublist_menuButton { - visibility: hidden; - width: 0; - margin: 0; - } - - .mx_RoomSublist_auxButton::before { - mask-image: url("@vector-im/compound-design-tokens/icons/plus.svg"); - } - - .mx_RoomSublist_menuButton::before { - mask-image: url("@vector-im/compound-design-tokens/icons/overflow-horizontal.svg"); - } - - .mx_RoomSublist_headerText { - flex: 1; - max-width: calc(100% - 16px); /* 16px is the badge width */ - font: var(--cpd-font-body-sm-semibold); - - /* Ellipsize any text overflow */ - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - - .mx_RoomSublist_collapseBtn { - display: inline-block; - position: relative; - width: 14px; - height: 14px; - margin-right: 6px; - - &::before { - content: ""; - width: 18px; - height: 18px; - position: absolute; - mask-position: center; - mask-size: contain; - mask-repeat: no-repeat; - background-color: var(--cpd-color-icon-secondary); - mask-image: url("@vector-im/compound-design-tokens/icons/chevron-down.svg"); - } - - &.mx_RoomSublist_collapseBtn_collapsed::before { - transform: rotate(-90deg); - } - } - } - } - - /* In the general case, we reserve space for each sublist header to prevent */ - /* scroll jumps when they become sticky. However, that leaves a gap when */ - /* scrolled to the top above the first sublist (whose header can only ever */ - /* stick to top), so we make sure to exclude the first visible sublist. */ - &:not(.mx_RoomSublist_hidden) ~ .mx_RoomSublist .mx_RoomSublist_stickableContainer { - height: 24px; - } - - .mx_RoomSublist_resizeBox { - position: relative; - - /* Create another flexbox column for the tiles */ - display: flex; - flex-direction: column; - overflow: hidden; - - .mx_RoomSublist_tiles { - flex: 1 0 0; - overflow: hidden; - overflow: clip; - /* need this to be flex otherwise the overflow hidden from above */ - /* sometimes vertically centers the clipped list ... no idea why it would do this */ - /* as the box model should be top aligned. Happens in both FF and Chromium */ - display: flex; - flex-direction: column; - align-self: stretch; - /* without this Firefox will prefer pushing the resizer & show more/less button into the overflow */ - min-height: 0; - - mask-image: linear-gradient(0deg, transparent, black 4px); - } - - &.mx_RoomSublist_resizeBox_forceExpanded .mx_RoomSublist_tiles { - /* in this state the div can collapse its height entirely in Chromium, */ - /* so prevent that by allowing overflow */ - overflow: visible; - /* clear the min-height to make it not collapse entirely in a state with no active resizer */ - min-height: unset; - } - - .mx_RoomSublist_resizerHandles_showNButton { - flex: 0 0 32px; - } - - .mx_RoomSublist_resizerHandles { - flex: 0 0 4px; - display: flex; - justify-content: center; - width: 100%; - } - - /* Class name comes from the ResizableBox component */ - /* The hover state needs to use the whole sublist, not just the resizable box, */ - /* so that selector is below and one level higher. */ - .mx_RoomSublist_resizerHandle { - cursor: ns-resize; - border-radius: 3px; - - /* Override styles from library */ - max-width: 64px; - height: 4px !important; /* Update RESIZE_HANDLE_HEIGHT if this changes */ - - /* This is positioned directly below the 'show more' button. */ - position: relative !important; - bottom: 0 !important; /* override from library */ - } - - &:hover, - &.mx_RoomSublist_hasMenuOpen { - .mx_RoomSublist_resizerHandle { - opacity: 0.8; - background-color: $primary-content; - } - } - } - - .mx_RoomSublist_showNButton { - cursor: pointer; - font-size: $font-13px; - line-height: $font-18px; - color: $secondary-content; - - /* Update the render() function for RoomSublist if these change */ - /* Update the ListLayout class for minVisibleTiles if these change. */ - height: 24px; - padding-bottom: 4px; - - /* We create a flexbox to cheat at alignment */ - display: flex; - align-items: center; - - .mx_RoomSublist_showNButtonChevron { - position: relative; - width: 18px; - height: 18px; - margin-left: 12px; - margin-right: 16px; - mask-position: center; - mask-size: contain; - mask-repeat: no-repeat; - background: $tertiary-content; - left: -1px; /* adjust for image position */ - } - - .mx_RoomSublist_showMoreButtonChevron, - .mx_RoomSublist_showLessButtonChevron { - mask-image: url("@vector-im/compound-design-tokens/icons/chevron-down.svg"); - } - - .mx_RoomSublist_showLessButtonChevron { - transform: rotate(180deg); - } - } - - &.mx_RoomSublist_hasMenuOpen, - &:not(.mx_RoomSublist_minimized) > .mx_RoomSublist_headerContainer:focus-within, - &:not(.mx_RoomSublist_minimized) > .mx_RoomSublist_headerContainer:hover { - .mx_RoomSublist_menuButton { - visibility: visible; - width: 24px; - margin-left: 8px; - } - } - - &.mx_RoomSublist_minimized { - .mx_RoomSublist_headerContainer { - height: auto; - flex-direction: column; - position: relative; - - .mx_RoomSublist_badgeContainer { - order: 0; - align-self: flex-end; - margin-right: 0; - } - - .mx_RoomSublist_stickable { - order: 1; - max-width: 100%; - } - - .mx_RoomSublist_auxButton { - order: 2; - visibility: visible; - width: 32px !important; /* !important to override hover styles */ - height: 32px !important; /* !important to override hover styles */ - margin-left: 0 !important; /* !important to override hover styles */ - background-color: $panel-actions; - margin-top: 8px; - - &::before { - top: 8px; - left: 8px; - } - } - } - - .mx_RoomSublist_resizeBox { - align-items: center; - } - - .mx_RoomSublist_showNButton { - flex-direction: column; - - .mx_RoomSublist_showNButtonChevron { - margin-right: 12px; /* to center */ - } - } - - .mx_RoomSublist_menuButton { - height: 16px; - } - - &.mx_RoomSublist_hasMenuOpen, - & > .mx_RoomSublist_headerContainer:hover { - .mx_RoomSublist_menuButton { - visibility: visible; - position: absolute; - bottom: 48px; /* align to middle of name, 40px for aux button (with padding) and 8px for alignment */ - right: 0; - width: 16px; - height: 16px; - border-radius: 0; - z-index: 1; /* occlude the list name */ - - /* This is the same color as the left panel background because it needs */ - /* to occlude the sublist title */ - background-color: $roomlist-bg-color; - - &::before { - top: 0; - left: 0; - } - } - - &.mx_RoomSublist_headerContainer:not(.mx_RoomSublist_headerContainer_withAux) { - .mx_RoomSublist_menuButton { - bottom: 8px; /* align to the middle of name, 40px less than the `bottom` above. */ - } - } - } - } -} - -.mx_RoomSublist_contextMenu { - padding: 20px 16px; - width: 250px; - - hr { - margin-top: 16px; - margin-bottom: 16px; - margin-right: 16px; /* additional 16px */ - border: 1px solid $primary-content; - opacity: 0.1; - } - - .mx_RoomSublist_contextMenu_title { - font-size: $font-15px; - line-height: $font-20px; - font-weight: var(--cpd-font-weight-semibold); - margin-bottom: 4px; - } - - .mx_StyledRadioButton { - margin-top: 8px; - } -} - -.mx_RoomSublist_skeletonUI { - position: relative; - margin-left: 4px; - height: 240px; - - &::before { - background-color: var(--cpd-color-bg-subtle-secondary); - width: 100%; - height: 100%; - - content: ""; - position: absolute; - mask-repeat: repeat-y; - mask-size: auto 48px; - mask-image: url("$(res)/img/element-icons/roomlist/skeleton-ui.svg"); - } -} - -.mx_RoomSublist_minimized .mx_RoomSublist_skeletonUI { - width: 32px; /* cut off the horizontal lines in the svg */ - margin-left: 10px; /* align with sublist + buttons */ -} diff --git a/res/css/views/rooms/_RoomTile.pcss b/res/css/views/rooms/_RoomTile.pcss deleted file mode 100644 index 23649228e6..0000000000 --- a/res/css/views/rooms/_RoomTile.pcss +++ /dev/null @@ -1,167 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2020-2023 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -/* Note: the room tile expects to be in a flexbox column container */ -.mx_RoomTile { - margin-bottom: 4px; - padding: 4px; - - /* The tile is also a flexbox row itself */ - display: flex; - contain: content; /* Not strict as it will break when resizing a sublist vertically */ - box-sizing: border-box; - - font-size: var(--cpd-font-size-body-sm); - - &.mx_RoomTile_selected, - &:hover, - &:focus-within, - &.mx_RoomTile_hasMenuOpen { - background-color: $panel-actions; - border-radius: 8px; - } - - .mx_DecoratedRoomAvatar, - .mx_RoomTile_avatarContainer { - margin-right: 10px; - } - - .mx_RoomTile_details { - min-width: 0; - } - - .mx_RoomTile_titleContainer { - height: 32px; - min-width: 0; - flex-basis: 0; - flex-grow: 1; - margin-right: 8px; /* spacing to buttons/badges */ - - /* Create a new column layout flexbox for the title parts */ - display: flex; - flex-direction: column; - justify-content: center; - - .mx_RoomTile_subtitle { - align-items: center; - color: $secondary-content; - display: flex; - gap: $spacing-4; - line-height: 1.25; - position: relative; - top: -1px; - } - - .mx_RoomTile_title, - .mx_RoomTile_subtitle_text { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - .mx_RoomTile_title { - font: var(--cpd-font-body-md-regular); - line-height: 1.25; - - &.mx_RoomTile_titleHasUnreadEvents { - font-weight: var(--cpd-font-weight-semibold); - } - } - - .mx_RoomTile_titleWithSubtitle { - margin-top: -2px; /* shift the title up a bit more */ - } - } - - .mx_RoomTile_notificationsButton { - margin-left: 4px; /* spacing between buttons */ - } - - .mx_RoomTile_badgeContainer { - height: 16px; - /* don't set width so that it takes no space when there is no badge to show */ - margin: auto 0; /* vertically align */ - - /* Create a flexbox to make aligning dot badges easier */ - display: flex; - align-items: center; - - .mx_NotificationBadge { - margin-right: 2px; /* centering */ - } - - .mx_NotificationBadge_dot { - /* make the smaller dot occupy the same width for centering */ - margin-left: 5px; - margin-right: 7px; - } - } - - /* The context menu buttons are hidden by default */ - .mx_RoomTile_menuButton, - .mx_RoomTile_notificationsButton { - width: 20px; - min-width: 20px; /* yay flex */ - height: 20px; - margin-top: auto; - margin-bottom: auto; - position: relative; - display: none; - - &::before { - top: 2px; - left: 2px; - content: ""; - width: 16px; - height: 16px; - position: absolute; - mask-position: center; - mask-size: contain; - mask-repeat: no-repeat; - background: var(--cpd-color-icon-primary); - } - } - - /* If the room has an overriden notification setting then we always show the notifications menu button */ - .mx_RoomTile_notificationsButton.mx_RoomTile_notificationsButton_show { - display: block; - } - - .mx_RoomTile_menuButton::before { - mask-image: url("@vector-im/compound-design-tokens/icons/overflow-horizontal.svg"); - } - - &:not(.mx_RoomTile_minimized, .mx_RoomTile_sticky) { - &:hover, - &:focus-within, - &.mx_RoomTile_hasMenuOpen { - /* Hide the badge container on hover because it'll be a menu button */ - .mx_RoomTile_badgeContainer { - width: 0; - height: 0; - display: none; - } - - .mx_RoomTile_notificationsButton, - .mx_RoomTile_menuButton { - display: block; - } - } - } - - &.mx_RoomTile_minimized { - flex-direction: column; - align-items: center; - position: relative; - - .mx_DecoratedRoomAvatar, - .mx_RoomTile_avatarContainer { - margin-right: 0; - } - } -} diff --git a/res/css/views/settings/_AvatarSetting.pcss b/res/css/views/settings/_AvatarSetting.pcss index b928522820..7c6e2b72da 100644 --- a/res/css/views/settings/_AvatarSetting.pcss +++ b/res/css/views/settings/_AvatarSetting.pcss @@ -13,10 +13,6 @@ Please see LICENSE files in the repository root for full details. margin-top: 8px; position: relative; - &.mx_AvatarSetting_avatarDisplay:hover .mx_AvatarSetting_hover { - opacity: 1; - } - & > * { box-sizing: border-box; } diff --git a/res/themes/light-high-contrast/css/_light-high-contrast.pcss b/res/themes/light-high-contrast/css/_light-high-contrast.pcss index 94774bc5b8..bd18c4ee86 100644 --- a/res/themes/light-high-contrast/css/_light-high-contrast.pcss +++ b/res/themes/light-high-contrast/css/_light-high-contrast.pcss @@ -95,17 +95,6 @@ $accent-1400: var(--cpd-color-green-1400); color: $primary-content; } -.mx_RoomSearch { - &.mx_RoomSearch_focused, - &.mx_RoomSearch_hasQuery { - .mx_RoomSearch_clearButton { - &::before { - background-color: $background !important; - } - } - } -} - .mx_PollCreateDialog { .mx_PollCreateDialog_option { .mx_PollCreateDialog_removeOption { diff --git a/res/themes/light/css/_mods.pcss b/res/themes/light/css/_mods.pcss index 357a8a11d1..7be60b0e1b 100644 --- a/res/themes/light/css/_mods.pcss +++ b/res/themes/light/css/_mods.pcss @@ -1,13 +1,3 @@ -/* sidebar blurred avatar background */ -// -/* if backdrop-filter is supported, */ -/* set the user avatar (if any) as a background so */ -/* it can be blurred by the tag panel and room list */ - -.mx_RoomSublist_showNButton { - background-color: transparent !important; -} - a:hover, a:link, a:visited { diff --git a/src/@types/polyfill.ts b/src/@types/polyfill.ts deleted file mode 100644 index 5e08725889..0000000000 --- a/src/@types/polyfill.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2020 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -// This is intended to fix re-resizer because of its unguarded `instanceof TouchEvent` checks. -export function polyfillTouchEvent(): void { - // Firefox doesn't have touch events without touch devices being present, so create a fake - // one we can rely on lying about. - if (!window.TouchEvent) { - // We have no intention of actually using this, so just lie. - window.TouchEvent = class TouchEvent extends UIEvent { - public get altKey(): boolean { - return false; - } - public get changedTouches(): any { - return []; - } - public get ctrlKey(): boolean { - return false; - } - public get metaKey(): boolean { - return false; - } - public get shiftKey(): boolean { - return false; - } - public get targetTouches(): any { - return []; - } - public get touches(): any { - return []; - } - public get rotation(): number { - return 0.0; - } - public get scale(): number { - return 0.0; - } - public constructor(eventType: string, params?: any) { - super(eventType, params); - } - }; - } -} diff --git a/src/accessibility/LandmarkNavigation.ts b/src/accessibility/LandmarkNavigation.ts index 404e01e99c..391efc2850 100644 --- a/src/accessibility/LandmarkNavigation.ts +++ b/src/accessibility/LandmarkNavigation.ts @@ -9,7 +9,6 @@ import { TimelineRenderingType } from "../contexts/RoomContext"; import { Action } from "../dispatcher/actions"; import defaultDispatcher from "../dispatcher/dispatcher"; -import SettingsStore from "../settings/SettingsStore"; export const enum Landmark { // This is the space/home button in the left panel. @@ -73,16 +72,10 @@ export class LandmarkNavigation { const landmarkToDomElementMap: Record HTMLElement | null | undefined> = { [Landmark.ACTIVE_SPACE_BUTTON]: () => document.querySelector(".mx_SpaceButton_active"), - [Landmark.ROOM_SEARCH]: () => - SettingsStore.getValue("feature_new_room_list") - ? document.querySelector(".mx_RoomListSearch_search") - : document.querySelector(".mx_RoomSearch"), + [Landmark.ROOM_SEARCH]: () => document.querySelector(".mx_RoomListSearch_search"), [Landmark.ROOM_LIST]: () => - SettingsStore.getValue("feature_new_room_list") - ? document.querySelector(".mx_RoomListItemView_selected") || - document.querySelector(".mx_RoomListItemView") - : document.querySelector(".mx_RoomTile_selected") || - document.querySelector(".mx_RoomTile"), + document.querySelector(".mx_RoomListItemView_selected") || + document.querySelector(".mx_RoomListItemView"), [Landmark.MESSAGE_COMPOSER_OR_HOME]: () => { const isComposerOpen = !!document.querySelector(".mx_MessageComposer"); diff --git a/src/accessibility/context_menu/StyledMenuItemCheckbox.tsx b/src/accessibility/context_menu/StyledMenuItemCheckbox.tsx deleted file mode 100644 index f208eb1091..0000000000 --- a/src/accessibility/context_menu/StyledMenuItemCheckbox.tsx +++ /dev/null @@ -1,75 +0,0 @@ -/* -Copyright 2024,2025 New Vector Ltd. -Copyright 2019 The Matrix.org Foundation C.I.C. -Copyright 2018 New Vector Ltd -Copyright 2015, 2016 OpenMarket Ltd - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -import React from "react"; - -import { useRovingTabIndex } from "../RovingTabIndex"; -import StyledCheckbox from "../../components/views/elements/StyledCheckbox"; -import { KeyBindingAction } from "../KeyboardShortcuts"; -import { getKeyBindingsManager } from "../../KeyBindingsManager"; - -interface IProps extends React.ComponentProps { - onChange(): void; // we handle keyup/down ourselves so lose the ChangeEvent - onClose(): void; // gets called after onChange on KeyBindingAction.ActivateSelectedButton -} - -// Semantic component for representing a styled role=menuitemcheckbox -export const StyledMenuItemCheckbox: React.FC = ({ children, onChange, onClose, ...props }) => { - const [onFocus, isActive, ref] = useRovingTabIndex(); - - const onKeyDown = (e: React.KeyboardEvent): void => { - let handled = true; - const action = getKeyBindingsManager().getAccessibilityAction(e); - - switch (action) { - case KeyBindingAction.Space: - onChange(); - break; - case KeyBindingAction.Enter: - onChange(); - // Implements https://www.w3.org/TR/wai-aria-practices/#keyboard-interaction-12 - onClose(); - break; - default: - handled = false; - } - - if (handled) { - e.stopPropagation(); - e.preventDefault(); - } - }; - const onKeyUp = (e: React.KeyboardEvent): void => { - const action = getKeyBindingsManager().getAccessibilityAction(e); - switch (action) { - case KeyBindingAction.Space: - case KeyBindingAction.Enter: - // prevent the input default handler as we handle it on keydown to match - // https://www.w3.org/TR/wai-aria-practices/examples/menubar/menubar-2/menubar-2.html - e.stopPropagation(); - e.preventDefault(); - break; - } - }; - return ( - - {children} - - ); -}; diff --git a/src/accessibility/context_menu/StyledMenuItemRadio.tsx b/src/accessibility/context_menu/StyledMenuItemRadio.tsx deleted file mode 100644 index 5bf6d4706c..0000000000 --- a/src/accessibility/context_menu/StyledMenuItemRadio.tsx +++ /dev/null @@ -1,77 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2019 The Matrix.org Foundation C.I.C. -Copyright 2018 New Vector Ltd -Copyright 2015, 2016 OpenMarket Ltd - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -import React from "react"; - -import { useRovingTabIndex } from "../RovingTabIndex"; -import StyledRadioButton from "../../components/views/elements/StyledRadioButton"; -import { KeyBindingAction } from "../KeyboardShortcuts"; -import { getKeyBindingsManager } from "../../KeyBindingsManager"; - -interface IProps extends React.ComponentProps { - label?: string; - onChange(): void; // we handle keyup/down ourselves so lose the ChangeEvent - onClose(): void; // gets called after onChange on KeyBindingAction.Enter -} - -// Semantic component for representing a styled role=menuitemradio -export const StyledMenuItemRadio: React.FC = ({ children, label, onChange, onClose, ...props }) => { - const [onFocus, isActive, ref] = useRovingTabIndex(); - - const onKeyDown = (e: React.KeyboardEvent): void => { - let handled = true; - const action = getKeyBindingsManager().getAccessibilityAction(e); - - switch (action) { - case KeyBindingAction.Space: - onChange(); - break; - case KeyBindingAction.Enter: - onChange(); - // Implements https://www.w3.org/TR/wai-aria-practices/#keyboard-interaction-12 - onClose(); - break; - default: - handled = false; - } - - if (handled) { - e.stopPropagation(); - e.preventDefault(); - } - }; - const onKeyUp = (e: React.KeyboardEvent): void => { - const action = getKeyBindingsManager().getAccessibilityAction(e); - switch (action) { - case KeyBindingAction.Enter: - case KeyBindingAction.Space: - // prevent the input default handler as we handle it on keydown to match - // https://www.w3.org/TR/wai-aria-practices/examples/menubar/menubar-2/menubar-2.html - e.stopPropagation(); - e.preventDefault(); - break; - } - }; - return ( - - {children} - - ); -}; diff --git a/src/components/structures/BackdropPanel.tsx b/src/components/structures/BackdropPanel.tsx deleted file mode 100644 index f3a44521fa..0000000000 --- a/src/components/structures/BackdropPanel.tsx +++ /dev/null @@ -1,33 +0,0 @@ -/* -Copyright 2021-2024 New Vector Ltd. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -import React, { type CSSProperties } from "react"; - -interface IProps { - backgroundImage?: string; - blurMultiplier?: number; -} - -export const BackdropPanel: React.FC = ({ backgroundImage, blurMultiplier }) => { - if (!backgroundImage) return null; - - const styles: CSSProperties = {}; - if (blurMultiplier) { - const rootStyle = getComputedStyle(document.documentElement); - const blurValue = rootStyle.getPropertyValue("--lp-background-blur"); - const pixelsValue = blurValue.replace("px", ""); - const parsed = parseInt(pixelsValue, 10); - if (!isNaN(parsed)) { - styles.filter = `blur(${parsed * blurMultiplier}px)`; - } - } - return ( -
- -
- ); -}; diff --git a/src/components/structures/ContextMenu.tsx b/src/components/structures/ContextMenu.tsx index 03538a21bf..a73f1cc4f3 100644 --- a/src/components/structures/ContextMenu.tsx +++ b/src/components/structures/ContextMenu.tsx @@ -583,6 +583,15 @@ export const alwaysAboveRightOf = ( return menuOptions; }; +// Placement method for to position context menu below elementRect +export const contextMenuBelow = (elementRect: DOMRect): MenuProps => { + // align the context menu's icons with the icon which opened the context menu + const left = elementRect.left + window.scrollX + elementRect.width; + const top = elementRect.bottom + window.scrollY; + const chevronFace = ChevronFace.None; + return { left, top, chevronFace }; +}; + type ContextMenuTuple = [ boolean, RefObject, @@ -622,5 +631,3 @@ export { ContextMenuTooltipButton } from "../../accessibility/context_menu/Conte export { MenuItem } from "../../accessibility/context_menu/MenuItem"; export { MenuItemCheckbox } from "../../accessibility/context_menu/MenuItemCheckbox"; export { MenuItemRadio } from "../../accessibility/context_menu/MenuItemRadio"; -export { StyledMenuItemCheckbox } from "../../accessibility/context_menu/StyledMenuItemCheckbox"; -export { StyledMenuItemRadio } from "../../accessibility/context_menu/StyledMenuItemRadio"; diff --git a/src/components/structures/LeftPanel.tsx b/src/components/structures/LeftPanel.tsx index 6447b0706b..d432a0002e 100644 --- a/src/components/structures/LeftPanel.tsx +++ b/src/components/structures/LeftPanel.tsx @@ -6,39 +6,22 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import React, { type JSX } from "react"; -import { createRef } from "react"; +import React, { createRef } from "react"; import classNames from "classnames"; -import dis from "../../dispatcher/dispatcher"; -import { _t } from "../../languageHandler"; -import LegacyRoomList from "../views/rooms/LegacyRoomList"; import LegacyCallHandler, { LegacyCallHandlerEvent } from "../../LegacyCallHandler"; -import { HEADER_HEIGHT } from "../views/rooms/RoomSublist"; -import { Action } from "../../dispatcher/actions"; -import RoomSearch from "./RoomSearch"; import type ResizeNotifier from "../../utils/ResizeNotifier"; import SpaceStore from "../../stores/spaces/SpaceStore"; -import { MetaSpace, type SpaceKey, UPDATE_SELECTED_SPACE } from "../../stores/spaces"; -import { getKeyBindingsManager } from "../../KeyBindingsManager"; +import { type SpaceKey, UPDATE_SELECTED_SPACE } from "../../stores/spaces"; import UIStore from "../../stores/UIStore"; -import { type IState as IRovingTabIndexState } from "../../accessibility/RovingTabIndex"; -import LegacyRoomListHeader from "../views/rooms/LegacyRoomListHeader"; import { BreadcrumbsStore } from "../../stores/BreadcrumbsStore"; import RoomListStore, { LISTS_UPDATE_EVENT } from "../../stores/room-list/RoomListStore"; import { UPDATE_EVENT } from "../../stores/AsyncStore"; -import IndicatorScrollbar from "./IndicatorScrollbar"; -import RoomBreadcrumbs from "../views/rooms/RoomBreadcrumbs"; -import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts"; -import { shouldShowComponent } from "../../customisations/helpers/UIComponents"; -import { UIComponent } from "../../settings/UIFeature"; -import AccessibleButton, { type ButtonEvent } from "../views/elements/AccessibleButton"; -import PosthogTrackers from "../../PosthogTrackers"; import type PageType from "../../PageTypes"; -import { Landmark, LandmarkNavigation } from "../../accessibility/LandmarkNavigation"; -import SettingsStore from "../../settings/SettingsStore"; import { RoomListPanel } from "../views/rooms/RoomListPanel"; +const HEADER_HEIGHT = 32; // As defined by CSS + interface IProps { isMinimized: boolean; pageType: PageType; @@ -58,8 +41,6 @@ interface IState { export default class LeftPanel extends React.Component { private listContainerRef = createRef(); - private roomListRef = createRef(); - private focusedElement: Element | null = null; private isDoingStickyHeaders = false; public constructor(props: IProps) { @@ -115,15 +96,6 @@ export default class LeftPanel extends React.Component { this.setState({ activeSpace }); }; - private onDialPad = (): void => { - dis.fire(Action.OpenDialPad); - }; - - private onExplore = (ev: ButtonEvent): void => { - dis.fire(Action.ViewRoomDirectory); - PosthogTrackers.trackInteraction("WebLeftPanelExploreRoomsButton", ev); - }; - private refreshStickyHeaders = (): void => { if (!this.listContainerRef.current) return; // ignore: no headers to sticky this.handleStickyHeaders(this.listContainerRef.current); @@ -289,145 +261,17 @@ export default class LeftPanel extends React.Component { this.handleStickyHeaders(list); }; - private onFocus = (ev: React.FocusEvent): void => { - this.focusedElement = ev.target; - }; - - private onBlur = (): void => { - this.focusedElement = null; - }; - - private onKeyDown = (ev: React.KeyboardEvent, state?: IRovingTabIndexState): void => { - if (!this.focusedElement) return; - - const action = getKeyBindingsManager().getRoomListAction(ev); - switch (action) { - case KeyBindingAction.NextRoom: - if (!state) { - ev.stopPropagation(); - ev.preventDefault(); - this.roomListRef.current?.focus(); - } - break; - } - - const navAction = getKeyBindingsManager().getNavigationAction(ev); - if (navAction === KeyBindingAction.PreviousLandmark || navAction === KeyBindingAction.NextLandmark) { - ev.stopPropagation(); - ev.preventDefault(); - LandmarkNavigation.findAndFocusNextLandmark( - Landmark.ROOM_SEARCH, - navAction === KeyBindingAction.PreviousLandmark, - ); - } - }; - - private renderBreadcrumbs(): React.ReactNode { - if (this.state.showBreadcrumbs === BreadcrumbsMode.Legacy && !this.props.isMinimized) { - return ( - - - - ); - } - } - - private renderSearchDialExplore(): React.ReactNode { - let dialPadButton: JSX.Element | undefined; - - // If we have dialer support, show a button to bring up the dial pad to start a new call - if (this.state.supportsPstnProtocol) { - dialPadButton = ( - - ); - } - - let rightButton: JSX.Element | undefined; - if (this.state.activeSpace === MetaSpace.Home && shouldShowComponent(UIComponent.ExploreRooms)) { - rightButton = ( - - ); - } - - return ( -
- - - {dialPadButton} - {rightButton} -
- ); - } - public render(): React.ReactNode { - const useNewRoomList = SettingsStore.getValue("feature_new_room_list"); const containerClasses = classNames({ mx_LeftPanel: true, - mx_LeftPanel_newRoomList: useNewRoomList, + mx_LeftPanel_newRoomList: true, mx_LeftPanel_minimized: this.props.isMinimized, }); - const roomListClasses = classNames("mx_LeftPanel_actualRoomListContainer", "mx_AutoHideScrollbar"); - if (useNewRoomList) { - return ( -
-
- -
-
- ); - } - - const roomList = ( - - ); - return (
- {shouldShowComponent(UIComponent.FilterContainer) && this.renderSearchDialExplore()} - {this.renderBreadcrumbs()} - {!this.props.isMinimized && } - +
); diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 5249f4ddd2..71f27d7844 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -53,7 +53,6 @@ import { UPDATE_EVENT } from "../../stores/AsyncStore"; import { RoomView } from "./RoomView"; import ToastContainer from "./ToastContainer"; import UserView from "./UserView"; -import { BackdropPanel } from "./BackdropPanel"; import { mediaFromMxc } from "../../customisations/Media"; import { UserTab } from "../views/dialogs/UserTab"; import { type OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload"; @@ -283,25 +282,14 @@ class LoggedInView extends React.Component { private createResizer(): Resizer { let panelSize: number | null; - let panelCollapsed: boolean; - const useNewRoomList = SettingsStore.getValue("feature_new_room_list"); // TODO decrease this once Spaces launches as it'll no longer need to include the 56px Community Panel - const toggleSize = useNewRoomList ? NEW_ROOM_LIST_MIN_WIDTH : 206 - 50; + const toggleSize = NEW_ROOM_LIST_MIN_WIDTH; const collapseConfig: ICollapseConfig = { toggleSize, onCollapsed: (collapsed) => { - if (useNewRoomList) { - // The new room list does not support collapsing. - return; - } - panelCollapsed = collapsed; - if (collapsed) { - dis.dispatch({ action: "hide_left_panel" }); - window.localStorage.setItem("mx_lhs_size", "0"); - } else { - dis.dispatch({ action: "show_left_panel" }); - } + // The new room list does not support collapsing. + return; }, onResized: (size) => { panelSize = size; @@ -312,12 +300,12 @@ class LoggedInView extends React.Component { }, onResizeStop: () => { // Always save the lhs size for the new room list. - if (useNewRoomList || !panelCollapsed) window.localStorage.setItem("mx_lhs_size", "" + panelSize); + window.localStorage.setItem("mx_lhs_size", "" + panelSize); this.context.resizeNotifier.stopResizing(); }, isItemCollapsed: (domNode) => { // New rooms list does not support collapsing. - return !useNewRoomList && domNode.classList.contains("mx_LeftPanel_minimized"); + return false; }, handler: this.resizeHandler.current ?? undefined, }; @@ -331,14 +319,8 @@ class LoggedInView extends React.Component { } private loadResizerPreferences(): void { - const useNewRoomList = SettingsStore.getValue("feature_new_room_list"); - let lhsSize = parseInt(window.localStorage.getItem("mx_lhs_size")!, 10); - // If the user has not set a size, or for the new room list if the size is less than the minimum width, - // set a default size. - if (isNaN(lhsSize) || (useNewRoomList && lhsSize < NEW_ROOM_LIST_MIN_WIDTH)) { - lhsSize = 350; - } - this.resizer?.forHandleWithId("lp-resizer")?.resize(lhsSize); + // New room list does not support resizing + this.resizer?.forHandleWithId("lp-resizer")?.resize(350); } private onAccountData = (event: MatrixEvent): void => { @@ -744,18 +726,15 @@ class LoggedInView extends React.Component { "mx_MatrixChat--with-avatar": this.state.backgroundImage, }); - const useNewRoomList = SettingsStore.getValue("feature_new_room_list"); - const leftPanelWrapperClasses = classNames({ mx_LeftPanel_wrapper: true, - mx_LeftPanel_newRoomList: useNewRoomList, + mx_LeftPanel_newRoomList: true, }); const audioFeedArraysForCalls = this.state.activeCalls.map((call) => { return ; }); - const shouldUseMinimizedUI = !useNewRoomList && this.props.collapseLhs; return (
{
- +
- {!useNewRoomList && ( - - )} - {!useNewRoomList && } {!moduleRenderer && (
diff --git a/src/components/structures/RoomSearch.tsx b/src/components/structures/RoomSearch.tsx deleted file mode 100644 index 4c8329b6dd..0000000000 --- a/src/components/structures/RoomSearch.tsx +++ /dev/null @@ -1,54 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2020, 2021 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -import classNames from "classnames"; -import React from "react"; -import { SearchIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; - -import { ALTERNATE_KEY_NAME } from "../../accessibility/KeyboardShortcuts"; -import defaultDispatcher from "../../dispatcher/dispatcher"; -import { IS_MAC, Key } from "../../Keyboard"; -import { _t } from "../../languageHandler"; -import AccessibleButton from "../views/elements/AccessibleButton"; -import { Action } from "../../dispatcher/actions"; - -interface IProps { - isMinimized: boolean; -} - -export default class RoomSearch extends React.PureComponent { - private openSpotlight(): void { - defaultDispatcher.fire(Action.OpenSpotlight); - } - - public render(): React.ReactNode { - const classes = classNames( - { - mx_RoomSearch: true, - mx_RoomSearch_minimized: this.props.isMinimized, - }, - "mx_RoomSearch_spotlightTrigger", - ); - - const shortcutPrompt = ( - - {IS_MAC ? "⌘ K" : _t(ALTERNATE_KEY_NAME[Key.CONTROL]) + " K"} - - ); - - return ( - - - {!this.props.isMinimized && ( -
{_t("action|search")}
- )} - {shortcutPrompt} -
- ); - } -} diff --git a/src/components/views/context_menus/KebabContextMenu.tsx b/src/components/views/context_menus/KebabContextMenu.tsx index f6fc9972a0..48fcf9b0f0 100644 --- a/src/components/views/context_menus/KebabContextMenu.tsx +++ b/src/components/views/context_menus/KebabContextMenu.tsx @@ -9,18 +9,10 @@ Please see LICENSE files in the repository root for full details. import React from "react"; import ContextMenuIcon from "@vector-im/compound-design-tokens/assets/web/icons/overflow-horizontal"; -import { ChevronFace, ContextMenuButton, type MenuProps, useContextMenu } from "../../structures/ContextMenu"; +import { contextMenuBelow, ContextMenuButton, useContextMenu } from "../../structures/ContextMenu"; import { type ButtonProps } from "../elements/AccessibleButton"; import IconizedContextMenu, { IconizedContextMenuOptionList } from "./IconizedContextMenu"; -const contextMenuBelow = (elementRect: DOMRect): MenuProps => { - // align the context menu's icons with the icon which opened the context menu - const left = elementRect.left + window.scrollX + elementRect.width; - const top = elementRect.bottom + window.scrollY; - const chevronFace = ChevronFace.None; - return { left, top, chevronFace }; -}; - type KebabContextMenuProps = Partial> & { options: React.ReactNode[]; title: string; diff --git a/src/components/views/context_menus/ThreadListContextMenu.tsx b/src/components/views/context_menus/ThreadListContextMenu.tsx index ef32d62282..368b003785 100644 --- a/src/components/views/context_menus/ThreadListContextMenu.tsx +++ b/src/components/views/context_menus/ThreadListContextMenu.tsx @@ -14,7 +14,7 @@ import dis from "../../../dispatcher/dispatcher"; import { Action } from "../../../dispatcher/actions"; import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"; import { copyPlaintext } from "../../../utils/strings"; -import { ChevronFace, ContextMenuTooltipButton, type MenuProps, useContextMenu } from "../../structures/ContextMenu"; +import { contextMenuBelow, ContextMenuTooltipButton, useContextMenu } from "../../structures/ContextMenu"; import { _t } from "../../../languageHandler"; import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList } from "./IconizedContextMenu"; import { WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore"; @@ -27,14 +27,6 @@ export interface ThreadListContextMenuProps { onMenuToggle?: (open: boolean) => void; } -const contextMenuBelow = (elementRect: DOMRect): MenuProps => { - // align the context menu's icons with the icon which opened the context menu - const left = elementRect.left + window.scrollX + elementRect.width; - const top = elementRect.bottom + window.scrollY; - const chevronFace = ChevronFace.None; - return { left, top, chevronFace }; -}; - const ThreadListContextMenu: React.FC = ({ mxEvent, permalinkCreator, diff --git a/src/components/views/dialogs/spotlight/RoomResultContextMenus.tsx b/src/components/views/dialogs/spotlight/RoomResultContextMenus.tsx index f7fd5c6cb5..529f1a1f49 100644 --- a/src/components/views/dialogs/spotlight/RoomResultContextMenus.tsx +++ b/src/components/views/dialogs/spotlight/RoomResultContextMenus.tsx @@ -18,9 +18,9 @@ import { RoomGeneralContextMenu } from "../../context_menus/RoomGeneralContextMe import { RoomNotificationContextMenu } from "../../context_menus/RoomNotificationContextMenu"; import SpaceContextMenu from "../../context_menus/SpaceContextMenu"; import { type ButtonEvent } from "../../elements/AccessibleButton"; -import { contextMenuBelow } from "../../rooms/RoomTile"; import { shouldShowComponent } from "../../../../customisations/helpers/UIComponents"; import { UIComponent } from "../../../../settings/UIFeature"; +import { contextMenuBelow } from "../../../structures/ContextMenu.tsx"; interface Props { room: Room; diff --git a/src/components/views/messages/DateSeparator.tsx b/src/components/views/messages/DateSeparator.tsx index 3b9a1bc393..fd9c0dd88a 100644 --- a/src/components/views/messages/DateSeparator.tsx +++ b/src/components/views/messages/DateSeparator.tsx @@ -23,8 +23,7 @@ import Modal from "../../../Modal"; import ErrorDialog from "../dialogs/ErrorDialog"; import BugReportDialog from "../dialogs/BugReportDialog"; import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton"; -import { contextMenuBelow } from "../rooms/RoomTile"; -import { ContextMenuTooltipButton } from "../../structures/ContextMenu"; +import { contextMenuBelow, ContextMenuTooltipButton } from "../../structures/ContextMenu"; import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList, diff --git a/src/components/views/rooms/LegacyRoomList.tsx b/src/components/views/rooms/LegacyRoomList.tsx deleted file mode 100644 index 6be226a172..0000000000 --- a/src/components/views/rooms/LegacyRoomList.tsx +++ /dev/null @@ -1,692 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2015-2018 , 2020, 2021 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -import { EventType, type Room, RoomType } from "matrix-js-sdk/src/matrix"; -import React, { type JSX, type ComponentType, createRef, type ReactComponentElement, type SyntheticEvent } from "react"; - -import { type IState as IRovingTabIndexState, RovingTabIndexProvider } from "../../../accessibility/RovingTabIndex.tsx"; -import MatrixClientContext from "../../../contexts/MatrixClientContext.tsx"; -import { shouldShowComponent } from "../../../customisations/helpers/UIComponents.ts"; -import { Action } from "../../../dispatcher/actions.ts"; -import defaultDispatcher from "../../../dispatcher/dispatcher.ts"; -import { type ActionPayload } from "../../../dispatcher/payloads.ts"; -import { type ViewRoomDeltaPayload } from "../../../dispatcher/payloads/ViewRoomDeltaPayload.ts"; -import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload.ts"; -import { useEventEmitterState } from "../../../hooks/useEventEmitter.ts"; -import { _t, _td, type TranslationKey } from "../../../languageHandler.tsx"; -import { MatrixClientPeg } from "../../../MatrixClientPeg.ts"; -import PosthogTrackers from "../../../PosthogTrackers.ts"; -import SettingsStore from "../../../settings/SettingsStore.ts"; -import { useFeatureEnabled } from "../../../hooks/useSettings.ts"; -import { UIComponent } from "../../../settings/UIFeature.ts"; -import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore.ts"; -import { type ITagMap } from "../../../stores/room-list/algorithms/models.ts"; -import { DefaultTagID, type TagID } from "../../../stores/room-list/models.ts"; -import { UPDATE_EVENT } from "../../../stores/AsyncStore.ts"; -import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore.ts"; -import { - isMetaSpace, - type ISuggestedRoom, - MetaSpace, - type SpaceKey, - UPDATE_SELECTED_SPACE, - UPDATE_SUGGESTED_ROOMS, -} from "../../../stores/spaces/index.ts"; -import SpaceStore from "../../../stores/spaces/SpaceStore.ts"; -import { arrayFastClone, arrayHasDiff } from "../../../utils/arrays.ts"; -import { objectShallowClone, objectWithOnly } from "../../../utils/objects.ts"; -import type ResizeNotifier from "../../../utils/ResizeNotifier.ts"; -import { - shouldShowSpaceInvite, - showAddExistingRooms, - showCreateNewRoom, - showSpaceInvite, -} from "../../../utils/space.tsx"; -import { - ChevronFace, - ContextMenuTooltipButton, - type MenuProps, - useContextMenu, -} from "../../structures/ContextMenu.tsx"; -import RoomAvatar from "../avatars/RoomAvatar.tsx"; -import { BetaPill } from "../beta/BetaCard.tsx"; -import IconizedContextMenu, { - IconizedContextMenuOption, - IconizedContextMenuOptionList, -} from "../context_menus/IconizedContextMenu.tsx"; -import ExtraTile from "./ExtraTile.tsx"; -import RoomSublist, { type IAuxButtonProps } from "./RoomSublist.tsx"; -import { SdkContextClass } from "../../../contexts/SDKContext.ts"; -import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts.ts"; -import { getKeyBindingsManager } from "../../../KeyBindingsManager.ts"; -import AccessibleButton from "../elements/AccessibleButton.tsx"; -import { Landmark, LandmarkNavigation } from "../../../accessibility/LandmarkNavigation.ts"; -import LegacyCallHandler, { LegacyCallHandlerEvent } from "../../../LegacyCallHandler.tsx"; - -interface IProps { - onKeyDown: (ev: React.KeyboardEvent, state: IRovingTabIndexState) => void; - onFocus: (ev: React.FocusEvent) => void; - onBlur: (ev: React.FocusEvent) => void; - onResize: () => void; - onListCollapse?: (isExpanded: boolean) => void; - resizeNotifier: ResizeNotifier; - isMinimized: boolean; - activeSpace: SpaceKey; -} - -interface IState { - sublists: ITagMap; - currentRoomId?: string; - suggestedRooms: ISuggestedRoom[]; -} - -export const TAG_ORDER: TagID[] = [ - DefaultTagID.Invite, - DefaultTagID.Favourite, - DefaultTagID.DM, - DefaultTagID.Untagged, - DefaultTagID.Conference, - DefaultTagID.LowPriority, - DefaultTagID.ServerNotice, - DefaultTagID.Suggested, - // DefaultTagID.Archived isn't here any more: we don't show it at all. - // The section still exists in the code as a place for rooms that we know - // about but aren't joined. At some point it could be removed entirely - // but we'd have to make sure that rooms you weren't in were hidden. -]; -const ALWAYS_VISIBLE_TAGS: TagID[] = [DefaultTagID.DM, DefaultTagID.Untagged]; - -interface ITagAesthetics { - sectionLabel: TranslationKey; - sectionLabelRaw?: string; - AuxButtonComponent?: ComponentType; - isInvite: boolean; - defaultHidden: boolean; -} - -type TagAestheticsMap = Partial<{ - [tagId in TagID]: ITagAesthetics; -}>; - -const auxButtonContextMenuPosition = (handle: HTMLDivElement): MenuProps => { - const rect = handle.getBoundingClientRect(); - return { - chevronFace: ChevronFace.None, - left: rect.left - 7, - top: rect.top + rect.height, - }; -}; - -const DmAuxButton: React.FC = ({ tabIndex, dispatcher = defaultDispatcher }) => { - const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu(); - const activeSpace = useEventEmitterState(SpaceStore.instance, UPDATE_SELECTED_SPACE, () => { - return SpaceStore.instance.activeSpaceRoom; - }); - - const showCreateRooms = shouldShowComponent(UIComponent.CreateRooms); - const showInviteUsers = shouldShowComponent(UIComponent.InviteUsers); - - if (activeSpace && (showCreateRooms || showInviteUsers)) { - let contextMenu: JSX.Element | undefined; - if (menuDisplayed && handle.current) { - const canInvite = shouldShowSpaceInvite(activeSpace); - - contextMenu = ( - - - {showCreateRooms && ( - { - e.preventDefault(); - e.stopPropagation(); - closeMenu(); - defaultDispatcher.dispatch({ action: Action.CreateChat }); - PosthogTrackers.trackInteraction( - "WebRoomListRoomsSublistPlusMenuCreateChatItem", - e, - ); - }} - /> - )} - {showInviteUsers && ( - { - e.preventDefault(); - e.stopPropagation(); - closeMenu(); - showSpaceInvite(activeSpace); - }} - disabled={!canInvite} - title={canInvite ? undefined : _t("spaces|error_no_permission_invite")} - /> - )} - - - ); - } - - return ( - <> - - - {contextMenu} - - ); - } else if (!activeSpace && showCreateRooms) { - return ( - { - dispatcher.dispatch({ action: Action.CreateChat }); - PosthogTrackers.trackInteraction("WebRoomListRoomsSublistPlusMenuCreateChatItem", e); - }} - className="mx_RoomSublist_auxButton" - aria-label={_t("action|start_chat")} - title={_t("action|start_chat")} - /> - ); - } - - return null; -}; - -const UntaggedAuxButton: React.FC = ({ tabIndex }) => { - const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu(); - const activeSpace = useEventEmitterState(SpaceStore.instance, UPDATE_SELECTED_SPACE, () => { - return SpaceStore.instance.activeSpaceRoom; - }); - - const showCreateRoom = shouldShowComponent(UIComponent.CreateRooms); - const showExploreRooms = shouldShowComponent(UIComponent.ExploreRooms); - - const videoRoomsEnabled = useFeatureEnabled("feature_video_rooms"); - const elementCallVideoRoomsEnabled = useFeatureEnabled("feature_element_call_video_rooms"); - - let contextMenuContent: JSX.Element | undefined; - if (menuDisplayed && activeSpace) { - const canAddRooms = activeSpace.currentState.maySendStateEvent( - EventType.SpaceChild, - MatrixClientPeg.safeGet().getSafeUserId(), - ); - - contextMenuContent = ( - - { - e.preventDefault(); - e.stopPropagation(); - closeMenu(); - defaultDispatcher.dispatch({ - action: Action.ViewRoom, - room_id: activeSpace.roomId, - metricsTrigger: undefined, // other - }); - PosthogTrackers.trackInteraction("WebRoomListRoomsSublistPlusMenuExploreRoomsItem", e); - }} - /> - {showCreateRoom ? ( - <> - { - e.preventDefault(); - e.stopPropagation(); - closeMenu(); - showCreateNewRoom(activeSpace); - PosthogTrackers.trackInteraction("WebRoomListRoomsSublistPlusMenuCreateRoomItem", e); - }} - disabled={!canAddRooms} - title={canAddRooms ? undefined : _t("spaces|error_no_permission_create_room")} - /> - {videoRoomsEnabled && ( - { - e.preventDefault(); - e.stopPropagation(); - closeMenu(); - showCreateNewRoom( - activeSpace, - elementCallVideoRoomsEnabled ? RoomType.UnstableCall : RoomType.ElementVideo, - ); - }} - disabled={!canAddRooms} - title={canAddRooms ? undefined : _t("spaces|error_no_permission_create_room")} - > - - - )} - { - e.preventDefault(); - e.stopPropagation(); - closeMenu(); - showAddExistingRooms(activeSpace); - }} - disabled={!canAddRooms} - title={canAddRooms ? undefined : _t("spaces|error_no_permission_add_room")} - /> - - ) : null} - - ); - } else if (menuDisplayed) { - contextMenuContent = ( - - {showCreateRoom && ( - <> - { - e.preventDefault(); - e.stopPropagation(); - closeMenu(); - defaultDispatcher.dispatch({ action: Action.CreateRoom }); - PosthogTrackers.trackInteraction("WebRoomListRoomsSublistPlusMenuCreateRoomItem", e); - }} - /> - {videoRoomsEnabled && ( - { - e.preventDefault(); - e.stopPropagation(); - closeMenu(); - defaultDispatcher.dispatch({ - action: Action.CreateRoom, - type: elementCallVideoRoomsEnabled - ? RoomType.UnstableCall - : RoomType.ElementVideo, - }); - }} - > - - - )} - - )} - {showExploreRooms ? ( - { - e.preventDefault(); - e.stopPropagation(); - closeMenu(); - PosthogTrackers.trackInteraction("WebRoomListRoomsSublistPlusMenuExploreRoomsItem", e); - defaultDispatcher.fire(Action.ViewRoomDirectory); - }} - /> - ) : null} - - ); - } - - let contextMenu: JSX.Element | null = null; - if (menuDisplayed && handle.current) { - contextMenu = ( - - {contextMenuContent} - - ); - } - - if (showCreateRoom || showExploreRooms) { - return ( - <> - - - {contextMenu} - - ); - } - - return null; -}; - -const TAG_AESTHETICS: TagAestheticsMap = { - [DefaultTagID.Invite]: { - sectionLabel: _td("action|invites_list"), - isInvite: true, - defaultHidden: false, - }, - [DefaultTagID.Favourite]: { - sectionLabel: _td("common|favourites"), - isInvite: false, - defaultHidden: false, - }, - [DefaultTagID.DM]: { - sectionLabel: _td("common|people"), - isInvite: false, - defaultHidden: false, - AuxButtonComponent: DmAuxButton, - }, - [DefaultTagID.Conference]: { - sectionLabel: _td("voip|metaspace_video_rooms|conference_room_section"), - isInvite: false, - defaultHidden: false, - }, - [DefaultTagID.Untagged]: { - sectionLabel: _td("common|rooms"), - isInvite: false, - defaultHidden: false, - AuxButtonComponent: UntaggedAuxButton, - }, - [DefaultTagID.LowPriority]: { - sectionLabel: _td("common|low_priority"), - isInvite: false, - defaultHidden: false, - }, - [DefaultTagID.ServerNotice]: { - sectionLabel: _td("common|system_alerts"), - isInvite: false, - defaultHidden: false, - }, - - // TODO: Replace with archived view: https://github.com/vector-im/element-web/issues/14038 - [DefaultTagID.Archived]: { - sectionLabel: _td("common|historical"), - isInvite: false, - defaultHidden: true, - }, - - [DefaultTagID.Suggested]: { - sectionLabel: _td("room_list|suggested_rooms_heading"), - isInvite: false, - defaultHidden: false, - }, -}; - -export default class LegacyRoomList extends React.PureComponent { - private dispatcherRef?: string; - private treeRef = createRef(); - - public static contextType = MatrixClientContext; - declare public context: React.ContextType; - - public constructor(props: IProps) { - super(props); - - this.state = { - sublists: {}, - suggestedRooms: SpaceStore.instance.suggestedRooms, - }; - } - - public componentDidMount(): void { - this.dispatcherRef = defaultDispatcher.register(this.onAction); - SdkContextClass.instance.roomViewStore.on(UPDATE_EVENT, this.onRoomViewStoreUpdate); - SpaceStore.instance.on(UPDATE_SUGGESTED_ROOMS, this.updateSuggestedRooms); - RoomListStore.instance.on(LISTS_UPDATE_EVENT, this.updateLists); - LegacyCallHandler.instance.on(LegacyCallHandlerEvent.ProtocolSupport, this.updateProtocolSupport); - this.updateLists(); // trigger the first update - } - - public componentWillUnmount(): void { - SpaceStore.instance.off(UPDATE_SUGGESTED_ROOMS, this.updateSuggestedRooms); - RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.updateLists); - defaultDispatcher.unregister(this.dispatcherRef); - SdkContextClass.instance.roomViewStore.off(UPDATE_EVENT, this.onRoomViewStoreUpdate); - LegacyCallHandler.instance.off(LegacyCallHandlerEvent.ProtocolSupport, this.updateProtocolSupport); - } - - private updateProtocolSupport = (): void => { - this.updateLists(); - }; - - private onRoomViewStoreUpdate = (): void => { - this.setState({ - currentRoomId: SdkContextClass.instance.roomViewStore.getRoomId() ?? undefined, - }); - }; - - private onAction = (payload: ActionPayload): void => { - if (payload.action === Action.ViewRoomDelta) { - const viewRoomDeltaPayload = payload as ViewRoomDeltaPayload; - const currentRoomId = SdkContextClass.instance.roomViewStore.getRoomId(); - if (!currentRoomId) return; - const room = this.getRoomDelta(currentRoomId, viewRoomDeltaPayload.delta, viewRoomDeltaPayload.unread); - if (room) { - defaultDispatcher.dispatch({ - action: Action.ViewRoom, - room_id: room.roomId, - show_room_tile: true, // to make sure the room gets scrolled into view - metricsTrigger: "WebKeyboardShortcut", - metricsViaKeyboard: true, - }); - } - } - }; - - private getRoomDelta = (roomId: string, delta: number, unread = false): Room => { - const lists = RoomListStore.instance.orderedLists; - const rooms: Room[] = []; - TAG_ORDER.forEach((t) => { - let listRooms = lists[t]; - - if (unread) { - // filter to only notification rooms (and our current active room so we can index properly) - listRooms = listRooms.filter((r) => { - const state = RoomNotificationStateStore.instance.getRoomState(r); - return state.room.roomId === roomId || state.isUnread; - }); - } - - rooms.push(...listRooms); - }); - - const currentIndex = rooms.findIndex((r) => r.roomId === roomId); - // use slice to account for looping around the start - const [room] = rooms.slice((currentIndex + delta) % rooms.length); - return room; - }; - - private updateSuggestedRooms = (suggestedRooms: ISuggestedRoom[]): void => { - this.setState({ suggestedRooms }); - }; - - private updateLists = (): void => { - const newLists = RoomListStore.instance.orderedLists; - const previousListIds = Object.keys(this.state.sublists); - const newListIds = Object.keys(newLists); - - let doUpdate = arrayHasDiff(previousListIds, newListIds); - if (!doUpdate) { - // so we didn't have the visible sublists change, but did the contents of those - // sublists change significantly enough to break the sticky headers? Probably, so - // let's check the length of each. - for (const tagId of newListIds) { - const oldRooms = this.state.sublists[tagId]; - const newRooms = newLists[tagId]; - if (oldRooms.length !== newRooms.length) { - doUpdate = true; - break; - } - } - } - - if (doUpdate) { - // We have to break our reference to the room list store if we want to be able to - // diff the object for changes, so do that. - // @ts-ignore - ITagMap is ts-ignored so this will have to be too - const newSublists = objectWithOnly(newLists, newListIds); - const sublists = objectShallowClone(newSublists, (k, v) => arrayFastClone(v)); - - this.setState({ sublists }, () => { - this.props.onResize(); - }); - } - }; - - private renderSuggestedRooms(): ReactComponentElement[] { - return this.state.suggestedRooms.map((room) => { - const name = room.name || room.canonical_alias || room.aliases?.[0] || _t("empty_room"); - const avatar = ( - - ); - const viewRoom = (ev: SyntheticEvent): void => { - defaultDispatcher.dispatch({ - action: Action.ViewRoom, - room_alias: room.canonical_alias || room.aliases?.[0], - room_id: room.room_id, - via_servers: room.viaServers, - oob_data: { - avatarUrl: room.avatar_url, - name, - }, - metricsTrigger: "RoomList", - metricsViaKeyboard: ev.type !== "click", - }); - }; - return ( - - ); - }); - } - - private renderSublists(): React.ReactElement[] { - // show a skeleton UI if the user is in no rooms and they are not filtering and have no suggested rooms - const showSkeleton = - !this.state.suggestedRooms?.length && - Object.values(RoomListStore.instance.orderedLists).every((list) => !list?.length); - - return TAG_ORDER.map((orderedTagId) => { - let extraTiles: ReactComponentElement[] | undefined; - if (orderedTagId === DefaultTagID.Suggested) { - extraTiles = this.renderSuggestedRooms(); - } - - const aesthetics = TAG_AESTHETICS[orderedTagId]; - if (!aesthetics) throw new Error(`Tag ${orderedTagId} does not have aesthetics`); - - let alwaysVisible = ALWAYS_VISIBLE_TAGS.includes(orderedTagId); - if ( - (this.props.activeSpace === MetaSpace.Favourites && orderedTagId !== DefaultTagID.Favourite) || - (this.props.activeSpace === MetaSpace.People && orderedTagId !== DefaultTagID.DM) || - (this.props.activeSpace === MetaSpace.Orphans && orderedTagId === DefaultTagID.DM) || - (this.props.activeSpace === MetaSpace.VideoRooms && orderedTagId === DefaultTagID.DM) || - (!isMetaSpace(this.props.activeSpace) && - orderedTagId === DefaultTagID.DM && - !SettingsStore.getValue("Spaces.showPeopleInSpace", this.props.activeSpace)) - ) { - alwaysVisible = false; - } - - let forceExpanded = false; - if ( - (this.props.activeSpace === MetaSpace.Favourites && orderedTagId === DefaultTagID.Favourite) || - (this.props.activeSpace === MetaSpace.People && orderedTagId === DefaultTagID.DM) - ) { - forceExpanded = true; - } - // The cost of mounting/unmounting this component offsets the cost - // of keeping it in the DOM and hiding it when it is not required - return ( - - ); - }); - } - - public focus(): void { - // focus the first focusable element in this aria treeview widget - const treeItems = this.treeRef.current?.querySelectorAll('[role="treeitem"]'); - if (!treeItems) return; - [...treeItems].find((e) => e.offsetParent !== null)?.focus(); - } - - public render(): React.ReactNode { - const sublists = this.renderSublists(); - return ( - - {({ onKeyDownHandler }) => ( -
{ - const navAction = getKeyBindingsManager().getNavigationAction(ev); - if ( - navAction === KeyBindingAction.NextLandmark || - navAction === KeyBindingAction.PreviousLandmark - ) { - LandmarkNavigation.findAndFocusNextLandmark( - Landmark.ROOM_LIST, - navAction === KeyBindingAction.PreviousLandmark, - ); - ev.stopPropagation(); - ev.preventDefault(); - return; - } - onKeyDownHandler(ev); - }} - className="mx_LegacyRoomList" - role="tree" - aria-label={_t("common|rooms")} - ref={this.treeRef} - > - {sublists} -
- )} -
- ); - } -} diff --git a/src/components/views/rooms/LegacyRoomListHeader.tsx b/src/components/views/rooms/LegacyRoomListHeader.tsx deleted file mode 100644 index 719831d865..0000000000 --- a/src/components/views/rooms/LegacyRoomListHeader.tsx +++ /dev/null @@ -1,426 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2021, 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -import { ClientEvent, EventType, type Room, RoomEvent, RoomType } from "matrix-js-sdk/src/matrix"; -import React, { type JSX, useContext, useEffect, useState } from "react"; -import { Tooltip } from "@vector-im/compound-web"; - -import MatrixClientContext from "../../../contexts/MatrixClientContext"; -import { shouldShowComponent } from "../../../customisations/helpers/UIComponents"; -import { Action } from "../../../dispatcher/actions"; -import defaultDispatcher from "../../../dispatcher/dispatcher"; -import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; -import { useDispatcher } from "../../../hooks/useDispatcher"; -import { useEventEmitterState, useTypedEventEmitter, useTypedEventEmitterState } from "../../../hooks/useEventEmitter"; -import { useFeatureEnabled } from "../../../hooks/useSettings"; -import { _t } from "../../../languageHandler"; -import PosthogTrackers from "../../../PosthogTrackers"; -import { UIComponent } from "../../../settings/UIFeature"; -import { - getMetaSpaceName, - MetaSpace, - type SpaceKey, - UPDATE_HOME_BEHAVIOUR, - UPDATE_SELECTED_SPACE, -} from "../../../stores/spaces"; -import SpaceStore from "../../../stores/spaces/SpaceStore"; -import { - shouldShowSpaceInvite, - showAddExistingRooms, - showCreateNewRoom, - showCreateNewSubspace, - showSpaceInvite, -} from "../../../utils/space"; -import { - ChevronFace, - ContextMenuButton, - ContextMenuTooltipButton, - type MenuProps, - useContextMenu, -} from "../../structures/ContextMenu"; -import { BetaPill } from "../beta/BetaCard"; -import IconizedContextMenu, { - IconizedContextMenuOption, - IconizedContextMenuOptionList, -} from "../context_menus/IconizedContextMenu"; -import SpaceContextMenu from "../context_menus/SpaceContextMenu"; -import InlineSpinner from "../elements/InlineSpinner"; -import { HomeButtonContextMenu } from "../spaces/SpacePanel"; - -const contextMenuBelow = (elementRect: DOMRect): MenuProps => { - // align the context menu's icons with the icon which opened the context menu - const left = elementRect.left + window.scrollX; - const top = elementRect.bottom + window.scrollY + 12; - const chevronFace = ChevronFace.None; - return { left, top, chevronFace }; -}; - -// Long-running actions that should trigger a spinner -enum PendingActionType { - JoinRoom, - BulkRedact, -} - -const usePendingActions = (): Map> => { - const cli = useContext(MatrixClientContext); - const [actions, setActions] = useState(new Map>()); - - const addAction = (type: PendingActionType, key: string): void => { - const keys = new Set(actions.get(type)); - keys.add(key); - setActions(new Map(actions).set(type, keys)); - }; - const removeAction = (type: PendingActionType, key: string): void => { - const keys = new Set(actions.get(type)); - if (keys.delete(key)) { - setActions(new Map(actions).set(type, keys)); - } - }; - - useDispatcher(defaultDispatcher, (payload) => { - switch (payload.action) { - case Action.JoinRoom: - addAction(PendingActionType.JoinRoom, payload.roomId); - break; - case Action.JoinRoomReady: - case Action.JoinRoomError: - removeAction(PendingActionType.JoinRoom, payload.roomId); - break; - case Action.BulkRedactStart: - addAction(PendingActionType.BulkRedact, payload.roomId); - break; - case Action.BulkRedactEnd: - removeAction(PendingActionType.BulkRedact, payload.roomId); - break; - } - }); - useTypedEventEmitter(cli, ClientEvent.Room, (room: Room) => removeAction(PendingActionType.JoinRoom, room.roomId)); - - return actions; -}; - -interface IProps { - onVisibilityChange?(): void; -} - -const LegacyRoomListHeader: React.FC = ({ onVisibilityChange }) => { - const cli = useContext(MatrixClientContext); - const [mainMenuDisplayed, mainMenuHandle, openMainMenu, closeMainMenu] = useContextMenu(); - const [plusMenuDisplayed, plusMenuHandle, openPlusMenu, closePlusMenu] = useContextMenu(); - const [spaceKey, activeSpace] = useEventEmitterState<[SpaceKey, Room | null]>( - SpaceStore.instance, - UPDATE_SELECTED_SPACE, - () => [SpaceStore.instance.activeSpace, SpaceStore.instance.activeSpaceRoom], - ); - const allRoomsInHome = useEventEmitterState(SpaceStore.instance, UPDATE_HOME_BEHAVIOUR, () => { - return SpaceStore.instance.allRoomsInHome; - }); - const videoRoomsEnabled = useFeatureEnabled("feature_video_rooms"); - const elementCallVideoRoomsEnabled = useFeatureEnabled("feature_element_call_video_rooms"); - const pendingActions = usePendingActions(); - - const canShowMainMenu = activeSpace || spaceKey === MetaSpace.Home; - - useEffect(() => { - if (mainMenuDisplayed && !canShowMainMenu) { - // Space changed under us and we no longer has a main menu to draw - closeMainMenu(); - } - }, [closeMainMenu, canShowMainMenu, mainMenuDisplayed]); - - const spaceName = useTypedEventEmitterState(activeSpace ?? undefined, RoomEvent.Name, () => activeSpace?.name); - - useEffect(() => { - onVisibilityChange?.(); - }, [onVisibilityChange]); - - const canExploreRooms = shouldShowComponent(UIComponent.ExploreRooms); - const canCreateRooms = shouldShowComponent(UIComponent.CreateRooms); - const canCreateSpaces = shouldShowComponent(UIComponent.CreateSpaces); - - const hasPermissionToAddSpaceChild = activeSpace?.currentState?.maySendStateEvent( - EventType.SpaceChild, - cli.getUserId()!, - ); - const canAddSubRooms = hasPermissionToAddSpaceChild && canCreateRooms; - const canAddSubSpaces = hasPermissionToAddSpaceChild && canCreateSpaces; - - // If the user can't do anything on the plus menu, don't show it. This aims to target the - // plus menu shown on the Home tab primarily: the user has options to use the menu for - // communities and spaces, but is at risk of no options on the Home tab. - const canShowPlusMenu = canCreateRooms || canExploreRooms || canCreateSpaces || activeSpace; - - let contextMenu: JSX.Element | undefined; - if (mainMenuDisplayed && mainMenuHandle.current) { - let ContextMenuComponent; - if (activeSpace) { - ContextMenuComponent = SpaceContextMenu; - } else { - ContextMenuComponent = HomeButtonContextMenu; - } - - contextMenu = ( - - ); - } else if (plusMenuDisplayed && activeSpace) { - let inviteOption: JSX.Element | undefined; - if (shouldShowSpaceInvite(activeSpace)) { - inviteOption = ( - { - e.preventDefault(); - e.stopPropagation(); - showSpaceInvite(activeSpace); - closePlusMenu(); - }} - /> - ); - } - - let newRoomOptions: JSX.Element | undefined; - if (activeSpace?.currentState.maySendStateEvent(EventType.RoomAvatar, cli.getUserId()!)) { - newRoomOptions = ( - <> - { - e.preventDefault(); - e.stopPropagation(); - showCreateNewRoom(activeSpace); - PosthogTrackers.trackInteraction("WebRoomListHeaderPlusMenuCreateRoomItem", e); - closePlusMenu(); - }} - /> - {videoRoomsEnabled && ( - { - e.preventDefault(); - e.stopPropagation(); - showCreateNewRoom( - activeSpace, - elementCallVideoRoomsEnabled ? RoomType.UnstableCall : RoomType.ElementVideo, - ); - closePlusMenu(); - }} - > - - - )} - - ); - } - - contextMenu = ( - - - {inviteOption} - {newRoomOptions} - { - e.preventDefault(); - e.stopPropagation(); - defaultDispatcher.dispatch({ - action: Action.ViewRoom, - room_id: activeSpace.roomId, - metricsTrigger: undefined, // other - }); - closePlusMenu(); - PosthogTrackers.trackInteraction("WebRoomListHeaderPlusMenuExploreRoomsItem", e); - }} - /> - { - e.preventDefault(); - e.stopPropagation(); - showAddExistingRooms(activeSpace); - closePlusMenu(); - }} - disabled={!canAddSubRooms} - title={!canAddSubRooms ? _t("spaces|error_no_permission_add_room") : undefined} - /> - {canCreateSpaces && ( - { - e.preventDefault(); - e.stopPropagation(); - showCreateNewSubspace(activeSpace); - closePlusMenu(); - }} - disabled={!canAddSubSpaces} - title={!canAddSubSpaces ? _t("spaces|error_no_permission_add_space") : undefined} - > - - - )} - - - ); - } else if (plusMenuDisplayed) { - let newRoomOpts: JSX.Element | undefined; - let joinRoomOpt: JSX.Element | undefined; - - if (canCreateRooms) { - newRoomOpts = ( - <> - { - e.preventDefault(); - e.stopPropagation(); - defaultDispatcher.dispatch({ action: Action.CreateChat }); - PosthogTrackers.trackInteraction("WebRoomListHeaderPlusMenuCreateChatItem", e); - closePlusMenu(); - }} - /> - { - e.preventDefault(); - e.stopPropagation(); - defaultDispatcher.dispatch({ action: Action.CreateRoom }); - PosthogTrackers.trackInteraction("WebRoomListHeaderPlusMenuCreateRoomItem", e); - closePlusMenu(); - }} - /> - {videoRoomsEnabled && ( - { - e.preventDefault(); - e.stopPropagation(); - defaultDispatcher.dispatch({ - action: Action.CreateRoom, - type: elementCallVideoRoomsEnabled ? RoomType.UnstableCall : RoomType.ElementVideo, - }); - closePlusMenu(); - }} - > - - - )} - - ); - } - if (canExploreRooms) { - joinRoomOpt = ( - { - e.preventDefault(); - e.stopPropagation(); - defaultDispatcher.dispatch({ action: Action.ViewRoomDirectory }); - PosthogTrackers.trackInteraction("WebRoomListHeaderPlusMenuExploreRoomsItem", e); - closePlusMenu(); - }} - /> - ); - } - - contextMenu = ( - - - {newRoomOpts} - {joinRoomOpt} - - - ); - } - - let title: string; - if (activeSpace && spaceName) { - title = spaceName; - } else { - title = getMetaSpaceName(spaceKey as MetaSpace, allRoomsInHome); - } - - const pendingActionSummary = [...pendingActions.entries()] - .filter(([type, keys]) => keys.size > 0) - .map(([type, keys]) => { - switch (type) { - case PendingActionType.JoinRoom: - return _t("room_list|joining_rooms_status", { count: keys.size }); - case PendingActionType.BulkRedact: - return _t("room_list|redacting_messages_status", { count: keys.size }); - } - }) - .join("\n"); - - let contextMenuButton: JSX.Element =
{title}
; - if (canShowMainMenu) { - const commonProps = { - ref: mainMenuHandle, - onClick: openMainMenu, - isExpanded: mainMenuDisplayed, - className: "mx_LegacyRoomListHeader_contextMenuButton", - children: title, - }; - - if (!!activeSpace) { - contextMenuButton = ( - - ); - } else { - contextMenuButton = ; - } - } - - return ( - - ); -}; - -export default LegacyRoomListHeader; diff --git a/src/components/views/rooms/RoomBreadcrumbs.tsx b/src/components/views/rooms/RoomBreadcrumbs.tsx deleted file mode 100644 index c677b32edc..0000000000 --- a/src/components/views/rooms/RoomBreadcrumbs.tsx +++ /dev/null @@ -1,142 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2020 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -import React, { createRef } from "react"; -import { type EmptyObject, type Room } from "matrix-js-sdk/src/matrix"; -import { CSSTransition } from "react-transition-group"; - -import { BreadcrumbsStore } from "../../../stores/BreadcrumbsStore"; -import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar"; -import { _t } from "../../../languageHandler"; -import defaultDispatcher from "../../../dispatcher/dispatcher"; -import { UPDATE_EVENT } from "../../../stores/AsyncStore"; -import { useRovingTabIndex } from "../../../accessibility/RovingTabIndex"; -import Toolbar from "../../../accessibility/Toolbar"; -import { Action } from "../../../dispatcher/actions"; -import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; -import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton"; - -interface IState { - // Both of these control the animation for the breadcrumbs. For details on the - // actual animation, see the CSS. - // - // doAnimation is to lie to the CSSTransition component (see onBreadcrumbsUpdate - // for info). skipFirst is used to try and reduce jerky animation - also see the - // breadcrumb update function for info on that. - doAnimation: boolean; - skipFirst: boolean; -} - -const RoomBreadcrumbTile: React.FC<{ room: Room; onClick: (ev: ButtonEvent) => void }> = ({ room, onClick }) => { - const [onFocus, isActive, ref] = useRovingTabIndex(); - - return ( - - - - ); -}; - -export default class RoomBreadcrumbs extends React.PureComponent { - private unmounted = false; - private toolbar = createRef(); - - public constructor(props: EmptyObject) { - super(props); - - this.state = { - doAnimation: true, // technically we want animation on mount, but it won't be perfect - skipFirst: false, // render the thing, as boring as it is - }; - } - - public componentDidMount(): void { - this.unmounted = false; - BreadcrumbsStore.instance.on(UPDATE_EVENT, this.onBreadcrumbsUpdate); - } - - public componentWillUnmount(): void { - this.unmounted = true; - BreadcrumbsStore.instance.off(UPDATE_EVENT, this.onBreadcrumbsUpdate); - } - - private onBreadcrumbsUpdate = (): void => { - if (this.unmounted) return; - - // We need to trick the CSSTransition component into updating, which means we need to - // tell it to not animate, then to animate a moment later. This causes two updates - // which means two renders. The skipFirst change is so that our don't-animate state - // doesn't show the breadcrumb we're about to reveal as it causes a visual jump/jerk. - // The second update, on the next available tick, causes the "enter" animation to start - // again and this time we want to show the newest breadcrumb because it'll be hidden - // off screen for the animation. - this.setState({ doAnimation: false, skipFirst: true }); - window.setTimeout(() => this.setState({ doAnimation: true, skipFirst: false }), 0); - }; - - private viewRoom = (room: Room, index: number, viaKeyboard = false): void => { - defaultDispatcher.dispatch({ - action: Action.ViewRoom, - room_id: room.roomId, - metricsTrigger: "WebHorizontalBreadcrumbs", - metricsViaKeyboard: viaKeyboard, - }); - }; - - public render(): React.ReactElement { - const tiles = BreadcrumbsStore.instance.rooms.map((r, i) => ( - this.viewRoom(r, i, ev.type !== "click")} - /> - )); - - if (tiles.length > 0) { - // NOTE: The CSSTransition timeout MUST match the timeout in our CSS! - return ( - - - {tiles.slice(this.state.skipFirst ? 1 : 0)} - - - ); - } else { - return ( -
-
{_t("room_list|breadcrumbs_empty")}
-
- ); - } - } -} diff --git a/src/components/views/rooms/RoomSublist.tsx b/src/components/views/rooms/RoomSublist.tsx deleted file mode 100644 index e5fd061b19..0000000000 --- a/src/components/views/rooms/RoomSublist.tsx +++ /dev/null @@ -1,857 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2020 The Matrix.org Foundation C.I.C. -Copyright 2017, 2018 Vector Creations Ltd -Copyright 2015, 2016 OpenMarket Ltd - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -import { type Room } from "matrix-js-sdk/src/matrix"; -import classNames from "classnames"; -import { type Enable, Resizable } from "re-resizable"; -import { type Direction } from "re-resizable/lib/resizer"; -import React, { type JSX, type ComponentType, createRef, type ReactComponentElement, type ReactNode } from "react"; - -import { polyfillTouchEvent } from "../../../@types/polyfill"; -import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts"; -import { RovingAccessibleButton, RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex"; -import { Action } from "../../../dispatcher/actions"; -import defaultDispatcher, { type MatrixDispatcher } from "../../../dispatcher/dispatcher"; -import { type ActionPayload } from "../../../dispatcher/payloads"; -import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; -import { getKeyBindingsManager } from "../../../KeyBindingsManager"; -import { _t } from "../../../languageHandler"; -import { type ListNotificationState } from "../../../stores/notifications/ListNotificationState"; -import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore"; -import { ListAlgorithm, SortAlgorithm } from "../../../stores/room-list/algorithms/models"; -import { type ListLayout } from "../../../stores/room-list/ListLayout"; -import { DefaultTagID, type TagID } from "../../../stores/room-list/models"; -import RoomListLayoutStore from "../../../stores/room-list/RoomListLayoutStore"; -import RoomListStore, { LISTS_UPDATE_EVENT, LISTS_LOADING_EVENT } from "../../../stores/room-list/RoomListStore"; -import { arrayFastClone, arrayHasOrderChange } from "../../../utils/arrays"; -import { objectExcluding, objectHasDiff } from "../../../utils/objects"; -import type ResizeNotifier from "../../../utils/ResizeNotifier"; -import ContextMenu, { - ChevronFace, - ContextMenuTooltipButton, - StyledMenuItemCheckbox, - StyledMenuItemRadio, -} from "../../structures/ContextMenu"; -import AccessibleButton, { type ButtonEvent } from "../../views/elements/AccessibleButton"; -import type ExtraTile from "./ExtraTile"; -import NotificationBadge from "./NotificationBadge"; -import RoomTile from "./RoomTile"; - -const SHOW_N_BUTTON_HEIGHT = 28; // As defined by CSS -const RESIZE_HANDLE_HEIGHT = 4; // As defined by CSS -export const HEADER_HEIGHT = 32; // As defined by CSS - -const MAX_PADDING_HEIGHT = SHOW_N_BUTTON_HEIGHT + RESIZE_HANDLE_HEIGHT; - -// HACK: We really shouldn't have to do this. -polyfillTouchEvent(); - -export interface IAuxButtonProps { - tabIndex: number; - dispatcher?: MatrixDispatcher; -} - -interface IProps { - forRooms: boolean; - startAsHidden: boolean; - label: string; - AuxButtonComponent?: ComponentType; - isMinimized: boolean; - tagId: TagID; - showSkeleton?: boolean; - alwaysVisible?: boolean; - forceExpanded?: boolean; - resizeNotifier: ResizeNotifier; - extraTiles?: ReactComponentElement[] | null; - onListCollapse?: (isExpanded: boolean) => void; -} - -function getLabelId(tagId: TagID): string { - return `mx_RoomSublist_label_${tagId}`; -} - -// TODO: Use re-resizer's NumberSize when it is exposed as the type -interface ResizeDelta { - width: number; - height: number; -} - -type PartialDOMRect = Pick; - -interface IState { - contextMenuPosition?: PartialDOMRect; - isResizing: boolean; - isExpanded: boolean; // used for the for expand of the sublist when the room list is being filtered - height: number; - rooms: Room[]; - roomsLoading: boolean; -} - -export default class RoomSublist extends React.Component { - private headerButton = createRef(); - private sublistRef = createRef(); - private tilesRef = createRef(); - private dispatcherRef?: string; - private layout: ListLayout; - private heightAtStart: number; - private notificationState: ListNotificationState; - - public constructor(props: IProps) { - super(props); - - this.layout = RoomListLayoutStore.instance.getLayoutFor(this.props.tagId); - this.heightAtStart = 0; - this.notificationState = RoomNotificationStateStore.instance.getListState(this.props.tagId); - this.state = { - isResizing: false, - isExpanded: !this.layout.isCollapsed, - height: 0, // to be fixed in a moment, we need `rooms` to calculate this. - rooms: arrayFastClone(RoomListStore.instance.orderedLists[this.props.tagId] || []), - roomsLoading: false, - }; - // Why Object.assign() and not this.state.height? Because TypeScript says no. - this.state = Object.assign(this.state, { height: this.calculateInitialHeight() }); - } - - private calculateInitialHeight(): number { - const requestedVisibleTiles = Math.max(Math.floor(this.layout.visibleTiles), this.layout.minVisibleTiles); - const tileCount = Math.min(this.numTiles, requestedVisibleTiles); - return this.layout.tilesToPixelsWithPadding(tileCount, this.padding); - } - - private get padding(): number { - let padding = RESIZE_HANDLE_HEIGHT; - // this is used for calculating the max height of the whole container, - // and takes into account whether there should be room reserved for the show more/less button - // when fully expanded. We can't rely purely on the layout's defaultVisible tile count - // because there are conditions in which we need to know that the 'show more' button - // is present while well under the default tile limit. - const needsShowMore = this.numTiles > this.numVisibleTiles; - - // ...but also check this or we'll miss if the section is expanded and we need a - // 'show less' - const needsShowLess = this.numTiles > this.layout.defaultVisibleTiles; - - if (needsShowMore || needsShowLess) { - padding += SHOW_N_BUTTON_HEIGHT; - } - return padding; - } - - private get extraTiles(): ReactComponentElement[] | null { - return this.props.extraTiles ?? null; - } - - private get numTiles(): number { - return RoomSublist.calcNumTiles(this.state.rooms, this.extraTiles); - } - - private static calcNumTiles(rooms: Room[], extraTiles?: any[] | null): number { - return (rooms || []).length + (extraTiles || []).length; - } - - private get numVisibleTiles(): number { - const nVisible = Math.ceil(this.layout.visibleTiles); - return Math.min(nVisible, this.numTiles); - } - - public componentDidUpdate(prevProps: Readonly, prevState: Readonly): void { - const prevExtraTiles = prevProps.extraTiles; - // as the rooms can come in one by one we need to reevaluate - // the amount of available rooms to cap the amount of requested visible rooms by the layout - if (RoomSublist.calcNumTiles(prevState.rooms, prevExtraTiles) !== this.numTiles) { - this.setState({ height: this.calculateInitialHeight() }); - } - } - - public shouldComponentUpdate(nextProps: Readonly, nextState: Readonly): boolean { - if (objectHasDiff(this.props, nextProps)) { - // Something we don't care to optimize has updated, so update. - return true; - } - - // Do the same check used on props for state, without the rooms we're going to no-op - const prevStateNoRooms = objectExcluding(this.state, ["rooms"]); - const nextStateNoRooms = objectExcluding(nextState, ["rooms"]); - if (objectHasDiff(prevStateNoRooms, nextStateNoRooms)) { - return true; - } - - // If we're supposed to handle extra tiles, take the performance hit and re-render all the - // time so we don't have to consider them as part of the visible room optimization. - const prevExtraTiles = this.props.extraTiles || []; - const nextExtraTiles = nextProps.extraTiles || []; - if (prevExtraTiles.length > 0 || nextExtraTiles.length > 0) { - return true; - } - - // If we're about to update the height of the list, we don't really care about which rooms - // are visible or not for no-op purposes, so ensure that the height calculation runs through. - if (RoomSublist.calcNumTiles(nextState.rooms, nextExtraTiles) !== this.numTiles) { - return true; - } - - // Before we go analyzing the rooms, we can see if we're collapsed. If we're collapsed, we don't need - // to render anything. We do this after the height check though to ensure that the height gets appropriately - // calculated for when/if we become uncollapsed. - if (!nextState.isExpanded) { - return false; - } - - // Quickly double check we're not about to break something due to the number of rooms changing. - if (this.state.rooms.length !== nextState.rooms.length) { - return true; - } - - // Finally, determine if the room update (as presumably that's all that's left) is within - // our visible range. If it is, then do a render. If the update is outside our visible range - // then we can skip the update. - // - // We also optimize for order changing here: if the update did happen in our visible range - // but doesn't result in the list re-sorting itself then there's no reason for us to update - // on our own. - const prevSlicedRooms = this.state.rooms.slice(0, this.numVisibleTiles); - const nextSlicedRooms = nextState.rooms.slice(0, this.numVisibleTiles); - if (arrayHasOrderChange(prevSlicedRooms, nextSlicedRooms)) { - return true; - } - - // Finally, nothing happened so no-op the update - return false; - } - - public componentDidMount(): void { - this.dispatcherRef = defaultDispatcher.register(this.onAction); - RoomListStore.instance.on(LISTS_UPDATE_EVENT, this.onListsUpdated); - RoomListStore.instance.on(LISTS_LOADING_EVENT, this.onListsLoading); - - // Using the passive option to not block the main thread - // https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#improving_scrolling_performance_with_passive_listeners - this.tilesRef.current?.addEventListener("scroll", this.onScrollPrevent, { passive: true }); - } - - public componentWillUnmount(): void { - defaultDispatcher.unregister(this.dispatcherRef); - RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.onListsUpdated); - RoomListStore.instance.off(LISTS_LOADING_EVENT, this.onListsLoading); - this.tilesRef.current?.removeEventListener("scroll", this.onScrollPrevent); - } - - private onListsLoading = (tagId: TagID, isLoading: boolean): void => { - if (this.props.tagId !== tagId) { - return; - } - this.setState({ - roomsLoading: isLoading, - }); - }; - - private onListsUpdated = (): void => { - const stateUpdates = {} as IState; - - const currentRooms = this.state.rooms; - const newRooms = arrayFastClone(RoomListStore.instance.orderedLists[this.props.tagId] || []); - if (arrayHasOrderChange(currentRooms, newRooms)) { - stateUpdates.rooms = newRooms; - } - - if (Object.keys(stateUpdates).length > 0) { - this.setState(stateUpdates); - } - }; - - private onAction = (payload: ActionPayload): void => { - if (payload.action === Action.ViewRoom && payload.show_room_tile && this.state.rooms) { - // XXX: we have to do this a tick later because we have incorrect intermediate props during a room change - // where we lose the room we are changing from temporarily and then it comes back in an update right after. - setTimeout(() => { - const roomIndex = this.state.rooms.findIndex((r) => r.roomId === payload.room_id); - - if (!this.state.isExpanded && roomIndex > -1) { - this.toggleCollapsed(); - } - // extend the visible section to include the room if it is entirely invisible - if (roomIndex >= this.numVisibleTiles) { - this.layout.visibleTiles = this.layout.tilesWithPadding(roomIndex + 1, MAX_PADDING_HEIGHT); - this.forceUpdate(); // because the layout doesn't trigger a re-render - } - }, 0); - } - }; - - private applyHeightChange(newHeight: number): void { - const heightInTiles = Math.ceil(this.layout.pixelsToTiles(newHeight - this.padding)); - this.layout.visibleTiles = Math.min(this.numTiles, heightInTiles); - } - - private onResize = ( - e: MouseEvent | TouchEvent, - travelDirection: Direction, - refToElement: HTMLElement, - delta: ResizeDelta, - ): void => { - const newHeight = this.heightAtStart + delta.height; - this.applyHeightChange(newHeight); - this.setState({ height: newHeight }); - }; - - private onResizeStart = (): void => { - this.heightAtStart = this.state.height; - this.setState({ isResizing: true }); - }; - - private onResizeStop = ( - e: MouseEvent | TouchEvent, - travelDirection: Direction, - refToElement: HTMLElement, - delta: ResizeDelta, - ): void => { - const newHeight = this.heightAtStart + delta.height; - this.applyHeightChange(newHeight); - this.setState({ isResizing: false, height: newHeight }); - }; - - private onShowAllClick = async (): Promise => { - // read number of visible tiles before we mutate it - const numVisibleTiles = this.numVisibleTiles; - const newHeight = this.layout.tilesToPixelsWithPadding(this.numTiles, this.padding); - this.applyHeightChange(newHeight); - this.setState({ height: newHeight }, () => { - // focus the top-most new room - this.focusRoomTile(numVisibleTiles); - }); - }; - - private onShowLessClick = (): void => { - const newHeight = this.layout.tilesToPixelsWithPadding(this.layout.defaultVisibleTiles, this.padding); - this.applyHeightChange(newHeight); - this.setState({ height: newHeight }); - }; - - private focusRoomTile = (index: number): void => { - if (!this.sublistRef.current) return; - const elements = this.sublistRef.current.querySelectorAll(".mx_RoomTile"); - const element = elements && elements[index]; - if (element) { - element.focus(); - } - }; - - private onOpenMenuClick = (ev: ButtonEvent): void => { - ev.preventDefault(); - ev.stopPropagation(); - const target = ev.target as HTMLButtonElement; - this.setState({ contextMenuPosition: target.getBoundingClientRect() }); - }; - - private onContextMenu = (ev: React.MouseEvent): void => { - ev.preventDefault(); - ev.stopPropagation(); - this.setState({ - contextMenuPosition: { - left: ev.clientX, - top: ev.clientY, - height: 0, - }, - }); - }; - - private onCloseMenu = (): void => { - this.setState({ contextMenuPosition: undefined }); - }; - - private onUnreadFirstChanged = (): void => { - const isUnreadFirst = RoomListStore.instance.getListOrder(this.props.tagId) === ListAlgorithm.Importance; - const newAlgorithm = isUnreadFirst ? ListAlgorithm.Natural : ListAlgorithm.Importance; - RoomListStore.instance.setListOrder(this.props.tagId, newAlgorithm); - this.forceUpdate(); // because if the sublist doesn't have any changes then we will miss the list order change - }; - - private onTagSortChanged = async (sort: SortAlgorithm): Promise => { - RoomListStore.instance.setTagSorting(this.props.tagId, sort); - this.forceUpdate(); - }; - - private onMessagePreviewChanged = (): void => { - this.layout.showPreviews = !this.layout.showPreviews; - this.forceUpdate(); // because the layout doesn't trigger a re-render - }; - - private onBadgeClick = (ev: React.MouseEvent): void => { - ev.preventDefault(); - ev.stopPropagation(); - - let room; - if (this.props.tagId === DefaultTagID.Invite) { - // switch to first room as that'll be the top of the list for the user - room = this.state.rooms && this.state.rooms[0]; - } else { - // find the first room with a count of the same colour as the badge count - room = RoomListStore.instance.orderedLists[this.props.tagId].find((r: Room) => { - const notifState = this.notificationState.getForRoom(r); - return notifState.count > 0 && notifState.level === this.notificationState.level; - }); - } - - if (room) { - defaultDispatcher.dispatch({ - action: Action.ViewRoom, - room_id: room.roomId, - show_room_tile: true, // to make sure the room gets scrolled into view - metricsTrigger: "WebRoomListNotificationBadge", - metricsViaKeyboard: ev.type !== "click", - }); - } - }; - - private onHeaderClick = (): void => { - const possibleSticky = this.headerButton.current?.parentElement; - const sublist = possibleSticky?.parentElement?.parentElement; - const list = sublist?.parentElement?.parentElement; - if (!possibleSticky || !list) return; - - // the scrollTop is capped at the height of the header in LeftPanel, the top header is always sticky - const listScrollTop = Math.round(list.scrollTop); - const isAtTop = listScrollTop <= Math.round(HEADER_HEIGHT); - const isAtBottom = listScrollTop >= Math.round(list.scrollHeight - list.offsetHeight); - const isStickyTop = possibleSticky.classList.contains("mx_RoomSublist_headerContainer_stickyTop"); - const isStickyBottom = possibleSticky.classList.contains("mx_RoomSublist_headerContainer_stickyBottom"); - - if ((isStickyBottom && !isAtBottom) || (isStickyTop && !isAtTop)) { - // is sticky - jump to list - sublist.scrollIntoView({ behavior: "smooth" }); - } else { - // on screen - toggle collapse - const isExpanded = this.state.isExpanded; - this.toggleCollapsed(); - // if the bottom list is collapsed then scroll it in so it doesn't expand off screen - if (!isExpanded && isStickyBottom) { - setTimeout(() => { - sublist.scrollIntoView({ behavior: "smooth" }); - }, 0); - } - } - }; - - private toggleCollapsed = (): void => { - if (this.props.forceExpanded) return; - this.layout.isCollapsed = this.state.isExpanded; - this.setState({ isExpanded: !this.layout.isCollapsed }); - if (this.props.onListCollapse) { - this.props.onListCollapse(!this.layout.isCollapsed); - } - }; - - private onHeaderKeyDown = (ev: React.KeyboardEvent): void => { - const action = getKeyBindingsManager().getRoomListAction(ev); - switch (action) { - case KeyBindingAction.CollapseRoomListSection: - ev.stopPropagation(); - if (this.state.isExpanded) { - // Collapse the room sublist if it isn't already - this.toggleCollapsed(); - } - break; - case KeyBindingAction.ExpandRoomListSection: { - ev.stopPropagation(); - if (!this.state.isExpanded) { - // Expand the room sublist if it isn't already - this.toggleCollapsed(); - } else if (this.sublistRef.current) { - // otherwise focus the first room - const element = this.sublistRef.current.querySelector(".mx_RoomTile") as HTMLDivElement; - if (element) { - element.focus(); - } - } - break; - } - } - }; - - private onKeyDown = (ev: React.KeyboardEvent): void => { - const action = getKeyBindingsManager().getAccessibilityAction(ev); - switch (action) { - // On ArrowLeft go to the sublist header - case KeyBindingAction.ArrowLeft: - ev.stopPropagation(); - this.headerButton.current?.focus(); - break; - // Consume ArrowRight so it doesn't cause focus to get sent to composer - case KeyBindingAction.ArrowRight: - ev.stopPropagation(); - } - }; - - private renderVisibleTiles(): React.ReactElement[] { - if (!this.state.isExpanded && !this.props.forceExpanded) { - // don't waste time on rendering - return []; - } - - const tiles: React.ReactElement[] = []; - - if (this.state.rooms) { - let visibleRooms = this.state.rooms; - if (!this.props.forceExpanded) { - visibleRooms = visibleRooms.slice(0, this.numVisibleTiles); - } - - for (const room of visibleRooms) { - tiles.push( - , - ); - } - } - - if (this.extraTiles) { - // HACK: We break typing here, but this 'extra tiles' property shouldn't exist. - (tiles as any[]).push(...this.extraTiles); - } - - // We only have to do this because of the extra tiles. We do it conditionally - // to avoid spending cycles on slicing. It's generally fine to do this though - // as users are unlikely to have more than a handful of tiles when the extra - // tiles are used. - if (tiles.length > this.numVisibleTiles && !this.props.forceExpanded) { - return tiles.slice(0, this.numVisibleTiles); - } - - return tiles; - } - - private renderMenu(): ReactNode { - if (this.props.tagId === DefaultTagID.Suggested) return null; // not sortable - - let contextMenu: JSX.Element | undefined; - if (this.state.contextMenuPosition) { - const isAlphabetical = RoomListStore.instance.getTagSorting(this.props.tagId) === SortAlgorithm.Alphabetic; - const isUnreadFirst = RoomListStore.instance.getListOrder(this.props.tagId) === ListAlgorithm.Importance; - - // Invites don't get some nonsense options, so only add them if we have to. - let otherSections: JSX.Element | undefined; - if (this.props.tagId !== DefaultTagID.Invite) { - otherSections = ( - -
-
- {_t("common|appearance")} - - {_t("room_list|sort_unread_first")} - - - {_t("room_list|show_previews")} - -
-
- ); - } - - contextMenu = ( - -
-
- {_t("room_list|sort_by")} - this.onTagSortChanged(SortAlgorithm.Recent)} - checked={!isAlphabetical} - name={`mx_${this.props.tagId}_sortBy`} - > - {_t("room_list|sort_by_activity")} - - this.onTagSortChanged(SortAlgorithm.Alphabetic)} - checked={isAlphabetical} - name={`mx_${this.props.tagId}_sortBy`} - > - {_t("room_list|sort_by_alphabet")} - -
- {otherSections} -
-
- ); - } - - return ( - - - {contextMenu} - - ); - } - - private renderHeader(): React.ReactElement { - return ( - - {({ onFocus, isActive, ref }) => { - const tabIndex = isActive ? 0 : -1; - - let ariaLabel = _t("a11y_jump_first_unread_room"); - if (this.props.tagId === DefaultTagID.Invite) { - ariaLabel = _t("a11y|jump_first_invite"); - } - - const badge = ( - - ); - - let addRoomButton: JSX.Element | undefined; - if (this.props.AuxButtonComponent) { - const AuxButtonComponent = this.props.AuxButtonComponent; - addRoomButton = ; - } - - const collapseClasses = classNames({ - mx_RoomSublist_collapseBtn: true, - mx_RoomSublist_collapseBtn_collapsed: !this.state.isExpanded && !this.props.forceExpanded, - }); - - const classes = classNames({ - mx_RoomSublist_headerContainer: true, - mx_RoomSublist_headerContainer_withAux: !!addRoomButton, - }); - - const badgeContainer =
{badge}
; - - // Note: the addRoomButton conditionally gets moved around - // the DOM depending on whether or not the list is minimized. - // If we're minimized, we want it below the header so it - // doesn't become sticky. - // The same applies to the notification badge. - return ( -
-
-
- - - {this.props.label} - - {this.renderMenu()} - {this.props.isMinimized ? null : badgeContainer} - {this.props.isMinimized ? null : addRoomButton} -
-
- {this.props.isMinimized ? badgeContainer : null} - {this.props.isMinimized ? addRoomButton : null} -
- ); - }} -
- ); - } - - private onScrollPrevent(e: Event): void { - // the RoomTile calls scrollIntoView and the browser may scroll a div we do not wish to be scrollable - // this fixes https://github.com/vector-im/element-web/issues/14413 - (e.target as HTMLDivElement).scrollTop = 0; - } - - public render(): React.ReactElement { - const visibleTiles = this.renderVisibleTiles(); - const hidden = !this.state.rooms.length && !this.props.extraTiles?.length && this.props.alwaysVisible !== true; - const classes = classNames({ - mx_RoomSublist: true, - mx_RoomSublist_hasMenuOpen: !!this.state.contextMenuPosition, - mx_RoomSublist_minimized: this.props.isMinimized, - mx_RoomSublist_hidden: hidden, - }); - - let content: JSX.Element | undefined; - if (this.state.roomsLoading) { - content =
; - } else if (visibleTiles.length > 0 && this.props.forceExpanded) { - content = ( -
-
- {visibleTiles} -
-
- ); - } else if (visibleTiles.length > 0) { - const layout = this.layout; // to shorten calls - - const minTiles = Math.min(layout.minVisibleTiles, this.numTiles); - const showMoreAtMinHeight = minTiles < this.numTiles; - const minHeightPadding = RESIZE_HANDLE_HEIGHT + (showMoreAtMinHeight ? SHOW_N_BUTTON_HEIGHT : 0); - const minTilesPx = layout.tilesToPixelsWithPadding(minTiles, minHeightPadding); - const maxTilesPx = layout.tilesToPixelsWithPadding(this.numTiles, this.padding); - const showMoreBtnClasses = classNames({ - mx_RoomSublist_showNButton: true, - }); - - // If we're hiding rooms, show a 'show more' button to the user. This button - // floats above the resize handle, if we have one present. If the user has all - // tiles visible, it becomes 'show less'. - let showNButton: JSX.Element | undefined; - - if (maxTilesPx > this.state.height) { - // the height of all the tiles is greater than the section height: we need a 'show more' button - const nonPaddedHeight = this.state.height - RESIZE_HANDLE_HEIGHT - SHOW_N_BUTTON_HEIGHT; - const amountFullyShown = Math.floor(nonPaddedHeight / this.layout.tileHeight); - const numMissing = this.numTiles - amountFullyShown; - const label = _t("room_list|show_n_more", { count: numMissing }); - let showMoreText: ReactNode = {label}; - if (this.props.isMinimized) showMoreText = null; - showNButton = ( - - - {/* set by CSS masking */} - - {showMoreText} - - ); - } else if (this.numTiles > this.layout.defaultVisibleTiles) { - // we have all tiles visible - add a button to show less - const label = _t("room_list|show_less"); - let showLessText: ReactNode = {label}; - if (this.props.isMinimized) showLessText = null; - showNButton = ( - - - {/* set by CSS masking */} - - {showLessText} - - ); - } - - // Figure out if we need a handle - const handles: Enable = { - bottom: true, // the only one we need, but the others must be explicitly false - bottomLeft: false, - bottomRight: false, - left: false, - right: false, - top: false, - topLeft: false, - topRight: false, - }; - if (layout.visibleTiles >= this.numTiles && this.numTiles <= layout.minVisibleTiles) { - // we're at a minimum, don't have a bottom handle - handles.bottom = false; - } - - // We have to account for padding so we can accommodate a 'show more' button and - // the resize handle, which are pinned to the bottom of the container. This is the - // easiest way to have a resize handle below the button as otherwise we're writing - // our own resize handling and that doesn't sound fun. - // - // The layout class has some helpers for dealing with padding, as we don't want to - // apply it in all cases. If we apply it in all cases, the resizing feels like it - // goes backwards and can become wildly incorrect (visibleTiles says 18 when there's - // only mathematically 7 possible). - - const handleWrapperClasses = classNames({ - mx_RoomSublist_resizerHandles: true, - mx_RoomSublist_resizerHandles_showNButton: !!showNButton, - }); - - content = ( - - -
- {visibleTiles} -
- {showNButton} -
-
- ); - } else if (this.props.showSkeleton && this.state.isExpanded) { - content =
; - } - - return ( -
- {this.renderHeader()} - {content} -
- ); - } -} diff --git a/src/components/views/rooms/RoomTile.tsx b/src/components/views/rooms/RoomTile.tsx deleted file mode 100644 index 3eafb671cb..0000000000 --- a/src/components/views/rooms/RoomTile.tsx +++ /dev/null @@ -1,483 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2018 Michael Telatynski <7t3chguy@gmail.com> -Copyright 2015-2017 , 2019-2021 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -import React, { createRef } from "react"; -import { type Room, RoomEvent } from "matrix-js-sdk/src/matrix"; -import { KnownMembership } from "matrix-js-sdk/src/types"; -import classNames from "classnames"; - -import type { Call } from "../../../models/Call"; -import { RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex"; -import AccessibleButton, { type ButtonEvent } from "../../views/elements/AccessibleButton"; -import defaultDispatcher from "../../../dispatcher/dispatcher"; -import { Action } from "../../../dispatcher/actions"; -import { _t } from "../../../languageHandler"; -import { ChevronFace, ContextMenuTooltipButton, type MenuProps } from "../../structures/ContextMenu"; -import { DefaultTagID, type TagID } from "../../../stores/room-list/models"; -import { type MessagePreview, MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore"; -import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar"; -import { RoomNotifState } from "../../../RoomNotifs"; -import { MatrixClientPeg } from "../../../MatrixClientPeg"; -import { RoomNotificationContextMenu } from "../context_menus/RoomNotificationContextMenu"; -import NotificationBadge from "./NotificationBadge"; -import { type ActionPayload } from "../../../dispatcher/payloads"; -import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore"; -import { type NotificationState, NotificationStateEvents } from "../../../stores/notifications/NotificationState"; -import { EchoChamber } from "../../../stores/local-echo/EchoChamber"; -import { CachedRoomKey, type RoomEchoChamber } from "../../../stores/local-echo/RoomEchoChamber"; -import { PROPERTY_UPDATED } from "../../../stores/local-echo/GenericEchoChamber"; -import PosthogTrackers from "../../../PosthogTrackers"; -import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; -import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts"; -import { getKeyBindingsManager } from "../../../KeyBindingsManager"; -import { RoomGeneralContextMenu } from "../context_menus/RoomGeneralContextMenu"; -import { CallStore, CallStoreEvent } from "../../../stores/CallStore"; -import { SdkContextClass } from "../../../contexts/SDKContext"; -import { RoomTileSubtitle } from "./RoomTileSubtitle"; -import { shouldShowComponent } from "../../../customisations/helpers/UIComponents"; -import { UIComponent } from "../../../settings/UIFeature"; -import { isKnockDenied } from "../../../utils/membership"; -import SettingsStore from "../../../settings/SettingsStore"; - -interface Props { - room: Room; - showMessagePreview: boolean; - isMinimized: boolean; - tag: TagID; -} - -type PartialDOMRect = Pick; - -interface State { - selected: boolean; - notificationsMenuPosition: PartialDOMRect | null; - generalMenuPosition: PartialDOMRect | null; - call: Call | null; - messagePreview: MessagePreview | null; -} - -const messagePreviewId = (roomId: string): string => `mx_RoomTile_messagePreview_${roomId}`; - -export const contextMenuBelow = (elementRect: PartialDOMRect): MenuProps => { - // align the context menu's icons with the icon which opened the context menu - const left = elementRect.left + window.scrollX - 9; - const top = elementRect.bottom + window.scrollY + 17; - const chevronFace = ChevronFace.None; - return { left, top, chevronFace }; -}; - -class RoomTile extends React.PureComponent { - private dispatcherRef?: string; - private roomTileRef = createRef(); - private notificationState: NotificationState; - private roomProps: RoomEchoChamber; - - public constructor(props: Props) { - super(props); - - this.state = { - selected: SdkContextClass.instance.roomViewStore.getRoomId() === this.props.room.roomId, - notificationsMenuPosition: null, - generalMenuPosition: null, - call: CallStore.instance.getCall(this.props.room.roomId), - // generatePreview() will return nothing if the user has previews disabled - messagePreview: null, - }; - - this.notificationState = RoomNotificationStateStore.instance.getRoomState(this.props.room); - this.roomProps = EchoChamber.forRoom(this.props.room); - } - - private onRoomNameUpdate = (room: Room): void => { - this.forceUpdate(); - }; - - private onNotificationUpdate = (): void => { - this.forceUpdate(); // notification state changed - update - }; - - private onRoomPropertyUpdate = (property: CachedRoomKey): void => { - if (property === CachedRoomKey.NotificationVolume) this.onNotificationUpdate(); - // else ignore - not important for this tile - }; - - private get showContextMenu(): boolean { - return ( - this.props.tag !== DefaultTagID.Invite && - this.props.room.getMyMembership() !== KnownMembership.Knock && - !isKnockDenied(this.props.room) && - shouldShowComponent(UIComponent.RoomOptionsMenu) - ); - } - - private get showMessagePreview(): boolean { - return !this.props.isMinimized && this.props.showMessagePreview; - } - - public componentDidUpdate(prevProps: Readonly, prevState: Readonly): void { - const showMessageChanged = prevProps.showMessagePreview !== this.props.showMessagePreview; - const minimizedChanged = prevProps.isMinimized !== this.props.isMinimized; - if (showMessageChanged || minimizedChanged) { - this.generatePreview(); - } - if (prevProps.room?.roomId !== this.props.room?.roomId) { - MessagePreviewStore.instance.off( - MessagePreviewStore.getPreviewChangedEventName(prevProps.room), - this.onRoomPreviewChanged, - ); - MessagePreviewStore.instance.on( - MessagePreviewStore.getPreviewChangedEventName(this.props.room), - this.onRoomPreviewChanged, - ); - prevProps.room?.off(RoomEvent.Name, this.onRoomNameUpdate); - this.props.room?.on(RoomEvent.Name, this.onRoomNameUpdate); - } - } - - public componentDidMount(): void { - this.generatePreview(); - - // 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(); - } - - SdkContextClass.instance.roomViewStore.addRoomListener(this.props.room.roomId, this.onActiveRoomUpdate); - this.dispatcherRef = defaultDispatcher.register(this.onAction); - MessagePreviewStore.instance.on( - MessagePreviewStore.getPreviewChangedEventName(this.props.room), - this.onRoomPreviewChanged, - ); - this.notificationState.on(NotificationStateEvents.Update, this.onNotificationUpdate); - this.roomProps.on(PROPERTY_UPDATED, this.onRoomPropertyUpdate); - this.props.room.on(RoomEvent.Name, this.onRoomNameUpdate); - CallStore.instance.on(CallStoreEvent.Call, this.onCallChanged); - - // Recalculate the call for this room, since it could've changed between - // construction and mounting - this.setState({ call: CallStore.instance.getCall(this.props.room.roomId) }); - } - - public componentWillUnmount(): void { - SdkContextClass.instance.roomViewStore.removeRoomListener(this.props.room.roomId, this.onActiveRoomUpdate); - MessagePreviewStore.instance.off( - MessagePreviewStore.getPreviewChangedEventName(this.props.room), - this.onRoomPreviewChanged, - ); - this.props.room.off(RoomEvent.Name, this.onRoomNameUpdate); - defaultDispatcher.unregister(this.dispatcherRef); - this.notificationState.off(NotificationStateEvents.Update, this.onNotificationUpdate); - this.roomProps.off(PROPERTY_UPDATED, this.onRoomPropertyUpdate); - CallStore.instance.off(CallStoreEvent.Call, this.onCallChanged); - } - - private onAction = (payload: ActionPayload): void => { - if ( - payload.action === Action.ViewRoom && - payload.room_id === this.props.room.roomId && - payload.show_room_tile - ) { - setTimeout(() => { - this.scrollIntoView(); - }); - } - }; - - private onRoomPreviewChanged = (room: Room): void => { - if (this.props.room && room.roomId === this.props.room.roomId) { - this.generatePreview(); - } - }; - - private onCallChanged = (call: Call, roomId: string): void => { - if (roomId === this.props.room?.roomId) this.setState({ call }); - }; - - private async generatePreview(): Promise { - if (!this.showMessagePreview) { - return; - } - - const messagePreview = - (await MessagePreviewStore.instance.getPreviewForRoom(this.props.room, this.props.tag)) ?? null; - this.setState({ messagePreview }); - } - - private scrollIntoView = (): void => { - if (!this.roomTileRef.current) return; - this.roomTileRef.current.scrollIntoView({ - block: "nearest", - behavior: "auto", - }); - }; - - private onTileClick = async (ev: ButtonEvent): Promise => { - ev.preventDefault(); - ev.stopPropagation(); - - const action = getKeyBindingsManager().getAccessibilityAction(ev as React.KeyboardEvent); - const clearSearch = ([KeyBindingAction.Enter, KeyBindingAction.Space] as Array).includes( - action, - ); - - defaultDispatcher.dispatch({ - action: Action.ViewRoom, - show_room_tile: true, // make sure the room is visible in the list - room_id: this.props.room.roomId, - clear_search: clearSearch, - metricsTrigger: "RoomList", - metricsViaKeyboard: ev.type !== "click", - }); - }; - - private onActiveRoomUpdate = (isActive: boolean): void => { - this.setState({ selected: isActive }); - }; - - private onNotificationsMenuOpenClick = (ev: ButtonEvent): void => { - ev.preventDefault(); - ev.stopPropagation(); - const target = ev.target as HTMLButtonElement; - this.setState({ notificationsMenuPosition: target.getBoundingClientRect() }); - - PosthogTrackers.trackInteraction("WebRoomListRoomTileNotificationsMenu", ev); - }; - - private onCloseNotificationsMenu = (): void => { - this.setState({ notificationsMenuPosition: null }); - }; - - private onGeneralMenuOpenClick = (ev: ButtonEvent): void => { - ev.preventDefault(); - ev.stopPropagation(); - const target = ev.target as HTMLButtonElement; - this.setState({ generalMenuPosition: target.getBoundingClientRect() }); - }; - - private onContextMenu = (ev: React.MouseEvent): void => { - // If we don't have a context menu to show, ignore the action. - if (!this.showContextMenu) return; - - ev.preventDefault(); - ev.stopPropagation(); - this.setState({ - generalMenuPosition: { - left: ev.clientX, - bottom: ev.clientY, - }, - }); - }; - - private onCloseGeneralMenu = (): void => { - this.setState({ generalMenuPosition: null }); - }; - - private renderNotificationsMenu(isActive: boolean): React.ReactElement | null { - if ( - MatrixClientPeg.safeGet().isGuest() || - this.props.tag === DefaultTagID.Archived || - !this.showContextMenu || - this.props.isMinimized - ) { - // the menu makes no sense in these cases so do not show one - return null; - } - - const state = this.roomProps.notificationVolume; - - const classes = classNames("mx_RoomTile_notificationsButton", { - // Show bell icon for the default case too. - mx_RoomNotificationContextMenu_iconBell: state === RoomNotifState.AllMessages, - mx_RoomNotificationContextMenu_iconBellDot: state === RoomNotifState.AllMessagesLoud, - mx_RoomNotificationContextMenu_iconBellMentions: state === RoomNotifState.MentionsOnly, - mx_RoomNotificationContextMenu_iconBellCrossed: state === RoomNotifState.Mute, - - // Only show the icon by default if the room is overridden to muted. - // TODO: [FTUE Notifications] Probably need to detect global mute state - mx_RoomTile_notificationsButton_show: state === RoomNotifState.Mute, - }); - - return ( - - - {this.state.notificationsMenuPosition && ( - - )} - - ); - } - - private renderGeneralMenu(): React.ReactElement | null { - if (!this.showContextMenu) return null; // no menu to show - return ( - - - {this.state.generalMenuPosition && ( - - PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuFavouriteToggle", ev) - } - onPostInviteClick={(ev: ButtonEvent) => - PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuInviteItem", ev) - } - onPostSettingsClick={(ev: ButtonEvent) => - PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuSettingsItem", ev) - } - onPostLeaveClick={(ev: ButtonEvent) => - PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuLeaveItem", ev) - } - onPostMarkAsReadClick={(ev: ButtonEvent) => - PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuMarkRead", ev) - } - onPostMarkAsUnreadClick={(ev: ButtonEvent) => - PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuMarkUnread", ev) - } - /> - )} - - ); - } - - /** - * RoomTile has a subtile if one of the following applies: - * - there is a call - * - message previews are enabled and there is a previewable message - */ - private get shouldRenderSubtitle(): boolean { - return !!this.state.call || (this.props.showMessagePreview && !!this.state.messagePreview); - } - - public render(): React.ReactElement { - const classes = classNames({ - mx_RoomTile: true, - mx_RoomTile_sticky: - SettingsStore.getValue("feature_ask_to_join") && - (this.props.room.getMyMembership() === KnownMembership.Knock || isKnockDenied(this.props.room)), - mx_RoomTile_selected: this.state.selected, - mx_RoomTile_hasMenuOpen: !!(this.state.generalMenuPosition || this.state.notificationsMenuPosition), - mx_RoomTile_minimized: this.props.isMinimized, - }); - - let name = this.props.room.name; - if (typeof name !== "string") name = ""; - name = name.replace(":", ":\u200b"); // add a zero-width space to allow linewrapping after the colon - - let badge: React.ReactNode; - if (!this.props.isMinimized && this.notificationState) { - // aria-hidden because we summarise the unread count/highlight status in a manual aria-label below - badge = ( - - ); - } - - const subtitle = this.shouldRenderSubtitle ? ( - - ) : null; - - const titleClasses = classNames({ - mx_RoomTile_title: true, - mx_RoomTile_titleWithSubtitle: !!subtitle, - mx_RoomTile_titleHasUnreadEvents: this.notificationState.isUnread, - }); - - const titleContainer = this.props.isMinimized ? null : ( -
-
- {name} -
- {subtitle} -
- ); - - let ariaLabel = name; - // The following labels are written in such a fashion to increase screen reader efficiency (speed). - if (this.props.tag === DefaultTagID.Invite) { - // append nothing - } else if (this.notificationState.hasMentions) { - ariaLabel += - " " + - _t("a11y|n_unread_messages_mentions", { - count: this.notificationState.count, - }); - } else if (this.notificationState.hasUnreadCount) { - ariaLabel += - " " + - _t("a11y|n_unread_messages", { - count: this.notificationState.count, - }); - } else if (this.notificationState.isUnread) { - ariaLabel += " " + _t("a11y|unread_messages"); - } - - let ariaDescribedBy: string; - if (this.showMessagePreview) { - ariaDescribedBy = messagePreviewId(this.props.room.roomId); - } - - return ( - - - {({ onFocus, isActive, ref }) => ( - - - {titleContainer} - {badge} - {this.renderGeneralMenu()} - {this.renderNotificationsMenu(isActive)} - - )} - - - ); - } -} - -export default RoomTile; diff --git a/src/components/views/rooms/RoomTileCallSummary.tsx b/src/components/views/rooms/RoomTileCallSummary.tsx deleted file mode 100644 index eed7d012e2..0000000000 --- a/src/components/views/rooms/RoomTileCallSummary.tsx +++ /dev/null @@ -1,45 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -import React, { type FC } from "react"; - -import type { Call } from "../../../models/Call"; -import { _t } from "../../../languageHandler"; -import { useConnectionState, useParticipantCount } from "../../../hooks/useCall"; -import { ConnectionState } from "../../../models/Call"; -import { LiveContentSummary, LiveContentType } from "./LiveContentSummary"; - -interface Props { - call: Call; -} - -export const RoomTileCallSummary: FC = ({ call }) => { - let text: string; - let active: boolean; - - switch (useConnectionState(call)) { - case ConnectionState.Disconnected: - text = _t("common|video"); - active = false; - break; - case ConnectionState.Connected: - case ConnectionState.Disconnecting: - text = _t("common|joined"); - active = true; - break; - } - - return ( - - ); -}; diff --git a/src/components/views/rooms/RoomTileSubtitle.tsx b/src/components/views/rooms/RoomTileSubtitle.tsx deleted file mode 100644 index 715a09c9c4..0000000000 --- a/src/components/views/rooms/RoomTileSubtitle.tsx +++ /dev/null @@ -1,51 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2023 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -import React from "react"; -import classNames from "classnames"; -import { ThreadsIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; - -import { type MessagePreview } from "../../../stores/room-list/MessagePreviewStore"; -import { type Call } from "../../../models/Call"; -import { RoomTileCallSummary } from "./RoomTileCallSummary"; - -interface Props { - call: Call | null; - messagePreview: MessagePreview | null; - roomId: string; - showMessagePreview: boolean; -} - -const messagePreviewId = (roomId: string): string => `mx_RoomTile_messagePreview_${roomId}`; - -export const RoomTileSubtitle: React.FC = ({ call, messagePreview, roomId, showMessagePreview }) => { - if (call) { - return ( -
- -
- ); - } - - if (showMessagePreview && messagePreview) { - const className = classNames("mx_RoomTile_subtitle", { - "mx_RoomTile_subtitle--thread-reply": messagePreview.isThreadReply, - }); - - const icon = messagePreview.isThreadReply ? : null; - - return ( -
- {icon} - {messagePreview.text} -
- ); - } - - return null; -}; diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx index d373aee60e..fdcbda5474 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx @@ -119,8 +119,6 @@ const SpellCheckSection: React.FC = () => { }; export default class PreferencesUserSettingsTab extends React.Component { - private static ROOM_LIST_SETTINGS: BooleanSettingKey[] = ["breadcrumbs"]; - private static SPACES_SETTINGS: BooleanSettingKey[] = ["Spaces.allRoomsInHome"]; private static KEYBINDINGS_SETTINGS: BooleanSettingKey[] = ["ctrlFForSearch"]; @@ -248,7 +246,6 @@ export default class PreferencesUserSettingsTab extends React.Component { @@ -278,11 +275,7 @@ export default class PreferencesUserSettingsTab extends React.Component - {!newRoomListEnabled && this.renderGroup(PreferencesUserSettingsTab.ROOM_LIST_SETTINGS)} - {/* The settings is on device level where the other room list settings are on account level */} - {newRoomListEnabled && ( - - )} + diff --git a/src/components/views/settings/tabs/user/SidebarUserSettingsTab.tsx b/src/components/views/settings/tabs/user/SidebarUserSettingsTab.tsx index 041e437661..1b494b885f 100644 --- a/src/components/views/settings/tabs/user/SidebarUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/SidebarUserSettingsTab.tsx @@ -7,12 +7,7 @@ Please see LICENSE files in the repository root for full details. */ import React, { type ChangeEvent, useMemo } from "react"; -import { - VideoCallSolidIcon, - HomeSolidIcon, - UserProfileSolidIcon, - FavouriteSolidIcon, -} from "@vector-im/compound-design-tokens/assets/web/icons"; +import { VideoCallSolidIcon, HomeSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; import { _t } from "../../../../../languageHandler"; import SettingsStore from "../../../../../settings/SettingsStore"; @@ -53,8 +48,6 @@ export const onMetaSpaceChangeFactory = const SidebarUserSettingsTab: React.FC = () => { const { [MetaSpace.Home]: homeEnabled, - [MetaSpace.Favourites]: favouritesEnabled, - [MetaSpace.People]: peopleEnabled, [MetaSpace.Orphans]: orphansEnabled, [MetaSpace.VideoRooms]: videoRoomsEnabled, } = useSettingValue("Spaces.enabledMetaSpaces"); @@ -71,9 +64,6 @@ const SidebarUserSettingsTab: React.FC = () => { PosthogTrackers.trackInteraction("WebSettingsSidebarTabSpacesCheckbox", event, 1); }; - // "Favourites" and "People" meta spaces are not available in the new room list - const newRoomListEnabled = useSettingValue("feature_new_room_list"); - return ( @@ -103,36 +93,6 @@ const SidebarUserSettingsTab: React.FC = () => { {_t("settings|sidebar|metaspaces_home_all_rooms")} - {!newRoomListEnabled && ( - <> - - - {_t("common|favourites")} - - - - - {_t("common|people")} - - - )} - = ({ isPanelCollapsed = false }) => { const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu(); - const { [MetaSpace.Favourites]: favouritesEnabled, [MetaSpace.People]: peopleEnabled } = - useSettingValue("Spaces.enabledMetaSpaces"); - const currentRoomId = SdkContextClass.instance.roomViewStore.getRoomId(); const developerModeEnabled = useSettingValue("developerMode"); - // "Favourites" and "People" meta spaces are not available in the new room list - const newRoomListEnabled = useSettingValue("feature_new_room_list"); let contextMenu: JSX.Element | undefined; if (menuDisplayed && handle.current) { contextMenu = ( )} - {!newRoomListEnabled && ( - <> -

- - {_t("quick_settings|metaspace_section")} -

- - - {_t("common|favourites")} - - - - {_t("common|people")} - - { - closeMenu(); - defaultDispatcher.dispatch({ - action: Action.ViewUserSettings, - initialTabId: UserTab.Sidebar, - }); - }} - > - - {_t("quick_settings|sidebar_settings")} - - - )}
); diff --git a/src/components/views/spaces/SpacePanel.tsx b/src/components/views/spaces/SpacePanel.tsx index c0d8faedfb..5f8081514a 100644 --- a/src/components/views/spaces/SpacePanel.tsx +++ b/src/components/views/spaces/SpacePanel.tsx @@ -389,8 +389,6 @@ const SpacePanel: React.FC = () => { } }); - const newRoomListEnabled = useSettingValue("feature_new_room_list"); - return ( {({ onKeyDownHandler, onDragEndHandler }) => ( @@ -416,7 +414,6 @@ const SpacePanel: React.FC = () => {