import React, { MouseEventHandler, forwardRef, useState } from 'react'; import { Room } from 'matrix-js-sdk'; import { Avatar, Box, Icon, IconButton, Icons, Text, Menu, MenuItem, config, PopOut, toRem, Line, RectCords, Badge, Spinner, } from 'folds'; import { useFocusWithin, useHover } from 'react-aria'; import FocusTrap from 'focus-trap-react'; 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 } 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 } 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 { useRoomCreators } from '../../hooks/useRoomCreators'; import { useRoomPermissions } from '../../hooks/useRoomPermissions'; 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 creators = useRoomCreators(room); const permissions = useRoomPermissions(creators, powerLevels); const canInvite = permissions.action('invite', mx.getSafeUserId()); 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)} /> )} )} ); } ); 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 typingMember = useRoomTypingMember(room.roomId).filter( (receipt) => receipt.userId !== mx.getUserId() ); 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 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} /> } > )} ); }