Pop out of Threads Activity Centre (#12136)

* Add `Thread Activity centre` labs flag

* Rename translation string

* WIP Thread Activity Centre

* Update supportedLevels

* css lint

* i18n lint

* Fix labs subsection test

* Update Threads Activity Centre label

* Rename Thread Activity Centre to Threads Activity Centre

* Use compound `MenuItem` instead of custom button

* Color thread icon when hovered

* Make the pop-up scrollable and add a max height

* Remove Math.random in key

* Remove unused class

* Change add comments on `mx_ThreadsActivityRows` and `mx_ThreadsActivityRow`

* Make threads activity centre labs flag split out unread counts

Just shows notif & unread counts for main thread if the TAC is enabled.

* Fix tests

* Simpler fix

* Open thread panel when thread clicke in Threads Activity Centre

Hopefully this is a sensible enough way. The panel will stay open of
course (ie. if you go to a different room & come back), but that's the
nature of the right panel.

* Dynamic state of room

* Add doc

* Use the StatelessNotificationBadge component in ThreadsActivityCentre

and re-use the existing NotificationLevel

* Remove unused style

* Add room sorting

* Fix `ThreadsActivityRow` props doc

* Pass in & cache the status of the TAC labs flag

* Pass includeThreads as setting to doesRoomHaveUnreadMessages too

* Fix tests

* Add analytics to the TAC (#12179)

* Update TAC label (#12186)

* Add `IndicatorIcon` to the TAC button (#12182)

Add `IndicatorIcon` to the TAC button

* Threads don't have activity if the room is muted

This makes it match the computation in determineUnreadState.
Ideally this logic should all be in one place.

* Re-use doesRoomHaveUnreadThreads for useRoomThreadNotifications

This incorporates the logic of not showing unread dots if the room
is muted

* Add TAC description in labs (#12197)

* Fox position & size of dot on the tac button

IndicatorIcon doesn't like having the size of its icon adjusted and
we probably shouldn't do it anyway: better to specify to the component
what size we want it.

* TAC: Utils tests (#12200)

* Add tests for `doesRoomHaveUnreadThreads`
* Add tests for `getThreadNotificationLevel`

* Add test for the ThreadsActivityCentre component

* Add snapshot test

* Fix narrow hover background on TAC button

Make the button 32x32 (and the inner icon 24x24)

* Add caption for empty TAC

* s/tac/threads_activity_centre/

* Fix i18n & add tests

* Add playwright tests for the TAC (#12227)

* Fox comments

---------

Co-authored-by: David Baker <dbkr@users.noreply.github.com>
This commit is contained in:
Florian Duros 2024-02-07 14:49:40 +01:00 committed by GitHub
parent 3052025dd0
commit a4987060b7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 1455 additions and 14 deletions

View file

@ -0,0 +1,104 @@
/*
*
* Copyright 2024 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 { useEffect, useState } from "react";
import { ClientEvent, MatrixClient, Room } from "matrix-js-sdk/src/matrix";
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 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(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 result;
}
/**
* 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 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 };
}
/**
* Sort notification level by the most important notification level to the least important
* Highlight > Notification > Activity
* @param notificationLevelA - notification level of room A
* @param notificationLevelB - notification level of room B
* @returns {number}
*/
function sortRoom(notificationLevelA: NotificationLevel, notificationLevelB: NotificationLevel): number {
// NotificationLevel is a numeric enum, so we can compare them directly
if (notificationLevelA > notificationLevelB) return -1;
else if (notificationLevelB > notificationLevelA) return 1;
else return 0;
}