Page MenuHomePhabricator

D4920.diff
No OneTemporary

D4920.diff

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<BaseProps, typeof ComposedMessage.defaultProps>;
type Props = {
...BaseProps,
- // Redux state
- +sidebarExistsOrCanBeCreated: boolean,
// withInputState
+inputState: ?InputState,
+ +onMouseLeave: ?() => mixed,
+ +onMouseEnter: (event: SyntheticEvent<HTMLDivElement>) => mixed,
+ +containsInlineSidebar: boolean,
};
class ComposedMessage extends React.PureComponent<Props> {
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 = <MessageTooltip messageTimestamp="" actions={[]} />;
- }
-
- let messageTooltipLinks;
- if (messageTooltip) {
- const tooltipLinksClassName = classNames({
- [css.messageTooltipActiveArea]: true,
- [css.viewerMessageTooltipActiveArea]: isViewer,
- [css.nonViewerMessageActiveArea]: !isViewer,
- });
-
- messageTooltipLinks = (
- <div className={tooltipLinksClassName}>{messageTooltip}</div>
- );
- }
-
- 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 = (
<div className={css.sidebarMarginBottom}>
@@ -167,14 +139,12 @@
<div className={contentClassName}>
<div
className={messageBoxContainerClassName}
- onMouseEnter={this.onMouseEnter}
- onMouseLeave={this.onMouseLeave}
+ onMouseEnter={this.props.onMouseEnter}
+ onMouseLeave={this.props.onMouseLeave}
>
- {viewerTooltipLinks}
<div className={messageBoxClassName} style={messageBoxStyle}>
{this.props.children}
</div>
- {nonViewerTooltipLinks}
</div>
{deliveryIcon}
</div>
@@ -183,23 +153,6 @@
</React.Fragment>
);
}
-
- onMouseEnter: (event: SyntheticEvent<HTMLDivElement>) => 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<ConnectedConfig> = React.memo<BaseConfig>(
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 (
<ComposedMessage
{...props}
- sidebarExistsOrCanBeCreated={sidebarExistsOrCanBeCreated}
inputState={inputState}
+ onMouseLeave={onMouseLeave}
+ onMouseEnter={onMouseEnter}
+ containsInlineSidebar={containsInlineSidebar}
/>
);
},
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 = (
- <TextMessage
- item={item}
- threadInfo={props.threadInfo}
- setMouseOverMessagePosition={props.setMouseOverMessagePosition}
- mouseOverMessagePosition={props.mouseOverMessagePosition}
- />
- );
+ message = <TextMessage item={item} threadInfo={props.threadInfo} />;
} else if (
item.messageInfo.type === messageTypes.IMAGES ||
item.messageInfo.type === messageTypes.MULTIMEDIA
) {
- message = (
- <MultimediaMessage
- item={item}
- threadInfo={props.threadInfo}
- setMouseOverMessagePosition={props.setMouseOverMessagePosition}
- mouseOverMessagePosition={props.mouseOverMessagePosition}
- />
- );
+ message = <MultimediaMessage item={item} threadInfo={props.threadInfo} />;
} 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 (
<ComposedMessage
item={props.item}
threadInfo={props.threadInfo}
sendFailed={textMessageSendFailed(props.item)}
- setMouseOverMessagePosition={props.setMouseOverMessagePosition}
- mouseOverMessagePosition={props.mouseOverMessagePosition}
- canReply={canReply}
>
<div className={messageClassName} style={messageStyle}>
<Markdown rules={rules}>{text}</Markdown>
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<TooltipPosition>,
+ +item: ChatMessageInfoItem,
+ +threadInfo: ThreadInfo,
+};
+
+type UseMessageTooltipResult = {
+ onMouseEnter: (event: SyntheticEvent<HTMLElement>) => 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<HTMLElement>) => {
+ 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 = (
+ <MessageTooltip
+ actions={tooltipActions}
+ messageTimestamp={messageTimestamp}
+ alignment={alignment}
+ />
+ );
+
+ 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,
};

File Metadata

Mime Type
text/plain
Expires
Sat, Nov 23, 6:52 AM (17 h, 59 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2569520
Default Alt Text
D4920.diff (14 KB)

Event Timeline