diff --git a/cypress/e2e/right-panel/notification-panel.spec.ts b/cypress/e2e/right-panel/notification-panel.spec.ts index 4068285070b..d08d88fd003 100644 --- a/cypress/e2e/right-panel/notification-panel.spec.ts +++ b/cypress/e2e/right-panel/notification-panel.spec.ts @@ -38,6 +38,7 @@ describe("NotificationPanel", () => { }); it("should render empty state", () => { + cy.enableLabsFeature("feature_notifications_panel"); cy.viewRoomByName(ROOM_NAME); cy.findByRole("button", { name: "Notifications" }).click(); diff --git a/cypress/e2e/room/room-header.spec.ts b/cypress/e2e/room/room-header.spec.ts index 835fb2bb3e3..0a6b6b3d048 100644 --- a/cypress/e2e/room/room-header.spec.ts +++ b/cypress/e2e/room/room-header.spec.ts @@ -46,7 +46,6 @@ describe("Room Header", () => { "Video call", "Search", "Threads", - "Notifications", "Room info", ]; @@ -56,7 +55,7 @@ describe("Room Header", () => { } // Assert that just those seven buttons exist on mx_LegacyRoomHeader by default - cy.findAllByRole("button").should("have.length", 7); + cy.findAllByRole("button").should("have.length", expectedButtonNames.length); }); cy.get(".mx_LegacyRoomHeader").percySnapshotElement("Room header"); @@ -97,7 +96,7 @@ describe("Room Header", () => { // Assert the size of buttons on RoomHeader are specified and the buttons are not compressed // Note these assertions do not check the size of mx_LegacyRoomHeader_name button cy.get(".mx_LegacyRoomHeader_button") - .should("have.length", 6) + .should("have.length", 5) .should("be.visible") .should("have.css", "height", "32px") .should("have.css", "width", "32px"); @@ -108,21 +107,6 @@ describe("Room Header", () => { }); }); - it("should have buttons highlighted by being clicked", () => { - cy.createRoom({ name: "Test Room" }).viewRoomByName("Test Room"); - - cy.get(".mx_LegacyRoomHeader").within(() => { - // Check these buttons - const buttonsHighlighted = ["Threads", "Notifications", "Room info"]; - - for (const name of buttonsHighlighted) { - cy.findByRole("button", { name: name }).click(); // Highlight the button - } - }); - - cy.get(".mx_LegacyRoomHeader").percySnapshotElement("Room header - with a highlighted button"); - }); - describe("with a video room", () => { const createVideoRoom = () => { // Enable video rooms. This command reloads the app @@ -160,7 +144,7 @@ describe("Room Header", () => { } // Assert that there is not a button except those buttons - cy.findAllByRole("button").should("have.length", 7); + cy.findAllByRole("button").should("have.length", 6); }); cy.get(".mx_LegacyRoomHeader").percySnapshotElement("Room header - with a video room"); diff --git a/res/css/structures/_QuickSettingsButton.pcss b/res/css/structures/_QuickSettingsButton.pcss index 631b098ad64..7c5884ca725 100644 --- a/res/css/structures/_QuickSettingsButton.pcss +++ b/res/css/structures/_QuickSettingsButton.pcss @@ -15,15 +15,6 @@ limitations under the License. */ .mx_QuickSettingsButton { - flex: 0 0 auto; - border-radius: 8px; - position: relative; - margin: 12px auto; - color: $secondary-content; - min-width: 32px; - min-height: 32px; - line-height: 32px; - &.expanded { margin-left: 20px; padding-left: 44px; /* align with toggle collapse button text */ @@ -32,9 +23,9 @@ limitations under the License. &::before { content: ""; - position: absolute; - width: 32px; - height: 32px; + display: block; + width: 100%; + height: 100%; left: 0; mask-image: url("$(res)/img/element-icons/settings.svg"); mask-repeat: no-repeat; diff --git a/res/css/structures/_RightPanel.pcss b/res/css/structures/_RightPanel.pcss index 7d39968c11a..68f101ab56e 100644 --- a/res/css/structures/_RightPanel.pcss +++ b/res/css/structures/_RightPanel.pcss @@ -38,11 +38,6 @@ limitations under the License. mask-image: url("$(res)/img/element-icons/room/thread.svg"); } -.mx_RightPanel_notifsButton::before { - mask-image: url("$(res)/img/element-icons/notifications.svg"); - mask-position: center; -} - .mx_RightPanel_roomSummaryButton::before { mask-image: url("$(res)/img/element-icons/room/room-summary.svg"); mask-position: center; diff --git a/res/css/structures/_SpacePanel.pcss b/res/css/structures/_SpacePanel.pcss index 76c328fa568..c4aa0febef0 100644 --- a/res/css/structures/_SpacePanel.pcss +++ b/res/css/structures/_SpacePanel.pcss @@ -24,7 +24,7 @@ limitations under the License. background-color: $spacePanel-bg-color; flex: 0 0 auto; - padding: 0; + padding: 0 0 12px 0; margin: 0; position: relative; /* Fix for the blurred avatar-background */ @@ -283,7 +283,6 @@ limitations under the License. } .mx_SpaceTreeLevel { - // Indent subspaces padding-left: 16px; } } @@ -439,3 +438,28 @@ limitations under the License. .mx_SpacePanel_sharePublicSpace { margin: 0; } + +.mx_SpacePanel_button { + flex: 0 0 auto; + border-radius: 8px; + position: relative; + margin: 0 auto; + color: $secondary-content; + width: 20px; + height: 20px; + line-height: 20px; + padding: 12px; + + &:not(.expanded):hover { + color: $quaternary-content; + background-color: $quaternary-content; + + &::before { + background-color: $primary-content; + } + + svg { + color: $primary-content; + } + } +} diff --git a/res/img/element-icons/notifications.svg b/res/img/element-icons/notifications.svg index 7002782129f..7709c673f11 100644 --- a/res/img/element-icons/notifications.svg +++ b/res/img/element-icons/notifications.svg @@ -1,5 +1,5 @@ - - - + + + diff --git a/src/components/views/right_panel/LegacyRoomHeaderButtons.tsx b/src/components/views/right_panel/LegacyRoomHeaderButtons.tsx index fa9afb1aa07..68957f25e69 100644 --- a/src/components/views/right_panel/LegacyRoomHeaderButtons.tsx +++ b/src/components/views/right_panel/LegacyRoomHeaderButtons.tsx @@ -33,12 +33,8 @@ import RightPanelStore from "../../../stores/right-panel/RightPanelStore"; import { useReadPinnedEvents, usePinnedEvents } from "./PinnedMessagesCard"; import { showThreadPanel } from "../../../dispatcher/dispatch-actions/threads"; import SettingsStore from "../../../settings/SettingsStore"; -import { - RoomNotificationStateStore, - UPDATE_STATUS_INDICATOR, -} from "../../../stores/notifications/RoomNotificationStateStore"; +import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore"; import { NotificationColor } from "../../../stores/notifications/NotificationColor"; -import { SummarizedNotificationState } from "../../../stores/notifications/SummarizedNotificationState"; import PosthogTrackers from "../../../PosthogTrackers"; import { ButtonEvent } from "../elements/AccessibleButton"; import { doesRoomOrThreadHaveUnreadMessages } from "../../../Unread"; @@ -57,7 +53,7 @@ interface IUnreadIndicatorProps { color?: NotificationColor; } -const UnreadIndicator: React.FC = ({ color }) => { +export const UnreadIndicator: React.FC = ({ color }) => { if (color === NotificationColor.None) { return null; } @@ -132,11 +128,9 @@ interface IProps { */ export default class LegacyRoomHeaderButtons extends HeaderButtons { private static readonly THREAD_PHASES = [RightPanelPhases.ThreadPanel, RightPanelPhases.ThreadView]; - private globalNotificationState: SummarizedNotificationState; public constructor(props: IProps) { super(props, HeaderKind.Room); - this.globalNotificationState = RoomNotificationStateStore.instance.globalState; } public componentDidMount(): void { @@ -153,7 +147,6 @@ export default class LegacyRoomHeaderButtons extends HeaderButtons { this.props.room?.on(ThreadEvent.New, this.onNotificationUpdate); this.props.room?.on(ThreadEvent.Update, this.onNotificationUpdate); this.onNotificationUpdate(); - RoomNotificationStateStore.instance.on(UPDATE_STATUS_INDICATOR, this.onUpdateStatus); } public componentWillUnmount(): void { @@ -166,7 +159,6 @@ export default class LegacyRoomHeaderButtons extends HeaderButtons { this.props.room?.off(RoomEvent.MyMembership, this.onNotificationUpdate); this.props.room?.off(ThreadEvent.New, this.onNotificationUpdate); this.props.room?.off(ThreadEvent.Update, this.onNotificationUpdate); - RoomNotificationStateStore.instance.off(UPDATE_STATUS_INDICATOR, this.onUpdateStatus); } private onNotificationUpdate = (): void => { @@ -196,14 +188,6 @@ export default class LegacyRoomHeaderButtons extends HeaderButtons { return NotificationColor.None; } - private onUpdateStatus = (notificationState: SummarizedNotificationState): void => { - // XXX: why don't we read from this.state.globalNotificationCount in the render methods? - this.globalNotificationState = notificationState; - this.setState({ - globalNotificationColor: notificationState.color, - }); - }; - protected onAction(payload: ActionPayload): void { if (payload.action === Action.ViewUser) { if (payload.member) { @@ -246,11 +230,6 @@ export default class LegacyRoomHeaderButtons extends HeaderButtons { } }; - private onNotificationsClicked = (): void => { - // This toggles for us, if needed - this.setPhase(RightPanelPhases.NotificationPanel); - }; - private onPinnedMessagesClicked = (): void => { // This toggles for us, if needed this.setPhase(RightPanelPhases.PinnedMessages); @@ -309,21 +288,6 @@ export default class LegacyRoomHeaderButtons extends HeaderButtons { , ); - rightPanelPhaseButtons.set( - RightPanelPhases.NotificationPanel, - - {this.globalNotificationState.color === NotificationColor.Red ? ( - - ) : null} - , - ); rightPanelPhaseButtons.set( RightPanelPhases.RoomSummary, { + const summarizedNotificationState = useGlobalNotificationState(); + return ( + +
{ + RightPanelStore.instance.setCard({ phase: RightPanelPhases.NotificationPanel }); + }} + role="button" + aria-label={_t("Notifications")} + > + + {summarizedNotificationState.color === NotificationColor.Red ? ( + + ) : null} +
+
+ ); +}; diff --git a/src/components/views/spaces/QuickSettingsButton.tsx b/src/components/views/spaces/QuickSettingsButton.tsx index 3966b04c7d0..bb4d4ef5fef 100644 --- a/src/components/views/spaces/QuickSettingsButton.tsx +++ b/src/components/views/spaces/QuickSettingsButton.tsx @@ -39,7 +39,8 @@ import { SdkContextClass } from "../../../contexts/SDKContext"; const QuickSettingsButton: React.FC<{ isPanelCollapsed: boolean; -}> = ({ isPanelCollapsed = false }) => { + className?: string; +}> = ({ isPanelCollapsed = false, className = "" }) => { const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu(); const { [MetaSpace.Favourites]: favouritesEnabled, [MetaSpace.People]: peopleEnabled } = @@ -131,7 +132,7 @@ const QuickSettingsButton: React.FC<{ return ( <> { const invites = useEventEmitterState(SpaceStore.instance, UPDATE_INVITED_SPACES, () => { @@ -344,6 +345,8 @@ const SpacePanel: React.FC = () => { } }); + const isNotifPanelEnabled = useFeatureEnabled("feature_notifications_panel"); + return ( {({ onKeyDownHandler, onDragEndHandler }) => ( @@ -399,7 +402,8 @@ const SpacePanel: React.FC = () => { )} - + {isNotifPanelEnabled && } + )} diff --git a/src/hooks/useGlobalNotification.ts b/src/hooks/useGlobalNotification.ts new file mode 100644 index 00000000000..ba625328658 --- /dev/null +++ b/src/hooks/useGlobalNotification.ts @@ -0,0 +1,40 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { useState } from "react"; + +import { SummarizedNotificationState } from "../stores/notifications/SummarizedNotificationState"; +import { + RoomNotificationStateStore, + UPDATE_STATUS_INDICATOR, +} from "../stores/notifications/RoomNotificationStateStore"; +import { useEventEmitter } from "./useEventEmitter"; + +export const useGlobalNotificationState = (): SummarizedNotificationState => { + const [summarizedNotificationState, setSummarizedNotificationState] = useState( + RoomNotificationStateStore.instance.globalState, + ); + + useEventEmitter( + RoomNotificationStateStore.instance, + UPDATE_STATUS_INDICATOR, + (notificationState: SummarizedNotificationState) => { + setSummarizedNotificationState(notificationState); + }, + ); + + return summarizedNotificationState; +}; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 2b8464e0923..e22d94c97f9 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -963,6 +963,8 @@ "Can I use text chat alongside the video call?": "Can I use text chat alongside the video call?", "Yes, the chat timeline is displayed alongside the video.": "Yes, the chat timeline is displayed alongside the video.", "Thank you for trying the beta, please go into as much detail as you can so we can improve it.": "Thank you for trying the beta, please go into as much detail as you can so we can improve it.", + "Notifications panel": "Notifications panel", + "A list of all events notifying you. Only works with unencrypted rooms": "A list of all events notifying you. Only works with unencrypted rooms", "New Notification Settings": "New Notification Settings", "Notification Settings": "Notification Settings", "Introducing a simpler way to change your notification settings. Customize your %(brand)s, just the way you like.": "Introducing a simpler way to change your notification settings. Customize your %(brand)s, just the way you like.", diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index b3af440b1a9..3bc32af0d7f 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -230,6 +230,18 @@ export const SETTINGS: { [setting: string]: ISetting } = { requiresRefresh: true, }, }, + "feature_notifications_panel": { + isFeature: true, + labsGroup: LabGroup.Messaging, + displayName: _td("Notifications panel"), + supportedLevels: LEVELS_FEATURE, + default: false, + // Reload to ensure that the left panel etc. get remounted + betaInfo: { + title: _td("Notifications panel"), + caption: () =>

{_t("A list of all events notifying you. Only works with unencrypted rooms")}

, + }, + }, [Features.NotificationSettings2]: { isFeature: true, labsGroup: LabGroup.Experimental, diff --git a/test/components/views/right_panel/__snapshots__/LegacyRoomHeaderButtons-test.tsx.snap b/test/components/views/right_panel/__snapshots__/LegacyRoomHeaderButtons-test.tsx.snap index bd706048be2..6dbe9ed2e3a 100644 --- a/test/components/views/right_panel/__snapshots__/LegacyRoomHeaderButtons-test.tsx.snap +++ b/test/components/views/right_panel/__snapshots__/LegacyRoomHeaderButtons-test.tsx.snap @@ -17,13 +17,6 @@ exports[`LegacyRoomHeaderButtons-test.tsx should render 1`] = ` role="button" tabindex="0" /> -
renders settings marked as beta as beta cards 1
+
+
+
+

+ + Notifications panel + + + Beta + +

+
+

+ A list of all events notifying you. Only works with unencrypted rooms +

+
+
+
+ Join the beta +
+
+
+
+ +
+
+