diff --git a/src/app/features/lobby/Lobby.tsx b/src/app/features/lobby/Lobby.tsx index 5dd54fa0..ef6ea8ef 100644 --- a/src/app/features/lobby/Lobby.tsx +++ b/src/app/features/lobby/Lobby.tsx @@ -1,4 +1,4 @@ -import React, { MouseEventHandler, useCallback, useMemo, useRef, useState } from 'react'; +import React, { MouseEventHandler, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { Box, Chip, Icon, IconButton, Icons, Line, Scroll, Spinner, Text, config } from 'folds'; import { useVirtualizer } from '@tanstack/react-virtual'; import { useAtom, useAtomValue } from 'jotai'; @@ -199,6 +199,11 @@ export function Lobby() { [mx] ); + const closedCategoriesCache = useRef(new Map()); + useEffect(() => { + closedCategoriesCache.current.clear(); + }, [closedCategories, roomToParents, getRoom]); + /** * Recursively checks if a given parentId (or all its ancestors) is in a closed category. * @@ -209,19 +214,29 @@ export function Lobby() { */ const getInClosedCategories = useCallback( (spaceId: string, parentId: string, previousId?: string): boolean => { + const categoryId = makeLobbyCategoryId(spaceId, parentId); + if (closedCategoriesCache.current.has(categoryId)) { + return closedCategoriesCache.current.get(categoryId); + } + // Ignore root space being collapsed if in a subspace, // this is due to many spaces dumping all rooms in the top-level space. if (parentId === spaceId) { if (previousId) { - if (getRoom(previousId)?.isSpaceRoom() || spaceRooms.has(previousId)) return false; + if (getRoom(previousId)?.isSpaceRoom() || spaceRooms.has(previousId)) { + closedCategoriesCache.current.set(categoryId, false); + return false; + } } } - if (closedCategories.has(makeLobbyCategoryId(spaceId, parentId))) { + if (closedCategories.has(categoryId)) { + closedCategoriesCache.current.set(categoryId, true); return true; } const parentParentIds = roomToParents.get(parentId); if (!parentParentIds || parentParentIds.size === 0) { + closedCategoriesCache.current.set(categoryId, false); return false; } @@ -232,6 +247,7 @@ export function Lobby() { } }); + closedCategoriesCache.current.set(categoryId, !anyOpen); return !anyOpen; }, [closedCategories, getRoom, roomToParents, spaceRooms] diff --git a/src/app/hooks/useSpaceHierarchy.ts b/src/app/hooks/useSpaceHierarchy.ts index 62cca99b..52dd12b7 100644 --- a/src/app/hooks/useSpaceHierarchy.ts +++ b/src/app/hooks/useSpaceHierarchy.ts @@ -208,9 +208,10 @@ const getSpaceJoinedHierarchy = ( const space = getRoom(spaceId); if (!space) return false; - const childEvents = getStateEvents(space, StateEvent.SpaceChild).filter(isValidChild); + const childEvents = getStateEvents(space, StateEvent.SpaceChild); return childEvents.some((childEvent): boolean => { + if (!isValidChild(childEvent)) return false; const childId = childEvent.getStateKey(); if (!childId || !isRoomId(childId)) return false; const room = getRoom(childId); diff --git a/src/app/pages/client/space/Space.tsx b/src/app/pages/client/space/Space.tsx index 5ad9cf68..6e8c51b8 100644 --- a/src/app/pages/client/space/Space.tsx +++ b/src/app/pages/client/space/Space.tsx @@ -2,6 +2,7 @@ import React, { MouseEventHandler, forwardRef, useCallback, + useEffect, useMemo, useRef, useState, @@ -315,6 +316,13 @@ export function Space() { [mx, allJoinedRooms] ); + const closedCategoriesCache = useRef(new Map()); + const ancestorsCollapsedCache = useRef(new Map()); + useEffect(() => { + closedCategoriesCache.current.clear(); + ancestorsCollapsedCache.current.clear(); + }, [closedCategories, roomToParents, getRoom]); + /** * Recursively checks if a given parentId (or all its ancestors) is in a closed category. * @@ -325,20 +333,30 @@ export function Space() { */ const getInClosedCategories = useCallback( (spaceId: string, parentId: string, previousId?: string): boolean => { + const categoryId = makeNavCategoryId(spaceId, parentId); + if (closedCategoriesCache.current.has(categoryId)) { + return closedCategoriesCache.current.get(categoryId); + } + // Ignore root space being collapsed if in a subspace, // this is due to many spaces dumping all rooms in the top-level space. if (parentId === spaceId) { if (previousId) { - if (getRoom(previousId)?.isSpaceRoom()) return false; + if (getRoom(previousId)?.isSpaceRoom()) { + closedCategoriesCache.current.set(categoryId, false); + return false; + } } } - if (closedCategories.has(makeNavCategoryId(spaceId, parentId))) { + if (closedCategories.has(categoryId)) { + closedCategoriesCache.current.set(categoryId, true); return true; } const parentParentIds = roomToParents.get(parentId); if (!parentParentIds || parentParentIds.size === 0) { + closedCategoriesCache.current.set(categoryId, false); return false; } @@ -349,6 +367,7 @@ export function Space() { } }); + closedCategoriesCache.current.set(categoryId, !anyOpen); return !anyOpen; }, [closedCategories, getRoom, roomToParents] @@ -391,8 +410,14 @@ export function Space() { * @returns True if every parent category is collapsed; false otherwise. */ const getAllAncestorsCollapsed = (spaceId: string, roomId: string): boolean => { + const categoryId = makeNavCategoryId(spaceId, roomId); + if (ancestorsCollapsedCache.current.has(categoryId)) { + return ancestorsCollapsedCache.current.get(categoryId); + } + const parentIds = roomToParents.get(roomId); if (!parentIds || parentIds.size === 0) { + ancestorsCollapsedCache.current.set(categoryId, false); return false; } @@ -402,6 +427,8 @@ export function Space() { allCollapsed = false; } }); + + ancestorsCollapsedCache.current.set(categoryId, allCollapsed); return allCollapsed; };