diff --git a/src/app/features/search/Search.tsx b/src/app/features/search/Search.tsx index cfc4ee5f..1890049a 100644 --- a/src/app/features/search/Search.tsx +++ b/src/app/features/search/Search.tsx @@ -27,9 +27,7 @@ import React, { } from 'react'; import { isKeyHotkey } from 'is-hotkey'; import { useAtom, useAtomValue } from 'jotai'; -import { Room } from 'matrix-js-sdk'; import { useDirects, useOrphanSpaces, useRooms, useSpaces } from '../../state/hooks/roomList'; -import { useMatrixClient } from '../../hooks/useMatrixClient'; import { mDirectAtom } from '../../state/mDirectList'; import { allRoomsAtom } from '../../state/room-list/roomList'; import { @@ -37,28 +35,31 @@ import { useAsyncSearch, UseAsyncSearchOptions, } from '../../hooks/useAsyncSearch'; -import { useAllJoinedRoomsSet, useGetRoom } from '../../hooks/useGetRoom'; import { RoomAvatar, RoomIcon } from '../../components/room-avatar'; -import { - getAllParents, - getDirectRoomAvatarUrl, - getRoomAvatarUrl, - guessPerfectParent, -} from '../../utils/room'; +import { getAllParents, guessPerfectParent } from '../../utils/room'; import { highlightText, makeHighlightRegex } from '../../plugins/react-custom-html-parser'; import { factoryRoomIdByActivity } from '../../utils/sort'; import { nameInitials } from '../../utils/common'; import { useRoomNavigate } from '../../hooks/useRoomNavigate'; import { useListFocusIndex } from '../../hooks/useListFocusIndex'; -import { getMxIdLocalPart, getMxIdServer, guessDmRoomUserId } from '../../utils/matrix'; +import { getMxIdLocalPart, getMxIdServer } from '../../utils/matrix'; import { roomToParentsAtom } from '../../state/room/roomToParents'; -import { roomToUnreadAtom } from '../../state/room/roomToUnread'; +import { unreadRoomsAtom } from '../../state/room/roomToUnread'; import { UnreadBadge, UnreadBadgeCenter } from '../../components/unread-badge'; import { searchModalAtom } from '../../state/searchModal'; import { useKeyDown } from '../../hooks/useKeyDown'; import { useMediaAuthentication } from '../../hooks/useMediaAuthentication'; import { KeySymbol } from '../../utils/key-symbol'; import { isMacOS } from '../../utils/user-agent'; +import { useClient } from '../../hooks/useClient'; +import { useObservableValue } from '../../hooks/useObservableValue'; +import { roomToNotificationsAtom } from '../../state/room/roomToNotifications'; +import { useGetRoomName } from '../../hooks/storage/useGetRoomName'; +import { useRoomHeroes } from '../../hooks/storage/useRoomHeros'; +import { useGetRoomAvatar } from '../../hooks/storage/useGetRoomAvatar'; +import { getRoomTotalNotifications } from '../../state/hooks/roomNotifications'; +import { mxcToHttp } from '../../../client/mxcToHttp'; +import { useRoomsPowerLevels } from '../../hooks/usePowerLevels'; enum SearchRoomType { Rooms = '#', @@ -79,30 +80,23 @@ const useTopActiveRooms = ( directs: string[], spaces: string[] ) => { - const mx = useMatrixClient(); + const ct = useClient(); + const roomToLatestEvent = useObservableValue(ct.roomToLatestEvent); return useMemo(() => { + const getTimestamp = (rId: string) => roomToLatestEvent.get(rId)?.origin_server_ts; + if (searchRoomType === SearchRoomType.Spaces) { return spaces; } if (searchRoomType === SearchRoomType.Directs) { - return [...directs].sort(factoryRoomIdByActivity(mx)).slice(0, 20); + return [...directs].sort(factoryRoomIdByActivity(getTimestamp)).slice(0, 20); } if (searchRoomType === SearchRoomType.Rooms) { - return [...rooms].sort(factoryRoomIdByActivity(mx)).slice(0, 20); + return [...rooms].sort(factoryRoomIdByActivity(getTimestamp)).slice(0, 20); } - return [...rooms, ...directs].sort(factoryRoomIdByActivity(mx)).slice(0, 20); - }, [mx, rooms, directs, spaces, searchRoomType]); -}; - -const getDmUserId = ( - roomId: string, - getRoom: (roomId: string) => Room | undefined, - myUserId: string -): string | undefined => { - const room = getRoom(roomId); - const targetUserId = room && guessDmRoomUserId(room, myUserId); - return targetUserId; + return [...rooms, ...directs].sort(factoryRoomIdByActivity(getTimestamp)).slice(0, 20); + }, [roomToLatestEvent, rooms, directs, spaces, searchRoomType]); }; const useSearchTargetRooms = ( @@ -135,39 +129,37 @@ type SearchProps = { requestClose: () => void; }; export function Search({ requestClose }: SearchProps) { - const mx = useMatrixClient(); + const ct = useClient(); const useAuthentication = useMediaAuthentication(); const scrollRef = useRef(null); const inputRef = useRef(null); const { navigateRoom, navigateSpace } = useRoomNavigate(); - const roomToUnread = useAtomValue(roomToUnreadAtom); + const unreadRooms = useAtomValue(unreadRoomsAtom); + const roomToNotifications = useAtomValue(roomToNotificationsAtom); const [searchRoomType, setSearchRoomType] = useState(); - const allRoomsSet = useAllJoinedRoomsSet(); - const getRoom = useGetRoom(allRoomsSet); - const roomToParents = useAtomValue(roomToParentsAtom); - const orphanSpaces = useOrphanSpaces(mx, allRoomsAtom, roomToParents); + const orphanSpaces = useOrphanSpaces(ct, allRoomsAtom, roomToParents); const mDirects = useAtomValue(mDirectAtom); - const rooms = useRooms(mx, allRoomsAtom, mDirects); - const spaces = useSpaces(mx, allRoomsAtom); - const directs = useDirects(mx, allRoomsAtom, mDirects); + const rooms = useRooms(ct, allRoomsAtom, mDirects); + const spaces = useSpaces(ct, allRoomsAtom); + const directs = useDirects(ct, allRoomsAtom, mDirects); + const directRoomToUser = useObservableValue(ct.directRoomToUser); + const roomToPowerLevels = useRoomsPowerLevels(ct.rooms.join.read); + + const roomToJoinRule = useObservableValue(ct.roomToJoinRule); const topActiveRooms = useTopActiveRooms(searchRoomType, rooms, directs, spaces); const targetRooms = useSearchTargetRooms(searchRoomType, rooms, directs, spaces); + const [getRoomHeroes, roomToHeroMembersResult] = useRoomHeroes(directs); + const getRoomName = useGetRoomName(getRoomHeroes, roomToHeroMembersResult.value); + const getRoomAvatar = useGetRoomAvatar(getRoomHeroes, roomToHeroMembersResult.value); + const getTargetStr: SearchItemStrGetter = useCallback( - (roomId: string) => { - const roomName = getRoom(roomId)?.name ?? roomId; - if (mDirects.has(roomId)) { - const targetUserId = getDmUserId(roomId, getRoom, mx.getSafeUserId()); - const targetUsername = targetUserId && getMxIdLocalPart(targetUserId); - if (targetUsername) return [roomName, targetUsername]; - } - return roomName; - }, - [getRoom, mDirects, mx] + (roomId: string) => getRoomName(roomId) ?? 'Unknown', + [getRoomName] ); const [result, search, resetSearch] = useAsyncSearch(targetRooms, getTargetStr, SEARCH_OPTIONS); @@ -299,25 +291,42 @@ export function Search({ requestClose }: SearchProps) {
{roomsToRender.map((roomId, index) => { - const room = getRoom(roomId); - if (!room) return null; - const dm = mDirects.has(roomId); - const dmUserId = dm && getDmUserId(roomId, getRoom, mx.getSafeUserId()); + const dmUserId = dm && directRoomToUser.get(roomId); const dmUsername = dmUserId && getMxIdLocalPart(dmUserId); const dmUserServer = dmUserId && getMxIdServer(dmUserId); + const isSpace = ct.spaceRooms.read.has(roomId); const allParents = getAllParents(roomToParents, roomId); const orphanParents = allParents && orphanSpaces.filter((o) => allParents.has(o)); const perfectOrphanParent = - orphanParents && guessPerfectParent(mx, roomId, orphanParents); + orphanParents && + guessPerfectParent( + roomId, + orphanParents, + (rId) => ct.roomToCreators.read.get(rId), + (rId) => roomToPowerLevels.get(rId) + ); const exactParents = roomToParents.get(roomId); const perfectParent = - exactParents && guessPerfectParent(mx, roomId, Array.from(exactParents)); + exactParents && + guessPerfectParent( + roomId, + Array.from(exactParents), + (rId) => ct.roomToCreators.read.get(rId), + (rId) => roomToPowerLevels.get(rId) + ); - const unread = roomToUnread.get(roomId); + const unread = unreadRooms.has(roomId); + const n = roomToNotifications.get(roomId); + const notifications = n && getRoomTotalNotifications(n); + const unreadOrNotification = + (notifications && notifications.count > 0) || unread; + + const avatarMxc = getRoomAvatar(roomId); + const roomName: string = getRoomName(roomId) ?? 'Unknown'; return ( - {getRoom(perfectOrphanParent)?.name ?? perfectOrphanParent} + {getRoomName(perfectOrphanParent) ?? perfectOrphanParent} )} - {unread && ( + {unreadOrNotification && ( 0} - count={unread.total} + highlight={notifications?.highlight} + count={notifications?.count ?? 0} /> )} @@ -354,26 +363,29 @@ export function Search({ requestClose }: SearchProps) { } before={ - {dm || room.isSpaceRoom() ? ( + {dm || isSpace ? ( ( - {nameInitials(room.name)} + {nameInitials(roomName)} )} /> ) : ( )} @@ -382,8 +394,8 @@ export function Search({ requestClose }: SearchProps) { {queryHighlighRegex - ? highlightText(queryHighlighRegex, [room.name]) - : room.name} + ? highlightText(queryHighlighRegex, [roomName]) + : roomName} {dmUsername && ( @@ -395,7 +407,7 @@ export function Search({ requestClose }: SearchProps) { )} {!dm && perfectParent && perfectParent !== perfectOrphanParent && ( - — {getRoom(perfectParent)?.name ?? perfectParent} + — {getRoomName(perfectParent) ?? perfectParent} )} @@ -445,5 +457,5 @@ export function SearchModalRenderer() { ) ); - return opened && setOpen(false)} />; + return opened ? setOpen(false)} /> : null; } diff --git a/src/app/pages/Router.tsx b/src/app/pages/Router.tsx index 508331e8..f6d7ae61 100644 --- a/src/app/pages/Router.tsx +++ b/src/app/pages/Router.tsx @@ -135,7 +135,7 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize) > - {/**/} + diff --git a/src/app/utils/sort.ts b/src/app/utils/sort.ts index ee8aa832..b44f24c3 100644 --- a/src/app/utils/sort.ts +++ b/src/app/utils/sort.ts @@ -1,18 +1,10 @@ -import { MatrixClient } from 'matrix-js-sdk'; - export type SortFunc = (a: T, b: T) => number; export const factoryRoomIdByActivity = - (mx: MatrixClient): SortFunc => - (a, b) => { - const room1 = mx.getRoom(a); - const room2 = mx.getRoom(b); - - return ( - (room2?.getLastActiveTimestamp() ?? Number.MIN_SAFE_INTEGER) - - (room1?.getLastActiveTimestamp() ?? Number.MIN_SAFE_INTEGER) - ); - }; + (getLatestEventTs: (roomId: string) => number | undefined): SortFunc => + (a, b) => + (getLatestEventTs(b) ?? Number.MIN_SAFE_INTEGER) - + (getLatestEventTs(a) ?? Number.MIN_SAFE_INTEGER); export const factoryRoomIdByAtoZ = (getRoomName: (roomId: string) => string | undefined): SortFunc => diff --git a/src/client/context.ts b/src/client/context.ts index d536c283..45a1fd14 100644 --- a/src/client/context.ts +++ b/src/client/context.ts @@ -81,6 +81,7 @@ export type ClientContext = { rooms: RoomsContext; decryption: DecryptionManager; + directRoomToUser: ObservableValue>; directRooms: ObservableValue>; spaceRooms: ObservableValue>; encryptedRooms: ObservableValue>; @@ -445,28 +446,48 @@ export const createEncryptedRoomsObservable = ( return encryptedRoomsObservable; }; -export const createDirectRoomsObservable = async ( +export const createDirectRoomToUserObservable = async ( storage: Storage, disposer: Disposer -): Promise>> => { +): Promise>> => { const mDirectObj = await storage.accountData.observableRead.get(AccountDataType.Direct); - const directRoomsObservable = new ComputedObservableValue(() => { - const rooms = new Set(); + + const roomToUserObservable = new ComputedObservableValue(() => { + const roomToUser = new Map(); const userIdToRooms = mDirectObj.read?.event.content; - if (typeof userIdToRooms !== 'object') return rooms; + if (typeof userIdToRooms !== 'object' || userIdToRooms === null) { + return roomToUser; + } - Object.values(userIdToRooms).forEach((roomIds) => { + Object.entries(userIdToRooms).forEach(([userId, roomIds]) => { if (!Array.isArray(roomIds)) return; - roomIds.forEach((id) => { - if (typeof id === 'string') rooms.add(id); + + roomIds.forEach((roomId) => { + if (typeof roomId === 'string') { + roomToUser.set(roomId, userId); + } }); }); - return rooms; + return roomToUser; }, [mDirectObj]); - disposer.collect(mDirectObj, directRoomsObservable); + disposer.collect(mDirectObj, roomToUserObservable); + + return roomToUserObservable; +}; + +export const createDirectRoomsObservable = ( + roomToUserObservable: ObservableValue>, + disposer: Disposer +): ObservableValue> => { + const directRoomsObservable = new ComputedObservableValue( + () => new Set(roomToUserObservable.read?.keys() ?? []), + [roomToUserObservable] + ); + + disposer.collect(directRoomsObservable); return directRoomsObservable; }; diff --git a/src/client/initialize.ts b/src/client/initialize.ts index 5324192b..e9b6ba48 100644 --- a/src/client/initialize.ts +++ b/src/client/initialize.ts @@ -4,6 +4,7 @@ import { Session } from '../app/state/sessions'; import { ClientContext, createDirectRoomsObservable, + createDirectRoomToUserObservable, createEncryptedRoomsObservable, createLogoutObservable, createPendingMemberRoomObservable, @@ -50,7 +51,8 @@ export const initializeClient = async (session: Session): Promise disposer ); const encryptedRooms = createEncryptedRoomsObservable(state, disposer); - const directRooms = await createDirectRoomsObservable(storage, disposer); + const directRoomToUser = await createDirectRoomToUserObservable(storage, disposer); + const directRooms = createDirectRoomsObservable(directRoomToUser, disposer); const { roomToParents, roomToChildEvents } = createSpaceMapping(state, disposer); const roomToSummary = await createRoomToSummaryObservable(storage, disposer); @@ -101,6 +103,7 @@ export const initializeClient = async (session: Session): Promise rooms, decryption, + directRoomToUser, directRooms, spaceRooms, encryptedRooms,