fix hierarchy indenting and order

This commit is contained in:
Gimle Larpes 2025-06-27 10:05:56 +02:00
parent c0799142d6
commit 2fae418132
2 changed files with 68 additions and 36 deletions

View file

@ -1,6 +1,6 @@
import { atom, useAtom, useAtomValue } from 'jotai'; import { atom, useAtom, useAtomValue } from 'jotai';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { MatrixError, Room } from 'matrix-js-sdk'; import { MatrixError, MatrixEvent, Room } from 'matrix-js-sdk';
import { IHierarchyRoom } from 'matrix-js-sdk/lib/@types/spaces'; import { IHierarchyRoom } from 'matrix-js-sdk/lib/@types/spaces';
import { QueryFunction, useInfiniteQuery } from '@tanstack/react-query'; import { QueryFunction, useInfiniteQuery } from '@tanstack/react-query';
import { useMatrixClient } from './useMatrixClient'; import { useMatrixClient } from './useMatrixClient';
@ -18,6 +18,7 @@ export type HierarchyItemSpace = {
ts: number; ts: number;
space: true; space: true;
parentId?: string; parentId?: string;
depth: number;
}; };
export type HierarchyItemRoom = { export type HierarchyItemRoom = {
@ -25,6 +26,7 @@ export type HierarchyItemRoom = {
content: MSpaceChildContent; content: MSpaceChildContent;
ts: number; ts: number;
parentId: string; parentId: string;
depth: number;
}; };
export type HierarchyItem = HierarchyItemSpace | HierarchyItemRoom; export type HierarchyItem = HierarchyItemSpace | HierarchyItemRoom;
@ -35,6 +37,10 @@ const hierarchyItemTs: SortFunc<HierarchyItem> = (a, b) => byTsOldToNew(a.ts, b.
const hierarchyItemByOrder: SortFunc<HierarchyItem> = (a, b) => const hierarchyItemByOrder: SortFunc<HierarchyItem> = (a, b) =>
byOrderKey(a.content.order, b.content.order); byOrderKey(a.content.order, b.content.order);
const childEventTs: SortFunc<MatrixEvent> = (a, b) => byTsOldToNew(a.getTs(), b.getTs());
const childEventByOrder: SortFunc<MatrixEvent> = (a, b) =>
byOrderKey(a.getContent<MSpaceChildContent>().order, b.getContent<MSpaceChildContent>().order);
const getHierarchySpaces = ( const getHierarchySpaces = (
rootSpaceId: string, rootSpaceId: string,
getRoom: GetRoomCallback, getRoom: GetRoomCallback,
@ -45,8 +51,9 @@ const getHierarchySpaces = (
content: { via: [] }, content: { via: [] },
ts: 0, ts: 0,
space: true, space: true,
depth: 0,
}; };
let spaceItems: HierarchyItemSpace[] = []; const spaceItems: HierarchyItemSpace[] = [];
const findAndCollectHierarchySpaces = (spaceItem: HierarchyItemSpace) => { const findAndCollectHierarchySpaces = (spaceItem: HierarchyItemSpace) => {
if (spaceItems.find((item) => item.roomId === spaceItem.roomId)) return; if (spaceItems.find((item) => item.roomId === spaceItem.roomId)) return;
@ -55,37 +62,37 @@ const getHierarchySpaces = (
if (!space) return; if (!space) return;
const childEvents = getStateEvents(space, StateEvent.SpaceChild); const childEvents = getStateEvents(space, StateEvent.SpaceChild);
childEvents
.filter((childEvent) => {
if (!isValidChild(childEvent)) return false;
const childId = childEvent.getStateKey();
if (!childId || !isRoomId(childId)) return false;
// because we can not find if a childId is space without joining
// or requesting room summary, we will look it into spaceRooms local
// cache which we maintain as we load summary in UI.
return getRoom(childId)?.isSpaceRoom() || spaceRooms.has(childId);
})
.sort(childEventTs)
.sort(childEventByOrder);
childEvents.forEach((childEvent) => { childEvents.forEach((childEvent) => {
if (!isValidChild(childEvent)) return;
const childId = childEvent.getStateKey(); const childId = childEvent.getStateKey();
if (!childId || !isRoomId(childId)) return; if (!childId || !isRoomId(childId)) return;
// because we can not find if a childId is space without joining const childItem: HierarchyItemSpace = {
// or requesting room summary, we will look it into spaceRooms local roomId: childId,
// cache which we maintain as we load summary in UI. content: childEvent.getContent<MSpaceChildContent>(),
if (getRoom(childId)?.isSpaceRoom() || spaceRooms.has(childId)) { ts: childEvent.getTs(),
const childItem: HierarchyItemSpace = { space: true,
roomId: childId, parentId: spaceItem.roomId,
content: childEvent.getContent<MSpaceChildContent>(), depth: spaceItem.depth + 1,
ts: childEvent.getTs(), };
space: true, findAndCollectHierarchySpaces(childItem);
parentId: spaceItem.roomId,
};
findAndCollectHierarchySpaces(childItem);
}
}); });
}; };
findAndCollectHierarchySpaces(rootSpaceItem); findAndCollectHierarchySpaces(rootSpaceItem);
spaceItems = [
rootSpaceItem,
...spaceItems
.filter((item) => item.roomId !== rootSpaceId)
.sort(hierarchyItemTs)
.sort(hierarchyItemByOrder),
];
return spaceItems; return spaceItems;
}; };
@ -121,6 +128,7 @@ const getSpaceHierarchy = (
content: childEvent.getContent<MSpaceChildContent>(), content: childEvent.getContent<MSpaceChildContent>(),
ts: childEvent.getTs(), ts: childEvent.getTs(),
parentId: spaceItem.roomId, parentId: spaceItem.roomId,
depth: spaceItem.depth,
}; };
childItems.push(childItem); childItems.push(childItem);
}); });
@ -208,6 +216,7 @@ const getSpaceJoinedHierarchy = (
content: childEvent.getContent<MSpaceChildContent>(), content: childEvent.getContent<MSpaceChildContent>(),
ts: childEvent.getTs(), ts: childEvent.getTs(),
parentId: spaceItem.roomId, parentId: spaceItem.roomId,
depth: spaceItem.depth,
}; };
childItems.push(childItem); childItems.push(childItem);
}); });

View file

@ -318,7 +318,8 @@ export function Space() {
useCallback( useCallback(
(parentId, roomId) => { (parentId, roomId) => {
if (!closedCategories.has(makeNavCategoryId(space.roomId, parentId))) { if (!closedCategories.has(makeNavCategoryId(space.roomId, parentId))) {
return false; // REWORK HOW THIS WORKS?
return false; // This does not account for sub-subspaces, best way to do? - first fix useSpaceHie...
} }
const showRoom = roomToUnread.has(roomId) || roomId === selectedRoomId; const showRoom = roomToUnread.has(roomId) || roomId === selectedRoomId;
if (showRoom) return false; if (showRoom) return false;
@ -346,6 +347,12 @@ export function Space() {
const getToLink = (roomId: string) => const getToLink = (roomId: string) =>
getSpaceRoomPath(spaceIdOrAlias, getCanonicalAliasOrRoomId(mx, roomId)); getSpaceRoomPath(spaceIdOrAlias, getCanonicalAliasOrRoomId(mx, roomId));
const getCategoryPadding = (depth: number): string | undefined => {
if (depth === 0) return undefined;
if (depth === 1) return config.space.S400;
return config.space.S200;
};
return ( return (
<PageNav> <PageNav>
<SpaceHeader /> <SpaceHeader />
@ -392,12 +399,17 @@ export function Space() {
}} }}
> >
{virtualizer.getVirtualItems().map((vItem) => { {virtualizer.getVirtualItems().map((vItem) => {
const { roomId } = hierarchy[vItem.index] ?? {}; const { roomId, depth } = hierarchy[vItem.index] ?? {};
const room = mx.getRoom(roomId); const room = mx.getRoom(roomId);
if (!room) return null; if (!room) return null;
const paddingLeft = `calc((${depth} - 1) * ${config.space.S200})`;
if (room.isSpaceRoom()) { if (room.isSpaceRoom()) {
const categoryId = makeNavCategoryId(space.roomId, roomId); const categoryId = makeNavCategoryId(space.roomId, roomId);
const closed = closedCategories.has(categoryId);
const paddingTop = getCategoryPadding(depth);
return ( return (
<VirtualTile <VirtualTile
@ -405,12 +417,18 @@ export function Space() {
key={vItem.index} key={vItem.index}
ref={virtualizer.measureElement} ref={virtualizer.measureElement}
> >
<div style={{ paddingTop: vItem.index === 0 ? undefined : config.space.S400 }}> <div
style={{
paddingTop,
paddingLeft,
}}
>
<NavCategoryHeader> <NavCategoryHeader>
<RoomNavCategoryButton <RoomNavCategoryButton
data-category-id={categoryId} data-category-id={categoryId}
onClick={handleCategoryClick} onClick={handleCategoryClick}
closed={closedCategories.has(categoryId)} closed={closed}
aria-expanded={!closed}
> >
{roomId === space.roomId ? 'Rooms' : room?.name} {roomId === space.roomId ? 'Rooms' : room?.name}
</RoomNavCategoryButton> </RoomNavCategoryButton>
@ -422,14 +440,19 @@ export function Space() {
return ( return (
<VirtualTile virtualItem={vItem} key={vItem.index} ref={virtualizer.measureElement}> <VirtualTile virtualItem={vItem} key={vItem.index} ref={virtualizer.measureElement}>
<RoomNavItem <div style={{ paddingLeft }}>
room={room} <RoomNavItem
selected={selectedRoomId === roomId} room={room}
showAvatar={mDirects.has(roomId)} selected={selectedRoomId === roomId}
direct={mDirects.has(roomId)} showAvatar={mDirects.has(roomId)}
linkPath={getToLink(roomId)} direct={mDirects.has(roomId)}
notificationMode={getRoomNotificationMode(notificationPreferences, room.roomId)} linkPath={getToLink(roomId)}
/> notificationMode={getRoomNotificationMode(
notificationPreferences,
room.roomId
)}
/>
</div>
</VirtualTile> </VirtualTile>
); );
})} })}