Fix chat scroll

This commit is contained in:
Rafael 2022-02-04 22:44:54 -03:00 committed by Thomas Zarebczan
parent 6a9b9247ce
commit 25182c7dcf
3 changed files with 45 additions and 30 deletions

View file

@ -74,15 +74,9 @@ export default function LivestreamChatLayout(props: Props) {
const allCommentsElem = document.querySelectorAll('.livestream__comment'); const allCommentsElem = document.querySelectorAll('.livestream__comment');
const lastCommentElem = allCommentsElem && allCommentsElem[allCommentsElem.length - 1]; const lastCommentElem = allCommentsElem && allCommentsElem[allCommentsElem.length - 1];
const minScrollPos = const minScrollPos =
discussionElement && lastCommentElem && discussionElement.scrollHeight - lastCommentElem.offsetHeight; discussionElement && lastCommentElem && discussionElement.scrollHeight - lastCommentElem.offsetHeight * 2;
const minOffset = discussionElement && minScrollPos && discussionElement.scrollHeight - minScrollPos; const minOffset = discussionElement && minScrollPos && discussionElement.scrollHeight - minScrollPos;
const restoreScrollPos = React.useCallback(() => {
if (discussionElement) discussionElement.scrollTop = !isMobile ? 0 : discussionElement.scrollHeight;
}, [discussionElement, isMobile]);
const commentsRef = React.createRef();
const [viewMode, setViewMode] = React.useState(VIEW_MODES.CHAT); const [viewMode, setViewMode] = React.useState(VIEW_MODES.CHAT);
const [scrollPos, setScrollPos] = React.useState(0); const [scrollPos, setScrollPos] = React.useState(0);
const [showPinned, setShowPinned] = React.useState(true); const [showPinned, setShowPinned] = React.useState(true);
@ -91,6 +85,7 @@ export default function LivestreamChatLayout(props: Props) {
const [chatHidden, setChatHidden] = React.useState(false); const [chatHidden, setChatHidden] = React.useState(false);
const [didInitialScroll, setDidInitialScroll] = React.useState(false); const [didInitialScroll, setDidInitialScroll] = React.useState(false);
const [bottomScrollTop, setBottomScrollTop] = React.useState(0); const [bottomScrollTop, setBottomScrollTop] = React.useState(0);
const [inputDrawerOpen, setInputDrawerOpen] = React.useState(false);
const recentScrollPos = isMobile ? (bottomScrollTop > 0 && minOffset ? bottomScrollTop - minOffset : 0) : 0; const recentScrollPos = isMobile ? (bottomScrollTop > 0 && minOffset ? bottomScrollTop - minOffset : 0) : 0;
const claimId = claim && claim.claim_id; const claimId = claim && claim.claim_id;
@ -98,6 +93,18 @@ export default function LivestreamChatLayout(props: Props) {
const commentsLength = commentsToDisplay && commentsToDisplay.length; const commentsLength = commentsToDisplay && commentsToDisplay.length;
const pinnedComment = pinnedComments.length > 0 ? pinnedComments[0] : null; const pinnedComment = pinnedComments.length > 0 ? pinnedComments[0] : null;
const { superChatsChannelUrls, superChatsFiatAmount, superChatsLBCAmount } = getTipValues(superChatsByAmount); const { superChatsChannelUrls, superChatsFiatAmount, superChatsLBCAmount } = getTipValues(superChatsByAmount);
const hasRecentComments = Boolean(
scrollPos && (!isMobile || recentScrollPos) && scrollPos < recentScrollPos && viewMode === VIEW_MODES.CHAT
);
const restoreScrollPos = React.useCallback(() => {
if (discussionElement) {
discussionElement.scrollTop = !isMobile ? 0 : discussionElement.scrollHeight;
setBottomScrollTop(discussionElement.scrollTop);
}
}, [discussionElement, isMobile]);
const commentsRef = React.createRef();
function toggleSuperChat() { function toggleSuperChat() {
if (superChatsChannelUrls && superChatsChannelUrls.length > 0) { if (superChatsChannelUrls && superChatsChannelUrls.length > 0) {
@ -129,14 +136,14 @@ export default function LivestreamChatLayout(props: Props) {
isMobile && isMobile &&
discussionElement && discussionElement &&
viewMode === VIEW_MODES.CHAT && viewMode === VIEW_MODES.CHAT &&
!didInitialScroll && (!didInitialScroll || bottomScrollTop === 0) &&
discussionElement.scrollTop < discussionElement.scrollHeight discussionElement.scrollTop < discussionElement.scrollHeight
) { ) {
discussionElement.scrollTop = discussionElement.scrollHeight; discussionElement.scrollTop = discussionElement.scrollHeight;
setDidInitialScroll(true); setDidInitialScroll(true);
setBottomScrollTop(discussionElement.scrollTop); setBottomScrollTop(discussionElement.scrollTop);
} }
}, [didInitialScroll, discussionElement, isMobile, viewMode]); }, [bottomScrollTop, didInitialScroll, discussionElement, isMobile, viewMode]);
// Register scroll handler (TODO: Should throttle/debounce) // Register scroll handler (TODO: Should throttle/debounce)
React.useEffect(() => { React.useEffect(() => {
@ -334,10 +341,15 @@ export default function LivestreamChatLayout(props: Props) {
<Spinner /> <Spinner />
</div> </div>
) : ( ) : (
<LivestreamComments uri={uri} commentsToDisplay={commentsToDisplay} isMobile={isMobile} /> <LivestreamComments
uri={uri}
commentsToDisplay={commentsToDisplay}
isMobile={isMobile}
restoreScrollPos={!hasRecentComments && !inputDrawerOpen && restoreScrollPos}
/>
)} )}
{scrollPos && (!isMobile || recentScrollPos) && scrollPos < recentScrollPos && viewMode === VIEW_MODES.CHAT ? ( {hasRecentComments ? (
<Button <Button
button="secondary" button="secondary"
className="livestream-comments__scroll-to-recent" className="livestream-comments__scroll-to-recent"
@ -354,13 +366,10 @@ export default function LivestreamChatLayout(props: Props) {
embed={embed} embed={embed}
uri={uri} uri={uri}
onDoneReplying={restoreScrollPos} onDoneReplying={restoreScrollPos}
onSlimInputClick={ onSlimInputClick={() => {
scrollPos && restoreScrollPos();
recentScrollPos && setInputDrawerOpen(!inputDrawerOpen);
scrollPos >= recentScrollPos && }}
viewMode === VIEW_MODES.CHAT &&
restoreScrollPos
}
/> />
</div> </div>
</div> </div>

View file

@ -28,10 +28,21 @@ type Props = {
stakedLevel: number, stakedLevel: number,
isMobile?: boolean, isMobile?: boolean,
handleDismissPin?: () => void, handleDismissPin?: () => void,
restoreScrollPos?: () => void,
}; };
export default function LivestreamComment(props: Props) { export default function LivestreamComment(props: Props) {
const { comment, forceUpdate, uri, claim, myChannelIds, stakedLevel, isMobile, handleDismissPin } = props; const {
comment,
forceUpdate,
uri,
claim,
myChannelIds,
stakedLevel,
isMobile,
handleDismissPin,
restoreScrollPos,
} = props;
const { const {
channel_url: authorUri, channel_url: authorUri,
@ -46,8 +57,6 @@ export default function LivestreamComment(props: Props) {
timestamp, timestamp,
} = comment; } = comment;
const commentRef = React.useRef();
const [hasUserMention, setUserMention] = React.useState(false); const [hasUserMention, setUserMention] = React.useState(false);
const isStreamer = claim && claim.signing_channel && claim.signing_channel.permanent_url === authorUri; const isStreamer = claim && claim.signing_channel && claim.signing_channel.permanent_url === authorUri;
@ -56,10 +65,6 @@ export default function LivestreamComment(props: Props) {
const isSticker = Boolean(stickerUrlFromMessage); const isSticker = Boolean(stickerUrlFromMessage);
const timePosted = timestamp * 1000; const timePosted = timestamp * 1000;
const commentIsMine = comment.channel_id && isMyComment(comment.channel_id); const commentIsMine = comment.channel_id && isMyComment(comment.channel_id);
const discussionElement = document.querySelector('.livestream__comments--mobile');
const currentComment = commentRef && commentRef.current;
const minScrollPos =
discussionElement && currentComment && discussionElement.scrollHeight - currentComment.offsetHeight;
// todo: implement comment_list --mine in SDK so redux can grab with selectCommentIsMine // todo: implement comment_list --mine in SDK so redux can grab with selectCommentIsMine
function isMyComment(channelId: string) { function isMyComment(channelId: string) {
@ -69,10 +74,10 @@ export default function LivestreamComment(props: Props) {
// For every new <LivestreamComment /> component that is rendered on mobile view, // For every new <LivestreamComment /> component that is rendered on mobile view,
// keep the scroll at the bottom (newest) // keep the scroll at the bottom (newest)
React.useEffect(() => { React.useEffect(() => {
if (isMobile && discussionElement && minScrollPos && discussionElement.scrollTop >= minScrollPos) { if (isMobile && restoreScrollPos) {
discussionElement.scrollTop = discussionElement.scrollHeight; restoreScrollPos();
} }
}, [discussionElement, isMobile, minScrollPos]); }, [isMobile, restoreScrollPos]);
return ( return (
<li <li
@ -82,7 +87,6 @@ export default function LivestreamComment(props: Props) {
'livestream__comment--mentioned': hasUserMention, 'livestream__comment--mentioned': hasUserMention,
'livestream__comment--mobile': isMobile, 'livestream__comment--mobile': isMobile,
})} })}
ref={commentRef}
> >
{supportAmount > 0 && ( {supportAmount > 0 && (
<div className="livestreamComment__superchatBanner"> <div className="livestreamComment__superchatBanner">

View file

@ -12,10 +12,11 @@ type Props = {
fetchingComments: boolean, fetchingComments: boolean,
uri: string, uri: string,
isMobile?: boolean, isMobile?: boolean,
restoreScrollPos?: () => void,
}; };
export default function LivestreamComments(props: Props) { export default function LivestreamComments(props: Props) {
const { commentsToDisplay, fetchingComments, uri, isMobile } = props; const { commentsToDisplay, fetchingComments, uri, isMobile, restoreScrollPos } = props;
const [forceUpdate, setForceUpdate] = React.useState(0); const [forceUpdate, setForceUpdate] = React.useState(0);
@ -56,6 +57,7 @@ export default function LivestreamComments(props: Props) {
uri={uri} uri={uri}
forceUpdate={forceUpdate} forceUpdate={forceUpdate}
isMobile isMobile
restoreScrollPos={restoreScrollPos}
/> />
))} ))}
</div> </div>