import React, { forwardRef, MouseEventHandler, useEffect, useMemo } from 'react'; import { MatrixError, Room } from 'matrix-js-sdk'; import { IHierarchyRoom } from 'matrix-js-sdk/lib/@types/spaces'; import { Box, config, Text } from 'folds'; import { HierarchyItem, HierarchyItemRoom, HierarchyItemSpace, useFetchSpaceHierarchyLevel, } from '../../hooks/useSpaceHierarchy'; import { IPowerLevels } from '../../hooks/usePowerLevels'; import { useMatrixClient } from '../../hooks/useMatrixClient'; import { SpaceItemCard } from './SpaceItem'; import { AfterItemDropTarget, CanDropCallback } from './DnD'; import { HierarchyItemMenu } from './HierarchyItemMenu'; import { RoomItemCard } from './RoomItem'; import { RoomType, StateEvent } from '../../../types/matrix/room'; import { SequenceCard } from '../../components/sequence-card'; import { getRoomCreatorsForRoomId } from '../../hooks/useRoomCreators'; import { getRoomPermissionsAPI } from '../../hooks/useRoomPermissions'; import { PaarrotSubRoomsContent } from '../../hooks/useRoomSubRooms'; type SpaceHierarchyProps = { summary: IHierarchyRoom | undefined; spaceItem: HierarchyItemSpace; roomItems?: HierarchyItemRoom[]; allJoinedRooms: Set; mDirects: Set; roomsPowerLevels: Map; categoryId: string; closed: boolean; handleClose: MouseEventHandler; draggingItem?: HierarchyItem; onDragging: (item?: HierarchyItem) => void; canDrop: CanDropCallback; disabledReorder?: boolean; nextSpaceId?: string; getRoom: (roomId: string) => Room | undefined; pinned: boolean; togglePinToSidebar: (roomId: string) => void; onSpacesFound: (spaceItems: IHierarchyRoom[]) => void; onOpenRoom: MouseEventHandler; }; export const SpaceHierarchy = forwardRef( ( { summary, spaceItem, roomItems, allJoinedRooms, mDirects, roomsPowerLevels, categoryId, closed, handleClose, draggingItem, onDragging, canDrop, disabledReorder, nextSpaceId, getRoom, pinned, togglePinToSidebar, onOpenRoom, onSpacesFound, }, ref ) => { const mx = useMatrixClient(); const { fetching, error, rooms } = useFetchSpaceHierarchyLevel(spaceItem.roomId, true); const subspaces = useMemo(() => { const s: Map = new Map(); rooms.forEach((r) => { if (r.room_type === RoomType.Space) { s.set(r.room_id, r); } }); return s; }, [rooms]); const spacePowerLevels = roomsPowerLevels.get(spaceItem.roomId); const spaceCreators = getRoomCreatorsForRoomId(mx, spaceItem.roomId); const spacePermissions = spacePowerLevels && getRoomPermissionsAPI(spaceCreators, spacePowerLevels); const draggingSpace = draggingItem?.roomId === spaceItem.roomId && draggingItem.parentId === spaceItem.parentId; const { parentId } = spaceItem; const parentPowerLevels = parentId ? roomsPowerLevels.get(parentId) : undefined; const parentCreators = parentId ? getRoomCreatorsForRoomId(mx, parentId) : undefined; const parentPermissions = parentCreators && parentPowerLevels && getRoomPermissionsAPI(parentCreators, parentPowerLevels); useEffect(() => { onSpacesFound(Array.from(subspaces.values())); }, [subspaces, onSpacesFound]); // Build a global set of all sub-room IDs by checking ALL joined rooms // This ensures sub-rooms are hidden even if their parent isn't in this space const globalSubRoomIds = useMemo(() => { const subRoomIds = new Set(); mx.getRooms().forEach((room) => { const subRoomsEvent = room.currentState.getStateEvents(StateEvent.PaarrotSubRooms, ''); const content = subRoomsEvent?.getContent(); content?.children?.forEach((childId: string) => subRoomIds.add(childId)); }); return subRoomIds; }, [mx, allJoinedRooms]); // Build a map of parent room -> sub-room IDs for nested rendering (only for rooms in this space) const subRoomsMap = useMemo(() => { const parentToSubRooms = new Map(); roomItems?.forEach((item) => { const room = mx.getRoom(item.roomId); if (room) { const subRoomsEvent = room.currentState.getStateEvents(StateEvent.PaarrotSubRooms, ''); const content = subRoomsEvent?.getContent(); const children = content?.children ?? []; if (children.length > 0) { parentToSubRooms.set(item.roomId, children); } } }); return parentToSubRooms; }, [mx, roomItems]); let childItems = roomItems?.filter((i) => !subspaces.has(i.roomId) && !globalSubRoomIds.has(i.roomId)); if (!spacePermissions?.stateEvent(StateEvent.SpaceChild, mx.getSafeUserId())) { // hide unknown rooms for normal user childItems = childItems?.filter((i) => { const forbidden = error instanceof MatrixError ? error.errcode === 'M_FORBIDDEN' : false; const inaccessibleRoom = !rooms.get(i.roomId) && !fetching && (error ? forbidden : true); return !inaccessibleRoom; }); } return ( ) } after={ } onDragging={onDragging} data-dragging={draggingSpace} /> {childItems && childItems.length > 0 ? ( {childItems.map((roomItem, index) => { const roomSummary = rooms.get(roomItem.roomId); const roomPowerLevels = roomsPowerLevels.get(roomItem.roomId) ?? {}; const lastItem = index === childItems.length; const nextRoomId = lastItem ? nextSpaceId : childItems[index + 1]?.roomId; const roomDragging = draggingItem?.roomId === roomItem.roomId && draggingItem.parentId === roomItem.parentId; // Get sub-rooms for this room const subRoomIds = subRoomsMap.get(roomItem.roomId) ?? []; return ( } after={ } data-dragging={roomDragging} onDragging={onDragging} /> {/* Render sub-rooms nested under this room */} {subRoomIds.length > 0 && ( {subRoomIds.map((subRoomId) => { const subRoom = mx.getRoom(subRoomId); const subRoomSummary = rooms.get(subRoomId); const subRoomPowerLevels = roomsPowerLevels.get(subRoomId) ?? {}; const subRoomItem: HierarchyItemRoom = { roomId: subRoomId, parentId: roomItem.roomId, content: {}, }; const subRoomDragging = draggingItem?.roomId === subRoomId && draggingItem.parentId === roomItem.roomId; // Skip sub-rooms the user hasn't joined yet if we have no summary if (!subRoom && !subRoomSummary) return null; return ( } data-dragging={subRoomDragging} onDragging={onDragging} /> ); })} )} ); })} ) : ( childItems && ( No Rooms This space does not contains rooms yet. ) )} ); } );