import React, { MouseEventHandler, forwardRef, useState } from 'react'; import FocusTrap from 'focus-trap-react'; import { Box, Avatar, Text, Overlay, OverlayCenter, OverlayBackdrop, IconButton, Icon, Icons, Tooltip, TooltipProvider, Menu, MenuItem, toRem, config, Line, PopOut, RectCords, Badge, Spinner, } from 'folds'; import { useNavigate } from 'react-router-dom'; import { JoinRule, Room } from 'matrix-js-sdk'; import { useAtomValue } from 'jotai'; import { useStateEvent } from '../../hooks/useStateEvent'; import { PageHeader } from '../../components/page'; import { RoomAvatar, RoomIcon } from '../../components/room-avatar'; import { UseStateProvider } from '../../components/UseStateProvider'; import { RoomTopicViewer } from '../../components/room-topic-viewer'; import { StateEvent } from '../../../types/matrix/room'; import { useMatrixClient } from '../../hooks/useMatrixClient'; import { useRoom } from '../../hooks/useRoom'; import { useSetSetting, useSetting } from '../../state/hooks/settings'; import { settingsAtom } from '../../state/settings'; import { useSpaceOptionally } from '../../hooks/useSpace'; import { getHomeSearchPath, getSpaceSearchPath, withSearchParam } from '../../pages/pathUtils'; import { getCanonicalAliasOrRoomId, isRoomAlias, mxcUrlToHttp } from '../../utils/matrix'; import { _SearchPathSearchParams } from '../../pages/paths'; import * as css from './RoomViewHeader.css'; import { useRoomUnread } from '../../state/hooks/unread'; import { usePowerLevelsAPI, usePowerLevelsContext } from '../../hooks/usePowerLevels'; import { markAsRead } from '../../../client/action/notifications'; import { roomToUnreadAtom } from '../../state/room/roomToUnread'; import { openInviteUser } from '../../../client/action/navigation'; import { copyToClipboard } from '../../utils/dom'; import { LeaveRoomPrompt } from '../../components/leave-room-prompt'; import { useRoomAvatar, useRoomName, useRoomTopic } from '../../hooks/useRoomMeta'; import { mDirectAtom } from '../../state/mDirectList'; import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize'; import { stopPropagation } from '../../utils/keyboard'; import { getMatrixToRoom } from '../../plugins/matrix-to'; import { getViaServers } from '../../plugins/via-servers'; import { BackRouteHandler } from '../../components/BackRouteHandler'; import { useMediaAuthentication } from '../../hooks/useMediaAuthentication'; import { useRoomPinnedEvents } from '../../hooks/useRoomPinnedEvents'; import { RoomPinMenu } from './room-pin-menu'; import { useOpenRoomSettings } from '../../state/hooks/roomSettings'; import { RoomNotificationModeSwitcher } from '../../components/RoomNotificationSwitcher'; import { getRoomNotificationMode, getRoomNotificationModeIcon, useRoomsNotificationPreferencesContext, } from '../../hooks/useRoomsNotificationPreferences'; import { useCallState } from '../../pages/client/CallProvider'; type RoomMenuProps = { room: Room; requestClose: () => void; }; const RoomMenu = forwardRef(({ room, requestClose }, ref) => { const mx = useMatrixClient(); const [hideActivity] = useSetting(settingsAtom, 'hideActivity'); const unread = useRoomUnread(room.roomId, roomToUnreadAtom); const powerLevels = usePowerLevelsContext(); const { getPowerLevel, canDoAction } = usePowerLevelsAPI(powerLevels); const canInvite = canDoAction('invite', getPowerLevel(mx.getUserId() ?? '')); const notificationPreferences = useRoomsNotificationPreferencesContext(); const notificationMode = getRoomNotificationMode(notificationPreferences, room.roomId); 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 openSettings = useOpenRoomSettings(); const parentSpace = useSpaceOptionally(); const handleOpenSettings = () => { openSettings(room.roomId, parentSpace?.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)} /> )} )} ); }); export function RoomViewHeader() { const navigate = useNavigate(); const mx = useMatrixClient(); const useAuthentication = useMediaAuthentication(); const screenSize = useScreenSizeContext(); const room = useRoom(); const space = useSpaceOptionally(); const [menuAnchor, setMenuAnchor] = useState(); const [pinMenuAnchor, setPinMenuAnchor] = useState(); const mDirects = useAtomValue(mDirectAtom); const { isChatOpen, toggleChat } = useCallState(); const pinnedEvents = useRoomPinnedEvents(room); const encryptionEvent = useStateEvent(room, StateEvent.RoomEncryption); const ecryptedRoom = !!encryptionEvent; const avatarMxc = useRoomAvatar(room, mDirects.has(room.roomId)); const name = useRoomName(room); const topic = useRoomTopic(room); const avatarUrl = avatarMxc ? mxcUrlToHttp(mx, avatarMxc, useAuthentication, 96, 96, 'crop') ?? undefined : undefined; const setPeopleDrawer = useSetSetting(settingsAtom, 'isPeopleDrawer'); // I assume there is a global state so I don't have to run this check every time but for now we'll stub this in const isDirectMessage = () => { const mDirectsEvent = mx.getAccountData('m.direct'); if (mDirectsEvent?.event?.content === undefined) { return false; } const { roomId } = room; return ( Object.values(mDirectsEvent?.event?.content).filter((e) => { if (e.indexOf(roomId) === 0) return true; }).length !== 0 ); }; const handleCall: MouseEventHandler = (evt) => { setMenuAnchor(evt.currentTarget.getBoundingClientRect()); }; const handleSearchClick = () => { const searchParams: _SearchPathSearchParams = { rooms: room.roomId, }; const path = space ? getSpaceSearchPath(getCanonicalAliasOrRoomId(mx, space.roomId)) : getHomeSearchPath(); navigate(withSearchParam(path, searchParams)); }; const handleOpenMenu: MouseEventHandler = (evt) => { setMenuAnchor(evt.currentTarget.getBoundingClientRect()); }; const handleOpenPinMenu: MouseEventHandler = (evt) => { setPinMenuAnchor(evt.currentTarget.getBoundingClientRect()); }; return ( {screenSize === ScreenSize.Mobile && ( {(onBack) => ( )} )} {screenSize !== ScreenSize.Mobile && ( ( )} /> )} {name} {topic && ( {(viewTopic, setViewTopic) => ( <> }> setViewTopic(false), escapeDeactivates: stopPropagation, }} > setViewTopic(false)} /> setViewTopic(true)} className={css.HeaderTopic} size="T200" priority="300" truncate > {topic} )} )} {(room.isCallRoom() && !isDirectMessage() && ( Chat } > {(triggerRef) => ( )} )) || ( {false && isDirectMessage() && ( Start a call } > {(triggerRef) => ( )} )} {!ecryptedRoom && ( Search } > {(triggerRef) => ( )} )} Pinned Messages } > {(triggerRef) => ( {pinnedEvents.length > 0 && ( {pinnedEvents.length} )} )} setPinMenuAnchor(undefined), clickOutsideDeactivates: true, isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown', isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', escapeDeactivates: stopPropagation, }} > setPinMenuAnchor(undefined)} /> } /> {screenSize === ScreenSize.Desktop && ( Members } > {(triggerRef) => ( setPeopleDrawer((drawer) => !drawer)}> )} )} More Options } > {(triggerRef) => ( )} setMenuAnchor(undefined), clickOutsideDeactivates: true, isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown', isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', escapeDeactivates: stopPropagation, }} > setMenuAnchor(undefined)} /> } /> )} ); }