import React, { MouseEventHandler, forwardRef, useState, MouseEvent } from 'react'; import { Room } from 'matrix-js-sdk'; import { Avatar, Box, Icon, IconButton, Icons, Text, Menu, MenuItem, config, PopOut, toRem, Line, RectCords, Badge, Spinner, Tooltip, TooltipProvider, } from 'folds'; import { useFocusWithin, useHover } from 'react-aria'; import FocusTrap from 'focus-trap-react'; import { useParams } from 'react-router-dom'; import { NavItem, NavItemContent, NavItemOptions, NavLink } from '../../components/nav'; import { UnreadBadge, UnreadBadgeCenter } from '../../components/unread-badge'; import { RoomAvatar, RoomIcon } from '../../components/room-avatar'; import { getDirectRoomAvatarUrl, getRoomAvatarUrl, isSpace } from '../../utils/room'; import { nameInitials } from '../../utils/common'; import { useMatrixClient } from '../../hooks/useMatrixClient'; import { useRoomUnread } from '../../state/hooks/unread'; import { roomToUnreadAtom } from '../../state/room/roomToUnread'; import { usePowerLevels, usePowerLevelsAPI } from '../../hooks/usePowerLevels'; import { copyToClipboard } from '../../utils/dom'; import { markAsRead } from '../../../client/action/notifications'; import { openInviteUser } from '../../../client/action/navigation'; import { UseStateProvider } from '../../components/UseStateProvider'; import { LeaveRoomPrompt } from '../../components/leave-room-prompt'; import { useRoomTypingMember } from '../../hooks/useRoomTypingMembers'; import { TypingIndicator } from '../../components/typing-indicator'; import { stopPropagation } from '../../utils/keyboard'; import { getMatrixToRoom } from '../../plugins/matrix-to'; import { getCanonicalAliasOrRoomId, isRoomAlias } from '../../utils/matrix'; import { getViaServers } from '../../plugins/via-servers'; import { useMediaAuthentication } from '../../hooks/useMediaAuthentication'; import { useSetting } from '../../state/hooks/settings'; import { settingsAtom } from '../../state/settings'; import { useOpenRoomSettings } from '../../state/hooks/roomSettings'; import { useSpaceOptionally } from '../../hooks/useSpace'; import { getRoomNotificationModeIcon, RoomNotificationMode, } from '../../hooks/useRoomsNotificationPreferences'; import { RoomNotificationModeSwitcher } from '../../components/RoomNotificationSwitcher'; import { useCallState } from '../../pages/client/call/CallProvider'; import { useRoomNavigate } from '../../hooks/useRoomNavigate'; import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize'; type RoomNavItemMenuProps = { room: Room; requestClose: () => void; notificationMode?: RoomNotificationMode; }; const RoomNavItemMenu = forwardRef( ({ room, requestClose, notificationMode }, ref) => { const mx = useMatrixClient(); const [hideActivity] = useSetting(settingsAtom, 'hideActivity'); const unread = useRoomUnread(room.roomId, roomToUnreadAtom); const powerLevels = usePowerLevels(room); const { getPowerLevel, canDoAction } = usePowerLevelsAPI(powerLevels); const canInvite = canDoAction('invite', getPowerLevel(mx.getUserId() ?? '')); const openRoomSettings = useOpenRoomSettings(); const space = useSpaceOptionally(); const handleMarkAsRead = () => { markAsRead(mx, room.roomId, hideActivity); requestClose(); }; const handleInvite = () => { openInviteUser(room.roomId); requestClose(); }; const handleCopyLink = () => { const roomIdOrAlias = getCanonicalAliasOrRoomId(mx, room.roomId); const viaServers = isRoomAlias(roomIdOrAlias) ? undefined : getViaServers(room); copyToClipboard(getMatrixToRoom(roomIdOrAlias, viaServers)); requestClose(); }; const handleRoomSettings = () => { openRoomSettings(room.roomId, space?.roomId); requestClose(); }; return ( } radii="300" disabled={!unread} > Mark as Read {(handleOpen, opened, changing) => ( ) : ( ) } radii="300" aria-pressed={opened} onClick={handleOpen} > Notifications )} } radii="300" disabled={!canInvite} > Invite } radii="300" > Copy Link } radii="300" > Room Settings {(promptLeave, setPromptLeave) => ( <> setPromptLeave(true)} variant="Critical" fill="None" size="300" after={} radii="300" aria-pressed={promptLeave} > Leave Room {promptLeave && ( setPromptLeave(false)} /> )} )} ); } ); RoomNavItemMenu.displayName = 'RoomNavItemMenu'; type RoomNavItemProps = { room: Room; selected: boolean; linkPath: string; notificationMode?: RoomNotificationMode; showAvatar?: boolean; direct?: boolean; }; export function RoomNavItem({ room, selected, showAvatar, direct, notificationMode, linkPath, }: RoomNavItemProps) { const mx = useMatrixClient(); const useAuthentication = useMediaAuthentication(); const [hover, setHover] = useState(false); const { hoverProps } = useHover({ onHoverChange: setHover }); const { focusWithinProps } = useFocusWithin({ onFocusWithinChange: setHover }); const [menuAnchor, setMenuAnchor] = useState(); const unread = useRoomUnread(room.roomId, roomToUnreadAtom); const { activeCallRoomId, setActiveCallRoomId, setViewedCallRoomId, isChatOpen, toggleChat, hangUp, } = useCallState(); const typingMember = useRoomTypingMember(room.roomId).filter( (receipt) => receipt.userId !== mx.getUserId() ); const { navigateRoom } = useRoomNavigate(); const { roomIdOrAlias: viewedRoomId } = useParams(); const screenSize = useScreenSizeContext(); const isMobile = screenSize === ScreenSize.Mobile; const handleContextMenu: MouseEventHandler = (evt) => { evt.preventDefault(); setMenuAnchor({ x: evt.clientX, y: evt.clientY, width: 0, height: 0, }); }; const handleOpenMenu: MouseEventHandler = (evt) => { setMenuAnchor(evt.currentTarget.getBoundingClientRect()); }; const handleNavItemClick: MouseEventHandler = (evt) => { const target = evt.target as HTMLElement; const chatButton = (evt.currentTarget as HTMLElement).querySelector( '[data-testid="chat-button"]' ); if (chatButton && chatButton.contains(target)) { return; } if (!isMobile) { if (room.isCallRoom() && activeCallRoomId !== room.roomId) { hangUp(); setActiveCallRoomId(room.roomId); if (mx.getRoom(viewedRoomId)?.isCallRoom()) { navigateRoom(room.roomId); } } else { navigateRoom(room.roomId); } } else { evt.stopPropagation(); if (isChatOpen) toggleChat(); setViewedCallRoomId(room.roomId); navigateRoom(room.roomId); } }; const handleChatButtonClick = (evt: MouseEvent) => { evt.stopPropagation(); if (!isChatOpen) toggleChat(); setViewedCallRoomId(room.roomId); }; const optionsVisible = hover || !!menuAnchor; return ( {showAvatar ? ( ( {nameInitials(room.name)} )} /> ) : ( )} {room.name} {!optionsVisible && !unread && !selected && typingMember.length > 0 && ( )} {!optionsVisible && unread && ( 0} count={unread.total} /> )} {!optionsVisible && notificationMode !== RoomNotificationMode.Unset && ( )} {optionsVisible && ( setMenuAnchor(undefined), clickOutsideDeactivates: true, isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown', isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', escapeDeactivates: stopPropagation, }} > setMenuAnchor(undefined)} notificationMode={notificationMode} /> } > {room.isCallRoom() && ( Open chat } > {(triggerRef) => ( )} )} )} ); }