diff --git a/web/chat/composed-message.react.js b/web/chat/composed-message.react.js --- a/web/chat/composed-message.react.js +++ b/web/chat/composed-message.react.js @@ -9,7 +9,6 @@ } from 'react-feather'; import { type ChatMessageInfoItem } from 'lib/selectors/chat-selectors'; -import { useSidebarExistsOrCanBeCreated } from 'lib/shared/thread-utils'; import { stringForUser } from 'lib/shared/user-utils'; import { assertComposableMessageType } from 'lib/types/message-types'; import { type ThreadInfo } from 'lib/types/thread-types'; @@ -18,31 +17,33 @@ import css from './chat-message-list.css'; import FailedSend from './failed-send.react'; import { InlineSidebar } from './inline-sidebar.react'; -import MessageTooltip from './message-tooltip.react'; -import { - type OnMessagePositionWithContainerInfo, - type MessagePositionInfo, -} from './position-types'; -import { tooltipPositions } from './tooltip-utils'; +import { tooltipPositions, useMessageTooltip } from './tooltip-utils'; const availableTooltipPositionsForViewerMessage = [ - tooltipPositions.RIGHT_TOP, tooltipPositions.LEFT, + tooltipPositions.LEFT_BOTTOM, + tooltipPositions.LEFT_TOP, + tooltipPositions.RIGHT, + tooltipPositions.RIGHT_BOTTOM, + tooltipPositions.RIGHT_TOP, + tooltipPositions.BOTTOM, + tooltipPositions.TOP, ]; const availableTooltipPositionsForNonViewerMessage = [ - tooltipPositions.LEFT_TOP, tooltipPositions.RIGHT, + tooltipPositions.RIGHT_BOTTOM, + tooltipPositions.RIGHT_TOP, + tooltipPositions.LEFT, + tooltipPositions.LEFT_BOTTOM, + tooltipPositions.LEFT_TOP, + tooltipPositions.BOTTOM, + tooltipPositions.TOP, ]; type BaseProps = { +item: ChatMessageInfoItem, +threadInfo: ThreadInfo, +sendFailed: boolean, - +setMouseOverMessagePosition: ( - messagePositionInfo: MessagePositionInfo, - ) => void, - +mouseOverMessagePosition?: ?OnMessagePositionWithContainerInfo, - +canReply: boolean, +children: React.Node, +fixedWidth?: boolean, +borderRadius: number, @@ -50,10 +51,11 @@ type BaseConfig = React.Config; type Props = { ...BaseProps, - // Redux state - +sidebarExistsOrCanBeCreated: boolean, // withInputState +inputState: ?InputState, + +onMouseLeave: ?() => mixed, + +onMouseEnter: (event: SyntheticEvent) => mixed, + +containsInlineSidebar: boolean, }; class ComposedMessage extends React.PureComponent { static defaultProps: { +borderRadius: number } = { @@ -118,38 +120,8 @@ ); } - let messageTooltip; - if ( - this.props.mouseOverMessagePosition && - this.props.mouseOverMessagePosition.item.messageInfo.id === id && - (this.props.sidebarExistsOrCanBeCreated || this.props.canReply) - ) { - // eslint-disable-next-line no-unused-vars - const availableTooltipPositions = isViewer - ? availableTooltipPositionsForViewerMessage - : availableTooltipPositionsForNonViewerMessage; - - messageTooltip = ; - } - - let messageTooltipLinks; - if (messageTooltip) { - const tooltipLinksClassName = classNames({ - [css.messageTooltipActiveArea]: true, - [css.viewerMessageTooltipActiveArea]: isViewer, - [css.nonViewerMessageActiveArea]: !isViewer, - }); - - messageTooltipLinks = ( -
{messageTooltip}
- ); - } - - const viewerTooltipLinks = isViewer ? messageTooltipLinks : null; - const nonViewerTooltipLinks = !isViewer ? messageTooltipLinks : null; - let inlineSidebar = null; - if (item.threadCreatedFromMessage) { + if (this.props.containsInlineSidebar && item.threadCreatedFromMessage) { const positioning = isViewer ? 'right' : 'left'; inlineSidebar = (
@@ -167,14 +139,12 @@
- {viewerTooltipLinks}
{this.props.children}
- {nonViewerTooltipLinks}
{deliveryIcon}
@@ -183,23 +153,6 @@ ); } - - onMouseEnter: (event: SyntheticEvent) => void = event => { - const { item } = this.props; - const rect = event.currentTarget.getBoundingClientRect(); - const { top, bottom, left, right, height, width } = rect; - const messagePosition = { top, bottom, left, right, height, width }; - this.props.setMouseOverMessagePosition({ - type: 'on', - item, - messagePosition, - }); - }; - - onMouseLeave: () => void = () => { - const { item } = this.props; - this.props.setMouseOverMessagePosition({ type: 'off', item }); - }; } type ConnectedConfig = React.Config< @@ -208,16 +161,27 @@ >; const ConnectedComposedMessage: React.ComponentType = React.memo( function ConnectedComposedMessage(props) { - const sidebarExistsOrCanBeCreated = useSidebarExistsOrCanBeCreated( - props.threadInfo, - props.item, - ); + const { item, threadInfo } = props; const inputState = React.useContext(InputStateContext); + const isViewer = props.item.messageInfo.creator.isViewer; + const availablePositions = isViewer + ? availableTooltipPositionsForViewerMessage + : availableTooltipPositionsForNonViewerMessage; + const containsInlineSidebar = !!item.threadCreatedFromMessage; + + const { onMouseLeave, onMouseEnter } = useMessageTooltip({ + item, + threadInfo, + availablePositions, + }); + return ( ); }, diff --git a/web/chat/message.react.js b/web/chat/message.react.js --- a/web/chat/message.react.js +++ b/web/chat/message.react.js @@ -39,26 +39,12 @@ } let message; if (item.messageInfo.type === messageTypes.TEXT) { - message = ( - - ); + message = ; } else if ( item.messageInfo.type === messageTypes.IMAGES || item.messageInfo.type === messageTypes.MULTIMEDIA ) { - message = ( - - ); + message = ; } else { invariant(item.robotext, "Flow can't handle our fancy types :("); message = ( diff --git a/web/chat/multimedia-message.react.js b/web/chat/multimedia-message.react.js --- a/web/chat/multimedia-message.react.js +++ b/web/chat/multimedia-message.react.js @@ -12,18 +12,9 @@ import css from './chat-message-list.css'; import ComposedMessage from './composed-message.react'; import sendFailed from './multimedia-message-send-failed'; -import type { - MessagePositionInfo, - OnMessagePositionWithContainerInfo, -} from './position-types'; - type BaseProps = { +item: ChatMessageInfoItem, +threadInfo: ThreadInfo, - +setMouseOverMessagePosition: ( - messagePositionInfo: MessagePositionInfo, - ) => void, - +mouseOverMessagePosition: ?OnMessagePositionWithContainerInfo, }; type Props = { ...BaseProps, @@ -71,9 +62,6 @@ item={item} threadInfo={this.props.threadInfo} sendFailed={sendFailed(item, inputState)} - setMouseOverMessagePosition={this.props.setMouseOverMessagePosition} - mouseOverMessagePosition={this.props.mouseOverMessagePosition} - canReply={false} fixedWidth={multimedia.length > 1} borderRadius={16} > diff --git a/web/chat/text-message.react.js b/web/chat/text-message.react.js --- a/web/chat/text-message.react.js +++ b/web/chat/text-message.react.js @@ -6,27 +6,19 @@ import type { ChatMessageInfoItem } from 'lib/selectors/chat-selectors'; import { onlyEmojiRegex } from 'lib/shared/emojis'; -import { colorIsDark, threadHasPermission } from 'lib/shared/thread-utils'; +import { colorIsDark } from 'lib/shared/thread-utils'; import { messageTypes } from 'lib/types/message-types'; -import { type ThreadInfo, threadPermissions } from 'lib/types/thread-types'; +import { type ThreadInfo } from 'lib/types/thread-types'; import Markdown from '../markdown/markdown.react'; import css from './chat-message-list.css'; import ComposedMessage from './composed-message.react'; import { MessageListContext } from './message-list-types'; -import type { - MessagePositionInfo, - OnMessagePositionWithContainerInfo, -} from './position-types'; import textMessageSendFailed from './text-message-send-failed'; type Props = { +item: ChatMessageInfoItem, +threadInfo: ThreadInfo, - +setMouseOverMessagePosition: ( - messagePositionInfo: MessagePositionInfo, - ) => void, - +mouseOverMessagePosition: ?OnMessagePositionWithContainerInfo, }; function TextMessage(props: Props): React.Node { invariant( @@ -59,18 +51,11 @@ const messageListContext = React.useContext(MessageListContext); invariant(messageListContext, 'DummyTextNode should have MessageListContext'); const rules = messageListContext.getTextMessageMarkdownRules(darkColor); - const canReply = threadHasPermission( - props.threadInfo, - threadPermissions.VOICED, - ); return (
{text} diff --git a/web/chat/tooltip-utils.js b/web/chat/tooltip-utils.js --- a/web/chat/tooltip-utils.js +++ b/web/chat/tooltip-utils.js @@ -12,9 +12,11 @@ import { isComposableMessageType } from 'lib/types/message-types'; import type { ThreadInfo } from 'lib/types/thread-types'; import { threadPermissions } from 'lib/types/thread-types'; +import { longAbsoluteDate } from 'lib/utils/date-utils'; import CommIcon from '../CommIcon.react'; import { InputStateContext } from '../input/input-state'; +import { useSelector } from '../redux/redux-utils'; import { useOnClickPendingSidebar, useOnClickThread, @@ -25,7 +27,9 @@ tooltipLabelStyle, tooltipStyle, } from './chat-constants'; +import MessageTooltip from './message-tooltip.react'; import type { PositionInfo } from './position-types'; +import { useTooltipContext } from './tooltip-provider'; export const tooltipPositions = Object.freeze({ LEFT: 'left', @@ -401,6 +405,111 @@ ]); } +type UseMessageTooltipArgs = { + +availablePositions: $ReadOnlyArray, + +item: ChatMessageInfoItem, + +threadInfo: ThreadInfo, +}; + +type UseMessageTooltipResult = { + onMouseEnter: (event: SyntheticEvent) => void, + onMouseLeave: ?() => mixed, +}; + +function useMessageTooltip({ + availablePositions, + item, + threadInfo, +}: UseMessageTooltipArgs): UseMessageTooltipResult { + const [onMouseLeave, setOnMouseLeave] = React.useState mixed>(null); + + const { renderTooltip } = useTooltipContext(); + const tooltipActions = useMessageTooltipActions(item, threadInfo); + + const containsInlineSidebar = !!item.threadCreatedFromMessage; + + const timeZone = useSelector(state => state.timeZone); + + const messageTimestamp = React.useMemo(() => { + const time = item.messageInfo.time; + return longAbsoluteDate(time, timeZone); + }, [item.messageInfo.time, timeZone]); + + const tooltipSize = React.useMemo(() => { + if (typeof document === 'undefined') { + return { + width: 0, + height: 0, + }; + } + const tooltipLabels = tooltipActions.map(action => action.label); + return calculateTooltipSize({ + tooltipLabels, + timestamp: messageTimestamp, + }); + }, [messageTimestamp, tooltipActions]); + + const onMouseEnter = React.useCallback( + (event: SyntheticEvent) => { + if (!renderTooltip) { + return; + } + const rect = event.currentTarget.getBoundingClientRect(); + const { top, bottom, left, right, height, width } = rect; + const messagePosition = { top, bottom, left, right, height, width }; + + const tooltipPosition = findTooltipPosition({ + sourcePositionInfo: messagePosition, + tooltipSize, + availablePositions, + defaultPosition: availablePositions[0], + preventDisplayingBelowSource: containsInlineSidebar, + }); + if (!tooltipPosition) { + return; + } + + const tooltipPositionStyle = getMessageActionTooltipStyle({ + tooltipPosition, + sourcePositionInfo: messagePosition, + tooltipSize: tooltipSize, + }); + + const { alignment } = tooltipPositionStyle; + + const tooltip = ( + + ); + + const renderTooltipResult = renderTooltip({ + newNode: tooltip, + tooltipPositionStyle, + }); + if (renderTooltipResult) { + const { onMouseLeaveCallback: callback } = renderTooltipResult; + setOnMouseLeave((() => callback: () => () => mixed)); + } + }, + [ + availablePositions, + containsInlineSidebar, + messageTimestamp, + renderTooltip, + tooltipActions, + tooltipSize, + ], + ); + + return { + onMouseEnter, + onMouseLeave, + }; +} + export { findTooltipPosition, calculateTooltipSize, @@ -408,5 +517,6 @@ useMessageTooltipSidebarAction, useMessageTooltipReplyAction, useMessageTooltipActions, + useMessageTooltip, sizeOfTooltipArrow, };