diff --git a/apps/desktop/src/macos-titlebar.ts b/apps/desktop/src/macos-titlebar.ts index b42338ac3fa..917ae75a62c 100644 --- a/apps/desktop/src/macos-titlebar.ts +++ b/apps/desktop/src/macos-titlebar.ts @@ -115,14 +115,9 @@ export function setupMacosTitleBar(window: BrowserWindow): void { .mx_LeftPanel::before { content: ""; - height: 20px; - -webkit-app-region: drag; - } - - .mx_LeftPanel_newRoomList::before { - /* Aligned with the room header */ height: 13px; border-right: 1px solid var(--cpd-color-bg-subtle-primary); + -webkit-app-region: drag; } .mx_RoomView::before, diff --git a/apps/web/playwright/e2e/left-panel/room-list-panel/room-list-custom-sections.spec.ts b/apps/web/playwright/e2e/left-panel/room-list-panel/room-list-custom-sections.spec.ts index 577cb46771a..37ca9bc341b 100644 --- a/apps/web/playwright/e2e/left-panel/room-list-panel/room-list-custom-sections.spec.ts +++ b/apps/web/playwright/e2e/left-panel/room-list-panel/room-list-custom-sections.spec.ts @@ -22,7 +22,6 @@ import { test.describe("Room list custom sections", () => { test.use({ displayName: "Alice", - labsFlags: ["feature_new_room_list"], botCreateOpts: { displayName: "BotBob", autoAcceptInvites: true, diff --git a/apps/web/playwright/e2e/left-panel/room-list-panel/room-list-filter-sort.spec.ts b/apps/web/playwright/e2e/left-panel/room-list-panel/room-list-filter-sort.spec.ts index 4f397d152e8..2ac857c7ce6 100644 --- a/apps/web/playwright/e2e/left-panel/room-list-panel/room-list-filter-sort.spec.ts +++ b/apps/web/playwright/e2e/left-panel/room-list-panel/room-list-filter-sort.spec.ts @@ -20,7 +20,6 @@ test.describe("Room list filters and sort", () => { displayName: "BotBob", autoAcceptInvites: true, }, - labsFlags: ["feature_new_room_list"], }); /** diff --git a/apps/web/playwright/e2e/left-panel/room-list-panel/room-list-header.spec.ts b/apps/web/playwright/e2e/left-panel/room-list-panel/room-list-header.spec.ts index 42a7a90bcca..5f94abc19d2 100644 --- a/apps/web/playwright/e2e/left-panel/room-list-panel/room-list-header.spec.ts +++ b/apps/web/playwright/e2e/left-panel/room-list-panel/room-list-header.spec.ts @@ -11,10 +11,6 @@ import { test, expect } from "../../../element-web-test"; import { getHeaderSection } from "./utils"; test.describe("Header section of the room list", () => { - test.use({ - labsFlags: ["feature_new_room_list"], - }); - test.beforeEach(async ({ page, app, user }) => { // The toasts are displayed above the search section await rejectToast(page, "Verify this device"); diff --git a/apps/web/playwright/e2e/left-panel/room-list-panel/room-list-panel.spec.ts b/apps/web/playwright/e2e/left-panel/room-list-panel/room-list-panel.spec.ts index 5f0bebd8fa1..a04f8e851d6 100644 --- a/apps/web/playwright/e2e/left-panel/room-list-panel/room-list-panel.spec.ts +++ b/apps/web/playwright/e2e/left-panel/room-list-panel/room-list-panel.spec.ts @@ -11,10 +11,6 @@ import { test, expect } from "../../../element-web-test"; import { getRoomListView } from "./utils"; test.describe("Room list panel", () => { - test.use({ - labsFlags: ["feature_new_room_list"], - }); - test.beforeEach(async ({ page, app, user }) => { // The toasts are displayed above the search section await rejectToast(page, "Verify this device"); diff --git a/apps/web/playwright/e2e/left-panel/room-list-panel/room-list-search.spec.ts b/apps/web/playwright/e2e/left-panel/room-list-panel/room-list-search.spec.ts index deebde3493a..665d2431d2a 100644 --- a/apps/web/playwright/e2e/left-panel/room-list-panel/room-list-search.spec.ts +++ b/apps/web/playwright/e2e/left-panel/room-list-panel/room-list-search.spec.ts @@ -11,10 +11,6 @@ import { test, expect } from "../../../element-web-test"; import { getSearchSection } from "./utils"; test.describe("Search section of the room list", () => { - test.use({ - labsFlags: ["feature_new_room_list"], - }); - test.beforeEach(async ({ page, app, user }) => { // The toasts are displayed above the search section await rejectToast(page, "Verify this device"); diff --git a/apps/web/playwright/e2e/left-panel/room-list-panel/room-list-sections.spec.ts b/apps/web/playwright/e2e/left-panel/room-list-panel/room-list-sections.spec.ts index 994c41f68cf..d042371e47d 100644 --- a/apps/web/playwright/e2e/left-panel/room-list-panel/room-list-sections.spec.ts +++ b/apps/web/playwright/e2e/left-panel/room-list-panel/room-list-sections.spec.ts @@ -13,7 +13,6 @@ import { assertRoomInSection, dragRoomToSection, getPrimaryFilters, getRoomList, test.describe("Room list sections", () => { test.use({ displayName: "Alice", - labsFlags: ["feature_new_room_list"], botCreateOpts: { displayName: "BotBob", autoAcceptInvites: true, diff --git a/apps/web/playwright/e2e/left-panel/room-list-panel/room-list-unread-toast.spec.ts b/apps/web/playwright/e2e/left-panel/room-list-panel/room-list-unread-toast.spec.ts index f1b94d16c80..3c1769f7cf0 100644 --- a/apps/web/playwright/e2e/left-panel/room-list-panel/room-list-unread-toast.spec.ts +++ b/apps/web/playwright/e2e/left-panel/room-list-panel/room-list-unread-toast.spec.ts @@ -45,8 +45,6 @@ test.describe("Room list unread activity toast", () => { } test.describe("flat list", () => { - test.use({ labsFlags: ["feature_new_room_list"] }); - test.beforeEach(async ({ page, app, user }) => { // Toasts are displayed above the room list; dismiss the unrelated ones. await rejectToast(page, "Verify this device"); @@ -139,8 +137,6 @@ test.describe("Room list unread activity toast", () => { }); test.describe("sections", () => { - test.use({ labsFlags: ["feature_new_room_list", "feature_room_list_sections"] }); - test.beforeEach(async ({ page, app, user }) => { await rejectToast(page, "Verify this device"); await rejectToast(page, "Notifications"); diff --git a/apps/web/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts b/apps/web/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts index 70d11b5f242..d1249977bcc 100644 --- a/apps/web/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts +++ b/apps/web/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts @@ -16,7 +16,6 @@ import { getRoomList } from "./utils"; test.describe("Room list", () => { test.use({ displayName: "Alice", - labsFlags: ["feature_new_room_list"], botCreateOpts: { displayName: "BotBob", }, @@ -315,7 +314,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/apps/web/playwright/e2e/settings/preferences-user-settings-tab.spec.ts b/apps/web/playwright/e2e/settings/preferences-user-settings-tab.spec.ts index 5414055534d..ebfb600dafe 100644 --- a/apps/web/playwright/e2e/settings/preferences-user-settings-tab.spec.ts +++ b/apps/web/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, axe }) => { diff --git a/apps/web/res/css/_components.pcss b/apps/web/res/css/_components.pcss index 46be865ad6b..f1496fd5952 100644 --- a/apps/web/res/css/_components.pcss +++ b/apps/web/res/css/_components.pcss @@ -56,7 +56,6 @@ @import "./compound/_SuccessDialog.pcss"; @import "./structures/_AutoHideScrollbar.pcss"; @import "./structures/_AutocompleteInput.pcss"; -@import "./structures/_BackdropPanel.pcss"; @import "./structures/_CompatibilityPage.pcss"; @import "./structures/_ContextualMenu.pcss"; @import "./structures/_ErrorMessage.pcss"; @@ -72,7 +71,6 @@ @import "./structures/_PictureInPictureDragger.pcss"; @import "./structures/_QuickSettingsButton.pcss"; @import "./structures/_RightPanel.pcss"; -@import "./structures/_RoomSearch.pcss"; @import "./structures/_RoomView.pcss"; @import "./structures/_SearchBox.pcss"; @import "./structures/_SpaceHierarchy.pcss"; @@ -259,8 +257,6 @@ @import "./views/rooms/_IRCLayout.pcss"; @import "./views/rooms/_InvitedIconView.pcss"; @import "./views/rooms/_JumpToBottomButton.pcss"; -@import "./views/rooms/_LegacyRoomList.pcss"; -@import "./views/rooms/_LegacyRoomListHeader.pcss"; @import "./views/rooms/_LiveContentSummary.pcss"; @import "./views/rooms/_MemberListHeaderView.pcss"; @import "./views/rooms/_MemberListView.pcss"; @@ -277,15 +273,12 @@ @import "./views/rooms/_ReadReceiptGroup.pcss"; @import "./views/rooms/_ReplyPreview.pcss"; @import "./views/rooms/_ReplyTile.pcss"; -@import "./views/rooms/_RoomBreadcrumbs.pcss"; @import "./views/rooms/_RoomHeader.pcss"; @import "./views/rooms/_RoomInfoLine.pcss"; @import "./views/rooms/_RoomKnocksBar.pcss"; @import "./views/rooms/_RoomPreviewBar.pcss"; @import "./views/rooms/_RoomPreviewCard.pcss"; @import "./views/rooms/_RoomSearchAuxPanel.pcss"; -@import "./views/rooms/_RoomSublist.pcss"; -@import "./views/rooms/_RoomTile.pcss"; @import "./views/rooms/_RoomUpgradeWarningBar.pcss"; @import "./views/rooms/_SendMessageComposer.pcss"; @import "./views/rooms/_Stickers.pcss"; diff --git a/apps/web/res/css/structures/_BackdropPanel.pcss b/apps/web/res/css/structures/_BackdropPanel.pcss deleted file mode 100644 index ee12886880b..00000000000 --- a/apps/web/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/apps/web/res/css/structures/_LeftPanel.pcss b/apps/web/res/css/structures/_LeftPanel.pcss index e72579064af..aa6f67b1966 100644 --- a/apps/web/res/css/structures/_LeftPanel.pcss +++ b/apps/web/res/css/structures/_LeftPanel.pcss @@ -43,7 +43,6 @@ Please see LICENSE files in the repository root for full details. height: 100%; /* ensure space panel is still scrollable with an outer wrapper */ .mx_LeftPanel_wrapper--user { - background-color: $roomlist-bg-color; display: flex; overflow: hidden; position: relative; @@ -57,7 +56,10 @@ Please see LICENSE files in the repository root for full details. } .mx_LeftPanel { - background-color: $roomlist-bg-color; + background-color: var(--cpd-color-bg-canvas-default); + /* The room list is not designed to be collapsed to just icons. */ + /* 224 + 68(spaces bar) was deemed by design to be a good minimum for the left panel. */ + --collapsedWidth: 224px; /* Create a row-based flexbox for the space panel and the room list */ display: flex; @@ -66,119 +68,12 @@ Please see LICENSE files in the repository root for full details. flex-grow: 1; overflow: hidden; - /* Note: The 'room list' in this context is actually everything that isn't the tag */ - /* panel, such as the menu options, breadcrumbs, filtering, etc */ .mx_LeftPanel_roomListContainer { - background-color: $roomlist-bg-color; flex: 1 0 0; min-width: 0; /* Create another flexbox (this time a column) for the room list components */ display: flex; flex-direction: column; - - .mx_LeftPanel_userHeader { - /* 12px top, 12px sides, 20px bottom (using 13px bottom to account - * for internal whitespace in the breadcrumbs) - */ - padding: 12px; - flex-shrink: 0; /* to convince safari's layout engine the flexbox is fine */ - - /* Create another flexbox column for the rows to stack within */ - display: flex; - flex-direction: column; - } - - .mx_LeftPanel_breadcrumbsContainer { - overflow-y: hidden; - overflow-x: scroll; - margin: 12px 12px 0 12px; - flex: 0 0 auto; - /* Create yet another flexbox, this time within the row, to ensure items stay */ - /* aligned correctly. This is also a row-based flexbox. */ - display: flex; - align-items: center; - contain: content; - - &.mx_IndicatorScrollbar_leftOverflow { - mask-image: linear-gradient(90deg, transparent, black 5%); - } - - &.mx_IndicatorScrollbar_rightOverflow { - mask-image: linear-gradient(90deg, black, black 95%, transparent); - } - - &.mx_IndicatorScrollbar_rightOverflow.mx_IndicatorScrollbar_leftOverflow { - mask-image: linear-gradient(90deg, transparent, black 5%, black 95%, transparent); - } - } - - .mx_LeftPanel_filterContainer { - margin: 0 12px; - padding: 12px 0 8px; - border-bottom: 1px solid $quinary-content; - - flex-shrink: 0; /* to convince safari's layout engine the flexbox is fine */ - - /* Create a flexbox to organize the inputs */ - display: flex; - align-items: center; - - & + .mx_LegacyRoomListHeader { - margin-top: 12px; - } - - .mx_LeftPanel_dialPadButton, - .mx_LeftPanel_exploreButton { - width: 20px; - height: 20px; - padding: var(--cpd-space-1-5x); - border-radius: 8px; - background-color: $panel-actions; - margin-left: 8px; - /* For enhanced visibility under contrast control */ - outline: 1px solid transparent; - - svg { - width: inherit; - height: inherit; - display: block; - color: $secondary-content; - } - - &:hover { - background-color: $tertiary-content; - - svg { - color: $background; - } - } - } - } - - .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 */ @@ -189,40 +84,10 @@ Please see LICENSE files in the repository root for full details. .mx_LeftPanel_roomListContainer { width: var(--collapsedWidth); - - .mx_LeftPanel_userHeader { - flex-direction: row; - justify-content: center; - } - - .mx_LeftPanel_filterContainer { - /* Organize the flexbox into a centered column layout */ - flex-direction: column; - justify-content: center; - - .mx_LeftPanel_dialPadButton { - margin-left: 0; - margin-top: 8px; - background-color: transparent; - } - - .mx_LeftPanel_exploreButton { - margin-left: 0; - margin-top: 8px; - } - } } } } -.mx_LeftPanel_newRoomList { - /* Thew new rooms list is not designed to be collapsed to just icons. */ - /* 224 + 68(spaces bar) was deemed by design to be a good minimum for the left panel. */ - --collapsedWidth: 224px; - /* Important to force the color on ED titlebar until we remove the old room list */ - background-color: var(--cpd-color-bg-canvas-default) !important; -} - #left-panel .mx_LeftPanel_wrapper--user { /* * Disable background when using the new room list. diff --git a/apps/web/res/css/structures/_QuickSettingsButton.pcss b/apps/web/res/css/structures/_QuickSettingsButton.pcss index b681cecb305..8df560c9ef4 100644 --- a/apps/web/res/css/structures/_QuickSettingsButton.pcss +++ b/apps/web/res/css/structures/_QuickSettingsButton.pcss @@ -110,9 +110,7 @@ Please see LICENSE files in the repository root for full details. display: flex; } } -} -.mx_QuickSettingsButton_ContextMenuWrapper_new_room_list { .mx_QuickThemeSwitcher { margin-top: var(--cpd-space-2x); } diff --git a/apps/web/res/css/structures/_RoomSearch.pcss b/apps/web/res/css/structures/_RoomSearch.pcss deleted file mode 100644 index beb60e7ef95..00000000000 --- a/apps/web/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/apps/web/res/css/structures/_SpacePanel.pcss b/apps/web/res/css/structures/_SpacePanel.pcss index a3c8b827010..a7c452c3ca2 100644 --- a/apps/web/res/css/structures/_SpacePanel.pcss +++ b/apps/web/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; @@ -33,11 +34,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; diff --git a/apps/web/res/css/views/rooms/_LegacyRoomList.pcss b/apps/web/res/css/views/rooms/_LegacyRoomList.pcss deleted file mode 100644 index d890b561eca..00000000000 --- a/apps/web/res/css/views/rooms/_LegacyRoomList.pcss +++ /dev/null @@ -1,11 +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 */ -} diff --git a/apps/web/res/css/views/rooms/_LegacyRoomListHeader.pcss b/apps/web/res/css/views/rooms/_LegacyRoomListHeader.pcss deleted file mode 100644 index ec93c4e746d..00000000000 --- a/apps/web/res/css/views/rooms/_LegacyRoomListHeader.pcss +++ /dev/null @@ -1,75 +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; - } - - svg { - width: 20px; - height: 20px; - top: 3px; - right: 0; - position: absolute; - color: $tertiary-content; - } - - &[aria-expanded="true"] { - background-color: $quinary-content; - } - } - - .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; - - svg { - width: 16px; - height: 16px; - position: absolute; - color: $secondary-content; - } - - &:hover { - background-color: $tertiary-content; - - svg { - color: $background; - } - } - } -} diff --git a/apps/web/res/css/views/rooms/_NotificationBadge.pcss b/apps/web/res/css/views/rooms/_NotificationBadge.pcss index d867f6f5017..20945183c34 100644 --- a/apps/web/res/css/views/rooms/_NotificationBadge.pcss +++ b/apps/web/res/css/views/rooms/_NotificationBadge.pcss @@ -18,7 +18,7 @@ Please see LICENSE files in the repository root for full details. /* ^- The count is an element floating within that. */ &.mx_NotificationBadge_visible { - background-color: $roomtile-default-badge-bg-color; + background-color: var(--cpd-color-icon-secondary); /* For enhanced visibility under contrast control */ outline: 1px solid transparent; diff --git a/apps/web/res/css/views/rooms/_RoomBreadcrumbs.pcss b/apps/web/res/css/views/rooms/_RoomBreadcrumbs.pcss deleted file mode 100644 index 7dd150cd27b..00000000000 --- a/apps/web/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/apps/web/res/css/views/rooms/_RoomSublist.pcss b/apps/web/res/css/views/rooms/_RoomSublist.pcss deleted file mode 100644 index f1118ebed67..00000000000 --- a/apps/web/res/css/views/rooms/_RoomSublist.pcss +++ /dev/null @@ -1,389 +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; - - svg { - width: 16px; - height: 16px; - position: absolute; - top: 4px; - left: 4px; - color: var(--cpd-color-icon-secondary); - } - } - - .mx_RoomSublist_auxButton:hover svg, - .mx_RoomSublist_menuButton:hover svg { - color: $panel-actions; - } - - /* Hide the menu button by default */ - .mx_RoomSublist_menuButton { - visibility: hidden; - width: 0; - margin: 0; - } - - .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; - - svg { - width: 18px; - height: 18px; - position: absolute; - color: var(--cpd-color-icon-secondary); - } - } - } - } - - /* 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; - color: $tertiary-content; - left: -1px; /* adjust for image position */ - } - } - - &.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; - - svg { - 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; - - svg { - 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/apps/web/res/css/views/rooms/_RoomTile.pcss b/apps/web/res/css/views/rooms/_RoomTile.pcss deleted file mode 100644 index babb70aec2a..00000000000 --- a/apps/web/res/css/views/rooms/_RoomTile.pcss +++ /dev/null @@ -1,158 +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: 16px; - height: 16px; - padding: var(--cpd-space-0-5x); - flex-shrink: 0; - margin-top: auto; - margin-bottom: auto; - position: relative; - display: none; - - svg { - width: inherit; - height: inherit; - display: block; - color: 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; - } - - &: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/apps/web/res/themes/dark/css/_dark.pcss b/apps/web/res/themes/dark/css/_dark.pcss index 99fd31f3716..62772896fbf 100644 --- a/apps/web/res/themes/dark/css/_dark.pcss +++ b/apps/web/res/themes/dark/css/_dark.pcss @@ -15,16 +15,10 @@ $panels: var(--cpd-color-bg-subtle-secondary); $panel-actions: var(--cpd-color-alpha-gray-300); $separator: var(--cpd-color-gray-400); +$system: var(--cpd-color-bg-subtle-secondary); /* ******************** */ -/* RoomList */ -/* ******************** */ -$roomlist-bg-color: rgba(38, 40, 45, 0.9); -$roomsublist-skeleton-ui-bg: linear-gradient(180deg, $background 0%, #ffffff00 100%); -$roomtile-default-badge-bg-color: var(--cpd-color-icon-secondary); -/* ******************** */ - /** * Creating a `semantic` color scale. This will not be needed with the new * visual language, but necessary during the transition period @@ -147,13 +141,6 @@ $dialog-close-fg-color: $icon-button-color; $dialog-close-external-color: $primary-content; /* ******************** */ -/* RoomList */ -/* ******************** */ -$system: var(--cpd-color-bg-subtle-secondary); -$roomsublist-skeleton-ui-bg: linear-gradient(180deg, #3e444c 0%, #3e444c00 100%); -$roomtile-default-badge-bg-color: var(--cpd-color-icon-secondary); -/* ******************** */ - /* Tabbed views */ /* ******************** */ $tab-label-fg-color: $secondary-content; diff --git a/apps/web/res/themes/legacy-dark/css/_legacy-dark.pcss b/apps/web/res/themes/legacy-dark/css/_legacy-dark.pcss index ea5228b6c74..ec537dc937e 100644 --- a/apps/web/res/themes/legacy-dark/css/_legacy-dark.pcss +++ b/apps/web/res/themes/legacy-dark/css/_legacy-dark.pcss @@ -103,7 +103,7 @@ $background: $primary-bg-color; $overlay-background: rgba($background, 0.85); $panels: rgba($system, 0.9); -$panel-actions: $roomtile-selected-bg-color; +$panel-actions: #1a1d23; $separator: var(--cpd-color-gray-400); @@ -148,16 +148,10 @@ $call-background: $background; $call-primary-content: $primary-content; $call-light-quaternary-content: #c1c6cd; -$roomlist-filter-active-bg-color: $panel-actions; -$roomlist-bg-color: rgba(38, 40, 45, 0.9); - $roomsublist-skeleton-ui-bg: linear-gradient(180deg, #3e444c 0%, #3e444c00 100%); $spacePanel-divider-color: $tertiary-content; -$roomtile-default-badge-bg-color: #61708b; -$roomtile-selected-bg-color: #1a1d23; - /* ******************** */ $widget-menu-bar-bg-color: $header-panel-bg-color; diff --git a/apps/web/res/themes/legacy-light/css/_legacy-light.pcss b/apps/web/res/themes/legacy-light/css/_legacy-light.pcss index c2b0bd5995f..678b375e84d 100644 --- a/apps/web/res/themes/legacy-light/css/_legacy-light.pcss +++ b/apps/web/res/themes/legacy-light/css/_legacy-light.pcss @@ -138,9 +138,6 @@ $room-icon-unread-color: var(--cpd-color-icon-tertiary); /* ******************** */ -$roomtile-default-badge-bg-color: #61708b; -$roomtile-selected-bg-color: #fff; - $presence-away: #d9b072; $presence-offline: #e3e8f0; $presence-busy: #ff5b55; @@ -161,19 +158,13 @@ $background: $primary-bg-color; $overlay-background: rgba($background, 0.85); $panels: rgba($system, 0.9); -$panel-actions: $roomtile-selected-bg-color; +$panel-actions: #fff; $separator: var(--cpd-color-gray-400); /* Legacy theme backports */ /* ******************** */ - -$roomlist-filter-active-bg-color: $panel-actions; -$roomlist-bg-color: rgba(245, 245, 245, 0.9); -$roomlist-header-color: $primary-fg-color; -$roomsublist-skeleton-ui-bg: linear-gradient(180deg, #ffffff 0%, #ffffff00 100%); - $voipcall-plinth-color: $system; $call-view-button-on-foreground: $secondary-content; diff --git a/apps/web/res/themes/light-custom/css/_custom.pcss b/apps/web/res/themes/light-custom/css/_custom.pcss index 1bf761a7e79..f03bbe8d624 100644 --- a/apps/web/res/themes/light-custom/css/_custom.pcss +++ b/apps/web/res/themes/light-custom/css/_custom.pcss @@ -69,8 +69,6 @@ $dialog-title-fg-color: var(--timeline-text-color); $authpage-lang-color: var(--timeline-text-color); /* was #232f32 */ $authpage-primary-color: var(--timeline-text-color); -/* --roomlist-text-secondary-color */ -$roomtile-default-badge-bg-color: var(--roomlist-text-secondary-color); /* --roomlist-separator-color */ $input-darker-bg-color: var(--roomlist-separator-color); diff --git a/apps/web/res/themes/light/css/_light.pcss b/apps/web/res/themes/light/css/_light.pcss index 3bd803c53e8..08b24f69b75 100644 --- a/apps/web/res/themes/light/css/_light.pcss +++ b/apps/web/res/themes/light/css/_light.pcss @@ -204,13 +204,6 @@ $imagebody-giflabel-border: rgba(0, 0, 0, 0.2); $imagebody-giflabel-color: $accent-fg-color; /* ******************** */ -/* RoomList */ -/* ******************** */ -$roomlist-bg-color: rgba(245, 245, 245, 0.9); -$roomsublist-skeleton-ui-bg: linear-gradient(180deg, $background 0%, #ffffff00 100%); -$roomtile-default-badge-bg-color: var(--cpd-color-icon-secondary); -/* ******************** */ - /* e2e */ /* ******************** */ $e2e-verified-color: var(--cpd-color-icon-success-primary); @@ -244,7 +237,7 @@ $togglesw-ball-color: var(--cpd-color-bg-action-primary-rest); /* ******************** */ $authpage-primary-color: #232f32; $authpage-bg-color: #2e3649; -$authpage-modal-bg-color: $roomlist-bg-color; +$authpage-modal-bg-color: rgb(245, 245, 245, 0.9); /* background colour of the modal, when the background is blurred */ $authpage-modal-content-with-blur-bg-color: rgb(255, 255, 255, 0.59); $authpage-focus-bg-color: $focus-bg-color; diff --git a/apps/web/src/@types/global.d.ts b/apps/web/src/@types/global.d.ts index 99cd6d4ba42..4a5d5e89cfb 100644 --- a/apps/web/src/@types/global.d.ts +++ b/apps/web/src/@types/global.d.ts @@ -15,9 +15,7 @@ import type ContentMessages from "../ContentMessages"; import { type IMatrixClientPeg } from "../MatrixClientPeg"; import type ToastStore from "../stores/ToastStore"; import { type DeviceListener } from "../device-listener"; -import { type RoomListStore } from "../stores/room-list/Interface"; import { type PlatformPeg } from "../PlatformPeg"; -import type RoomListLayoutStore from "../stores/room-list/RoomListLayoutStore"; import { type IntegrationManagers } from "../integrations/IntegrationManagers"; import { type ModalManager } from "../Modal"; import type SettingsStore from "../settings/SettingsStore"; @@ -94,9 +92,7 @@ declare global { mxContentMessages: ContentMessages; mxToastStore: ToastStore; mxDeviceListener: DeviceListener; - mxRoomListStore: RoomListStore; getRoomListStoreV3: () => RoomListStoreV3Class; - mxRoomListLayoutStore: RoomListLayoutStore; mxPlatformPeg: PlatformPeg; mxIntegrationManagers: typeof IntegrationManagers; singletonModalManager: ModalManager; diff --git a/apps/web/src/accessibility/LandmarkNavigation.ts b/apps/web/src/accessibility/LandmarkNavigation.ts index 632c7ec6134..acb6812ca3e 100644 --- a/apps/web/src/accessibility/LandmarkNavigation.ts +++ b/apps/web/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("#room-list-search-button") - : document.querySelector(".mx_RoomSearch"), + [Landmark.ROOM_SEARCH]: () => document.querySelector("#room-list-search-button"), [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/apps/web/src/accessibility/context_menu/StyledMenuItemCheckbox.tsx b/apps/web/src/accessibility/context_menu/StyledMenuItemCheckbox.tsx deleted file mode 100644 index 82175199aea..00000000000 --- a/apps/web/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(this: void): void; // we handle keyup/down ourselves so lose the ChangeEvent - onClose(this: void): 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/apps/web/src/accessibility/context_menu/StyledMenuItemRadio.tsx b/apps/web/src/accessibility/context_menu/StyledMenuItemRadio.tsx deleted file mode 100644 index 62213b33c64..00000000000 --- a/apps/web/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(this: void): void; // we handle keyup/down ourselves so lose the ChangeEvent - onClose(this: void): 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/apps/web/src/components/structures/BackdropPanel.tsx b/apps/web/src/components/structures/BackdropPanel.tsx deleted file mode 100644 index f3a44521fa1..00000000000 --- a/apps/web/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/apps/web/src/components/structures/ContextMenu.tsx b/apps/web/src/components/structures/ContextMenu.tsx index c0fde685b31..887b9bb47d1 100644 --- a/apps/web/src/components/structures/ContextMenu.tsx +++ b/apps/web/src/components/structures/ContextMenu.tsx @@ -609,5 +609,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/apps/web/src/components/structures/LeftPanel.tsx b/apps/web/src/components/structures/LeftPanel.tsx index 630d2a9e165..f261e222921 100644 --- a/apps/web/src/components/structures/LeftPanel.tsx +++ b/apps/web/src/components/structures/LeftPanel.tsx @@ -6,37 +6,12 @@ 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 from "react"; import classNames from "classnames"; -import { ExploreIcon, DialPadIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; -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 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 { Landmark, LandmarkNavigation } from "../../accessibility/LandmarkNavigation"; -import SettingsStore from "../../settings/SettingsStore"; +import { type SpaceKey, UPDATE_SELECTED_SPACE } from "../../stores/spaces"; import { RoomListPanel } from "../views/rooms/RoomListPanel"; interface IProps { @@ -44,393 +19,41 @@ interface IProps { resizeNotifier: ResizeNotifier; } -enum BreadcrumbsMode { - Disabled, - Legacy, -} - interface IState { - showBreadcrumbs: BreadcrumbsMode; activeSpace: SpaceKey; - supportsPstnProtocol: boolean; } 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) { super(props); this.state = { activeSpace: SpaceStore.instance.activeSpace, - showBreadcrumbs: LeftPanel.breadcrumbsMode, - supportsPstnProtocol: LegacyCallHandler.instance.getSupportsPstnProtocol(), }; } - private static get breadcrumbsMode(): BreadcrumbsMode { - return !BreadcrumbsStore.instance.visible ? BreadcrumbsMode.Disabled : BreadcrumbsMode.Legacy; - } - public componentDidMount(): void { - BreadcrumbsStore.instance.on(UPDATE_EVENT, this.onBreadcrumbsUpdate); - RoomListStore.instance.on(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate); SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.updateActiveSpace); - LegacyCallHandler.instance.on(LegacyCallHandlerEvent.ProtocolSupport, this.updateProtocolSupport); - - if (this.listContainerRef.current) { - UIStore.instance.trackElementDimensions("ListContainer", this.listContainerRef.current); - // 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.listContainerRef.current.addEventListener("scroll", this.onScroll, { passive: true }); - } - UIStore.instance.on("ListContainer", this.refreshStickyHeaders); } public componentWillUnmount(): void { - BreadcrumbsStore.instance.off(UPDATE_EVENT, this.onBreadcrumbsUpdate); - RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate); SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.updateActiveSpace); - LegacyCallHandler.instance.off(LegacyCallHandlerEvent.ProtocolSupport, this.updateProtocolSupport); - UIStore.instance.stopTrackingElementDimensions("ListContainer"); - UIStore.instance.removeListener("ListContainer", this.refreshStickyHeaders); - this.listContainerRef.current?.removeEventListener("scroll", this.onScroll); - } - - public componentDidUpdate(prevProps: IProps, prevState: IState): void { - if (prevState.activeSpace !== this.state.activeSpace) { - this.refreshStickyHeaders(); - } } - private updateProtocolSupport = (): void => { - this.setState({ supportsPstnProtocol: LegacyCallHandler.instance.getSupportsPstnProtocol() }); - }; - private updateActiveSpace = (activeSpace: SpaceKey): void => { 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); - }; - - private onBreadcrumbsUpdate = (): void => { - const newVal = LeftPanel.breadcrumbsMode; - if (newVal !== this.state.showBreadcrumbs) { - this.setState({ showBreadcrumbs: newVal }); - - // Update the sticky headers too as the breadcrumbs will be popping in or out. - if (!this.listContainerRef.current) return; // ignore: no headers to sticky - this.handleStickyHeaders(this.listContainerRef.current); - } - }; - - private handleStickyHeaders(list: HTMLDivElement): void { - if (this.isDoingStickyHeaders) return; - this.isDoingStickyHeaders = true; - window.requestAnimationFrame(() => { - this.doStickyHeaders(list); - this.isDoingStickyHeaders = false; - }); - } - - private doStickyHeaders(list: HTMLDivElement): void { - if (!list.parentElement) return; - const topEdge = list.scrollTop; - const bottomEdge = list.offsetHeight + list.scrollTop; - const sublists = list.querySelectorAll(".mx_RoomSublist:not(.mx_RoomSublist_hidden)"); - - // We track which styles we want on a target before making the changes to avoid - // excessive layout updates. - const targetStyles = new Map< - HTMLDivElement, - { - stickyTop?: boolean; - stickyBottom?: boolean; - makeInvisible?: boolean; - } - >(); - - let lastTopHeader: HTMLDivElement | undefined; - let firstBottomHeader: HTMLDivElement | undefined; - for (const sublist of sublists) { - const header = sublist.querySelector(".mx_RoomSublist_stickable"); - if (!header) continue; // this should never occur - header.style.removeProperty("display"); // always clear display:none first - - // When an element is <=40% off screen, make it take over - const offScreenFactor = 0.4; - const isOffTop = sublist.offsetTop + offScreenFactor * HEADER_HEIGHT <= topEdge; - const isOffBottom = sublist.offsetTop + offScreenFactor * HEADER_HEIGHT >= bottomEdge; - - if (isOffTop || sublist === sublists[0]) { - targetStyles.set(header, { stickyTop: true }); - if (lastTopHeader) { - lastTopHeader.style.display = "none"; - targetStyles.set(lastTopHeader, { makeInvisible: true }); - } - lastTopHeader = header; - } else if (isOffBottom && !firstBottomHeader) { - targetStyles.set(header, { stickyBottom: true }); - firstBottomHeader = header; - } else { - targetStyles.set(header, {}); // nothing == clear - } - } - - // Run over the style changes and make them reality. We check to see if we're about to - // cause a no-op update, as adding/removing properties that are/aren't there cause - // layout updates. - for (const header of targetStyles.keys()) { - const style = targetStyles.get(header)!; - - if (style.makeInvisible) { - // we will have already removed the 'display: none', so add it back. - header.style.display = "none"; - continue; // nothing else to do, even if sticky somehow - } - - if (style.stickyTop) { - if (!header.classList.contains("mx_RoomSublist_headerContainer_stickyTop")) { - header.classList.add("mx_RoomSublist_headerContainer_stickyTop"); - } - - const newTop = `${list.parentElement.offsetTop}px`; - if (header.style.top !== newTop) { - header.style.top = newTop; - } - } else { - if (header.classList.contains("mx_RoomSublist_headerContainer_stickyTop")) { - header.classList.remove("mx_RoomSublist_headerContainer_stickyTop"); - } - if (header.style.top) { - header.style.removeProperty("top"); - } - } - - if (style.stickyBottom) { - if (!header.classList.contains("mx_RoomSublist_headerContainer_stickyBottom")) { - header.classList.add("mx_RoomSublist_headerContainer_stickyBottom"); - } - - const offset = - UIStore.instance.windowHeight - (list.parentElement.offsetTop + list.parentElement.offsetHeight); - const newBottom = `${offset}px`; - if (header.style.bottom !== newBottom) { - header.style.bottom = newBottom; - } - } else { - if (header.classList.contains("mx_RoomSublist_headerContainer_stickyBottom")) { - header.classList.remove("mx_RoomSublist_headerContainer_stickyBottom"); - } - if (header.style.bottom) { - header.style.removeProperty("bottom"); - } - } - - if (style.stickyTop || style.stickyBottom) { - if (!header.classList.contains("mx_RoomSublist_headerContainer_sticky")) { - header.classList.add("mx_RoomSublist_headerContainer_sticky"); - } - - const listDimensions = UIStore.instance.getElementDimensions("ListContainer"); - if (listDimensions) { - const headerRightMargin = 15; // calculated from margins and widths to align with non-sticky tiles - const headerStickyWidth = listDimensions.width - headerRightMargin; - const newWidth = `${headerStickyWidth}px`; - if (header.style.width !== newWidth) { - header.style.width = newWidth; - } - } - } else if (!style.stickyTop && !style.stickyBottom) { - if (header.classList.contains("mx_RoomSublist_headerContainer_sticky")) { - header.classList.remove("mx_RoomSublist_headerContainer_sticky"); - } - - if (header.style.width) { - header.style.removeProperty("width"); - } - } - } - - // add appropriate sticky classes to wrapper so it has - // the necessary top/bottom padding to put the sticky header in - const listWrapper = list.parentElement; // .mx_LeftPanel_roomListWrapper - if (!listWrapper) return; - if (lastTopHeader) { - listWrapper.classList.add("mx_LeftPanel_roomListWrapper_stickyTop"); - } else { - listWrapper.classList.remove("mx_LeftPanel_roomListWrapper_stickyTop"); - } - if (firstBottomHeader) { - listWrapper.classList.add("mx_LeftPanel_roomListWrapper_stickyBottom"); - } else { - listWrapper.classList.remove("mx_LeftPanel_roomListWrapper_stickyBottom"); - } - } - - private onScroll = (ev: Event): void => { - const list = ev.target as HTMLDivElement; - 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_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/apps/web/src/components/structures/LoggedInView.tsx b/apps/web/src/components/structures/LoggedInView.tsx index a5256dde075..4a530075276 100644 --- a/apps/web/src/components/structures/LoggedInView.tsx +++ b/apps/web/src/components/structures/LoggedInView.tsx @@ -31,8 +31,6 @@ import dis from "../../dispatcher/dispatcher"; import { type IMatrixClientCreds } from "../../MatrixClientPeg"; import SettingsStore from "../../settings/SettingsStore"; import { SettingLevel } from "../../settings/SettingLevel"; -import ResizeHandle from "../views/elements/ResizeHandle"; -import { CollapseDistributor, Resizer } from "../../resizer"; import PlatformPeg from "../../PlatformPeg"; import { hideToast as hideServerLimitToast, showToast as showServerLimitToast } from "../../toasts/ServerLimitToast"; import { Action } from "../../dispatcher/actions"; @@ -42,7 +40,6 @@ import RoomListStoreV3 from "../../stores/room-list-v3/RoomListStoreV3"; import NonUrgentToastContainer from "./NonUrgentToastContainer"; import { type IOOBData, type IThreepidInvite } from "../../stores/ThreepidInviteStore"; import Modal from "../../Modal"; -import { type CollapseItem, type ICollapseConfig } from "../../resizer/distributors/collapse"; import { getKeyBindingsManager } from "../../KeyBindingsManager"; import { type IOpts } from "../../createRoom"; import SpacePanel from "../views/spaces/SpacePanel"; @@ -53,7 +50,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"; @@ -94,7 +90,6 @@ interface IProps { threepidInvite?: IThreepidInvite; roomOobData?: IOOBData; currentRoomId: string | null; - collapseLhs: boolean; currentUserId: string | null; justRegistered?: boolean; roomJustCreatedOpts?: IOpts; @@ -111,7 +106,6 @@ interface IState { backgroundImage?: string; } -const NEW_ROOM_LIST_MIN_WIDTH = 224; /** * This is what our MatrixChat shows when we are logged in. The precise view is * determined by the page_type property. @@ -126,13 +120,10 @@ class LoggedInView extends React.Component { protected readonly _matrixClient: MatrixClient; protected readonly _roomView: React.RefObject; - protected readonly _resizeContainer: React.RefObject; - protected readonly resizeHandler: React.RefObject; protected layoutWatcherRef?: string; protected compactLayoutWatcherRef?: string; protected backgroundImageWatcherRef?: string; protected timezoneProfileUpdateRef?: string[]; - protected resizer?: Resizer; private resizerViewModel?: ResizerViewModel; @@ -156,8 +147,6 @@ class LoggedInView extends React.Component { MediaDeviceHandler.loadDevices(); this._roomView = React.createRef(); - this._resizeContainer = React.createRef(); - this.resizeHandler = React.createRef(); } public componentDidMount(): void { @@ -194,8 +183,6 @@ class LoggedInView extends React.Component { // system time has changed between sessions. void this.onTimezoneUpdate(); - this.loadResizer(); - OwnProfileStore.instance.on(UPDATE_EVENT, this.refreshBackgroundImage); this.refreshBackgroundImage(); } @@ -212,24 +199,6 @@ class LoggedInView extends React.Component { this.resizerViewModel = undefined; } - /** - * Load or reload the resizer for the left panel - */ - private loadResizer(): void { - // If the resizer already exists, detach it first - this.resizer?.detach(); - - this.resizer = this.createResizer(); - this.resizer.attach(); - this.loadResizerPreferences(); - } - - public componentDidUpdate(nextProps: Readonly, nextState: Readonly, nextContext: any): void { - if (nextProps.page_type !== this.props.page_type) { - this.loadResizer(); - } - } - private onTimezoneUpdate = async (): Promise => { // TODO: In a future app release, remove support for legacy key. if (!SettingsStore.getValue("userTimezonePublish")) { @@ -269,7 +238,6 @@ class LoggedInView extends React.Component { SettingsStore.unwatchSetting(this.compactLayoutWatcherRef); SettingsStore.unwatchSetting(this.backgroundImageWatcherRef); this.timezoneProfileUpdateRef?.forEach((s) => SettingsStore.unwatchSetting(s)); - this.resizer?.detach(); this.resizerViewModel?.dispose(); } @@ -297,66 +265,6 @@ class LoggedInView extends React.Component { return this._roomView.current.canResetTimeline(); }; - 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 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" }); - } - }, - onResized: (size) => { - panelSize = size; - this.context.resizeNotifier.notifyLeftHandleResized(); - }, - onResizeStart: () => { - this.context.resizeNotifier.startResizing(); - }, - onResizeStop: () => { - // Always save the lhs size for the new room list. - if (useNewRoomList || !panelCollapsed) 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"); - }, - handler: this.resizeHandler.current ?? undefined, - }; - const resizer = new Resizer(this._resizeContainer.current, CollapseDistributor, collapseConfig); - resizer.setClassNames({ - handle: "mx_ResizeHandle", - vertical: "mx_ResizeHandle--vertical", - reverse: "mx_ResizeHandle_reverse", - }); - return resizer; - } - - 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); - } - private onAccountData = (event: MatrixEvent): void => { if (event.getType() === "m.ignored_user_list") { dis.dispatch({ action: "ignore_state_changed" }); @@ -763,38 +671,19 @@ 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, - }); + const leftPanelWrapperClasses = classNames("mx_LeftPanel_wrapper"); const audioFeedArraysForCalls = this.state.activeCalls.map((call) => { return ; }); - const shouldUseMinimizedUI = !useNewRoomList && this.props.collapseLhs; - const leftPanel = (
- +
- {!useNewRoomList && ( - - )} - {!useNewRoomList && } - {!useNewRoomList && } {!moduleRenderer && ( -
- +
+
)}
@@ -805,9 +694,9 @@ class LoggedInView extends React.Component { let content: React.ReactNode; const resizerViewModel = !moduleRenderer ? this.getResizerViewModel() : undefined; - if (useNewRoomList && resizerViewModel && !moduleRenderer) { - // New room list owned by element-web: resizable layout with a draggable separator. - // The SpacePanel lives inside GroupView (leftPanel omits it when the new room list is enabled). + if (resizerViewModel && !moduleRenderer) { + // Resizable layout with a draggable separator. The SpacePanel lives inside GroupView + // (leftPanel omits it). content = ( @@ -825,15 +714,13 @@ class LoggedInView extends React.Component { ); } else { - // Fallback layout: the old room list, or a module's full-screen view (e.g. multiroom) which - // must not use the resizable layout above. SpacePanel is guarded on useNewRoomList to avoid a - // duplicate (the old room list already renders one inside leftPanel); the legacy ResizeHandle is - // dropped for module views, which manage their own layout. + // Fallback layout for a module's full-screen view (e.g. multiroom) which must not use the + // resizable layout above. The ResizeHandle is dropped for module views, which manage their + // own layout. content = ( <> - {useNewRoomList && } + {leftPanel} - {!moduleRenderer && } {roomView} ); diff --git a/apps/web/src/components/structures/MatrixChat.tsx b/apps/web/src/components/structures/MatrixChat.tsx index f617bc8e580..e27ffadb565 100644 --- a/apps/web/src/components/structures/MatrixChat.tsx +++ b/apps/web/src/components/structures/MatrixChat.tsx @@ -75,8 +75,6 @@ import { UIFeature } from "../../settings/UIFeature"; import DialPadModal from "../views/voip/DialPadModal"; import { showToast as showMobileGuideToast } from "../../toasts/MobileGuideToast"; import { shouldUseLoginForWelcome } from "../../utils/pages"; -import RoomListStore from "../../stores/room-list/RoomListStore"; -import { RoomUpdateCause } from "../../stores/room-list/models"; import { ModuleRunner } from "../../modules/ModuleRunner"; import Spinner from "../views/elements/Spinner"; import QuestionDialog from "../views/dialogs/QuestionDialog"; @@ -191,8 +189,6 @@ interface IState { currentRoomId: string | null; // If we're trying to just view a user ID (i.e. /user URL), this is it currentUserId: string | null; - // this is persisted as mx_lhs_size, loaded in LoggedInView - collapseLhs: boolean; // Parameters used in the registration dance with the IS // eslint-disable-next-line camelcase register_client_secret?: string; @@ -253,7 +249,6 @@ export default class MatrixChat extends React.PureComponent { this.state = { view: Views.LOADING, - collapseLhs: false, currentRoomId: null, currentUserId: null, @@ -873,24 +868,8 @@ export default class MatrixChat extends React.PureComponent { break; } case "hide_left_panel": - this.setState( - { - collapseLhs: true, - }, - () => { - this.stores.resizeNotifier.notifyLeftHandleResized(); - }, - ); - break; case "show_left_panel": - this.setState( - { - collapseLhs: false, - }, - () => { - this.stores.resizeNotifier.notifyLeftHandleResized(); - }, - ); + this.stores.resizeNotifier.notifyLeftHandleResized(); break; case Action.OpenDialPad: Modal.createDialog(DialPadModal, {}, "mx_Dialog_dialPadWrapper"); @@ -1364,9 +1343,7 @@ export default class MatrixChat extends React.PureComponent { } if (room) { - // Legacy room list store needs to be told to manually remove this room - RoomListStore.instance.manualRoomUpdate(room, RoomUpdateCause.RoomRemoved); - // New room list store will remove the room on the following dispatch + // The room list store will remove the room on the following dispatch dis.dispatch({ action: Action.AfterForgetRoom, room }); } }) @@ -1576,7 +1553,6 @@ export default class MatrixChat extends React.PureComponent { private onLoggedOut(): void { this.viewWelcome({ ready: false, - collapseLhs: false, currentRoomId: null, }); this.subTitleStatus = ""; @@ -1592,7 +1568,6 @@ export default class MatrixChat extends React.PureComponent { this.setStateForNewView({ view: Views.SOFT_LOGOUT, ready: false, - collapseLhs: false, currentRoomId: null, }); this.subTitleStatus = ""; diff --git a/apps/web/src/components/structures/RoomSearch.tsx b/apps/web/src/components/structures/RoomSearch.tsx deleted file mode 100644 index f099032a637..00000000000 --- a/apps/web/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(this: void): 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/apps/web/src/components/views/dialogs/spotlight/RoomResultContextMenus.tsx b/apps/web/src/components/views/dialogs/spotlight/RoomResultContextMenus.tsx index c11bd777e2e..838fbcc45d0 100644 --- a/apps/web/src/components/views/dialogs/spotlight/RoomResultContextMenus.tsx +++ b/apps/web/src/components/views/dialogs/spotlight/RoomResultContextMenus.tsx @@ -22,7 +22,7 @@ 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 { ChevronFace, type MenuProps } from "../../../structures/ContextMenu"; import { shouldShowComponent } from "../../../../customisations/helpers/UIComponents"; import { UIComponent } from "../../../../settings/UIFeature"; @@ -30,6 +30,14 @@ interface Props { room: Room; } +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 - 9; + const top = elementRect.bottom + window.scrollY + 17; + const chevronFace = ChevronFace.None; + return { left, top, chevronFace }; +}; + export function getNotificationIcon(state: RoomNotifState): ReactNode { switch (state) { case RoomNotifState.Mute: diff --git a/apps/web/src/components/views/rooms/ExtraTile.tsx b/apps/web/src/components/views/rooms/ExtraTile.tsx deleted file mode 100644 index 05bbb312231..00000000000 --- a/apps/web/src/components/views/rooms/ExtraTile.tsx +++ /dev/null @@ -1,87 +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. -*/ - -import React, { type JSX } from "react"; -import classNames from "classnames"; - -import { RovingAccessibleButton } from "../../../accessibility/RovingTabIndex"; -import NotificationBadge from "./NotificationBadge"; -import { type NotificationState } from "../../../stores/notifications/NotificationState"; -import { type ButtonEvent } from "../elements/AccessibleButton"; -import useHover from "../../../hooks/useHover"; - -interface ExtraTileProps { - isMinimized: boolean; - isSelected: boolean; - displayName: string; - avatar: React.ReactElement; - notificationState?: NotificationState; - onClick: (ev: ButtonEvent) => void; -} - -export default function ExtraTile({ - isSelected, - isMinimized, - notificationState, - displayName, - onClick, - avatar, -}: ExtraTileProps): JSX.Element { - const [, { onMouseOver, onMouseLeave }] = useHover(() => false); - - // XXX: We copy classes because it's easier - const classes = classNames({ - mx_ExtraTile: true, - mx_RoomTile: true, - mx_RoomTile_selected: isSelected, - mx_RoomTile_minimized: isMinimized, - }); - - let badge: JSX.Element | null = null; - if (notificationState) { - badge = ; - } - - let name = displayName; - if (typeof name !== "string") name = ""; - name = name.replace(":", ":\u200b"); // add a zero-width space to allow linewrapping after the colon - - const nameClasses = classNames({ - mx_RoomTile_title: true, - mx_RoomTile_titleHasUnreadEvents: notificationState?.isUnread, - }); - - let nameContainer: JSX.Element | null = ( -
-
- {name} -
-
- ); - if (isMinimized) nameContainer = null; - - return ( - -
{avatar}
-
-
- {nameContainer} -
{badge}
-
-
-
- ); -} diff --git a/apps/web/src/components/views/rooms/LegacyRoomList.tsx b/apps/web/src/components/views/rooms/LegacyRoomList.tsx deleted file mode 100644 index a5c4946709d..00000000000 --- a/apps/web/src/components/views/rooms/LegacyRoomList.tsx +++ /dev/null @@ -1,706 +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 { - PlusIcon, - UserAddSolidIcon, - RoomIcon, - SearchIcon, - ShareIcon, - VideoCallSolidIcon, -} from "@vector-im/compound-design-tokens/assets/web/icons"; - -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 } 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-v3/skip-list/tag"; -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/SDKContextClass"; -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 && ( - } - onClick={(e) => { - e.preventDefault(); - e.stopPropagation(); - closeMenu(); - defaultDispatcher.dispatch({ action: Action.CreateChat }); - PosthogTrackers.trackInteraction( - "WebRoomListRoomsSublistPlusMenuCreateChatItem", - e, - ); - }} - /> - )} - {showInviteUsers && ( - } - onClick={(e) => { - 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 = ( - - } - onClick={(e) => { - e.preventDefault(); - e.stopPropagation(); - closeMenu(); - defaultDispatcher.dispatch({ - action: Action.ViewRoom, - room_id: activeSpace.roomId, - metricsTrigger: undefined, // other - }); - PosthogTrackers.trackInteraction("WebRoomListRoomsSublistPlusMenuExploreRoomsItem", e); - }} - /> - {showCreateRoom ? ( - <> - } - onClick={(e) => { - e.preventDefault(); - e.stopPropagation(); - closeMenu(); - showCreateNewRoom(activeSpace); - PosthogTrackers.trackInteraction("WebRoomListRoomsSublistPlusMenuCreateRoomItem", e); - }} - disabled={!canAddRooms} - title={canAddRooms ? undefined : _t("spaces|error_no_permission_create_room")} - /> - {videoRoomsEnabled && ( - } - onClick={(e) => { - e.preventDefault(); - e.stopPropagation(); - closeMenu(); - showCreateNewRoom( - activeSpace, - elementCallVideoRoomsEnabled ? RoomType.UnstableCall : RoomType.ElementVideo, - ); - }} - disabled={!canAddRooms} - title={canAddRooms ? undefined : _t("spaces|error_no_permission_create_room")} - > - - - )} - } - onClick={(e) => { - 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 && ( - <> - } - onClick={(e) => { - e.preventDefault(); - e.stopPropagation(); - closeMenu(); - defaultDispatcher.dispatch({ action: Action.CreateRoom }); - PosthogTrackers.trackInteraction("WebRoomListRoomsSublistPlusMenuCreateRoomItem", e); - }} - /> - {videoRoomsEnabled && ( - } - onClick={(e) => { - e.preventDefault(); - e.stopPropagation(); - closeMenu(); - defaultDispatcher.dispatch({ - action: Action.CreateRoom, - type: elementCallVideoRoomsEnabled - ? RoomType.UnstableCall - : RoomType.ElementVideo, - }); - }} - > - - - )} - - )} - {showExploreRooms ? ( - } - onClick={(e) => { - 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/apps/web/src/components/views/rooms/LegacyRoomListHeader.tsx b/apps/web/src/components/views/rooms/LegacyRoomListHeader.tsx deleted file mode 100644 index 6c9c15a7780..00000000000 --- a/apps/web/src/components/views/rooms/LegacyRoomListHeader.tsx +++ /dev/null @@ -1,441 +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 { - PlusIcon, - UserAddSolidIcon, - SearchIcon, - UserAddIcon, - VideoCallSolidIcon, - ChevronDownIcon, - ChevronUpIcon, -} from "@vector-im/compound-design-tokens/assets/web/icons"; - -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?(this: void): 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 = ( - } - onClick={(e) => { - e.preventDefault(); - e.stopPropagation(); - showSpaceInvite(activeSpace); - closePlusMenu(); - }} - /> - ); - } - - let newRoomOptions: JSX.Element | undefined; - if (activeSpace?.currentState.maySendStateEvent(EventType.RoomAvatar, cli.getUserId()!)) { - newRoomOptions = ( - <> - } - label={_t("action|new_room")} - onClick={(e) => { - e.preventDefault(); - e.stopPropagation(); - showCreateNewRoom(activeSpace); - PosthogTrackers.trackInteraction("WebRoomListHeaderPlusMenuCreateRoomItem", e); - closePlusMenu(); - }} - /> - {videoRoomsEnabled && ( - } - label={_t("action|new_video_room")} - onClick={(e) => { - e.preventDefault(); - e.stopPropagation(); - showCreateNewRoom( - activeSpace, - elementCallVideoRoomsEnabled ? RoomType.UnstableCall : RoomType.ElementVideo, - ); - closePlusMenu(); - }} - > - - - )} - - ); - } - - contextMenu = ( - - - {inviteOption} - {newRoomOptions} - } - onClick={(e) => { - e.preventDefault(); - e.stopPropagation(); - defaultDispatcher.dispatch({ - action: Action.ViewRoom, - room_id: activeSpace.roomId, - metricsTrigger: undefined, // other - }); - closePlusMenu(); - PosthogTrackers.trackInteraction("WebRoomListHeaderPlusMenuExploreRoomsItem", e); - }} - /> - } - onClick={(e) => { - e.preventDefault(); - e.stopPropagation(); - showAddExistingRooms(activeSpace); - closePlusMenu(); - }} - disabled={!canAddSubRooms} - title={!canAddSubRooms ? _t("spaces|error_no_permission_add_room") : undefined} - /> - {canCreateSpaces && ( - } - onClick={(e) => { - 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 = ( - <> - } - onClick={(e) => { - e.preventDefault(); - e.stopPropagation(); - defaultDispatcher.dispatch({ action: Action.CreateChat }); - PosthogTrackers.trackInteraction("WebRoomListHeaderPlusMenuCreateChatItem", e); - closePlusMenu(); - }} - /> - } - onClick={(e) => { - e.preventDefault(); - e.stopPropagation(); - defaultDispatcher.dispatch({ action: Action.CreateRoom }); - PosthogTrackers.trackInteraction("WebRoomListHeaderPlusMenuCreateRoomItem", e); - closePlusMenu(); - }} - /> - {videoRoomsEnabled && ( - } - onClick={(e) => { - e.preventDefault(); - e.stopPropagation(); - defaultDispatcher.dispatch({ - action: Action.CreateRoom, - type: elementCallVideoRoomsEnabled ? RoomType.UnstableCall : RoomType.ElementVideo, - }); - closePlusMenu(); - }} - > - - - )} - - ); - } - if (canExploreRooms) { - joinRoomOpt = ( - } - onClick={(e) => { - 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} {mainMenuDisplayed ? : } - - ), - }; - - if (!!activeSpace) { - contextMenuButton = ( - - ); - } else { - contextMenuButton = ; - } - } - - return ( - - ); -}; - -export default LegacyRoomListHeader; diff --git a/apps/web/src/components/views/rooms/RoomBreadcrumbs.tsx b/apps/web/src/components/views/rooms/RoomBreadcrumbs.tsx deleted file mode 100644 index c677b32edca..00000000000 --- a/apps/web/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/apps/web/src/components/views/rooms/RoomSublist.tsx b/apps/web/src/components/views/rooms/RoomSublist.tsx deleted file mode 100644 index 4051bc18a4c..00000000000 --- a/apps/web/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 { - ChevronDownIcon, - ChevronRightIcon, - ChevronUpIcon, - OverflowHorizontalIcon, -} from "@vector-im/compound-design-tokens/assets/web/icons"; - -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-v3/skip-list/tag"; -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 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 ( -
-
-
- - - {collapsed ? : } - - {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(this: void, 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 = ( - - - {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 = ( - - - {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/apps/web/src/components/views/rooms/RoomTile.tsx b/apps/web/src/components/views/rooms/RoomTile.tsx deleted file mode 100644 index d3230a2efd4..00000000000 --- a/apps/web/src/components/views/rooms/RoomTile.tsx +++ /dev/null @@ -1,481 +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 { OverflowHorizontalIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; - -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-v3/skip-list/tag"; -import { type MessagePreview, MessagePreviewStore } from "../../../stores/message-preview"; -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/SDKContextClass"; -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"; -import { getNotificationIcon } from "../dialogs/spotlight/RoomResultContextMenus.tsx"; - -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", { - // 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 ( - - - {getNotificationIcon(state!)} - - {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/apps/web/src/components/views/rooms/RoomTileCallSummary.tsx b/apps/web/src/components/views/rooms/RoomTileCallSummary.tsx deleted file mode 100644 index 32e3b609e50..00000000000 --- a/apps/web/src/components/views/rooms/RoomTileCallSummary.tsx +++ /dev/null @@ -1,38 +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 } 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/apps/web/src/components/views/rooms/RoomTileSubtitle.tsx b/apps/web/src/components/views/rooms/RoomTileSubtitle.tsx deleted file mode 100644 index 6e11c91e0e2..00000000000 --- a/apps/web/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/message-preview"; -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/apps/web/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx b/apps/web/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx index 976e1ec2e6d..563045274af 100644 --- a/apps/web/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx +++ b/apps/web/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx @@ -117,8 +117,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"]; @@ -241,7 +239,6 @@ export default class PreferencesUserSettingsTab extends React.Component { @@ -274,11 +271,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/apps/web/src/components/views/settings/tabs/user/SidebarUserSettingsTab.tsx b/apps/web/src/components/views/settings/tabs/user/SidebarUserSettingsTab.tsx index 041e437661e..1b494b885f3 100644 --- a/apps/web/src/components/views/settings/tabs/user/SidebarUserSettingsTab.tsx +++ b/apps/web/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/apps/web/src/components/views/spaces/SpacePanel.tsx b/apps/web/src/components/views/spaces/SpacePanel.tsx index 58ec07cd988..20754a429be 100644 --- a/apps/web/src/components/views/spaces/SpacePanel.tsx +++ b/apps/web/src/components/views/spaces/SpacePanel.tsx @@ -405,8 +405,6 @@ const SpacePanel: React.FC = () => { } }); - const newRoomListEnabled = useSettingValue("feature_new_room_list"); - const userMenuVm = useCreateAutoDisposedViewModel( () => new UserMenuViewModel( @@ -444,7 +442,6 @@ const SpacePanel: React.FC = () => {