diff --git a/src/app/features/room/message/Message.tsx b/src/app/features/room/message/Message.tsx index ae971ab8..39d172db 100644 --- a/src/app/features/room/message/Message.tsx +++ b/src/app/features/room/message/Message.tsx @@ -36,6 +36,7 @@ import { MatrixEvent, Room } from 'matrix-js-sdk'; import { Relations } from 'matrix-js-sdk/lib/models/relations'; import classNames from 'classnames'; import { RoomPinnedEventsEventContent } from 'matrix-js-sdk/lib/types'; +import { useLongPress } from 'use-long-press'; import { AvatarBase, BubbleLayout, @@ -79,6 +80,9 @@ import { StateEvent } from '../../../../types/matrix/room'; import { getTagIconSrc, PowerLevelTag } from '../../../hooks/usePowerLevelTags'; import { PowerIcon } from '../../../components/power'; import colorMXID from '../../../../util/colorMXID'; +import { MessageDropdownMenu, MessageOptionsMenu } from '../MessageOptionsMenu'; +import { BottomSheetMenu } from '../MobileClickMenu'; +import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize'; export type ReactionHandler = (keyOrMxc: string, shortcode: string) => void; @@ -714,12 +718,15 @@ export const Message = as<'div', MessageProps>( const mx = useMatrixClient(); const useAuthentication = useMediaAuthentication(); const senderId = mEvent.getSender() ?? ''; - const [hover, setHover] = useState(false); - const { hoverProps } = useHover({ onHoverChange: setHover }); - const { focusWithinProps } = useFocusWithin({ onFocusWithinChange: setHover }); + const { hoverProps, isHovered } = useHover({}); + const { focusWithinProps, isFocusWithin } = useFocusWithin({}); const [menuAnchor, setMenuAnchor] = useState(); const [emojiBoardAnchor, setEmojiBoardAnchor] = useState(); - + const screenSize = useScreenSizeContext(); + const isMobile = screenSize === ScreenSize.Mobile; + const [isDesktopOptionsActive, setDesktopOptionsActive] = useState(false); + const showDesktopOptions = !isMobile && (isHovered || isFocusWithin || isDesktopOptionsActive); + const [isMobileSheetOpen, setMobileSheetOpen] = useState(false); const senderDisplayName = getMemberDisplayName(room, senderId) ?? getMxIdLocalPart(senderId) ?? senderId; const senderAvatarMxc = getMemberAvatarMxc(room, senderId); @@ -733,6 +740,18 @@ export const Message = as<'div', MessageProps>( const usernameColor = legacyUsernameColor ? colorMXID(senderId) : tagColor; + const longPressBinder = useLongPress( + () => { + if (isMobile) { + setMobileSheetOpen(true); + } + }, + { + threshold: 400, + cancelOnMovement: true, + } + ); + const headerJSX = !collapse && ( ( {tagIconSrc && } - {messageLayout === MessageLayout.Modern && hover && ( + {messageLayout === MessageLayout.Modern && isHovered && ( <> {senderId} @@ -833,263 +852,80 @@ export const Message = as<'div', MessageProps>( }); }; - const handleOpenMenu: MouseEventHandler = (evt) => { - const target = evt.currentTarget.parentElement?.parentElement ?? evt.currentTarget; - setMenuAnchor(target.getBoundingClientRect()); - }; - - const closeMenu = () => { - setMenuAnchor(undefined); - }; - - const handleOpenEmojiBoard: MouseEventHandler = (evt) => { - const target = evt.currentTarget.parentElement?.parentElement ?? evt.currentTarget; - setEmojiBoardAnchor(target.getBoundingClientRect()); - }; - const handleAddReactions: MouseEventHandler = () => { - const rect = menuAnchor; - closeMenu(); - // open it with timeout because closeMenu - // FocusTrap will return focus from emojiBoard - - setTimeout(() => { - setEmojiBoardAnchor(rect); - }, 100); - }; - return ( - - {!edit && (hover || !!menuAnchor || !!emojiBoardAnchor) && ( -
- - - {canSendReaction && ( - { - onReactionToggle(mEvent.getId()!, key); - setEmojiBoardAnchor(undefined); - }} - onCustomEmojiSelect={(mxc, shortcode) => { - onReactionToggle(mEvent.getId()!, mxc, shortcode); - setEmojiBoardAnchor(undefined); - }} - requestClose={() => { - setEmojiBoardAnchor(undefined); - }} - /> - } - > - - - - - )} - - - - {canEditEvent(mx, mEvent) && onEditId && ( - onEditId(mEvent.getId())} - variant="SurfaceVariant" - size="300" - radii="300" - > - - - )} - setMenuAnchor(undefined), - clickOutsideDeactivates: true, - isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown', - isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', - escapeDeactivates: stopPropagation, - }} - > - - {canSendReaction && ( - { - onReactionToggle(mEvent.getId()!, key, shortcode); - closeMenu(); - }} - /> - )} - - {canSendReaction && ( - } - radii="300" - onClick={handleAddReactions} - > - - Add Reaction - - - )} - {relations && ( - - )} - } - radii="300" - data-event-id={mEvent.getId()} - onClick={(evt: any) => { - onReplyClick(evt); - closeMenu(); - }} - > - - Reply - - - {canEditEvent(mx, mEvent) && onEditId && ( - } - radii="300" - data-event-id={mEvent.getId()} - onClick={() => { - onEditId(mEvent.getId()); - closeMenu(); - }} - > - - Edit Message - - - )} - {!hideReadReceipts && ( - - )} - - - {canPinEvent && ( - - )} - - {((!mEvent.isRedacted() && canDelete) || - mEvent.getSender() !== mx.getUserId()) && ( - <> - - - {!mEvent.isRedacted() && canDelete && ( - - )} - {mEvent.getSender() !== mx.getUserId() && ( - - )} - - - )} - - - } - > - - - - - - -
+ <> + + {!edit && (isHovered || !!menuAnchor || !!emojiBoardAnchor) && ( + + )} + {messageLayout === MessageLayout.Compact && ( + + {msgContentJSX} + + )} + {messageLayout === MessageLayout.Bubble && ( + + {headerJSX} + {msgContentJSX} + + )} + {messageLayout !== MessageLayout.Compact && messageLayout !== MessageLayout.Bubble && ( + + {headerJSX} + {msgContentJSX} + + )} + + + {isMobile && ( + setMobileSheetOpen(false)} isOpen={isMobileSheetOpen}> + setMobileSheetOpen(false)} + mEvent={mEvent} + eventId={mEvent.getId()} + room={room} + mx={mx} + relations={relations} + canSendReaction={canSendReaction} + canEdit={canEditEvent(mx, mEvent)} + canDelete={canDelete || mEvent?.getSender() === mx.getUserId()} + canPinEvent={canPinEvent} + hideReadReceipts={hideReadReceipts} + onReactionToggle={onReactionToggle} + onReplyClick={onReplyClick} + onEditId={onEditId} + handleAddReactions={null} + /> + )} - {messageLayout === MessageLayout.Compact && ( - - {msgContentJSX} - - )} - {messageLayout === MessageLayout.Bubble && ( - - {headerJSX} - {msgContentJSX} - - )} - {messageLayout !== MessageLayout.Compact && messageLayout !== MessageLayout.Bubble && ( - - {headerJSX} - {msgContentJSX} - - )} -
+ ); } ); @@ -1155,7 +991,7 @@ export const Event = as<'div', EventProps>( highlight={highlight} selected={!!menuAnchor} {...props} - {...hoverProps} + {...hoverProps} // Impacts hover {...focusWithinProps} ref={ref} >