Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Add IndicatorIcon to the TAC button #12182

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions res/css/structures/_ThreadsActivityCentre.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -29,24 +29,29 @@
/* align with settings icon */
margin-left: 20px;

.mx_ThreadsActivityCentreButton_Icon {
& > .mx_ThreadsActivityCentreButton_IndicatorIcon {
/* align with settings label */
margin-right: 12px;
}
}

&:not(.expanded) {
&:hover {
&:hover,
&:hover .mx_ThreadsActivityCentreButton_Icon {
background-color: $quaternary-content;
color: $primary-content;
}
}

.mx_ThreadsActivityCentreButton_Icon {
& > .mx_ThreadsActivityCentreButton_IndicatorIcon {
height: 24px;
width: 24px;
padding: 4px;
}

& .mx_ThreadsActivityCentreButton_Icon {
color: $secondary-content;
}
}

.mx_ThreadsActivity_rows {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,16 @@ export function ThreadsActivityCentre({ displayButtonLabel }: ThreadsActivityCen
}}
side="right"
title={_t("threads_activity_centre|header")}
trigger={<ThreadsActivityCentreButton displayLabel={displayButtonLabel} />}
trigger={
<ThreadsActivityCentreButton
displayLabel={displayButtonLabel}
notificationLevel={roomsAndNotifications.greatestNotificationLevel}
/>
}
>
{/* Make the content of the pop-up scrollable */}
<div className="mx_ThreadsActivity_rows">
{roomsAndNotifications.map(({ room, notificationLevel }) => (
{roomsAndNotifications.rooms.map(({ room, notificationLevel }) => (
<ThreadsActivityRow
key={room.roomId}
room={room}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,29 @@
import React, { forwardRef, HTMLProps } from "react";
import { Icon } from "@vector-im/compound-design-tokens/icons/threads-solid.svg";
import classNames from "classnames";
import { IndicatorIcon } from "@vector-im/compound-web";

import { _t } from "../../../../languageHandler";
import AccessibleTooltipButton from "../../elements/AccessibleTooltipButton";
import { NotificationLevel } from "../../../../stores/notifications/NotificationLevel";
import { notificationLevelToIndicator } from "../../../../utils/notifications";

interface ThreadsActivityCentreButtonProps extends HTMLProps<HTMLDivElement> {
/**
* Display the `Treads` label next to the icon.
*/
displayLabel?: boolean;
/**
* The notification level of the threads.
*/
notificationLevel: NotificationLevel;
}

/**
* A button to open the thread activity centre.
*/
export const ThreadsActivityCentreButton = forwardRef<HTMLDivElement, ThreadsActivityCentreButtonProps>(
function ThreadsActivityCentreButton({ displayLabel, ...props }, ref): React.JSX.Element {
function ThreadsActivityCentreButton({ displayLabel, notificationLevel, ...props }, ref): React.JSX.Element {
return (
<AccessibleTooltipButton
className={classNames("mx_ThreadsActivityCentreButton", { expanded: displayLabel })}
Expand All @@ -46,7 +53,12 @@ export const ThreadsActivityCentreButton = forwardRef<HTMLDivElement, ThreadsAct
aria-expanded={displayLabel}
{...props}
>
<Icon className="mx_ThreadsActivityCentreButton_Icon" />
<IndicatorIcon
className="mx_ThreadsActivityCentreButton_IndicatorIcon"
indicator={notificationLevelToIndicator(notificationLevel)}
>
<Icon className="mx_ThreadsActivityCentreButton_Icon" />
</IndicatorIcon>
{displayLabel && _t("common|threads")}
</AccessibleTooltipButton>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,49 +16,77 @@
* /
*/

import { useMemo } from "react";
import { NotificationCountType, Room } from "matrix-js-sdk/src/matrix";
import { useEffect, useState } from "react";
import { ClientEvent, MatrixClient, Room } from "matrix-js-sdk/src/matrix";

import RoomListStore from "../../../../stores/room-list/RoomListStore";
import { doesRoomHaveUnreadThreads } from "../../../../Unread";
import { NotificationLevel } from "../../../../stores/notifications/NotificationLevel";
import { getThreadNotificationLevel } from "../../../../utils/notifications";
import { useSettingValue } from "../../../../hooks/useSettings";
import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext";
import { useEventEmitter } from "../../../../hooks/useEventEmitter";
import { VisibilityProvider } from "../../../../stores/room-list/filters/VisibilityProvider";

type Result = {
greatestNotificationLevel: NotificationLevel;
rooms: Array<{ room: Room; notificationLevel: NotificationLevel }>;
};

/**
* Return the list of rooms with unread threads, and their notification level.
* The list is computed when open is true
* @param open
* @returns {Array<{ room: Room; notificationLevel: NotificationLevel }>}
* Return the greatest notification level of all thread, the list of rooms with unread threads, and their notification level.
* The result is computed when the client syncs, or when forceComputation is true
* @param forceComputation
* @returns {Result}
*/
export function useUnreadThreadRooms(open: boolean): Array<{ room: Room; notificationLevel: NotificationLevel }> {
return useMemo(() => {
if (!open) return [];
export function useUnreadThreadRooms(forceComputation: boolean): Result {
const msc3946ProcessDynamicPredecessor = useSettingValue<boolean>("feature_dynamic_room_predecessors");
const mxClient = useMatrixClientContext();

const [result, setResult] = useState<Result>({ greatestNotificationLevel: NotificationLevel.None, rooms: [] });

// Listen to sync events to update the result
useEventEmitter(mxClient, ClientEvent.Sync, () => {
setResult(computeUnreadThreadRooms(mxClient, msc3946ProcessDynamicPredecessor));
});

// Force the list computation
useEffect(() => {
if (forceComputation) {
setResult(computeUnreadThreadRooms(mxClient, msc3946ProcessDynamicPredecessor));
}
}, [mxClient, msc3946ProcessDynamicPredecessor, forceComputation]);

return Object.values(RoomListStore.instance.orderedLists)
.reduce((acc, rooms) => {
acc.push(...rooms);
return acc;
}, [])
.filter((room) => doesRoomHaveUnreadThreads(room))
.map((room) => ({ room, notificationLevel: getNotificationLevel(room) }))
.sort((a, b) => sortRoom(a.notificationLevel, b.notificationLevel));
}, [open]);
return result;
}

/**
* Return the notification level for a room
* @param room
* @returns {NotificationLevel}
* Compute the greatest notification level of all thread, the list of rooms with unread threads, and their notification level.
* @param mxClient - MatrixClient
* @param msc3946ProcessDynamicPredecessor
*/
function getNotificationLevel(room: Room): NotificationLevel {
const notificationCountType = room.threadsAggregateNotificationType;
switch (notificationCountType) {
case NotificationCountType.Highlight:
return NotificationLevel.Highlight;
case NotificationCountType.Total:
return NotificationLevel.Notification;
default:
return NotificationLevel.Activity;
function computeUnreadThreadRooms(mxClient: MatrixClient, msc3946ProcessDynamicPredecessor: boolean): Result {
// Only count visible rooms to not torment the user with notification counts in rooms they can't see.
// This will include highlights from the previous version of the room internally
const visibleRooms = mxClient.getVisibleRooms(msc3946ProcessDynamicPredecessor);

let greatestNotificationLevel = NotificationLevel.None;
const rooms = [];

for (const room of visibleRooms) {
// We only care about rooms with unread threads
if (VisibilityProvider.instance.isRoomVisible(room) && doesRoomHaveUnreadThreads(room)) {
// Get the greatest notification level of all rooms
const notificationLevel = getThreadNotificationLevel(room);
if (notificationLevel > greatestNotificationLevel) {
greatestNotificationLevel = notificationLevel;
}

rooms.push({ room, notificationLevel });
}
}

const sortedRooms = rooms.sort((a, b) => sortRoom(a.notificationLevel, b.notificationLevel));
return { greatestNotificationLevel, rooms: sortedRooms };
}

/**
Expand Down
17 changes: 17 additions & 0 deletions src/utils/notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,20 @@ export function notificationLevelToIndicator(
return "critical";
}
}

/**
* Return the thread notification level for a room
* @param room
* @returns {NotificationLevel}
*/
export function getThreadNotificationLevel(room: Room): NotificationLevel {
const notificationCountType = room.threadsAggregateNotificationType;
switch (notificationCountType) {
case NotificationCountType.Highlight:
return NotificationLevel.Highlight;
case NotificationCountType.Total:
return NotificationLevel.Notification;
default:
return NotificationLevel.Activity;
}
}
Loading