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,40 +9,51 @@ } 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'; +import { longAbsoluteDate } from 'lib/utils/date-utils'; import { type InputState, InputStateContext } from '../input/input-state'; +import { useSelector } from '../redux/redux-utils'; 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 { useTooltipContext } from './tooltip-provider'; import { - type OnMessagePositionWithContainerInfo, - type MessagePositionInfo, -} from './position-types'; -import { tooltipPositions } from './tooltip-utils'; + calculateTooltipSize, + findTooltipPosition, + useComposedMessageTooltipActions, + tooltipPositions, + getMessageActionTooltipStyle, +} 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 +61,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 +130,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 +149,12 @@
- {viewerTooltipLinks}
{this.props.children}
- {nonViewerTooltipLinks}
{deliveryIcon}
@@ -183,23 +163,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 +171,93 @@ >; const ConnectedComposedMessage: React.ComponentType = React.memo( function ConnectedComposedMessage(props) { - const sidebarExistsOrCanBeCreated = useSidebarExistsOrCanBeCreated( - props.threadInfo, + const inputState = React.useContext(InputStateContext); + + const [ + onMouseLeaveCallback, + setOnMouseLeaveCallback, + ] = React.useState mixed>(null); + + const { renderTooltip } = useTooltipContext(); + const tooltipActions = useComposedMessageTooltipActions( props.item, + props.threadInfo, ); - const inputState = React.useContext(InputStateContext); + + const containsInlineSidebar = !!props.item.threadCreatedFromMessage; + + const timeZone = useSelector(state => state.timeZone); + + const messageTimestamp = React.useMemo(() => { + const time = props.item.messageInfo.time; + return longAbsoluteDate(time, timeZone); + }, [props.item.messageInfo.time, timeZone]); + + const isViewer = props.item.messageInfo.creator.isViewer; + const availableTooltipPositions = isViewer + ? availableTooltipPositionsForViewerMessage + : availableTooltipPositionsForNonViewerMessage; + + const onMouseEnter = React.useCallback( + (event: SyntheticEvent) => { + const rect = event.currentTarget.getBoundingClientRect(); + const { top, bottom, left, right, height, width } = rect; + const messagePosition = { top, bottom, left, right, height, width }; + const tooltipLabels = tooltipActions.map(action => action.label); + const tooltipSize = calculateTooltipSize({ + tooltipLabels, + timestamp: messageTimestamp, + }); + const tooltipPosition = findTooltipPosition({ + sourcePositionInfo: messagePosition, + tooltipSize, + availablePositions: availableTooltipPositions, + preventDisplayingBelowSource: containsInlineSidebar, + }); + if (!tooltipPosition) { + return; + } + + const tooltipPositionStyle = getMessageActionTooltipStyle({ + tooltipPosition, + sourcePositionInfo: messagePosition, + tooltipSize: tooltipSize, + }); + + const tooltip = ( + + ); + + if (renderTooltip) { + const renderTooltipResult = renderTooltip({ + newNode: tooltip, + tooltipPosition: tooltipPositionStyle, + }); + if (renderTooltipResult) { + const { onMouseLeaveCallback: callback } = renderTooltipResult; + setOnMouseLeaveCallback((() => callback: () => () => mixed)); + } + } + }, + [ + availableTooltipPositions, + containsInlineSidebar, + messageTimestamp, + renderTooltip, + tooltipActions, + ], + ); + 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}