diff --git a/src/app/features/room/message/MessageOptionsMenu.tsx b/src/app/features/room/message/MessageOptionsMenu.tsx new file mode 100644 index 00000000..d0c7277b --- /dev/null +++ b/src/app/features/room/message/MessageOptionsMenu.tsx @@ -0,0 +1,356 @@ +import { + Box, + Header, + Icon, + IconButton, + Icons, + Line, + Menu, + MenuItem, + PopOut, + RectCords, + Text, +} from 'folds'; +import React, { forwardRef, MouseEventHandler, useEffect, useState } from 'react'; + +import FocusTrap from 'focus-trap-react'; +import classNames from 'classnames'; +import { MatrixClient, MatrixEvent, Relations, Room } from 'matrix-js-sdk'; +import { EmojiBoard } from '../../../components/emoji-board'; +import { stopPropagation } from '../../../utils/keyboard'; +import * as css from './styles.css'; + +import { + MessageAllReactionItem, + MessageCopyLinkItem, + MessageDeleteItem, + MessagePinItem, + MessageQuickReactions, + MessageReadReceiptItem, + MessageReportItem, + MessageSourceCodeItem, +} from './Message'; +import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize'; +import { BottomSheetMenu } from './MobileContextMenu'; + +type BaseOptionProps = { + mEvent: MatrixEvent; + room: Room; + mx: MatrixClient; + relations: Relations | undefined; + canSendReaction: boolean | undefined; + canEdit: boolean | undefined; + canDelete: boolean | undefined; + canPinEvent: boolean | undefined; + hideReadReceipts: boolean | undefined; + onReactionToggle: (targetEventId: string, key: string, shortcode?: string | undefined) => void; + onReplyClick: MouseEventHandler; + onEditId: ((eventId?: string | undefined) => void) | undefined; + handleAddReactions: MouseEventHandler; + closeMenu: () => void; +}; + +export const MessageDropdownMenu = forwardRef( + ( + { + mEvent, + room, + mx, + relations, + canSendReaction, + canEdit, + canDelete, + canPinEvent, + hideReadReceipts, + onReactionToggle, + onReplyClick, + onEditId, + handleAddReactions, + closeMenu, + }, + ref + ) => ( + + {canSendReaction && ( + { + onReactionToggle(mEvent.getId() ?? '', key, shortcode); + closeMenu(); + }} + /> + )} + + {canSendReaction && ( + } + radii="300" + onClick={handleAddReactions} + > + + Add Reaction + + + )} + {relations && ( + + )} + } + radii="300" + data-event-id={mEvent.getId()} + onClick={(evt) => { + onReplyClick(evt); + closeMenu(); + }} + > + + Reply + + + {canEdit && 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() && ( + + )} + + + )} + + ) +); + +type ExtendedOptionsProps = BaseOptionProps & { + imagePackRooms: Room[] | undefined; + onActiveStateChange: React.Dispatch>; + menuAnchor: RectCords | undefined; + emojiBoardAnchor: RectCords | undefined; + handleOpenEmojiBoard: MouseEventHandler; + handleOpenMenu: MouseEventHandler; + setMenuAnchor: React.Dispatch>; + setEmojiBoardAnchor: React.Dispatch>; + isMobileSheetOpen; + setMobileSheetOpen; +}; + +export function MessageOptionsMenu({ + mEvent, + room, + mx, + relations, + imagePackRooms, + canSendReaction, + canEdit, + canDelete, + canPinEvent, + hideReadReceipts, + onReactionToggle, + onReplyClick, + onEditId, + onActiveStateChange, + closeMenu, + menuAnchor, + emojiBoardAnchor, + handleOpenEmojiBoard, + handleOpenMenu, + handleAddReactions, + setMenuAnchor, + setEmojiBoardAnchor, + isMobileSheetOpen, + setMobileSheetOpen, +}: ExtendedOptionsProps) { + useEffect(() => { + onActiveStateChange?.(!!menuAnchor || !!emojiBoardAnchor); + }, [emojiBoardAnchor, menuAnchor, onActiveStateChange]); + + const screenSize = useScreenSizeContext(); + const isMobile = screenSize === ScreenSize.Mobile; + const [view, setView] = useState('options'); + + const eventId = mEvent.getId(); + if (!eventId) return null; + + const optionProps: BaseOptionProps = { + mEvent, + room, + mx, + relations, + canSendReaction, + canEdit, + canDelete, + canPinEvent, + hideReadReceipts, + onReactionToggle, + onReplyClick, + onEditId, + handleAddReactions, + closeMenu, + }; + + if (isMobile) { + return ( + setMobileSheetOpen(false)} isOpen={isMobileSheetOpen}> + {view === 'options' ? ( + { + closeMenu(); + setMobileSheetOpen(false); + }} + handleAddReactions={() => setView('emoji')} + /> + ) : ( + +
+ setView('options')}> + + + + Add Reaction + +
+ { + onReactionToggle(mEvent.getId(), key); + setEmojiBoardAnchor(undefined); + closeMenu(); + setMobileSheetOpen(false); + }} + onCustomEmojiSelect={(mxc, shortcode) => { + onReactionToggle(mEvent.getId(), mxc, shortcode); + setEmojiBoardAnchor(undefined); + closeMenu(); + setMobileSheetOpen(false); + }} + requestClose={() => setEmojiBoardAnchor(undefined)} + /> +
+ )} +
+ ); + } + + return ( +
+ + + {canSendReaction && ( + { + onReactionToggle(eventId, key); + setEmojiBoardAnchor(undefined); + }} + onCustomEmojiSelect={(mxc, shortcode) => { + onReactionToggle(eventId, mxc, shortcode); + setEmojiBoardAnchor(undefined); + }} + requestClose={() => setEmojiBoardAnchor(undefined)} + /> + } + > + + + + + )} + + + + {canEdit && onEditId && ( + onEditId(eventId)} + variant="SurfaceVariant" + size="300" + radii="300" + > + + + )} + setMenuAnchor(undefined), + clickOutsideDeactivates: true, + isKeyForward: (evt) => evt.key === 'ArrowDown', + isKeyBackward: (evt) => evt.key === 'ArrowUp', + escapeDeactivates: stopPropagation, + }} + > + + + } + > + + + + + + +
+ ); +}