Changeset View
Changeset View
Standalone View
Standalone View
web/chat/message-tooltip.react.js
Show All 9 Lines | ||||||||||||||||||
import type { ThreadInfo } from 'lib/types/thread-types.js'; | import type { ThreadInfo } from 'lib/types/thread-types.js'; | |||||||||||||||||
import { | import { | |||||||||||||||||
tooltipButtonStyle, | tooltipButtonStyle, | |||||||||||||||||
tooltipLabelStyle, | tooltipLabelStyle, | |||||||||||||||||
tooltipStyle, | tooltipStyle, | |||||||||||||||||
} from './chat-constants.js'; | } from './chat-constants.js'; | |||||||||||||||||
import css from './message-tooltip.css'; | import css from './message-tooltip.css'; | |||||||||||||||||
import { useSendReaction } from './reaction-message-utils.js'; | import { | |||||||||||||||||
useSendReaction, | ||||||||||||||||||
getEmojiKeyboardPosition, | ||||||||||||||||||
} from './reaction-message-utils.js'; | ||||||||||||||||||
import { useTooltipContext } from './tooltip-provider.js'; | import { useTooltipContext } from './tooltip-provider.js'; | |||||||||||||||||
import { useSelector } from '../redux/redux-utils.js'; | import { useSelector } from '../redux/redux-utils.js'; | |||||||||||||||||
import { type MessageTooltipAction } from '../utils/tooltip-utils.js'; | import type { | |||||||||||||||||
MessageTooltipAction, | ||||||||||||||||||
TooltipSize, | ||||||||||||||||||
TooltipPositionStyle, | ||||||||||||||||||
} from '../utils/tooltip-utils.js'; | ||||||||||||||||||
type MessageTooltipProps = { | type MessageTooltipProps = { | |||||||||||||||||
+actions: $ReadOnlyArray<MessageTooltipAction>, | +actions: $ReadOnlyArray<MessageTooltipAction>, | |||||||||||||||||
+messageTimestamp: string, | +messageTimestamp: string, | |||||||||||||||||
+alignment?: 'left' | 'center' | 'right', | +tooltipPositionStyle: TooltipPositionStyle, | |||||||||||||||||
+tooltipSize: TooltipSize, | ||||||||||||||||||
+item: ChatMessageInfoItem, | +item: ChatMessageInfoItem, | |||||||||||||||||
+threadInfo: ThreadInfo, | +threadInfo: ThreadInfo, | |||||||||||||||||
}; | }; | |||||||||||||||||
function MessageTooltip(props: MessageTooltipProps): React.Node { | function MessageTooltip(props: MessageTooltipProps): React.Node { | |||||||||||||||||
const { | const { | |||||||||||||||||
actions, | actions, | |||||||||||||||||
messageTimestamp, | messageTimestamp, | |||||||||||||||||
alignment = 'left', | tooltipPositionStyle, | |||||||||||||||||
tooltipSize, | ||||||||||||||||||
item, | item, | |||||||||||||||||
threadInfo, | threadInfo, | |||||||||||||||||
} = props; | } = props; | |||||||||||||||||
const { messageInfo, reactions } = item; | const { messageInfo, reactions } = item; | |||||||||||||||||
const { alignment = 'left' } = tooltipPositionStyle; | ||||||||||||||||||
const [activeTooltipLabel, setActiveTooltipLabel] = React.useState<?string>(); | const [activeTooltipLabel, setActiveTooltipLabel] = React.useState<?string>(); | |||||||||||||||||
const { renderEmojiKeyboard } = useTooltipContext(); | const { renderEmojiKeyboard } = useTooltipContext(); | |||||||||||||||||
const [emojiKeyboardNode, setEmojiKeyboardNode] = React.useState(null); | ||||||||||||||||||
const emojiKeyboardRef = React.useCallback(node => { | ||||||||||||||||||
if (node !== null) { | ||||||||||||||||||
setEmojiKeyboardNode(node); | ||||||||||||||||||
} | ||||||||||||||||||
}, []); | ||||||||||||||||||
tomek: Do we need this null check? If that's not necessary, we can (probably) avoid using callback and… | ||||||||||||||||||
const messageActionButtonsContainerClassName = classNames( | const messageActionButtonsContainerClassName = classNames( | |||||||||||||||||
css.messageActionContainer, | css.messageActionContainer, | |||||||||||||||||
css.messageActionButtons, | css.messageActionButtons, | |||||||||||||||||
); | ); | |||||||||||||||||
const messageTooltipButtonStyle = React.useMemo(() => tooltipButtonStyle, []); | const messageTooltipButtonStyle = React.useMemo(() => tooltipButtonStyle, []); | |||||||||||||||||
const tooltipButtons = React.useMemo(() => { | const tooltipButtons = React.useMemo(() => { | |||||||||||||||||
▲ Show 20 Lines • Show All 56 Lines • ▼ Show 20 Lines | const tooltipTimestamp = React.useMemo(() => { | |||||||||||||||||
} | } | |||||||||||||||||
return ( | return ( | |||||||||||||||||
<div className={css.messageTooltipLabel} style={messageTooltipLabelStyle}> | <div className={css.messageTooltipLabel} style={messageTooltipLabelStyle}> | |||||||||||||||||
{messageTimestamp} | {messageTimestamp} | |||||||||||||||||
</div> | </div> | |||||||||||||||||
); | ); | |||||||||||||||||
}, [messageTimestamp, messageTooltipLabelStyle]); | }, [messageTimestamp, messageTooltipLabelStyle]); | |||||||||||||||||
const emojiKeyboardPosition = React.useMemo(() => { | ||||||||||||||||||
return getEmojiKeyboardPosition( | ||||||||||||||||||
emojiKeyboardNode, | ||||||||||||||||||
tooltipPositionStyle, | ||||||||||||||||||
tooltipSize, | ||||||||||||||||||
); | ||||||||||||||||||
}, [emojiKeyboardNode, tooltipPositionStyle, tooltipSize]); | ||||||||||||||||||
tomekUnsubmitted Not Done Inline ActionsInitially emojiKeyboardNode might be null - do we handle this correctly in getEmojiKeyboardPosition? tomek: Initially `emojiKeyboardNode` might be null - do we handle this correctly in… | ||||||||||||||||||
ginsuAuthorUnsubmitted Done Inline ActionsYup there are default values in getEmojiKeyboardPosition that are equal to the expected values of the width and height for the emoji keyboard in case emojiKeyboardNode is null ginsu: Yup there are default values in `getEmojiKeyboardPosition` that are equal to the expected… | ||||||||||||||||||
tomekUnsubmitted Not Done Inline Actions
This can be simplified tomek: This can be simplified | ||||||||||||||||||
const emojiKeyboardPositionStyle = React.useMemo( | ||||||||||||||||||
() => ({ | ||||||||||||||||||
bottom: emojiKeyboardPosition.bottom, | ||||||||||||||||||
left: emojiKeyboardPosition.left, | ||||||||||||||||||
}), | ||||||||||||||||||
[emojiKeyboardPosition.bottom, emojiKeyboardPosition.left], | ||||||||||||||||||
); | ||||||||||||||||||
const nextLocalID = useSelector(state => state.nextLocalID); | const nextLocalID = useSelector(state => state.nextLocalID); | |||||||||||||||||
const localID = `${localIDPrefix}${nextLocalID}`; | const localID = `${localIDPrefix}${nextLocalID}`; | |||||||||||||||||
const sendReaction = useSendReaction(messageInfo.id, localID, threadInfo.id); | const sendReaction = useSendReaction(messageInfo.id, localID, threadInfo.id); | |||||||||||||||||
const onEmojiSelect = React.useCallback( | const onEmojiSelect = React.useCallback( | |||||||||||||||||
emoji => { | emoji => { | |||||||||||||||||
const reactionInput = emoji.native; | const reactionInput = emoji.native; | |||||||||||||||||
const viewerReacted = !!reactions.get(reactionInput)?.viewerReacted; | const viewerReacted = !!reactions.get(reactionInput)?.viewerReacted; | |||||||||||||||||
const action = viewerReacted ? 'remove_reaction' : 'add_reaction'; | const action = viewerReacted ? 'remove_reaction' : 'add_reaction'; | |||||||||||||||||
sendReaction(reactionInput, action); | sendReaction(reactionInput, action); | |||||||||||||||||
}, | }, | |||||||||||||||||
[sendReaction, reactions], | [sendReaction, reactions], | |||||||||||||||||
); | ); | |||||||||||||||||
const emojiKeyboard = React.useMemo(() => { | const emojiKeyboard = React.useMemo(() => { | |||||||||||||||||
if (!renderEmojiKeyboard) { | if (!renderEmojiKeyboard) { | |||||||||||||||||
return null; | return null; | |||||||||||||||||
} | } | |||||||||||||||||
return <Picker data={data} onEmojiSelect={onEmojiSelect} />; | ||||||||||||||||||
}, [onEmojiSelect, renderEmojiKeyboard]); | return ( | |||||||||||||||||
<div | ||||||||||||||||||
ref={emojiKeyboardRef} | ||||||||||||||||||
style={emojiKeyboardPositionStyle} | ||||||||||||||||||
className={css.emojiKeyboard} | ||||||||||||||||||
> | ||||||||||||||||||
<Picker data={data} onEmojiSelect={onEmojiSelect} /> | ||||||||||||||||||
</div> | ||||||||||||||||||
); | ||||||||||||||||||
}, [ | ||||||||||||||||||
emojiKeyboardPositionStyle, | ||||||||||||||||||
emojiKeyboardRef, | ||||||||||||||||||
onEmojiSelect, | ||||||||||||||||||
renderEmojiKeyboard, | ||||||||||||||||||
]); | ||||||||||||||||||
const messageTooltipContainerStyle = React.useMemo(() => tooltipStyle, []); | const messageTooltipContainerStyle = React.useMemo(() => tooltipStyle, []); | |||||||||||||||||
const containerClassName = classNames({ | const containerClassName = classNames({ | |||||||||||||||||
[css.container]: true, | ||||||||||||||||||
[css.containerLeftAlign]: alignment === 'left', | ||||||||||||||||||
[css.containerCenterAlign]: alignment === 'center', | ||||||||||||||||||
}); | ||||||||||||||||||
const messageTooltipContainerClassNames = classNames({ | ||||||||||||||||||
[css.messageTooltipContainer]: true, | [css.messageTooltipContainer]: true, | |||||||||||||||||
[css.leftTooltipAlign]: alignment === 'left', | [css.leftTooltipAlign]: alignment === 'left', | |||||||||||||||||
[css.centerTooltipAlign]: alignment === 'center', | [css.centerTooltipAlign]: alignment === 'center', | |||||||||||||||||
[css.rightTooltipAlign]: alignment === 'right', | [css.rightTooltipAlign]: alignment === 'right', | |||||||||||||||||
}); | }); | |||||||||||||||||
return ( | return ( | |||||||||||||||||
<div className={containerClassName}> | <> | |||||||||||||||||
{emojiKeyboard} | {emojiKeyboard} | |||||||||||||||||
<div | <div className={containerClassName} style={messageTooltipContainerStyle}> | |||||||||||||||||
className={messageTooltipContainerClassNames} | ||||||||||||||||||
style={messageTooltipContainerStyle} | ||||||||||||||||||
> | ||||||||||||||||||
<div style={messageTooltipTopLabelStyle}>{tooltipLabel}</div> | <div style={messageTooltipTopLabelStyle}>{tooltipLabel}</div> | |||||||||||||||||
{tooltipButtons} | {tooltipButtons} | |||||||||||||||||
{tooltipTimestamp} | {tooltipTimestamp} | |||||||||||||||||
</div> | </div> | |||||||||||||||||
</div> | </> | |||||||||||||||||
); | ); | |||||||||||||||||
} | } | |||||||||||||||||
export default MessageTooltip; | export default MessageTooltip; |
Do we need this null check? If that's not necessary, we can (probably) avoid using callback and use setter directly