You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
session-desktop/ts/components/conversation/message/reactions/Reaction.tsx

192 lines
6.3 KiB
TypeScript

import React, { useRef, useState } from 'react';
import { useMouse } from 'react-use';
import styled from 'styled-components';
import { useRightOverlayMode } from '../../../../hooks/useUI';
import { isUsAnySogsFromCache } from '../../../../session/apis/open_group_api/sogsv3/knownBlindedkeys';
import { UserUtils } from '../../../../session/utils';
import { useIsMessageSelectionMode } from '../../../../state/selectors/selectedConversation';
import { THEME_GLOBALS } from '../../../../themes/globals';
import { SortedReactionList } from '../../../../types/Reaction';
import { abbreviateNumber } from '../../../../util/abbreviateNumber';
import { nativeEmojiData } from '../../../../util/emoji';
import { popupXDefault, popupYDefault } from '../message-content/MessageReactions';
import { POPUP_WIDTH, ReactionPopup, TipPosition } from './ReactionPopup';
const StyledReaction = styled.button<{
selected: boolean;
inModal: boolean;
showCount: boolean;
hasOnClick?: boolean;
}>`
display: flex;
justify-content: ${props => (props.showCount ? 'flex-start' : 'center')};
align-items: center;
background-color: var(--message-bubbles-received-background-color);
border-width: 1px;
border-style: solid;
border-color: ${props => (props.selected ? 'var(--primary-color)' : 'var(--transparent-color)')};
border-radius: 11px;
box-sizing: border-box;
padding: 0 7px;
margin: 0 4px var(--margins-sm);
height: 24px;
min-width: ${props => (props.showCount ? '48px' : '24px')};
span {
width: 100%;
}
${props => !props.hasOnClick && 'cursor: not-allowed;'}
`;
const StyledReactionContainer = styled.div<{
inModal: boolean;
}>`
position: relative;
${props => props.inModal && 'white-space: nowrap; margin-right: 8px;'}
`;
export type ReactionProps = {
emoji: string;
messageId: string;
reactions: SortedReactionList;
inModal: boolean;
inGroup: boolean;
handlePopupX: (x: number) => void;
handlePopupY: (y: number) => void;
onClick?: (emoji: string) => void;
popupReaction?: string;
onSelected?: (emoji: string) => boolean;
handlePopupReaction?: (emoji: string) => void;
handlePopupClick?: () => void;
};
export const Reaction = (props: ReactionProps) => {
const {
emoji,
messageId,
reactions,
inModal,
inGroup,
handlePopupX,
handlePopupY,
onClick,
popupReaction,
onSelected,
handlePopupReaction,
handlePopupClick,
} = props;
const rightOverlayMode = useRightOverlayMode();
const isMessageSelection = useIsMessageSelectionMode();
const reactionsMap = (reactions && Object.fromEntries(reactions)) || {};
const senders = reactionsMap[emoji]?.senders || [];
const count = reactionsMap[emoji]?.count;
const showCount = count !== undefined && (count > 1 || inGroup);
const reactionRef = useRef<HTMLDivElement>(null);
const { docX: _docX, elW } = useMouse(reactionRef);
const gutterWidth = 380; // TODOLATER make this a variable which can be shared in CSS and JS
const tooltipMidPoint = POPUP_WIDTH / 2; // px
const [tooltipPosition, setTooltipPosition] = useState<TipPosition>('center');
const me = UserUtils.getOurPubKeyStrFromCache();
const isBlindedMe =
senders && senders.length > 0 && senders.filter(isUsAnySogsFromCache).length > 0;
const selected = () => {
if (onSelected) {
return onSelected(emoji);
}
return senders && senders.length > 0 && (senders.includes(me) || isBlindedMe);
};
const handleReactionClick = () => {
if (!isMessageSelection) {
if (onClick) {
onClick(emoji);
}
}
};
return (
<StyledReactionContainer ref={reactionRef} inModal={inModal}>
<StyledReaction
showCount={showCount}
selected={selected()}
inModal={inModal}
onClick={handleReactionClick}
hasOnClick={Boolean(onClick)}
onMouseEnter={() => {
if (inGroup && !isMessageSelection) {
const { innerWidth } = window;
let windowWidth = innerWidth;
let docX = _docX;
// if the right panel is open we may need to show a reaction tooltip relative to it
if (rightOverlayMode && rightOverlayMode.type === 'message_info') {
const rightPanelWidth = Number(THEME_GLOBALS['--right-panel-width'].split('px')[0]);
// we need to check that the reaction we are hovering over is inside of the right panel and not in the messages list
if (docX > windowWidth - rightPanelWidth) {
// make the values relative to the right panel
docX = docX - windowWidth + rightPanelWidth;
windowWidth = rightPanelWidth;
}
}
if (handlePopupReaction) {
// overflow on far right means we shift left
if (docX + elW + tooltipMidPoint > innerWidth) {
handlePopupX(Math.abs(popupXDefault) * 1.5 * -1);
setTooltipPosition('right');
// overflow onto conversations means we lock to the right
} else if (docX - elW <= gutterWidth + tooltipMidPoint) {
const offset = -12.5;
handlePopupX(offset);
setTooltipPosition('left');
} else {
handlePopupX(popupXDefault);
setTooltipPosition('center');
}
handlePopupReaction(emoji);
}
}
}}
>
<span
role={'img'}
aria-label={nativeEmojiData?.ariaLabels ? nativeEmojiData.ariaLabels[emoji] : undefined}
>
{emoji}
{showCount && `\u00A0\u00A0${abbreviateNumber(count)}`}
</span>
</StyledReaction>
{inGroup && popupReaction && popupReaction === emoji && (
<ReactionPopup
messageId={messageId}
emoji={popupReaction}
count={reactionsMap[popupReaction]?.count}
senders={reactionsMap[popupReaction]?.senders}
tooltipPosition={tooltipPosition}
onClick={() => {
if (handlePopupReaction) {
handlePopupReaction('');
}
handlePopupX(popupXDefault);
handlePopupY(popupYDefault);
setTooltipPosition('center');
if (handlePopupClick) {
handlePopupClick();
}
}}
/>
)}
</StyledReactionContainer>
);
};