import { useCallback } from 'react'; import { useAtomValue } from 'jotai'; import { MatrixClient } from 'matrix-js-sdk'; import { AccountDataEvent } from '../../types/matrix/accountData'; import { threadNotificationsAtom } from '../state/threadNotifications'; import { getThreadNotificationMode, pruneThreadNotifications, ThreadNotificationEntry, ThreadNotificationMode, ThreadNotificationsContent, } from '../utils/threadNotifications'; import { useMatrixClient } from './useMatrixClient'; import { AsyncState, useAsyncCallback } from './useAsyncCallback'; /** Read the current notification mode for a thread from the bound atom. */ export function useThreadNotificationMode( roomId: string, threadRootId: string, ): ThreadNotificationMode { const content = useAtomValue(threadNotificationsAtom); return getThreadNotificationMode(content, roomId, threadRootId); } const readContent = (mx: MatrixClient): ThreadNotificationsContent => ((mx as any).getAccountData(AccountDataEvent.LotusThreadNotifications)?.getContent() as | ThreadNotificationsContent | undefined) ?? {}; const getJoinedRoomIds = (mx: MatrixClient): Set => { const joined = new Set(); mx.getRooms().forEach((room) => { if (room.getMyMembership() === 'join') { joined.add(room.roomId); } }); return joined; }; const writeThreadNotificationMode = async ( mx: MatrixClient, roomId: string, threadRootId: string, mode: ThreadNotificationMode, ): Promise => { const current = readContent(mx); const now = Date.now(); // Work on a mutable clone; prune produces a fresh object so the mutations // below never touch the atom's/account-data's current content. const next: ThreadNotificationsContent = { ...current, rooms: Object.fromEntries( Object.entries(current.rooms ?? {}).map(([rid, entries]) => [rid, { ...entries }]), ), }; const rooms = next.rooms as Record>; if (mode === ThreadNotificationMode.Default) { if (rooms[roomId]) { delete rooms[roomId][threadRootId]; if (Object.keys(rooms[roomId]).length === 0) { delete rooms[roomId]; } } } else { if (!rooms[roomId]) { rooms[roomId] = {}; } rooms[roomId][threadRootId] = { mode, ts: now }; } // ALWAYS prune before persisting to keep account data bounded. const finalContent = pruneThreadNotifications(next, getJoinedRoomIds(mx), now); await (mx as any).setAccountData(AccountDataEvent.LotusThreadNotifications, finalContent); }; export function useSetThreadNotificationMode( roomId: string, threadRootId: string, ): { modeState: AsyncState; setMode: (mode: ThreadNotificationMode) => Promise; } { const mx = useMatrixClient(); const [modeState, setMode] = useAsyncCallback( useCallback( (mode: ThreadNotificationMode) => writeThreadNotificationMode(mx, roomId, threadRootId, mode), [mx, roomId, threadRootId], ), ); return { modeState, setMode }; }