import { useCallback } from 'react'; import { NavigateOptions, useLocation, useNavigate } from 'react-router-dom'; import { useAtomValue } from 'jotai'; import { getCanonicalAliasOrRoomId } from '../utils/matrix'; import { getDirectRoomPath, getHomeRoomPath, getSpacePath, getSpaceRoomPath, withRoomEventId, } from '../pages/pathUtils'; import { useMatrixClient } from './useMatrixClient'; import { getOrphanParents, guessPerfectParent, isSpace } from '../utils/room'; import { roomToParentsAtom } from '../state/room/roomToParents'; import { mDirectAtom } from '../state/mDirectList'; import { useSelectedRoom } from './router/useSelectedRoom'; import { useSelectedSpace } from './router/useSelectedSpace'; import { settingsAtom } from '../state/settings'; import { useSetting } from '../state/hooks/settings'; export const useRoomNavigate = () => { const navigate = useNavigate(); const location = useLocation(); const mx = useMatrixClient(); const roomToParents = useAtomValue(roomToParentsAtom); const mDirects = useAtomValue(mDirectAtom); const spaceSelectedId = useSelectedSpace(); const selectedRoomId = useSelectedRoom(); const [developerTools] = useSetting(settingsAtom, 'developerTools'); const navigateSpace = useCallback( (roomId: string) => { const roomIdOrAlias = getCanonicalAliasOrRoomId(mx, roomId); navigate(getSpacePath(roomIdOrAlias)); }, [mx, navigate] ); const navigateRoom = useCallback( (roomId: string, eventId?: string, opts?: NavigateOptions) => { // Already viewing this room — keep the current home/direct/space URL and only // change the event id. Otherwise guessPerfectParent can yank the sidebar to // wherever the room "belongs". if (selectedRoomId === roomId) { navigate(withRoomEventId(location.pathname, eventId), opts); return; } const room = mx.getRoom(roomId); const roomIdOrAlias = getCanonicalAliasOrRoomId(mx, roomId); const openSpaceTimeline = developerTools && spaceSelectedId === roomId; if (room && isSpace(room) && !openSpaceTimeline) { navigate(getSpacePath(roomIdOrAlias), opts); return; } const orphanParents = openSpaceTimeline ? [roomId] : getOrphanParents(roomToParents, roomId); // Prefer Direct for m.direct rooms unless we are already browsing a parent space // of this room (keep space chrome). Opening via space while roomToParents is still // catching up used to hit JoinBeforeNavigate even though the DM was listed. if (mDirects.has(roomId)) { const stayInSelectedSpace = Boolean(spaceSelectedId) && orphanParents.includes(spaceSelectedId!); if (!stayInSelectedSpace) { navigate(getDirectRoomPath(roomIdOrAlias, eventId), opts); return; } } if (orphanParents.length > 0) { let parentSpace: string; if (spaceSelectedId && orphanParents.includes(spaceSelectedId)) { parentSpace = spaceSelectedId; } else { parentSpace = guessPerfectParent(mx, roomId, orphanParents) ?? orphanParents[0]; } const pSpaceIdOrAlias = getCanonicalAliasOrRoomId(mx, parentSpace); navigate( getSpaceRoomPath(pSpaceIdOrAlias, openSpaceTimeline ? roomId : roomIdOrAlias, eventId), opts ); return; } navigate(getHomeRoomPath(roomIdOrAlias, eventId), opts); }, [ mx, navigate, location.pathname, selectedRoomId, spaceSelectedId, roomToParents, mDirects, developerTools, ] ); return { navigateSpace, navigateRoom, }; };