diff --git a/web/chat/message-action-buttons.css b/web/chat/message-action-buttons.css index 089c49cb7..5c18e019d 100644 --- a/web/chat/message-action-buttons.css +++ b/web/chat/message-action-buttons.css @@ -1,77 +1,100 @@ div.messageActionContainer { display: flex; flex-direction: row; align-items: center; justify-content: center; padding: 0 6px; background-color: var(--message-action-tooltip-bg); border-radius: 8px; + width: fit-content; +} + +div.timestampContainer { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + padding: 0 6px; + margin-top: 6px; + background-color: var(--message-action-tooltip-bg); + border-radius: 8px; + color: var(--tool-tip-color); +} + +div.timestampContainer p { + white-space: nowrap; + padding: 5px; + font-size: var(--s-font-14); } div.messageActionButtons { display: flex; font-size: 16px; } div.messageActionButtons svg { padding: 10px 6px 6px; color: var(--color-disabled); } div.messageActionButtons svg:hover { cursor: pointer; color: var(--fg); } div.messageActionButtonsViewer { flex-direction: row; + margin-left: auto; + margin-right: 0; } div.messageActionButtonsNonViewer { flex-direction: row-reverse; + margin-left: 0; + margin-right: auto; } div.messageActionLinkIcon { margin: 0 3px; position: relative; } div.messageActionExtraAreaTop:before { height: 15px; width: 55px; content: ''; position: absolute; bottom: -15px; } div.messageActionExtraAreaTopRight:before { right: 0; } div.messageActionExtraAreaTopLeft:before { left: 0; } div.messageActionExtraArea:before { height: 30px; width: 20px; content: ''; position: absolute; } div.messageActionExtraAreaRight:before { left: -20px; } div.messageActionExtraAreaLeft:before { right: -20px; } div.messageActionTopRightTooltip { bottom: 100%; margin-bottom: 1px; right: 0; } div.messageActionTopLeftTooltip { bottom: 100%; margin-bottom: 1px; left: 0; } div.messageActionLeftTooltip { top: 50%; right: 100%; margin-right: 7px; } div.messageActionRightTooltip { top: 50%; left: 100%; margin-left: 7px; } diff --git a/web/chat/message-action-buttons.js b/web/chat/message-action-buttons.js index b6c2d39fa..c03663914 100644 --- a/web/chat/message-action-buttons.js +++ b/web/chat/message-action-buttons.js @@ -1,262 +1,275 @@ // @flow import classNames from 'classnames'; import invariant from 'invariant'; import * as React from 'react'; import type { ChatMessageInfoItem } from 'lib/selectors/chat-selectors'; import { useSidebarExistsOrCanBeCreated } from 'lib/shared/thread-utils'; import type { ThreadInfo } from 'lib/types/thread-types'; +import { longAbsoluteDate } from 'lib/utils/date-utils.js'; import type { InputState } from '../input/input-state.js'; +import { useSelector } from '../redux/redux-utils.js'; import { useOnClickThread, useOnClickPendingSidebar, } from '../selectors/nav-selectors'; import SWMansionIcon from '../SWMansionIcon.react'; import css from './message-action-buttons.css'; import MessageReplyButton from './message-reply-button.react'; import type { ItemAndContainerPositionInfo, MessagePositionInfo, OnMessagePositionWithContainerInfo, PositionInfo, } from './position-types'; import { tooltipPositions, type TooltipPosition } from './tooltip-utils'; import { TooltipMenu, type TooltipStyle, TooltipTextItem, } from './tooltip.react'; const messageActionIconExcessVerticalWhitespace = 10; const openSidebarText = 'Go to sidebar'; const createSidebarText = 'Create sidebar'; type TooltipType = 'sidebar' | 'reply'; type MessageActionButtonsProps = { +threadInfo: ThreadInfo, +item: ChatMessageInfoItem, +availableTooltipPositions: $ReadOnlyArray, +setMouseOverMessagePosition?: ( messagePositionInfo: MessagePositionInfo, ) => void, +mouseOverMessagePosition: OnMessagePositionWithContainerInfo, +canReply?: boolean, +inputState?: ?InputState, }; function MessageActionButtons(props: MessageActionButtonsProps): React.Node { const { threadInfo, item, availableTooltipPositions, setMouseOverMessagePosition, mouseOverMessagePosition, canReply, inputState, } = props; const { containerPosition } = mouseOverMessagePosition; const [activeTooltip, setActiveTooltip] = React.useState(); const [pointingTo, setPointingTo] = React.useState(); const showTooltip = React.useCallback( (tooltipType: TooltipType, iconPosition: ItemAndContainerPositionInfo) => { if (activeTooltip) { return; } setActiveTooltip(tooltipType); setPointingTo(iconPosition); }, [activeTooltip], ); const hideTooltip = React.useCallback(() => { setActiveTooltip(null); }, []); const showSidebarTooltip = React.useCallback( (event: SyntheticEvent) => { const rect = event.currentTarget.getBoundingClientRect(); const iconPosition = getIconPosition(rect, containerPosition); showTooltip('sidebar', iconPosition); }, [containerPosition, showTooltip], ); const showReplyTooltip = React.useCallback( (event: SyntheticEvent) => { const rect = event.currentTarget.getBoundingClientRect(); const iconPosition = getIconPosition(rect, containerPosition); showTooltip('reply', iconPosition); }, [containerPosition, showTooltip], ); const { threadCreatedFromMessage, messageInfo } = item; const onThreadOpen = useOnClickThread(threadCreatedFromMessage); const onPendingSidebarOpen = useOnClickPendingSidebar( messageInfo, threadInfo, ); const onSidebarButtonClick = React.useCallback( (event: SyntheticEvent) => { if (threadCreatedFromMessage) { onThreadOpen(event); } else { onPendingSidebarOpen(event); } }, [onPendingSidebarOpen, onThreadOpen, threadCreatedFromMessage], ); const onReplyButtonClick = React.useCallback(() => { invariant( setMouseOverMessagePosition, 'setMouseOverMessagePosition should be set if replyButton exists', ); setMouseOverMessagePosition({ type: 'off', item: item }); }, [item, setMouseOverMessagePosition]); let tooltipText = ''; if (activeTooltip === 'reply') { tooltipText = 'Reply'; } else if (activeTooltip === 'sidebar') { tooltipText = threadCreatedFromMessage ? openSidebarText : createSidebarText; } let tooltipMenu = null; if (pointingTo && activeTooltip) { tooltipMenu = ( ); } let replyButton; if (canReply) { invariant(inputState, 'inputState must be set if replyButton exists'); invariant( mouseOverMessagePosition, 'mouseOverMessagePosition must be set if replyButton exists', ); replyButton = (
{activeTooltip === 'reply' ? tooltipMenu : null}
); } const sidebarExistsOrCanBeCreated = useSidebarExistsOrCanBeCreated( threadInfo, item, ); let sidebarButton; if (sidebarExistsOrCanBeCreated) { sidebarButton = (
{activeTooltip === 'sidebar' ? tooltipMenu : null}
); } + const timezone = useSelector(state => state.timeZone); + const timestampText = React.useMemo( + () => longAbsoluteDate(messageInfo.time, timezone), + [messageInfo.time, timezone], + ); + const { isViewer } = messageInfo.creator; const messageActionButtonsContainerClassName = classNames({ [css.messageActionContainer]: true, [css.messageActionButtons]: true, [css.messageActionButtonsViewer]: isViewer, [css.messageActionButtonsNonViewer]: !isViewer, }); return ( -
- {sidebarButton} - {replyButton} +
+
+ {sidebarButton} + {replyButton} +
+
+

{timestampText}

+
); } function getIconPosition( rect: ClientRect, containerPosition: PositionInfo, ): ItemAndContainerPositionInfo { const { top, bottom, left, right, width, height } = rect; return { containerPosition, itemPosition: { top: top - containerPosition.top + messageActionIconExcessVerticalWhitespace, bottom: bottom - containerPosition.top - messageActionIconExcessVerticalWhitespace, left: left - containerPosition.left, right: right - containerPosition.left, width, height: height - messageActionIconExcessVerticalWhitespace * 2, }, }; } function getMessageActionTooltipStyle( tooltipPosition: TooltipPosition, ): TooltipStyle { let className; if (tooltipPosition === tooltipPositions.TOP_RIGHT) { className = classNames( css.messageActionTopRightTooltip, css.messageActionExtraAreaTop, css.messageActionExtraAreaTopRight, ); } else if (tooltipPosition === tooltipPositions.TOP_LEFT) { className = classNames( css.messageActionTopLeftTooltip, css.messageActionExtraAreaTop, css.messageActionExtraAreaTopLeft, ); } else if (tooltipPosition === tooltipPositions.RIGHT) { className = classNames( css.messageActionRightTooltip, css.messageActionExtraArea, css.messageActionExtraAreaRight, ); } else if (tooltipPosition === tooltipPositions.LEFT) { className = classNames( css.messageActionLeftTooltip, css.messageActionExtraArea, css.messageActionExtraAreaLeft, ); } invariant(className, `${tooltipPosition} is not valid for message tooltip`); return { className }; } export default MessageActionButtons;