fix(channels): render rooms list non-virtualized so re-picking a workspace on native doesn't strand the list empty after a remount
This commit is contained in:
parent
136aacded1
commit
fda6c7bd7e
1 changed files with 41 additions and 58 deletions
|
|
@ -3,14 +3,12 @@ import { useTranslation } from 'react-i18next';
|
|||
import { useAtom, useAtomValue } from 'jotai';
|
||||
import { Box, config } from 'folds';
|
||||
import { Room } from 'matrix-js-sdk';
|
||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||
import { mDirectAtom } from '../../../state/mDirectList';
|
||||
import { NavCategory, NavCategoryHeader } from '../../../components/nav';
|
||||
import { getCanonicalAliasOrRoomId } from '../../../utils/matrix';
|
||||
import { useSelectedRoom } from '../../../hooks/router/useSelectedRoom';
|
||||
import { useSpace } from '../../../hooks/useSpace';
|
||||
import { VirtualTile } from '../../../components/virtualizer';
|
||||
import { RoomNavCategoryButton, RoomNavItem } from '../../../features/room-nav';
|
||||
import { makeNavCategoryId } from '../../../state/closedNavCategories';
|
||||
import { roomToUnreadAtom } from '../../../state/room/roomToUnread';
|
||||
|
|
@ -26,19 +24,28 @@ import {
|
|||
import { getChannelsRoomPath } from '../../pathUtils';
|
||||
|
||||
type ChannelsListProps = {
|
||||
// Scroll container ref — owned by the parent `PageNavContent` so virtualizer
|
||||
// measures the actual scrollable element instead of an inner div nested
|
||||
// inside `<Scroll>`. Wrong scroll target → clientHeight reads as 0 →
|
||||
// virtualizer renders no rows. Match `Space.tsx` pattern.
|
||||
// Kept for API compatibility with PageNavContent's scrollRef wiring even
|
||||
// though the list itself no longer virtualizes — parent still passes the
|
||||
// ref so other consumers (StreamHeader curtain gesture) can read it.
|
||||
scrollRef: MutableRefObject<HTMLDivElement | null>;
|
||||
};
|
||||
|
||||
// Joined-hierarchy list rendered inside the channels left pane. Mirrors the
|
||||
// scaffold of `Space.tsx` (virtualized hierarchy of joined rooms grouped by
|
||||
// sub-spaces) but routes selections through the /channels/<space>/<room>/
|
||||
// path so the room timeline opens inside the channels surface, not the
|
||||
// legacy /<spaceId>/<room>/ tree.
|
||||
export function ChannelsList({ scrollRef }: ChannelsListProps) {
|
||||
// scaffold of `Space.tsx` (hierarchy of joined rooms grouped by sub-spaces)
|
||||
// but routes selections through the /channels/<space>/<room>/ path so the
|
||||
// room timeline opens inside the channels surface, not the legacy
|
||||
// /<spaceId>/<room>/ tree.
|
||||
//
|
||||
// NOT virtualized on purpose. Lists in the channels pane are bounded (one
|
||||
// orphan workspace + its joined rooms — typically tens, not thousands),
|
||||
// and @tanstack/react-virtual had a fragile race here with folds' Scroll
|
||||
// component: every re-render of `<Scroll>` creates a fresh inline ref
|
||||
// callback, React 18 detaches the old callback (nulling scrollRef.current)
|
||||
// before children's useLayoutEffects run, and the virtualizer's
|
||||
// `_willUpdate` saw null on its first chance to subscribe — leaving the
|
||||
// list permanently empty on the 2nd+ remount of the same workspace. Plain
|
||||
// `.map()` sidesteps the whole subscription dance.
|
||||
export function ChannelsList({ scrollRef: _scrollRef }: ChannelsListProps) {
|
||||
const { t } = useTranslation();
|
||||
const mx = useMatrixClient();
|
||||
const space = useSpace();
|
||||
|
|
@ -84,13 +91,6 @@ export function ChannelsList({ scrollRef }: ChannelsListProps) {
|
|||
)
|
||||
);
|
||||
|
||||
const virtualizer = useVirtualizer({
|
||||
count: hierarchy.length,
|
||||
getScrollElement: () => scrollRef.current,
|
||||
estimateSize: () => 0,
|
||||
overscan: 10,
|
||||
});
|
||||
|
||||
const handleCategoryClick = useCategoryHandler(setClosedCategories, (categoryId) =>
|
||||
closedCategories.has(categoryId)
|
||||
);
|
||||
|
|
@ -100,59 +100,42 @@ export function ChannelsList({ scrollRef }: ChannelsListProps) {
|
|||
|
||||
return (
|
||||
<Box direction="Column" gap="300">
|
||||
<NavCategory
|
||||
style={{
|
||||
height: virtualizer.getTotalSize(),
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
{virtualizer.getVirtualItems().map((vItem) => {
|
||||
const { roomId } = hierarchy[vItem.index] ?? {};
|
||||
<NavCategory>
|
||||
{hierarchy.map((item, index) => {
|
||||
const { roomId } = item;
|
||||
const room = mx.getRoom(roomId);
|
||||
if (!room) return null;
|
||||
|
||||
if (room.isSpaceRoom()) {
|
||||
const categoryId = makeNavCategoryId(space.roomId, roomId);
|
||||
return (
|
||||
<VirtualTile
|
||||
virtualItem={vItem}
|
||||
key={vItem.index}
|
||||
ref={virtualizer.measureElement}
|
||||
>
|
||||
<div style={{ paddingTop: vItem.index === 0 ? undefined : config.space.S400 }}>
|
||||
<NavCategoryHeader>
|
||||
<RoomNavCategoryButton
|
||||
data-category-id={categoryId}
|
||||
onClick={handleCategoryClick}
|
||||
closed={closedCategories.has(categoryId)}
|
||||
>
|
||||
{roomId === space.roomId ? t('Channels.root_category') : room?.name}
|
||||
</RoomNavCategoryButton>
|
||||
</NavCategoryHeader>
|
||||
</div>
|
||||
</VirtualTile>
|
||||
<div key={roomId} style={{ paddingTop: index === 0 ? undefined : config.space.S400 }}>
|
||||
<NavCategoryHeader>
|
||||
<RoomNavCategoryButton
|
||||
data-category-id={categoryId}
|
||||
onClick={handleCategoryClick}
|
||||
closed={closedCategories.has(categoryId)}
|
||||
>
|
||||
{roomId === space.roomId ? t('Channels.root_category') : room?.name}
|
||||
</RoomNavCategoryButton>
|
||||
</NavCategoryHeader>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<VirtualTile
|
||||
virtualItem={vItem}
|
||||
key={vItem.index}
|
||||
ref={virtualizer.measureElement}
|
||||
>
|
||||
<RoomNavItem
|
||||
room={room}
|
||||
selected={selectedRoomId === roomId}
|
||||
showAvatar={mDirects.has(roomId)}
|
||||
direct={mDirects.has(roomId)}
|
||||
linkPath={getToLink(roomId)}
|
||||
notificationMode={getRoomNotificationMode(notificationPreferences, room.roomId)}
|
||||
/>
|
||||
</VirtualTile>
|
||||
<RoomNavItem
|
||||
key={roomId}
|
||||
room={room}
|
||||
selected={selectedRoomId === roomId}
|
||||
showAvatar={mDirects.has(roomId)}
|
||||
direct={mDirects.has(roomId)}
|
||||
linkPath={getToLink(roomId)}
|
||||
notificationMode={getRoomNotificationMode(notificationPreferences, room.roomId)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</NavCategory>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue