import React, { MouseEventHandler, ReactNode, useCallback, useRef, useState } from 'react'; import { Box, Avatar, Text, Chip, Icon, Icons, as, Badge, toRem, Spinner, PopOut, Menu, MenuItem, RectCords, config, IconButton, TooltipProvider, Tooltip, } from 'folds'; import FocusTrap from 'focus-trap-react'; import classNames from 'classnames'; import { MatrixError, Room } from 'matrix-js-sdk'; import { IHierarchyRoom } from 'matrix-js-sdk/lib/@types/spaces'; import { HierarchyItem } from '../../hooks/useSpaceHierarchy'; import { useMatrixClient } from '../../hooks/useMatrixClient'; import { RoomAvatar } from '../../components/room-avatar'; import { nameInitials } from '../../utils/common'; import { LocalRoomSummaryLoader } from '../../components/RoomSummaryLoader'; import { getRoomAvatarUrl } from '../../utils/room'; import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback'; import * as css from './SpaceItem.css'; import * as styleCss from './style.css'; import { useDraggableItem } from './DnD'; import { stopPropagation } from '../../utils/keyboard'; import { mxcUrlToHttp } from '../../utils/matrix'; import { useMediaAuthentication } from '../../hooks/useMediaAuthentication'; import { useOpenCreateRoomModal } from '../../state/hooks/createRoomModal'; import { useOpenCreateSpaceModal } from '../../state/hooks/createSpaceModal'; import { AddExistingModal } from '../add-existing'; function SpaceProfileLoading() { return ( ); } type InaccessibleSpaceProfileProps = { roomId: string; suggested?: boolean; }; function InaccessibleSpaceProfile({ roomId, suggested }: InaccessibleSpaceProfileProps) { return ( ( U )} /> } > Unknown Inaccessible {suggested && ( Suggested )} ); } type UnjoinedSpaceProfileProps = { roomId: string; via?: string[]; name?: string; avatarUrl?: string; suggested?: boolean; }; function UnjoinedSpaceProfile({ roomId, via, name, avatarUrl, suggested, }: UnjoinedSpaceProfileProps) { const mx = useMatrixClient(); const [joinState, join] = useAsyncCallback( useCallback(() => mx.joinRoom(roomId, { viaServers: via }), [mx, roomId, via]) ); const canJoin = joinState.status === AsyncStatus.Idle || joinState.status === AsyncStatus.Error; return ( ( {nameInitials(name)} )} /> } after={ canJoin ? : } > {name || 'Unknown'} {suggested && ( Suggested )} {joinState.status === AsyncStatus.Error && ( {joinState.error.name} )} ); } type SpaceProfileProps = { roomId: string; name: string; avatarUrl?: string; suggested?: boolean; closed: boolean; categoryId: string; handleClose?: MouseEventHandler; }; function SpaceProfile({ roomId, name, avatarUrl, suggested, closed, categoryId, handleClose, }: SpaceProfileProps) { return ( ( {nameInitials(name)} )} /> } after={} > {name} {suggested && ( Suggested )} ); } type RootSpaceProfileProps = { closed: boolean; categoryId: string; handleClose?: MouseEventHandler; }; function RootSpaceProfile({ closed, categoryId, handleClose }: RootSpaceProfileProps) { return ( } > Rooms ); } function AddRoomButton({ item }: { item: HierarchyItem }) { const [cords, setCords] = useState(); const openCreateRoomModal = useOpenCreateRoomModal(); const [addExisting, setAddExisting] = useState(false); const handleAddRoom: MouseEventHandler = (evt) => { setCords(evt.currentTarget.getBoundingClientRect()); }; const handleCreateRoom = () => { openCreateRoomModal(item.roomId); setCords(undefined); }; const handleAddExisting = () => { setAddExisting(true); setCords(undefined); }; return ( setCords(undefined), clickOutsideDeactivates: true, isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown', isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', escapeDeactivates: stopPropagation, }} > New Room Existing Room } > } onClick={handleAddRoom} aria-pressed={!!cords} > Add Room {addExisting && ( setAddExisting(false)} /> )} ); } function AddSpaceButton({ item }: { item: HierarchyItem }) { const [cords, setCords] = useState(); const openCreateSpaceModal = useOpenCreateSpaceModal(); const [addExisting, setAddExisting] = useState(false); const handleAddSpace: MouseEventHandler = (evt) => { setCords(evt.currentTarget.getBoundingClientRect()); }; const handleCreateSpace = () => { openCreateSpaceModal(item.roomId as any); setCords(undefined); }; const handleAddExisting = () => { setAddExisting(true); setCords(undefined); }; return ( setCords(undefined), clickOutsideDeactivates: true, isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown', isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', escapeDeactivates: stopPropagation, }} > New Space Existing Space } > {item.parentId === undefined ? ( } onClick={handleAddSpace} aria-pressed={!!cords} > Add Space ) : ( Add Space } > {(triggerRef) => ( )} )} {addExisting && ( setAddExisting(false)} /> )} ); } type SpaceItemCardProps = { summary: IHierarchyRoom | undefined; loading?: boolean; item: HierarchyItem; joined?: boolean; categoryId: string; closed: boolean; handleClose?: MouseEventHandler; options?: ReactNode; before?: ReactNode; after?: ReactNode; canEditChild: boolean; canReorder: boolean; onDragging: (item?: HierarchyItem) => void; getRoom: (roomId: string) => Room | undefined; }; export const SpaceItemCard = as<'div', SpaceItemCardProps>( ( { className, summary, loading, joined, closed, categoryId, item, handleClose, options, before, after, canEditChild, canReorder, onDragging, getRoom, ...props }, ref ) => { const mx = useMatrixClient(); const useAuthentication = useMediaAuthentication(); const { roomId, content } = item; const space = getRoom(roomId); const targetRef = useRef(null); useDraggableItem(item, targetRef, onDragging); return ( {before} {space ? ( {(localSummary) => item.parentId ? ( ) : ( ) } ) : ( <> {!summary && (loading ? ( ) : ( ))} {summary && ( )} )} {space && canEditChild && ( )} {options} {after} ); } );