// @flow import React from 'react'; import classnames from 'classnames'; import Spinner from 'component/spinner'; import CommentCreate from 'component/commentCreate'; import LivestreamComment from 'component/livestreamComment'; import Button from 'component/button'; import UriIndicator from 'component/uriIndicator'; import CreditAmount from 'component/common/credit-amount'; import ChannelThumbnail from 'component/channelThumbnail'; import Tooltip from 'component/common/tooltip'; type Props = { uri: string, claim: ?StreamClaim, activeViewers: number, embed?: boolean, doCommentSocketConnect: (string, string) => void, doCommentSocketDisconnect: (string) => void, doCommentList: (string, string, number, number) => void, comments: Array, fetchingComments: boolean, doSuperChatList: (string) => void, superChats: Array, myChannels: ?Array, pinnedCommentsById: { [claimId: string]: Array }, }; const VIEW_MODE_CHAT = 'view_chat'; const VIEW_MODE_SUPER_CHAT = 'view_superchat'; const COMMENT_SCROLL_OFFSET = 100; const COMMENT_SCROLL_TIMEOUT = 25; export default function LivestreamComments(props: Props) { const { claim, uri, embed, doCommentSocketConnect, doCommentSocketDisconnect, comments: commentsByChronologicalOrder, doCommentList, fetchingComments, doSuperChatList, myChannels, superChats: superChatsByTipAmount, pinnedCommentsById, } = props; let superChatsFiatAmount, superChatsTotalAmount; const commentsRef = React.createRef(); const [scrollBottom, setScrollBottom] = React.useState(true); const [viewMode, setViewMode] = React.useState(VIEW_MODE_CHAT); const [performedInitialScroll, setPerformedInitialScroll] = React.useState(false); const claimId = claim && claim.claim_id; const commentsLength = commentsByChronologicalOrder && commentsByChronologicalOrder.length; const commentsToDisplay = viewMode === VIEW_MODE_CHAT ? commentsByChronologicalOrder : superChatsByTipAmount; const discussionElement = document.querySelector('.livestream__comments'); const commentElement = document.querySelector('.livestream-comment'); let pinnedComment; const pinnedCommentIds = (claimId && pinnedCommentsById[claimId]) || []; if (pinnedCommentIds.length > 0) { pinnedComment = commentsByChronologicalOrder.find((c) => c.comment_id === pinnedCommentIds[0]); } React.useEffect(() => { if (claimId) { doCommentList(uri, '', 1, 75); doSuperChatList(uri); doCommentSocketConnect(uri, claimId); } return () => { if (claimId) { doCommentSocketDisconnect(claimId); } }; }, [claimId, uri, doCommentList, doSuperChatList, doCommentSocketConnect, doCommentSocketDisconnect]); const handleScroll = React.useCallback(() => { if (discussionElement) { const negativeCommentHeight = commentElement && -1 * commentElement.offsetHeight; const isAtRecent = negativeCommentHeight && discussionElement.scrollTop >= negativeCommentHeight; setScrollBottom(isAtRecent); } }, [commentElement, discussionElement]); React.useEffect(() => { if (discussionElement) { discussionElement.addEventListener('scroll', handleScroll); if (commentsLength > 0) { // Only update comment scroll if the user hasn't scrolled up to view old comments // If they have, do nothing if (!performedInitialScroll) { setTimeout( () => (discussionElement.scrollTop = discussionElement.scrollHeight - discussionElement.offsetHeight + COMMENT_SCROLL_OFFSET), COMMENT_SCROLL_TIMEOUT ); setPerformedInitialScroll(true); } } return () => discussionElement.removeEventListener('scroll', handleScroll); } }, [commentsLength, discussionElement, handleScroll, performedInitialScroll, setPerformedInitialScroll]); // sum total amounts for fiat tips and lbc tips if (superChatsByTipAmount) { let fiatAmount = 0; let LBCAmount = 0; for (const superChat of superChatsByTipAmount) { if (superChat.is_fiat) { fiatAmount = fiatAmount + superChat.support_amount; } else { LBCAmount = LBCAmount + superChat.support_amount; } } superChatsFiatAmount = fiatAmount; superChatsTotalAmount = LBCAmount; } let superChatsReversed; // array of superchats organized by fiat or not first, then support amount if (superChatsByTipAmount) { const clonedSuperchats = JSON.parse(JSON.stringify(superChatsByTipAmount)); // sort by fiat first then by support amount superChatsReversed = clonedSuperchats .sort((a, b) => { // if both are fiat, organize by support if (a.is_fiat === b.is_fiat) { return b.support_amount - a.support_amount; // otherwise, if they are not both fiat, put the fiat transaction first } else { return a.is_fiat === b.is_fiat ? 0 : a.is_fiat ? -1 : 1; } }) .reverse(); } // todo: implement comment_list --mine in SDK so redux can grab with selectCommentIsMine function isMyComment(channelId: string) { if (myChannels != null && channelId != null) { for (let i = 0; i < myChannels.length; i++) { if (myChannels[i].claim_id === channelId) { return true; } } } return false; } if (!claim) { return null; } function scrollBack() { if (discussionElement) { discussionElement.scrollTop = 0; setScrollBottom(true); } } return (
{__('Live discussion')}
{(superChatsTotalAmount || 0) > 0 && (
{/* the superchats in chronological order button */}
)}
<> {fetchingComments && !commentsByChronologicalOrder && (
)}
{viewMode === VIEW_MODE_CHAT && superChatsByTipAmount && (superChatsTotalAmount || 0) > 0 && (
{superChatsByTipAmount.map((superChat: Comment) => (
))}
)} {pinnedComment && (
)} {/* top to bottom comment display */} {!fetchingComments && commentsByChronologicalOrder.length > 0 ? (
{viewMode === VIEW_MODE_CHAT && commentsToDisplay.map((comment) => ( ))} {viewMode === VIEW_MODE_SUPER_CHAT && superChatsReversed && superChatsReversed.map((comment) => ( ))}
) : (
)} {!scrollBottom && (
); }