mirror of
https://github.com/element-hq/element-web
synced 2024-11-22 09:15:41 +03:00
Add activity toggle for TAC (#12413)
* Add activity toggle for TAC * Update test snapshots for new toggles * Add test for TAC activity setting set to false * Update snapshot for old notifications panel test too * Fix test * Rename setting * Rename variables too * Sort i18n keys * Use functional component
This commit is contained in:
parent
aadb46358b
commit
14cc44e820
8 changed files with 262 additions and 46 deletions
|
@ -58,6 +58,7 @@ import { Caption } from "../typography/Caption";
|
|||
import { SettingsSubsectionHeading } from "./shared/SettingsSubsectionHeading";
|
||||
import SettingsSubsection from "./shared/SettingsSubsection";
|
||||
import { doesRoomHaveUnreadMessages } from "../../../Unread";
|
||||
import SettingsFlag from "../elements/SettingsFlag";
|
||||
|
||||
// TODO: this "view" component still has far too much application logic in it,
|
||||
// which should be factored out to other files.
|
||||
|
@ -200,6 +201,18 @@ const maximumVectorState = (
|
|||
return vectorState;
|
||||
};
|
||||
|
||||
const NotificationActivitySettings = (): JSX.Element => {
|
||||
return (
|
||||
<div>
|
||||
<SettingsFlag name="Notifications.showbold" level={SettingLevel.DEVICE} />
|
||||
<SettingsFlag name="Notifications.tac_only_notifications" level={SettingLevel.DEVICE} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* The old, deprecated notifications tab view, only displayed if the user has the labs flag disabled.
|
||||
*/
|
||||
export default class Notifications extends React.PureComponent<IProps, IState> {
|
||||
private settingWatchers: string[];
|
||||
|
||||
|
@ -731,43 +744,10 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
|
|||
}
|
||||
|
||||
private renderCategory(category: RuleClass): ReactNode {
|
||||
if (category !== RuleClass.VectorOther && this.isInhibited) {
|
||||
if (this.isInhibited) {
|
||||
return null; // nothing to show for the section
|
||||
}
|
||||
|
||||
let clearNotifsButton: JSX.Element | undefined;
|
||||
if (
|
||||
category === RuleClass.VectorOther &&
|
||||
MatrixClientPeg.safeGet()
|
||||
.getRooms()
|
||||
.some((r) => doesRoomHaveUnreadMessages(r, true))
|
||||
) {
|
||||
clearNotifsButton = (
|
||||
<AccessibleButton
|
||||
onClick={this.onClearNotificationsClicked}
|
||||
disabled={this.state.clearingNotifications}
|
||||
kind="danger"
|
||||
className="mx_UserNotifSettings_clearNotifsButton"
|
||||
data-testid="clear-notifications"
|
||||
>
|
||||
{_t("notifications|mark_all_read")}
|
||||
</AccessibleButton>
|
||||
);
|
||||
}
|
||||
|
||||
if (category === RuleClass.VectorOther && this.isInhibited) {
|
||||
// only render the utility buttons (if needed)
|
||||
if (clearNotifsButton) {
|
||||
return (
|
||||
<div className="mx_UserNotifSettings_floatingSection">
|
||||
<div>{_t("notifications|class_other")}</div>
|
||||
{clearNotifsButton}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
let keywordComposer: JSX.Element | undefined;
|
||||
if (category === RuleClass.VectorMentions) {
|
||||
const tags = filterBoolean<string>(this.state.vectorKeywordRuleInfo?.rules.map((r) => r.pattern) || []);
|
||||
|
@ -842,7 +822,6 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
|
|||
<span className="mx_UserNotifSettings_gridColumnLabel">{VectorStateToLabel[VectorState.Loud]}</span>
|
||||
{fieldsetRows}
|
||||
</div>
|
||||
{clearNotifsButton}
|
||||
{keywordComposer}
|
||||
</div>
|
||||
);
|
||||
|
@ -878,6 +857,25 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
|
|||
return <p data-testid="error-message">{_t("settings|notifications|error_loading")}</p>;
|
||||
}
|
||||
|
||||
let clearNotifsButton: JSX.Element | undefined;
|
||||
if (
|
||||
MatrixClientPeg.safeGet()
|
||||
.getRooms()
|
||||
.some((r) => doesRoomHaveUnreadMessages(r, true))
|
||||
) {
|
||||
clearNotifsButton = (
|
||||
<AccessibleButton
|
||||
onClick={this.onClearNotificationsClicked}
|
||||
disabled={this.state.clearingNotifications}
|
||||
kind="danger"
|
||||
className="mx_UserNotifSettings_clearNotifsButton"
|
||||
data-testid="clear-notifications"
|
||||
>
|
||||
{_t("notifications|mark_all_read")}
|
||||
</AccessibleButton>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{this.renderTopSection()}
|
||||
|
@ -885,6 +883,8 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
|
|||
{this.renderCategory(RuleClass.VectorMentions)}
|
||||
{this.renderCategory(RuleClass.VectorOther)}
|
||||
{this.renderTargets()}
|
||||
<NotificationActivitySettings />
|
||||
{clearNotifsButton}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ import { SettingsBanner } from "../shared/SettingsBanner";
|
|||
import { SettingsSection } from "../shared/SettingsSection";
|
||||
import SettingsSubsection from "../shared/SettingsSubsection";
|
||||
import { NotificationPusherSettings } from "./NotificationPusherSettings";
|
||||
import SettingsFlag from "../../elements/SettingsFlag";
|
||||
|
||||
enum NotificationDefaultLevels {
|
||||
AllMessages = "all_messages",
|
||||
|
@ -71,6 +72,9 @@ function useHasUnreadNotifications(): boolean {
|
|||
return cli.getRooms().some((room) => room.getUnreadNotificationCount() > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* The new notification settings tab view, only displayed if the user has Features.NotificationSettings2 enabled
|
||||
*/
|
||||
export default function NotificationSettings2(): JSX.Element {
|
||||
const cli = useMatrixClientContext();
|
||||
|
||||
|
@ -352,6 +356,9 @@ export default function NotificationSettings2(): JSX.Element {
|
|||
label={_t("notifications|keyword")}
|
||||
placeholder={_t("notifications|keyword_new")}
|
||||
/>
|
||||
|
||||
<SettingsFlag name="Notifications.showbold" level={SettingLevel.DEVICE} />
|
||||
<SettingsFlag name="Notifications.tac_only_notifications" level={SettingLevel.DEVICE} />
|
||||
</SettingsSubsection>
|
||||
<NotificationPusherSettings />
|
||||
<SettingsSubsection heading={_t("settings|notifications|quick_actions_section")}>
|
||||
|
|
|
@ -43,13 +43,14 @@ type Result = {
|
|||
*/
|
||||
export function useUnreadThreadRooms(forceComputation: boolean): Result {
|
||||
const msc3946ProcessDynamicPredecessor = useSettingValue<boolean>("feature_dynamic_room_predecessors");
|
||||
const settingTACOnlyNotifs = useSettingValue<boolean>("Notifications.tac_only_notifications");
|
||||
const mxClient = useMatrixClientContext();
|
||||
|
||||
const [result, setResult] = useState<Result>({ greatestNotificationLevel: NotificationLevel.None, rooms: [] });
|
||||
|
||||
const doUpdate = useCallback(() => {
|
||||
setResult(computeUnreadThreadRooms(mxClient, msc3946ProcessDynamicPredecessor));
|
||||
}, [mxClient, msc3946ProcessDynamicPredecessor]);
|
||||
setResult(computeUnreadThreadRooms(mxClient, msc3946ProcessDynamicPredecessor, settingTACOnlyNotifs));
|
||||
}, [mxClient, msc3946ProcessDynamicPredecessor, settingTACOnlyNotifs]);
|
||||
|
||||
// The exhautive deps lint rule can't compute dependencies here since it's not a plain inline func.
|
||||
// We make this as simple as possible so its only dep is doUpdate itself.
|
||||
|
@ -83,7 +84,11 @@ export function useUnreadThreadRooms(forceComputation: boolean): Result {
|
|||
* @param mxClient - MatrixClient
|
||||
* @param msc3946ProcessDynamicPredecessor
|
||||
*/
|
||||
function computeUnreadThreadRooms(mxClient: MatrixClient, msc3946ProcessDynamicPredecessor: boolean): Result {
|
||||
function computeUnreadThreadRooms(
|
||||
mxClient: MatrixClient,
|
||||
msc3946ProcessDynamicPredecessor: boolean,
|
||||
settingTACOnlyNotifs: 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);
|
||||
|
@ -98,7 +103,7 @@ function computeUnreadThreadRooms(mxClient: MatrixClient, msc3946ProcessDynamicP
|
|||
const notificationLevel = getThreadNotificationLevel(room);
|
||||
|
||||
// If the room has an activity notification or less, we ignore it
|
||||
if (notificationLevel <= NotificationLevel.Activity) {
|
||||
if (settingTACOnlyNotifs && notificationLevel <= NotificationLevel.Activity) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -2846,6 +2846,7 @@
|
|||
"show_redaction_placeholder": "Show a placeholder for removed messages",
|
||||
"show_stickers_button": "Show stickers button",
|
||||
"show_typing_notifications": "Show typing notifications",
|
||||
"showbold": "Show all activity in the room list (dots or number of unread messages)",
|
||||
"sidebar": {
|
||||
"metaspaces_favourites_description": "Group all your favourite rooms and people in one place.",
|
||||
"metaspaces_home_all_rooms": "Show all rooms",
|
||||
|
@ -2862,6 +2863,7 @@
|
|||
"title": "Sidebar"
|
||||
},
|
||||
"start_automatically": "Start automatically after system login",
|
||||
"tac_only_notifications": "Only show notifications in the thread activity centre",
|
||||
"use_12_hour_format": "Show timestamps in 12 hour format (e.g. 2:30pm)",
|
||||
"use_command_enter_send_message": "Use Command + Enter to send a message",
|
||||
"use_command_f_search": "Use Command + F to search timeline",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
Copyright 2017 Travis Ralston
|
||||
Copyright 2018 - 2023 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2018 - 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.
|
||||
|
@ -586,14 +586,23 @@ export const SETTINGS: { [setting: string]: ISetting } = {
|
|||
supportedLevels: LEVELS_ROOM_OR_ACCOUNT,
|
||||
default: false,
|
||||
},
|
||||
// Used to be a feature, name kept for backwards compat
|
||||
"feature_hidebold": {
|
||||
isFeature: true,
|
||||
labsGroup: LabGroup.Rooms,
|
||||
configDisablesSetting: true,
|
||||
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG,
|
||||
displayName: _td("labs|hidebold"),
|
||||
default: false,
|
||||
},
|
||||
"Notifications.showbold": {
|
||||
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG,
|
||||
displayName: _td("settings|showbold"),
|
||||
default: false,
|
||||
invertedSettingName: "feature_hidebold",
|
||||
},
|
||||
"Notifications.tac_only_notifications": {
|
||||
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG,
|
||||
displayName: _td("settings|tac_only_notifications"),
|
||||
default: true,
|
||||
},
|
||||
"feature_ask_to_join": {
|
||||
isFeature: true,
|
||||
labsGroup: LabGroup.Rooms,
|
||||
|
|
|
@ -35,5 +35,61 @@ exports[`<Notifications /> main notification switches renders only enable notifi
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="mx_SettingsFlag"
|
||||
>
|
||||
<label
|
||||
class="mx_SettingsFlag_label"
|
||||
for="mx_SettingsFlag_testid_1"
|
||||
>
|
||||
<span
|
||||
class="mx_SettingsFlag_labelText"
|
||||
>
|
||||
Show all activity in the room list (dots or number of unread messages)
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
aria-checked="true"
|
||||
aria-disabled="false"
|
||||
aria-label="Show all activity in the room list (dots or number of unread messages)"
|
||||
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
|
||||
id="mx_SettingsFlag_testid_1"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_ToggleSwitch_ball"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsFlag"
|
||||
>
|
||||
<label
|
||||
class="mx_SettingsFlag_label"
|
||||
for="mx_SettingsFlag_testid_2"
|
||||
>
|
||||
<span
|
||||
class="mx_SettingsFlag_labelText"
|
||||
>
|
||||
Only show notifications in the thread activity centre
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
aria-checked="true"
|
||||
aria-disabled="false"
|
||||
aria-label="Only show notifications in the thread activity centre"
|
||||
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
|
||||
id="mx_SettingsFlag_testid_2"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_ToggleSwitch_ball"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
|
@ -641,6 +641,60 @@ exports[`<Notifications /> correctly handles the loading/disabled state 1`] = `
|
|||
role="list"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsFlag"
|
||||
>
|
||||
<label
|
||||
class="mx_SettingsFlag_label"
|
||||
for="mx_SettingsFlag_QRlYy75nfv5b"
|
||||
>
|
||||
<span
|
||||
class="mx_SettingsFlag_labelText"
|
||||
>
|
||||
Show all activity in the room list (dots or number of unread messages)
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
aria-checked="true"
|
||||
aria-disabled="false"
|
||||
aria-label="Show all activity in the room list (dots or number of unread messages)"
|
||||
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
|
||||
id="mx_SettingsFlag_QRlYy75nfv5b"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_ToggleSwitch_ball"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsFlag"
|
||||
>
|
||||
<label
|
||||
class="mx_SettingsFlag_label"
|
||||
for="mx_SettingsFlag_OEPN1su1JYVt"
|
||||
>
|
||||
<span
|
||||
class="mx_SettingsFlag_labelText"
|
||||
>
|
||||
Only show notifications in the thread activity centre
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
aria-checked="true"
|
||||
aria-disabled="false"
|
||||
aria-label="Only show notifications in the thread activity centre"
|
||||
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
|
||||
id="mx_SettingsFlag_OEPN1su1JYVt"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_ToggleSwitch_ball"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
@ -1472,6 +1526,60 @@ exports[`<Notifications /> matches the snapshot 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsFlag"
|
||||
>
|
||||
<label
|
||||
class="mx_SettingsFlag_label"
|
||||
for="mx_SettingsFlag_QRlYy75nfv5b"
|
||||
>
|
||||
<span
|
||||
class="mx_SettingsFlag_labelText"
|
||||
>
|
||||
Show all activity in the room list (dots or number of unread messages)
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
aria-checked="true"
|
||||
aria-disabled="false"
|
||||
aria-label="Show all activity in the room list (dots or number of unread messages)"
|
||||
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
|
||||
id="mx_SettingsFlag_QRlYy75nfv5b"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_ToggleSwitch_ball"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsFlag"
|
||||
>
|
||||
<label
|
||||
class="mx_SettingsFlag_label"
|
||||
for="mx_SettingsFlag_OEPN1su1JYVt"
|
||||
>
|
||||
<span
|
||||
class="mx_SettingsFlag_labelText"
|
||||
>
|
||||
Only show notifications in the thread activity centre
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
aria-checked="true"
|
||||
aria-disabled="false"
|
||||
aria-label="Only show notifications in the thread activity centre"
|
||||
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
|
||||
id="mx_SettingsFlag_OEPN1su1JYVt"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_ToggleSwitch_ball"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
@ -1523,11 +1631,11 @@ exports[`<Notifications /> matches the snapshot 1`] = `
|
|||
class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
|
||||
>
|
||||
<input
|
||||
id="checkbox_QRlYy75nfv"
|
||||
id="checkbox_OyR5kbu3pE"
|
||||
type="checkbox"
|
||||
/>
|
||||
<label
|
||||
for="checkbox_QRlYy75nfv"
|
||||
for="checkbox_OyR5kbu3pE"
|
||||
>
|
||||
<div
|
||||
class="mx_Checkbox_background"
|
||||
|
|
|
@ -30,6 +30,7 @@ import { stubClient } from "../../../test-utils";
|
|||
import { populateThread } from "../../../test-utils/threads";
|
||||
import { NotificationLevel } from "../../../../src/stores/notifications/NotificationLevel";
|
||||
import { useUnreadThreadRooms } from "../../../../src/components/views/spaces/threads-activity-centre/useUnreadThreadRooms";
|
||||
import SettingsStore from "../../../../src/settings/SettingsStore";
|
||||
|
||||
describe("useUnreadThreadRooms", () => {
|
||||
let client: MatrixClient;
|
||||
|
@ -43,6 +44,10 @@ describe("useUnreadThreadRooms", () => {
|
|||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("has no notifications with no rooms", async () => {
|
||||
const { result } = renderHook(() => useUnreadThreadRooms(false));
|
||||
const { greatestNotificationLevel, rooms } = result.current;
|
||||
|
@ -51,7 +56,7 @@ describe("useUnreadThreadRooms", () => {
|
|||
expect(rooms.length).toEqual(0);
|
||||
});
|
||||
|
||||
it("an activity notification is ignored", async () => {
|
||||
it("an activity notification is ignored by default", async () => {
|
||||
const notifThreadInfo = await populateThread({
|
||||
room: room,
|
||||
client: client,
|
||||
|
@ -73,6 +78,30 @@ describe("useUnreadThreadRooms", () => {
|
|||
expect(rooms.length).toEqual(0);
|
||||
});
|
||||
|
||||
it("an activity notification is displayed with the setting enabled", async () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
|
||||
|
||||
const notifThreadInfo = await populateThread({
|
||||
room: room,
|
||||
client: client,
|
||||
authorId: "@foo:bar",
|
||||
participantUserIds: ["@fee:bar"],
|
||||
});
|
||||
room.setThreadUnreadNotificationCount(notifThreadInfo.thread.id, NotificationCountType.Total, 0);
|
||||
|
||||
client.getVisibleRooms = jest.fn().mockReturnValue([room]);
|
||||
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<MatrixClientContext.Provider value={client}>{children}</MatrixClientContext.Provider>
|
||||
);
|
||||
|
||||
const { result } = renderHook(() => useUnreadThreadRooms(true), { wrapper });
|
||||
const { greatestNotificationLevel, rooms } = result.current;
|
||||
|
||||
expect(greatestNotificationLevel).toBe(NotificationLevel.Activity);
|
||||
expect(rooms.length).toEqual(1);
|
||||
});
|
||||
|
||||
it("a notification and a highlight summarise to a highlight", async () => {
|
||||
const notifThreadInfo = await populateThread({
|
||||
room: room,
|
||||
|
|
Loading…
Reference in a new issue