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, | ||||
tomek: Can we make this readonly? | |||||
+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 emojiKeyboardRef = React.useRef(); | |||||
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( | |||||
emojiKeyboardRef.current, | |||||
tooltipPositionStyle, | |||||
tooltipSize, | |||||
); | |||||
// eslint-disable-next-line react-hooks/exhaustive-deps | |||||
tomekUnsubmitted Not Done Inline ActionsWhy do we have to use this exception? We should have a really good reason to do that tomek: Why do we have to use this exception? We should have a really good reason to do that | |||||
ginsuAuthorUnsubmitted Done Inline Actionsginsu: I added this exception because I was getting this error in my editor when I added… | |||||
tomekUnsubmitted Not Done Inline ActionsThis error was a good thing as it highlighted a bigger issue. Changing the value of ref won't result in render and in recomputing the memo. It's even explicitly included in the error message! That's the difference between state and refs. tomek: This error was a good thing as it highlighted a bigger issue. Changing the value of ref won't… | |||||
ginsuAuthorUnsubmitted Done Inline ActionsOkay sounds like I need to read up more on this, thanks for pointing this out! ginsu: Okay sounds like I need to read up more on this, thanks for pointing this out! | |||||
ginsuAuthorUnsubmitted Done Inline ActionsFound this guide in the react docs that gave me a ton of context and helped with my solution ginsu: Found this [[ https://reactjs.org/docs/hooks-faq.html#how-can-i-measure-a-dom-node | guide ]]… | |||||
tomekUnsubmitted Not Done Inline ActionsGreat! tomek: Great! | |||||
}, [tooltipPositionStyle, tooltipSize, emojiKeyboardRef.current]); | |||||
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, 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; |
Can we make this readonly?