// @flow import React from 'react'; import Button from 'component/button'; import ChannelSelector from 'component/channelSelector'; import ClaimPreview from 'component/claimPreview'; import Comment from 'component/comment'; import Card from 'component/common/card'; import Empty from 'component/common/empty'; import Page from 'component/page'; import Spinner from 'component/spinner'; import { COMMENT_PAGE_SIZE_TOP_LEVEL } from 'constants/comment'; import * as ICONS from 'constants/icons'; import useFetched from 'effects/use-fetched'; import debounce from 'util/debounce'; function scaleToDevicePixelRatio(value) { const devicePixelRatio = window.devicePixelRatio || 1.0; if (devicePixelRatio < 1.0) { return Math.ceil(value / devicePixelRatio); } return Math.ceil(value * devicePixelRatio); } type Props = { activeChannelClaim: ?ChannelClaim, allComments: Array<Comment>, totalComments: number, topLevelTotalPages: number, isFetchingComments: boolean, claimsById: any, doCommentReset: (claimId: string) => void, doCommentListOwn: (channelId: string, page: number, pageSize: number) => void, }; export default function OwnComments(props: Props) { const { activeChannelClaim, allComments, totalComments, isFetchingComments, claimsById, doCommentReset, doCommentListOwn, } = props; const spinnerRef = React.useRef(); const [page, setPage] = React.useState(0); const [activeChannelId, setActiveChannelId] = React.useState(''); // Since we are sharing the key for Discussion and MyComments, don't show // the list until we've gone through the initial reset. const wasResetAndReady = useFetched(isFetchingComments); const totalPages = Math.ceil(totalComments / COMMENT_PAGE_SIZE_TOP_LEVEL); const moreBelow = page < totalPages; function getCommentsElem(comments) { return comments.map((comment) => { const contentClaim = claimsById[comment.claim_id]; const isChannel = contentClaim && contentClaim.value_type === 'channel'; return ( <div key={comment.comment_id} className="comments-own card__main-actions"> <div className="section__actions"> <div className="comments-own--claim"> {contentClaim && ( <ClaimPreview uri={contentClaim.canonical_url} searchParams={{ ...(isChannel ? { view: 'discussion' } : {}), }} hideActions hideMenu properties={() => null} /> )} {!contentClaim && <Empty text={__('Content or channel was deleted.')} />} </div> <Comment isTopLevel hideActions authorUri={comment.channel_url} author={comment.channel_name} commentId={comment.comment_id} message={comment.comment} timePosted={comment.timestamp * 1000} commentIsMine supportAmount={comment.support_amount} numDirectReplies={0} // Don't show replies here isModerator={comment.is_moderator} isGlobalMod={comment.is_global_mod} isFiat={comment.is_fiat} /> </div> </div> ); }); } // Active channel changed React.useEffect(() => { if (activeChannelClaim && activeChannelClaim.claim_id !== activeChannelId) { setActiveChannelId(activeChannelClaim.claim_id); setPage(0); } }, [activeChannelClaim, activeChannelId]); // Reset comments React.useEffect(() => { if (page === 0 && activeChannelId) { doCommentReset(activeChannelId); setPage(1); } }, [page, activeChannelId]); // eslint-disable-line react-hooks/exhaustive-deps // Fetch own comments React.useEffect(() => { if (page !== 0 && activeChannelId) { doCommentListOwn(activeChannelId, page, COMMENT_PAGE_SIZE_TOP_LEVEL); } }, [page]); // eslint-disable-line react-hooks/exhaustive-deps // Infinite scroll React.useEffect(() => { function shouldFetchNextPage(page, topLevelTotalPages, window, document, yPrefetchPx = 1000) { if (!spinnerRef || !spinnerRef.current) { return false; } const rect = spinnerRef.current.getBoundingClientRect(); // $FlowFixMe const windowH = window.innerHeight || document.documentElement.clientHeight; // $FlowFixMe const windowW = window.innerWidth || document.documentElement.clientWidth; // $FlowFixMe const isApproachingViewport = yPrefetchPx !== 0 && rect.top < windowH + scaleToDevicePixelRatio(yPrefetchPx); const isInViewport = rect.width > 0 && rect.height > 0 && rect.bottom >= 0 && rect.right >= 0 && // $FlowFixMe rect.top <= windowH && // $FlowFixMe rect.left <= windowW; return (isInViewport || isApproachingViewport) && page < topLevelTotalPages; } const handleCommentScroll = debounce(() => { if (shouldFetchNextPage(page, totalPages, window, document)) { setPage(page + 1); } }, 200); if (!isFetchingComments && moreBelow && spinnerRef && spinnerRef.current) { if (shouldFetchNextPage(page, totalPages, window, document, 0)) { setPage(page + 1); } else { window.addEventListener('scroll', handleCommentScroll); return () => window.removeEventListener('scroll', handleCommentScroll); } } }, [page, spinnerRef, isFetchingComments, moreBelow, totalPages]); // ************************************************************************** // ************************************************************************** if (!activeChannelClaim) { return null; } return ( <Page noFooter noSideNavigation settingsPage backout={{ title: __('Your comments'), backLabel: __('Back') }}> <ChannelSelector hideAnon /> <Card isBodyList title={ totalComments > 0 ? totalComments === 1 ? __('1 comment') : __('%total_comments% comments', { total_comments: totalComments }) : isFetchingComments ? '' : __('No comments') } titleActions={ <Button button="alt" icon={ICONS.REFRESH} title={__('Refresh')} onClick={() => { setPage(0); }} /> } body={ <> {wasResetAndReady && <ul className="comments">{allComments && getCommentsElem(allComments)}</ul>} {(isFetchingComments || moreBelow) && ( <div className="main--empty" ref={spinnerRef}> <Spinner type="small" /> </div> )} </> } /> </Page> ); }