import React, { ReactNode, useCallback, useRef, useState } from 'react'; import { MatrixError, Room } from 'matrix-js-sdk'; import { Avatar, Badge, Box, Button, Dialog, Icon, Icons, Overlay, OverlayBackdrop, OverlayCenter, Spinner, Text, as, color, config, } from 'folds'; import classNames from 'classnames'; import FocusTrap from 'focus-trap-react'; import * as css from './style.css'; import { RoomAvatar } from '../room-avatar'; import { getMxIdLocalPart, mxcUrlToHttp } from '../../utils/matrix'; import { nameInitials } from '../../utils/common'; import { millify } from '../../plugins/millify'; import { useMatrixClient } from '../../hooks/useMatrixClient'; import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback'; import { onEnterOrSpace, stopPropagation } from '../../utils/keyboard'; import { RoomType, StateEvent } from '../../../types/matrix/room'; import { useJoinedRoomId } from '../../hooks/useJoinedRoomId'; import { useElementSizeObserver } from '../../hooks/useElementSizeObserver'; import { getRoomAvatarUrl, getStateEvent } from '../../utils/room'; import { useStateEventCallback } from '../../hooks/useStateEventCallback'; import { useMediaAuthentication } from '../../hooks/useMediaAuthentication'; type GridColumnCount = '1' | '2' | '3'; const getGridColumnCount = (gridWidth: number): GridColumnCount => { if (gridWidth <= 498) return '1'; if (gridWidth <= 748) return '2'; return '3'; }; const setGridColumnCount = (grid: HTMLElement, count: GridColumnCount): void => { grid.style.setProperty('grid-template-columns', `repeat(${count}, 1fr)`); }; export function RoomCardGrid({ children }: { children: ReactNode }) { const gridRef = useRef(null); useElementSizeObserver( useCallback(() => gridRef.current, []), useCallback((width, _, target) => setGridColumnCount(target, getGridColumnCount(width)), []) ); return ( {children} ); } export const RoomCardBase = as<'div'>(({ className, ...props }, ref) => ( )); export const RoomCardName = as<'h6'>(({ ...props }, ref) => ( )); export const RoomCardTopic = as<'p'>(({ className, ...props }, ref) => ( )); function ErrorDialog({ title, message, children, }: { title: string; message: string; children: (openError: () => void) => ReactNode; }) { const [viewError, setViewError] = useState(false); const closeError = () => setViewError(false); const openError = () => setViewError(true); return ( <> {children(openError)} }> {title} {message} ); } type RoomCardProps = { roomIdOrAlias: string; allRooms: string[]; avatarUrl?: string; name?: string; topic?: string; memberCount?: number; roomType?: string; viaServers?: string[]; onView?: (roomId: string) => void; renderTopicViewer: (name: string, topic: string, requestClose: () => void) => ReactNode; }; export const RoomCard = as<'div', RoomCardProps>( ( { roomIdOrAlias, allRooms, avatarUrl, name, topic, memberCount, roomType, viaServers, onView, renderTopicViewer, ...props }, ref ) => { const mx = useMatrixClient(); const useAuthentication = useMediaAuthentication(); const joinedRoomId = useJoinedRoomId(allRooms, roomIdOrAlias); const joinedRoom = mx.getRoom(joinedRoomId); const [topicEvent, setTopicEvent] = useState(() => joinedRoom ? getStateEvent(joinedRoom, StateEvent.RoomTopic) : undefined ); const fallbackName = getMxIdLocalPart(roomIdOrAlias) ?? roomIdOrAlias; const fallbackTopic = roomIdOrAlias; const avatar = joinedRoom ? getRoomAvatarUrl(mx, joinedRoom, 96, useAuthentication) : avatarUrl && mxcUrlToHttp(mx, avatarUrl, useAuthentication, 96, 96, 'crop'); const roomName = joinedRoom?.name || name || fallbackName; const roomTopic = (topicEvent?.getContent().topic as string) || undefined || topic || fallbackTopic; const joinedMemberCount = joinedRoom?.getJoinedMemberCount() ?? memberCount; useStateEventCallback( mx, useCallback( (event) => { if ( joinedRoom && event.getRoomId() === joinedRoom.roomId && event.getType() === StateEvent.RoomTopic ) { setTopicEvent(getStateEvent(joinedRoom, StateEvent.RoomTopic)); } }, [joinedRoom] ) ); const [joinState, join] = useAsyncCallback( useCallback(() => mx.joinRoom(roomIdOrAlias, { viaServers }), [mx, roomIdOrAlias, viaServers]) ); const joining = joinState.status === AsyncStatus.Loading || joinState.status === AsyncStatus.Success; const [viewTopic, setViewTopic] = useState(false); const closeTopic = () => setViewTopic(false); const openTopic = () => setViewTopic(true); return ( ( {nameInitials(roomName)} )} /> {(roomType === RoomType.Space || joinedRoom?.isSpaceRoom()) && ( Space )} {roomName} {roomTopic} }> {renderTopicViewer(roomName, roomTopic, closeTopic)} {typeof joinedMemberCount === 'number' && ( {`${millify(joinedMemberCount)} Members`} )} {typeof joinedRoomId === 'string' && ( )} {typeof joinedRoomId !== 'string' && joinState.status !== AsyncStatus.Error && ( )} {typeof joinedRoomId !== 'string' && joinState.status === AsyncStatus.Error && ( {(openError) => ( )} )} ); } );