// @flow import 'scss/component/_livestream-chat.scss'; // $FlowFixMe import { Global } from '@emotion/react'; // $FlowFixMe import { grey } from '@mui/material/colors'; import { formatLbryUrlForWeb } from 'util/url'; import { useIsMobile } from 'effects/use-screensize'; import * as ICONS from 'constants/icons'; import Button from 'component/button'; import classnames from 'classnames'; import CommentCreate from 'component/commentCreate'; import CreditAmount from 'component/common/credit-amount'; import LivestreamComment from 'component/livestreamComment'; import LivestreamComments from 'component/livestreamComments'; import LivestreamSuperchats from './livestream-superchats'; import LivestreamMenu from './livestream-menu'; import React from 'react'; import Spinner from 'component/spinner'; import Yrbl from 'component/yrbl'; import { getTipValues } from 'util/livestream'; import Slide from '@mui/material/Slide'; const VIEW_MODES = { CHAT: 'chat', SUPERCHAT: 'sc', }; const COMMENT_SCROLL_TIMEOUT = 25; const LARGE_SUPER_CHAT_LIST_THRESHOLD = 20; type Props = { claim: ?StreamClaim, comments: Array, embed?: boolean, isPopoutWindow?: boolean, pinnedComments: Array, superChats: Array, uri: string, hideHeader?: boolean, superchatsHidden?: boolean, customViewMode?: string, theme: string, doCommentList: (string, string, number, number) => void, doResolveUris: (Array, boolean) => void, doSuperChatList: (string) => void, }; export default function LivestreamChatLayout(props: Props) { const { claim, comments: commentsByChronologicalOrder, embed, isPopoutWindow, pinnedComments, superChats: superChatsByAmount, uri, hideHeader, superchatsHidden, customViewMode, theme, doCommentList, doResolveUris, doSuperChatList, } = props; const isMobile = useIsMobile() && !isPopoutWindow; const discussionElement = document.querySelector('.livestream__comments'); const restoreScrollPos = React.useCallback(() => { if (discussionElement) discussionElement.scrollTop = 0; }, [discussionElement]); const commentsRef = React.createRef(); const [viewMode, setViewMode] = React.useState(VIEW_MODES.CHAT); const [scrollPos, setScrollPos] = React.useState(0); const [showPinned, setShowPinned] = React.useState(true); const [resolvingSuperChats, setResolvingSuperChats] = React.useState(false); const [mention, setMention] = React.useState(); const [openedPopoutWindow, setPopoutWindow] = React.useState(undefined); const [chatHidden, setChatHidden] = React.useState(false); const quickMention = mention && formatLbryUrlForWeb(mention).substring(1, formatLbryUrlForWeb(mention).indexOf(':') + 3); const claimId = claim && claim.claim_id; const commentsToDisplay = viewMode === VIEW_MODES.CHAT ? commentsByChronologicalOrder : superChatsByAmount; const commentsLength = commentsToDisplay && commentsToDisplay.length; const pinnedComment = pinnedComments.length > 0 ? pinnedComments[0] : null; const { superChatsChannelUrls, superChatsFiatAmount, superChatsLBCAmount } = getTipValues(superChatsByAmount); function toggleSuperChat() { if (superChatsChannelUrls && superChatsChannelUrls.length > 0) { doResolveUris(superChatsChannelUrls, true); if (superChatsByAmount.length > LARGE_SUPER_CHAT_LIST_THRESHOLD) { setResolvingSuperChats(true); } } setViewMode(VIEW_MODES.SUPERCHAT); } React.useEffect(() => { if (customViewMode && customViewMode !== viewMode) { setViewMode(customViewMode); } }, [customViewMode, viewMode]); React.useEffect(() => { if (claimId) { doCommentList(uri, '', 1, 75); doSuperChatList(uri); } }, [claimId, uri, doCommentList, doSuperChatList]); // Register scroll handler (TODO: Should throttle/debounce) React.useEffect(() => { function handleScroll() { if (discussionElement) { const scrollTop = discussionElement.scrollTop; if (scrollTop !== scrollPos) { setScrollPos(scrollTop); } } } if (discussionElement) { discussionElement.addEventListener('scroll', handleScroll); return () => discussionElement.removeEventListener('scroll', handleScroll); } }, [discussionElement, scrollPos, viewMode]); // Retain scrollPos=0 when receiving new messages. React.useEffect(() => { if (discussionElement && commentsLength > 0) { // Only update comment scroll if the user hasn't scrolled up to view old comments if (scrollPos >= 0) { // +ve scrollPos: not scrolled (Usually, there'll be a few pixels beyond 0). // -ve scrollPos: user scrolled. const timer = setTimeout(() => { // Use a timer here to ensure we reset after the new comment has been rendered. discussionElement.scrollTop = 0; }, COMMENT_SCROLL_TIMEOUT); return () => clearTimeout(timer); } } // eslint-disable-next-line react-hooks/exhaustive-deps }, [commentsLength]); // (Just respond to 'commentsLength' updates and nothing else) // Stop spinner for resolving superchats React.useEffect(() => { if (resolvingSuperChats) { // The real solution to the sluggishness is to fix the claim store/selectors // and to paginate the long superchat list. This serves as a band-aid, // showing a spinner while we batch-resolve. The duration is just a rough // estimate -- the lag will handle the remaining time. const timer = setTimeout(() => { setResolvingSuperChats(false); // Scroll to the top: if (discussionElement) { const divHeight = discussionElement.scrollHeight; discussionElement.scrollTop = divHeight * -1; } }, 1000); return () => clearTimeout(timer); } }, [discussionElement, resolvingSuperChats]); if (!claim) return null; const chatContentToggle = (toggleMode: string, label: any) => (