Add option to change room notification settings (#2281)
This commit is contained in:
parent
074a5e855d
commit
71bfc96b5c
12 changed files with 409 additions and 164 deletions
src/app
components
features/room-nav
hooks
pages
state
120
src/app/components/RoomNotificationSwitcher.tsx
Normal file
120
src/app/components/RoomNotificationSwitcher.tsx
Normal file
|
@ -0,0 +1,120 @@
|
|||
import { Box, config, Icon, Menu, MenuItem, PopOut, RectCords, Text } from 'folds';
|
||||
import React, { MouseEventHandler, ReactNode, useMemo, useState } from 'react';
|
||||
import FocusTrap from 'focus-trap-react';
|
||||
import { stopPropagation } from '../utils/keyboard';
|
||||
import {
|
||||
getRoomNotificationModeIcon,
|
||||
RoomNotificationMode,
|
||||
useSetRoomNotificationPreference,
|
||||
} from '../hooks/useRoomsNotificationPreferences';
|
||||
import { AsyncStatus } from '../hooks/useAsyncCallback';
|
||||
|
||||
const useRoomNotificationModes = (): RoomNotificationMode[] =>
|
||||
useMemo(
|
||||
() => [
|
||||
RoomNotificationMode.Unset,
|
||||
RoomNotificationMode.AllMessages,
|
||||
RoomNotificationMode.SpecialMessages,
|
||||
RoomNotificationMode.Mute,
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
const useRoomNotificationModeStr = (): Record<RoomNotificationMode, string> =>
|
||||
useMemo(
|
||||
() => ({
|
||||
[RoomNotificationMode.Unset]: 'Default',
|
||||
[RoomNotificationMode.AllMessages]: 'All Messages',
|
||||
[RoomNotificationMode.SpecialMessages]: 'Mention & Keywords',
|
||||
[RoomNotificationMode.Mute]: 'Mute',
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
type NotificationModeSwitcherProps = {
|
||||
roomId: string;
|
||||
value?: RoomNotificationMode;
|
||||
children: (
|
||||
handleOpen: MouseEventHandler<HTMLButtonElement>,
|
||||
opened: boolean,
|
||||
changing: boolean
|
||||
) => ReactNode;
|
||||
};
|
||||
export function RoomNotificationModeSwitcher({
|
||||
roomId,
|
||||
value = RoomNotificationMode.Unset,
|
||||
children,
|
||||
}: NotificationModeSwitcherProps) {
|
||||
const modes = useRoomNotificationModes();
|
||||
const modeToStr = useRoomNotificationModeStr();
|
||||
|
||||
const { modeState, setMode } = useSetRoomNotificationPreference(roomId);
|
||||
const changing = modeState.status === AsyncStatus.Loading;
|
||||
|
||||
const [menuCords, setMenuCords] = useState<RectCords>();
|
||||
|
||||
const handleOpenMenu: MouseEventHandler<HTMLButtonElement> = (evt) => {
|
||||
setMenuCords(evt.currentTarget.getBoundingClientRect());
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setMenuCords(undefined);
|
||||
};
|
||||
|
||||
const handleSelect = (mode: RoomNotificationMode) => {
|
||||
if (changing) return;
|
||||
setMode(mode, value);
|
||||
handleClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<PopOut
|
||||
anchor={menuCords}
|
||||
offset={5}
|
||||
position="Right"
|
||||
align="Start"
|
||||
content={
|
||||
<FocusTrap
|
||||
focusTrapOptions={{
|
||||
initialFocus: false,
|
||||
onDeactivate: handleClose,
|
||||
clickOutsideDeactivates: true,
|
||||
isKeyForward: (evt: KeyboardEvent) =>
|
||||
evt.key === 'ArrowDown' || evt.key === 'ArrowRight',
|
||||
isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp' || evt.key === 'ArrowLeft',
|
||||
escapeDeactivates: stopPropagation,
|
||||
}}
|
||||
>
|
||||
<Menu>
|
||||
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
|
||||
{modes.map((mode) => (
|
||||
<MenuItem
|
||||
key={mode}
|
||||
size="300"
|
||||
variant="Surface"
|
||||
aria-pressed={mode === value}
|
||||
radii="300"
|
||||
disabled={changing}
|
||||
onClick={() => handleSelect(mode)}
|
||||
before={
|
||||
<Icon
|
||||
size="100"
|
||||
src={getRoomNotificationModeIcon(mode)}
|
||||
filled={mode === value}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Text size="T300">
|
||||
{mode === value ? <b>{modeToStr[mode]}</b> : modeToStr[mode]}
|
||||
</Text>
|
||||
</MenuItem>
|
||||
))}
|
||||
</Box>
|
||||
</Menu>
|
||||
</FocusTrap>
|
||||
}
|
||||
>
|
||||
{children(handleOpenMenu, !!menuCords, changing)}
|
||||
</PopOut>
|
||||
);
|
||||
}
|
|
@ -15,6 +15,7 @@ import {
|
|||
Line,
|
||||
RectCords,
|
||||
Badge,
|
||||
Spinner,
|
||||
} from 'folds';
|
||||
import { useFocusWithin, useHover } from 'react-aria';
|
||||
import FocusTrap from 'focus-trap-react';
|
||||
|
@ -43,13 +44,19 @@ import { useSetting } from '../../state/hooks/settings';
|
|||
import { settingsAtom } from '../../state/settings';
|
||||
import { useOpenRoomSettings } from '../../state/hooks/roomSettings';
|
||||
import { useSpaceOptionally } from '../../hooks/useSpace';
|
||||
import {
|
||||
getRoomNotificationModeIcon,
|
||||
RoomNotificationMode,
|
||||
} from '../../hooks/useRoomsNotificationPreferences';
|
||||
import { RoomNotificationModeSwitcher } from '../../components/RoomNotificationSwitcher';
|
||||
|
||||
type RoomNavItemMenuProps = {
|
||||
room: Room;
|
||||
requestClose: () => void;
|
||||
notificationMode?: RoomNotificationMode;
|
||||
};
|
||||
const RoomNavItemMenu = forwardRef<HTMLDivElement, RoomNavItemMenuProps>(
|
||||
({ room, requestClose }, ref) => {
|
||||
({ room, requestClose, notificationMode }, ref) => {
|
||||
const mx = useMatrixClient();
|
||||
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
|
||||
const unread = useRoomUnread(room.roomId, roomToUnreadAtom);
|
||||
|
@ -95,6 +102,27 @@ const RoomNavItemMenu = forwardRef<HTMLDivElement, RoomNavItemMenuProps>(
|
|||
Mark as Read
|
||||
</Text>
|
||||
</MenuItem>
|
||||
<RoomNotificationModeSwitcher roomId={room.roomId} value={notificationMode}>
|
||||
{(handleOpen, opened, changing) => (
|
||||
<MenuItem
|
||||
size="300"
|
||||
after={
|
||||
changing ? (
|
||||
<Spinner size="100" variant="Secondary" />
|
||||
) : (
|
||||
<Icon size="100" src={getRoomNotificationModeIcon(notificationMode)} />
|
||||
)
|
||||
}
|
||||
radii="300"
|
||||
aria-pressed={opened}
|
||||
onClick={handleOpen}
|
||||
>
|
||||
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
|
||||
Notifications
|
||||
</Text>
|
||||
</MenuItem>
|
||||
)}
|
||||
</RoomNotificationModeSwitcher>
|
||||
</Box>
|
||||
<Line variant="Surface" size="300" />
|
||||
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
|
||||
|
@ -170,7 +198,7 @@ type RoomNavItemProps = {
|
|||
room: Room;
|
||||
selected: boolean;
|
||||
linkPath: string;
|
||||
muted?: boolean;
|
||||
notificationMode?: RoomNotificationMode;
|
||||
showAvatar?: boolean;
|
||||
direct?: boolean;
|
||||
};
|
||||
|
@ -179,7 +207,7 @@ export function RoomNavItem({
|
|||
selected,
|
||||
showAvatar,
|
||||
direct,
|
||||
muted,
|
||||
notificationMode,
|
||||
linkPath,
|
||||
}: RoomNavItemProps) {
|
||||
const mx = useMatrixClient();
|
||||
|
@ -263,7 +291,9 @@ export function RoomNavItem({
|
|||
<UnreadBadge highlight={unread.highlight > 0} count={unread.total} />
|
||||
</UnreadBadgeCenter>
|
||||
)}
|
||||
{muted && !optionsVisible && <Icon size="50" src={Icons.BellMute} />}
|
||||
{!optionsVisible && notificationMode !== RoomNotificationMode.Unset && (
|
||||
<Icon size="50" src={getRoomNotificationModeIcon(notificationMode)} />
|
||||
)}
|
||||
</Box>
|
||||
</NavItemContent>
|
||||
</NavLink>
|
||||
|
@ -287,7 +317,11 @@ export function RoomNavItem({
|
|||
escapeDeactivates: stopPropagation,
|
||||
}}
|
||||
>
|
||||
<RoomNavItemMenu room={room} requestClose={() => setMenuAnchor(undefined)} />
|
||||
<RoomNavItemMenu
|
||||
room={room}
|
||||
requestClose={() => setMenuAnchor(undefined)}
|
||||
notificationMode={notificationMode}
|
||||
/>
|
||||
</FocusTrap>
|
||||
}
|
||||
>
|
||||
|
|
|
@ -7,6 +7,19 @@ export enum NotificationMode {
|
|||
NotifyLoud = 'NotifyLoud',
|
||||
}
|
||||
|
||||
export const getNotificationMode = (actions: PushRuleAction[]): NotificationMode => {
|
||||
const soundTweak = actions.find(
|
||||
(action) => typeof action === 'object' && action.set_tweak === TweakName.Sound
|
||||
);
|
||||
const notify = actions.find(
|
||||
(action) => typeof action === 'string' && action === PushRuleActionName.Notify
|
||||
);
|
||||
|
||||
if (notify && soundTweak) return NotificationMode.NotifyLoud;
|
||||
if (notify) return NotificationMode.Notify;
|
||||
return NotificationMode.OFF;
|
||||
};
|
||||
|
||||
export type NotificationModeOptions = {
|
||||
soundValue?: string;
|
||||
highlight?: boolean;
|
||||
|
@ -49,18 +62,7 @@ export const useNotificationModeActions = (
|
|||
};
|
||||
|
||||
export const useNotificationActionsMode = (actions: PushRuleAction[]): NotificationMode => {
|
||||
const mode: NotificationMode = useMemo(() => {
|
||||
const soundTweak = actions.find(
|
||||
(action) => typeof action === 'object' && action.set_tweak === TweakName.Sound
|
||||
);
|
||||
const notify = actions.find(
|
||||
(action) => typeof action === 'string' && action === PushRuleActionName.Notify
|
||||
);
|
||||
|
||||
if (notify && soundTweak) return NotificationMode.NotifyLoud;
|
||||
if (notify) return NotificationMode.Notify;
|
||||
return NotificationMode.OFF;
|
||||
}, [actions]);
|
||||
const mode: NotificationMode = useMemo(() => getNotificationMode(actions), [actions]);
|
||||
|
||||
return mode;
|
||||
};
|
||||
|
|
169
src/app/hooks/useRoomsNotificationPreferences.ts
Normal file
169
src/app/hooks/useRoomsNotificationPreferences.ts
Normal file
|
@ -0,0 +1,169 @@
|
|||
import { createContext, useCallback, useContext, useMemo } from 'react';
|
||||
import { ConditionKind, IPushRules, MatrixClient, PushRuleKind } from 'matrix-js-sdk';
|
||||
import { Icons, IconSrc } from 'folds';
|
||||
import { AccountDataEvent } from '../../types/matrix/accountData';
|
||||
import { useAccountData } from './useAccountData';
|
||||
import { isRoomId } from '../utils/matrix';
|
||||
import {
|
||||
getNotificationMode,
|
||||
getNotificationModeActions,
|
||||
NotificationMode,
|
||||
} from './useNotificationMode';
|
||||
import { useAsyncCallback } from './useAsyncCallback';
|
||||
import { useMatrixClient } from './useMatrixClient';
|
||||
|
||||
export type RoomsNotificationPreferences = {
|
||||
mute: Set<string>;
|
||||
specialMessages: Set<string>;
|
||||
allMessages: Set<string>;
|
||||
};
|
||||
|
||||
const RoomsNotificationPreferencesContext = createContext<RoomsNotificationPreferences | null>(
|
||||
null
|
||||
);
|
||||
export const RoomsNotificationPreferencesProvider = RoomsNotificationPreferencesContext.Provider;
|
||||
|
||||
export const useRoomsNotificationPreferencesContext = (): RoomsNotificationPreferences => {
|
||||
const preferences = useContext(RoomsNotificationPreferencesContext);
|
||||
|
||||
if (!preferences) {
|
||||
throw new Error('No RoomsNotificationPreferences provided!');
|
||||
}
|
||||
|
||||
return preferences;
|
||||
};
|
||||
|
||||
export const useRoomsNotificationPreferences = (): RoomsNotificationPreferences => {
|
||||
const pushRules = useAccountData(AccountDataEvent.PushRules)?.getContent<IPushRules>();
|
||||
|
||||
const preferences: RoomsNotificationPreferences = useMemo(() => {
|
||||
const global = pushRules?.global;
|
||||
const room = global?.room ?? [];
|
||||
const override = global?.override ?? [];
|
||||
|
||||
const pref: RoomsNotificationPreferences = {
|
||||
mute: new Set(),
|
||||
specialMessages: new Set(),
|
||||
allMessages: new Set(),
|
||||
};
|
||||
|
||||
override.forEach((rule) => {
|
||||
if (isRoomId(rule.rule_id) && getNotificationMode(rule.actions) === NotificationMode.OFF) {
|
||||
pref.mute.add(rule.rule_id);
|
||||
}
|
||||
});
|
||||
room.forEach((rule) => {
|
||||
if (getNotificationMode(rule.actions) === NotificationMode.OFF) {
|
||||
pref.specialMessages.add(rule.rule_id);
|
||||
}
|
||||
});
|
||||
room.forEach((rule) => {
|
||||
if (getNotificationMode(rule.actions) !== NotificationMode.OFF) {
|
||||
pref.allMessages.add(rule.rule_id);
|
||||
}
|
||||
});
|
||||
|
||||
return pref;
|
||||
}, [pushRules]);
|
||||
|
||||
return preferences;
|
||||
};
|
||||
|
||||
export enum RoomNotificationMode {
|
||||
Unset = 'Unset',
|
||||
Mute = 'Mute',
|
||||
SpecialMessages = 'SpecialMessages',
|
||||
AllMessages = 'AllMessages',
|
||||
}
|
||||
|
||||
export const getRoomNotificationMode = (
|
||||
preferences: RoomsNotificationPreferences,
|
||||
roomId: string
|
||||
): RoomNotificationMode => {
|
||||
if (preferences.mute.has(roomId)) {
|
||||
return RoomNotificationMode.Mute;
|
||||
}
|
||||
if (preferences.specialMessages.has(roomId)) {
|
||||
return RoomNotificationMode.SpecialMessages;
|
||||
}
|
||||
if (preferences.allMessages.has(roomId)) {
|
||||
return RoomNotificationMode.AllMessages;
|
||||
}
|
||||
|
||||
return RoomNotificationMode.Unset;
|
||||
};
|
||||
|
||||
export const useRoomNotificationPreference = (
|
||||
preferences: RoomsNotificationPreferences,
|
||||
roomId: string
|
||||
): RoomNotificationMode =>
|
||||
useMemo(() => getRoomNotificationMode(preferences, roomId), [preferences, roomId]);
|
||||
|
||||
export const getRoomNotificationModeIcon = (mode?: RoomNotificationMode): IconSrc => {
|
||||
if (mode === RoomNotificationMode.Mute) return Icons.BellMute;
|
||||
if (mode === RoomNotificationMode.SpecialMessages) return Icons.BellPing;
|
||||
if (mode === RoomNotificationMode.AllMessages) return Icons.BellRing;
|
||||
|
||||
return Icons.Bell;
|
||||
};
|
||||
|
||||
export const setRoomNotificationPreference = async (
|
||||
mx: MatrixClient,
|
||||
roomId: string,
|
||||
mode: RoomNotificationMode,
|
||||
previousMode: RoomNotificationMode
|
||||
): Promise<void> => {
|
||||
// remove the old preference
|
||||
if (
|
||||
previousMode === RoomNotificationMode.AllMessages ||
|
||||
previousMode === RoomNotificationMode.SpecialMessages
|
||||
) {
|
||||
await mx.deletePushRule('global', PushRuleKind.RoomSpecific, roomId);
|
||||
}
|
||||
if (previousMode === RoomNotificationMode.Mute) {
|
||||
await mx.deletePushRule('global', PushRuleKind.Override, roomId);
|
||||
}
|
||||
|
||||
// set new preference
|
||||
if (mode === RoomNotificationMode.Unset) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode === RoomNotificationMode.Mute) {
|
||||
await mx.addPushRule('global', PushRuleKind.Override, roomId, {
|
||||
conditions: [
|
||||
{
|
||||
kind: ConditionKind.EventMatch,
|
||||
key: 'room_id',
|
||||
pattern: roomId,
|
||||
},
|
||||
],
|
||||
actions: getNotificationModeActions(NotificationMode.OFF),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await mx.addPushRule('global', PushRuleKind.RoomSpecific, roomId, {
|
||||
actions:
|
||||
mode === RoomNotificationMode.AllMessages
|
||||
? getNotificationModeActions(NotificationMode.NotifyLoud)
|
||||
: getNotificationModeActions(NotificationMode.OFF),
|
||||
});
|
||||
};
|
||||
|
||||
export const useSetRoomNotificationPreference = (roomId: string) => {
|
||||
const mx = useMatrixClient();
|
||||
|
||||
const [modeState, setMode] = useAsyncCallback(
|
||||
useCallback(
|
||||
(mode: RoomNotificationMode, previousMode: RoomNotificationMode) =>
|
||||
setRoomNotificationPreference(mx, roomId, mode, previousMode),
|
||||
[mx, roomId]
|
||||
)
|
||||
);
|
||||
|
||||
return {
|
||||
modeState,
|
||||
setMode,
|
||||
};
|
||||
};
|
|
@ -59,6 +59,7 @@ import { AuthRouteThemeManager, UnAuthRouteThemeManager } from './ThemeManager';
|
|||
import { ReceiveSelfDeviceVerification } from '../components/DeviceVerification';
|
||||
import { AutoRestoreBackupOnVerification } from '../components/BackupRestore';
|
||||
import { RoomSettingsRenderer } from '../features/room-settings';
|
||||
import { ClientRoomsNotificationPreferences } from './client/ClientRoomsNotificationPreferences';
|
||||
|
||||
export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize) => {
|
||||
const { hashRouter } = clientConfig;
|
||||
|
@ -111,22 +112,24 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize)
|
|||
<>
|
||||
<ClientRoot>
|
||||
<ClientInitStorageAtom>
|
||||
<ClientBindAtoms>
|
||||
<ClientNonUIFeatures>
|
||||
<ClientLayout
|
||||
nav={
|
||||
<MobileFriendlyClientNav>
|
||||
<SidebarNav />
|
||||
</MobileFriendlyClientNav>
|
||||
}
|
||||
>
|
||||
<Outlet />
|
||||
</ClientLayout>
|
||||
<RoomSettingsRenderer />
|
||||
<ReceiveSelfDeviceVerification />
|
||||
<AutoRestoreBackupOnVerification />
|
||||
</ClientNonUIFeatures>
|
||||
</ClientBindAtoms>
|
||||
<ClientRoomsNotificationPreferences>
|
||||
<ClientBindAtoms>
|
||||
<ClientNonUIFeatures>
|
||||
<ClientLayout
|
||||
nav={
|
||||
<MobileFriendlyClientNav>
|
||||
<SidebarNav />
|
||||
</MobileFriendlyClientNav>
|
||||
}
|
||||
>
|
||||
<Outlet />
|
||||
</ClientLayout>
|
||||
<RoomSettingsRenderer />
|
||||
<ReceiveSelfDeviceVerification />
|
||||
<AutoRestoreBackupOnVerification />
|
||||
</ClientNonUIFeatures>
|
||||
</ClientBindAtoms>
|
||||
</ClientRoomsNotificationPreferences>
|
||||
</ClientInitStorageAtom>
|
||||
</ClientRoot>
|
||||
<AuthRouteThemeManager />
|
||||
|
|
15
src/app/pages/client/ClientRoomsNotificationPreferences.tsx
Normal file
15
src/app/pages/client/ClientRoomsNotificationPreferences.tsx
Normal file
|
@ -0,0 +1,15 @@
|
|||
import React, { ReactNode } from 'react';
|
||||
import {
|
||||
RoomsNotificationPreferencesProvider,
|
||||
useRoomsNotificationPreferences,
|
||||
} from '../../hooks/useRoomsNotificationPreferences';
|
||||
|
||||
export function ClientRoomsNotificationPreferences({ children }: { children: ReactNode }) {
|
||||
const preferences = useRoomsNotificationPreferences();
|
||||
|
||||
return (
|
||||
<RoomsNotificationPreferencesProvider value={preferences}>
|
||||
{children}
|
||||
</RoomsNotificationPreferencesProvider>
|
||||
);
|
||||
}
|
|
@ -33,7 +33,6 @@ import { getCanonicalAliasOrRoomId } from '../../../utils/matrix';
|
|||
import { useSelectedRoom } from '../../../hooks/router/useSelectedRoom';
|
||||
import { VirtualTile } from '../../../components/virtualizer';
|
||||
import { RoomNavCategoryButton, RoomNavItem } from '../../../features/room-nav';
|
||||
import { muteChangesAtom } from '../../../state/room-list/mutedRoomList';
|
||||
import { makeNavCategoryId } from '../../../state/closedNavCategories';
|
||||
import { roomToUnreadAtom } from '../../../state/room/roomToUnread';
|
||||
import { useCategoryHandler } from '../../../hooks/useCategoryHandler';
|
||||
|
@ -47,6 +46,10 @@ import { markAsRead } from '../../../../client/action/notifications';
|
|||
import { stopPropagation } from '../../../utils/keyboard';
|
||||
import { useSetting } from '../../../state/hooks/settings';
|
||||
import { settingsAtom } from '../../../state/settings';
|
||||
import {
|
||||
getRoomNotificationMode,
|
||||
useRoomsNotificationPreferencesContext,
|
||||
} from '../../../hooks/useRoomsNotificationPreferences';
|
||||
|
||||
type DirectMenuProps = {
|
||||
requestClose: () => void;
|
||||
|
@ -167,8 +170,7 @@ export function Direct() {
|
|||
useNavToActivePathMapper('direct');
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
const directs = useDirectRooms();
|
||||
const muteChanges = useAtomValue(muteChangesAtom);
|
||||
const mutedRooms = muteChanges.added;
|
||||
const notificationPreferences = useRoomsNotificationPreferencesContext();
|
||||
const roomToUnread = useAtomValue(roomToUnreadAtom);
|
||||
|
||||
const selectedRoomId = useSelectedRoom();
|
||||
|
@ -254,7 +256,10 @@ export function Direct() {
|
|||
showAvatar
|
||||
direct
|
||||
linkPath={getDirectRoomPath(getCanonicalAliasOrRoomId(mx, roomId))}
|
||||
muted={mutedRooms.includes(roomId)}
|
||||
notificationMode={getRoomNotificationMode(
|
||||
notificationPreferences,
|
||||
room.roomId
|
||||
)}
|
||||
/>
|
||||
</VirtualTile>
|
||||
);
|
||||
|
|
|
@ -37,7 +37,6 @@ import { useHomeRooms } from './useHomeRooms';
|
|||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||
import { VirtualTile } from '../../../components/virtualizer';
|
||||
import { RoomNavCategoryButton, RoomNavItem } from '../../../features/room-nav';
|
||||
import { muteChangesAtom } from '../../../state/room-list/mutedRoomList';
|
||||
import { makeNavCategoryId } from '../../../state/closedNavCategories';
|
||||
import { roomToUnreadAtom } from '../../../state/room/roomToUnread';
|
||||
import { useCategoryHandler } from '../../../hooks/useCategoryHandler';
|
||||
|
@ -50,6 +49,10 @@ import { useClosedNavCategoriesAtom } from '../../../state/hooks/closedNavCatego
|
|||
import { stopPropagation } from '../../../utils/keyboard';
|
||||
import { useSetting } from '../../../state/hooks/settings';
|
||||
import { settingsAtom } from '../../../state/settings';
|
||||
import {
|
||||
getRoomNotificationMode,
|
||||
useRoomsNotificationPreferencesContext,
|
||||
} from '../../../hooks/useRoomsNotificationPreferences';
|
||||
|
||||
type HomeMenuProps = {
|
||||
requestClose: () => void;
|
||||
|
@ -199,8 +202,7 @@ export function Home() {
|
|||
useNavToActivePathMapper('home');
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
const rooms = useHomeRooms();
|
||||
const muteChanges = useAtomValue(muteChangesAtom);
|
||||
const mutedRooms = muteChanges.added;
|
||||
const notificationPreferences = useRoomsNotificationPreferencesContext();
|
||||
const roomToUnread = useAtomValue(roomToUnreadAtom);
|
||||
|
||||
const selectedRoomId = useSelectedRoom();
|
||||
|
@ -321,7 +323,10 @@ export function Home() {
|
|||
room={room}
|
||||
selected={selected}
|
||||
linkPath={getHomeRoomPath(getCanonicalAliasOrRoomId(mx, roomId))}
|
||||
muted={mutedRooms.includes(roomId)}
|
||||
notificationMode={getRoomNotificationMode(
|
||||
notificationPreferences,
|
||||
room.roomId
|
||||
)}
|
||||
/>
|
||||
</VirtualTile>
|
||||
);
|
||||
|
|
|
@ -45,7 +45,6 @@ import {
|
|||
import { useSpace } from '../../../hooks/useSpace';
|
||||
import { VirtualTile } from '../../../components/virtualizer';
|
||||
import { RoomNavCategoryButton, RoomNavItem } from '../../../features/room-nav';
|
||||
import { muteChangesAtom } from '../../../state/room-list/mutedRoomList';
|
||||
import { makeNavCategoryId } from '../../../state/closedNavCategories';
|
||||
import { roomToUnreadAtom } from '../../../state/room/roomToUnread';
|
||||
import { useCategoryHandler } from '../../../hooks/useCategoryHandler';
|
||||
|
@ -71,6 +70,10 @@ import { getMatrixToRoom } from '../../../plugins/matrix-to';
|
|||
import { getViaServers } from '../../../plugins/via-servers';
|
||||
import { useSetting } from '../../../state/hooks/settings';
|
||||
import { settingsAtom } from '../../../state/settings';
|
||||
import {
|
||||
getRoomNotificationMode,
|
||||
useRoomsNotificationPreferencesContext,
|
||||
} from '../../../hooks/useRoomsNotificationPreferences';
|
||||
|
||||
type SpaceMenuProps = {
|
||||
room: Room;
|
||||
|
@ -269,8 +272,7 @@ export function Space() {
|
|||
const roomToUnread = useAtomValue(roomToUnreadAtom);
|
||||
const allRooms = useAtomValue(allRoomsAtom);
|
||||
const allJoinedRooms = useMemo(() => new Set(allRooms), [allRooms]);
|
||||
const muteChanges = useAtomValue(muteChangesAtom);
|
||||
const mutedRooms = muteChanges.added;
|
||||
const notificationPreferences = useRoomsNotificationPreferencesContext();
|
||||
|
||||
const selectedRoomId = useSelectedRoom();
|
||||
const lobbySelected = useSpaceLobbySelected(spaceIdOrAlias);
|
||||
|
@ -404,7 +406,7 @@ export function Space() {
|
|||
showAvatar={mDirects.has(roomId)}
|
||||
direct={mDirects.has(roomId)}
|
||||
linkPath={getToLink(roomId)}
|
||||
muted={mutedRooms.includes(roomId)}
|
||||
notificationMode={getRoomNotificationMode(notificationPreferences, room.roomId)}
|
||||
/>
|
||||
</VirtualTile>
|
||||
);
|
||||
|
|
|
@ -2,7 +2,6 @@ import { MatrixClient } from 'matrix-js-sdk';
|
|||
import { allInvitesAtom, useBindAllInvitesAtom } from '../room-list/inviteList';
|
||||
import { allRoomsAtom, useBindAllRoomsAtom } from '../room-list/roomList';
|
||||
import { mDirectAtom, useBindMDirectAtom } from '../mDirectList';
|
||||
import { muteChangesAtom, mutedRoomsAtom, useBindMutedRoomsAtom } from '../room-list/mutedRoomList';
|
||||
import { roomToUnreadAtom, useBindRoomToUnreadAtom } from '../room/roomToUnread';
|
||||
import { roomToParentsAtom, useBindRoomToParentsAtom } from '../room/roomToParents';
|
||||
import { roomIdToTypingMembersAtom, useBindRoomIdToTypingMembersAtom } from '../typingMembers';
|
||||
|
@ -12,8 +11,7 @@ export const useBindAtoms = (mx: MatrixClient) => {
|
|||
useBindAllInvitesAtom(mx, allInvitesAtom);
|
||||
useBindAllRoomsAtom(mx, allRoomsAtom);
|
||||
useBindRoomToParentsAtom(mx, roomToParentsAtom);
|
||||
useBindMutedRoomsAtom(mx, mutedRoomsAtom);
|
||||
useBindRoomToUnreadAtom(mx, roomToUnreadAtom, muteChangesAtom);
|
||||
useBindRoomToUnreadAtom(mx, roomToUnreadAtom);
|
||||
|
||||
useBindRoomIdToTypingMembersAtom(mx, roomIdToTypingMembersAtom);
|
||||
};
|
||||
|
|
|
@ -1,98 +0,0 @@
|
|||
import { atom, useSetAtom } from 'jotai';
|
||||
import { ClientEvent, IPushRule, IPushRules, MatrixClient, MatrixEvent } from 'matrix-js-sdk';
|
||||
import { useEffect } from 'react';
|
||||
import { MuteChanges } from '../../../types/matrix/room';
|
||||
import { findMutedRule, isMutedRule } from '../../utils/room';
|
||||
|
||||
export type MutedRoomsUpdate =
|
||||
| {
|
||||
type: 'INITIALIZE';
|
||||
addRooms: string[];
|
||||
}
|
||||
| {
|
||||
type: 'UPDATE';
|
||||
addRooms: string[];
|
||||
removeRooms: string[];
|
||||
};
|
||||
|
||||
export const muteChangesAtom = atom<MuteChanges>({
|
||||
added: [],
|
||||
removed: [],
|
||||
});
|
||||
|
||||
const baseMutedRoomsAtom = atom(new Set<string>());
|
||||
export const mutedRoomsAtom = atom<Set<string>, [MutedRoomsUpdate], undefined>(
|
||||
(get) => get(baseMutedRoomsAtom),
|
||||
(get, set, action) => {
|
||||
const mutedRooms = new Set([...get(mutedRoomsAtom)]);
|
||||
if (action.type === 'INITIALIZE') {
|
||||
set(baseMutedRoomsAtom, new Set([...action.addRooms]));
|
||||
set(muteChangesAtom, {
|
||||
added: [...action.addRooms],
|
||||
removed: [],
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (action.type === 'UPDATE') {
|
||||
action.removeRooms.forEach((roomId) => mutedRooms.delete(roomId));
|
||||
action.addRooms.forEach((roomId) => mutedRooms.add(roomId));
|
||||
set(baseMutedRoomsAtom, mutedRooms);
|
||||
set(muteChangesAtom, {
|
||||
added: [...action.addRooms],
|
||||
removed: [...action.removeRooms],
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const useBindMutedRoomsAtom = (mx: MatrixClient, mutedAtom: typeof mutedRoomsAtom) => {
|
||||
const setMuted = useSetAtom(mutedAtom);
|
||||
|
||||
useEffect(() => {
|
||||
const overrideRules = mx.getAccountData('m.push_rules')?.getContent<IPushRules>()
|
||||
?.global?.override;
|
||||
if (overrideRules) {
|
||||
const mutedRooms = overrideRules.reduce<string[]>((rooms, rule) => {
|
||||
if (isMutedRule(rule)) rooms.push(rule.rule_id);
|
||||
return rooms;
|
||||
}, []);
|
||||
setMuted({
|
||||
type: 'INITIALIZE',
|
||||
addRooms: mutedRooms,
|
||||
});
|
||||
}
|
||||
}, [mx, setMuted]);
|
||||
|
||||
useEffect(() => {
|
||||
const handlePushRules = (mEvent: MatrixEvent, oldMEvent?: MatrixEvent) => {
|
||||
if (mEvent.getType() === 'm.push_rules') {
|
||||
const override = mEvent?.getContent()?.global?.override as IPushRule[] | undefined;
|
||||
const oldOverride = oldMEvent?.getContent()?.global?.override as IPushRule[] | undefined;
|
||||
if (!override || !oldOverride) return;
|
||||
|
||||
const isMuteToggled = (rule: IPushRule, otherOverride: IPushRule[]) => {
|
||||
const roomId = rule.rule_id;
|
||||
|
||||
const isMuted = isMutedRule(rule);
|
||||
if (!isMuted) return false;
|
||||
const isOtherMuted = findMutedRule(otherOverride, roomId);
|
||||
if (isOtherMuted) return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
const mutedRules = override.filter((rule) => isMuteToggled(rule, oldOverride));
|
||||
const unMutedRules = oldOverride.filter((rule) => isMuteToggled(rule, override));
|
||||
|
||||
setMuted({
|
||||
type: 'UPDATE',
|
||||
addRooms: mutedRules.map((rule) => rule.rule_id),
|
||||
removeRooms: unMutedRules.map((rule) => rule.rule_id),
|
||||
});
|
||||
}
|
||||
};
|
||||
mx.on(ClientEvent.AccountData, handlePushRules);
|
||||
return () => {
|
||||
mx.removeListener(ClientEvent.AccountData, handlePushRules);
|
||||
};
|
||||
}, [mx, setMuted]);
|
||||
};
|
|
@ -1,5 +1,5 @@
|
|||
import produce from 'immer';
|
||||
import { atom, useSetAtom, PrimitiveAtom, useAtomValue } from 'jotai';
|
||||
import { atom, useSetAtom } from 'jotai';
|
||||
import {
|
||||
IRoomTimelineData,
|
||||
MatrixClient,
|
||||
|
@ -11,7 +11,6 @@ import {
|
|||
import { ReceiptContent, ReceiptType } from 'matrix-js-sdk/lib/@types/read_receipts';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import {
|
||||
MuteChanges,
|
||||
Membership,
|
||||
NotificationType,
|
||||
RoomToUnread,
|
||||
|
@ -25,11 +24,11 @@ import {
|
|||
getUnreadInfo,
|
||||
getUnreadInfos,
|
||||
isNotificationEvent,
|
||||
roomHaveUnread,
|
||||
} from '../../utils/room';
|
||||
import { roomToParentsAtom } from './roomToParents';
|
||||
import { useStateEventCallback } from '../../hooks/useStateEventCallback';
|
||||
import { useSyncState } from '../../hooks/useSyncState';
|
||||
import { useRoomsNotificationPreferencesContext } from '../../hooks/useRoomsNotificationPreferences';
|
||||
|
||||
export type RoomToUnreadAction =
|
||||
| {
|
||||
|
@ -167,13 +166,9 @@ export const roomToUnreadAtom = atom<RoomToUnread, [RoomToUnreadAction], undefin
|
|||
}
|
||||
);
|
||||
|
||||
export const useBindRoomToUnreadAtom = (
|
||||
mx: MatrixClient,
|
||||
unreadAtom: typeof roomToUnreadAtom,
|
||||
muteChangesAtom: PrimitiveAtom<MuteChanges>
|
||||
) => {
|
||||
export const useBindRoomToUnreadAtom = (mx: MatrixClient, unreadAtom: typeof roomToUnreadAtom) => {
|
||||
const setUnreadAtom = useSetAtom(unreadAtom);
|
||||
const muteChanges = useAtomValue(muteChangesAtom);
|
||||
const roomsNotificationPreferences = useRoomsNotificationPreferencesContext();
|
||||
|
||||
useEffect(() => {
|
||||
setUnreadAtom({
|
||||
|
@ -249,16 +244,11 @@ export const useBindRoomToUnreadAtom = (
|
|||
}, [mx, setUnreadAtom]);
|
||||
|
||||
useEffect(() => {
|
||||
muteChanges.removed.forEach((roomId) => {
|
||||
const room = mx.getRoom(roomId);
|
||||
if (!room) return;
|
||||
if (!roomHaveUnread(mx, room)) return;
|
||||
setUnreadAtom({ type: 'PUT', unreadInfo: getUnreadInfo(room) });
|
||||
setUnreadAtom({
|
||||
type: 'RESET',
|
||||
unreadInfos: getUnreadInfos(mx),
|
||||
});
|
||||
muteChanges.added.forEach((roomId) => {
|
||||
setUnreadAtom({ type: 'DELETE', roomId });
|
||||
});
|
||||
}, [mx, setUnreadAtom, muteChanges]);
|
||||
}, [mx, setUnreadAtom, roomsNotificationPreferences]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleMembershipChange = (room: Room, membership: string) => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue