Improve scrolling behavior, fix tips sorting (#863)
This commit is contained in:
parent
5c5b46ddb3
commit
ecc3599a85
5 changed files with 163 additions and 132 deletions
|
@ -14,7 +14,7 @@ type Props = {
|
||||||
children: React.Node,
|
children: React.Node,
|
||||||
description: ?string,
|
description: ?string,
|
||||||
isResolvingUri: boolean,
|
isResolvingUri: boolean,
|
||||||
doResolveUri: (string) => void,
|
doResolveUri: (string, boolean) => void,
|
||||||
playingUri: ?PlayingUri,
|
playingUri: ?PlayingUri,
|
||||||
parentCommentId?: string,
|
parentCommentId?: string,
|
||||||
isMarkdownPost?: boolean,
|
isMarkdownPost?: boolean,
|
||||||
|
@ -43,7 +43,7 @@ class ClaimLink extends React.Component<Props> {
|
||||||
const { isResolvingUri, doResolveUri, claim, uri } = props;
|
const { isResolvingUri, doResolveUri, claim, uri } = props;
|
||||||
|
|
||||||
if (!isResolvingUri && claim === undefined && uri) {
|
if (!isResolvingUri && claim === undefined && uri) {
|
||||||
doResolveUri(uri);
|
doResolveUri(uri, true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ import { doCommentList, doSuperChatList } from 'redux/actions/comments';
|
||||||
import {
|
import {
|
||||||
selectTopLevelCommentsForUri,
|
selectTopLevelCommentsForUri,
|
||||||
selectSuperChatsForUri,
|
selectSuperChatsForUri,
|
||||||
selectSuperChatTotalAmountForUri,
|
|
||||||
selectPinnedCommentsForUri,
|
selectPinnedCommentsForUri,
|
||||||
} from 'redux/selectors/comments';
|
} from 'redux/selectors/comments';
|
||||||
import { selectThemePath } from 'redux/selectors/settings';
|
import { selectThemePath } from 'redux/selectors/settings';
|
||||||
|
@ -14,19 +13,21 @@ import LivestreamChatLayout from './view';
|
||||||
|
|
||||||
const select = (state, props) => {
|
const select = (state, props) => {
|
||||||
const { uri } = props;
|
const { uri } = props;
|
||||||
|
const claim = selectClaimForUri(state, uri);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
claim: selectClaimForUri(state, uri),
|
claimId: claim && claim.claim_id,
|
||||||
comments: selectTopLevelCommentsForUri(state, uri, MAX_LIVESTREAM_COMMENTS),
|
comments: selectTopLevelCommentsForUri(state, uri, MAX_LIVESTREAM_COMMENTS),
|
||||||
pinnedComments: selectPinnedCommentsForUri(state, uri),
|
pinnedComments: selectPinnedCommentsForUri(state, uri),
|
||||||
superChats: selectSuperChatsForUri(state, uri),
|
superChats: selectSuperChatsForUri(state, uri),
|
||||||
superChatsTotalAmount: selectSuperChatTotalAmountForUri(state, uri),
|
|
||||||
theme: selectThemePath(state),
|
theme: selectThemePath(state),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(select, {
|
const perform = {
|
||||||
doCommentList,
|
doCommentList,
|
||||||
doSuperChatList,
|
doSuperChatList,
|
||||||
doResolveUris,
|
doResolveUris,
|
||||||
})(LivestreamChatLayout);
|
};
|
||||||
|
|
||||||
|
export default connect(select, perform)(LivestreamChatLayout);
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
import 'scss/component/_livestream-chat.scss';
|
import 'scss/component/_livestream-chat.scss';
|
||||||
|
|
||||||
// $FlowFixMe
|
|
||||||
import { Global } from '@emotion/react';
|
|
||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
import { grey } from '@mui/material/colors';
|
import { grey } from '@mui/material/colors';
|
||||||
|
|
||||||
|
@ -17,39 +15,38 @@ import LivestreamComments from 'component/livestreamComments';
|
||||||
import LivestreamSuperchats from './livestream-superchats';
|
import LivestreamSuperchats from './livestream-superchats';
|
||||||
import LivestreamMenu from './livestream-menu';
|
import LivestreamMenu from './livestream-menu';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Spinner from 'component/spinner';
|
|
||||||
import Yrbl from 'component/yrbl';
|
import Yrbl from 'component/yrbl';
|
||||||
import { getTipValues } from 'util/livestream';
|
import { getTipValues } from 'util/livestream';
|
||||||
import Slide from '@mui/material/Slide';
|
import Slide from '@mui/material/Slide';
|
||||||
|
|
||||||
const VIEW_MODES = {
|
export const VIEW_MODES = {
|
||||||
CHAT: 'chat',
|
CHAT: 'chat',
|
||||||
SUPERCHAT: 'sc',
|
SUPERCHAT: 'sc',
|
||||||
};
|
};
|
||||||
const COMMENT_SCROLL_TIMEOUT = 25;
|
const COMMENT_SCROLL_TIMEOUT = 25;
|
||||||
const LARGE_SUPER_CHAT_LIST_THRESHOLD = 20;
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
claim: ?StreamClaim,
|
|
||||||
comments: Array<Comment>,
|
|
||||||
embed?: boolean,
|
embed?: boolean,
|
||||||
isPopoutWindow?: boolean,
|
isPopoutWindow?: boolean,
|
||||||
pinnedComments: Array<Comment>,
|
|
||||||
superChats: Array<Comment>,
|
|
||||||
uri: string,
|
uri: string,
|
||||||
hideHeader?: boolean,
|
hideHeader?: boolean,
|
||||||
superchatsHidden?: boolean,
|
superchatsHidden?: boolean,
|
||||||
customViewMode?: string,
|
customViewMode?: string,
|
||||||
theme: string,
|
|
||||||
setCustomViewMode?: (any) => void,
|
setCustomViewMode?: (any) => void,
|
||||||
doCommentList: (string, string, number, number) => void,
|
// redux
|
||||||
doResolveUris: (Array<string>, boolean) => void,
|
claimId?: string,
|
||||||
doSuperChatList: (string) => void,
|
comments: Array<Comment>,
|
||||||
|
pinnedComments: Array<Comment>,
|
||||||
|
superChats: Array<Comment>,
|
||||||
|
theme: string,
|
||||||
|
doCommentList: (uri: string, parentId: string, page: number, pageSize: number) => void,
|
||||||
|
doResolveUris: (uris: Array<string>, cache: boolean) => void,
|
||||||
|
doSuperChatList: (uri: string) => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function LivestreamChatLayout(props: Props) {
|
export default function LivestreamChatLayout(props: Props) {
|
||||||
const {
|
const {
|
||||||
claim,
|
claimId,
|
||||||
comments: commentsByChronologicalOrder,
|
comments: commentsByChronologicalOrder,
|
||||||
embed,
|
embed,
|
||||||
isPopoutWindow,
|
isPopoutWindow,
|
||||||
|
@ -83,14 +80,21 @@ export default function LivestreamChatLayout(props: Props) {
|
||||||
const [didInitialScroll, setDidInitialScroll] = React.useState(false);
|
const [didInitialScroll, setDidInitialScroll] = React.useState(false);
|
||||||
const [minScrollHeight, setMinScrollHeight] = React.useState(0);
|
const [minScrollHeight, setMinScrollHeight] = React.useState(0);
|
||||||
const [keyboardOpened, setKeyboardOpened] = React.useState(false);
|
const [keyboardOpened, setKeyboardOpened] = React.useState(false);
|
||||||
|
const [superchatsAmount, setSuperchatsAmount] = React.useState(false);
|
||||||
|
const [chatElement, setChatElement] = React.useState();
|
||||||
|
|
||||||
const claimId = claim && claim.claim_id;
|
const superChatsByChronologicalOrder =
|
||||||
const commentsToDisplay = viewMode === VIEW_MODES.CHAT ? commentsByChronologicalOrder : superChatsByAmount;
|
superChatsByAmount && superChatsByAmount.sort((a, b) => b.timestamp - a.timestamp);
|
||||||
|
const commentsToDisplay =
|
||||||
|
viewMode === VIEW_MODES.CHAT ? commentsByChronologicalOrder : superChatsByChronologicalOrder;
|
||||||
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(
|
||||||
|
superChatsByChronologicalOrder
|
||||||
|
);
|
||||||
const scrolledPastRecent = Boolean(
|
const scrolledPastRecent = Boolean(
|
||||||
viewMode === VIEW_MODES.CHAT && !isMobile ? scrollPos < 0 : scrollPos < minScrollHeight
|
(viewMode !== VIEW_MODES.SUPERCHAT || !resolvingSuperChats) &&
|
||||||
|
(!isMobile ? scrollPos < 0 : scrollPos < minScrollHeight)
|
||||||
);
|
);
|
||||||
|
|
||||||
const restoreScrollPos = React.useCallback(() => {
|
const restoreScrollPos = React.useCallback(() => {
|
||||||
|
@ -107,16 +111,26 @@ export default function LivestreamChatLayout(props: Props) {
|
||||||
}
|
}
|
||||||
}, [discussionElement, isMobile, lastCommentElem, minScrollHeight]);
|
}, [discussionElement, isMobile, lastCommentElem, minScrollHeight]);
|
||||||
|
|
||||||
const commentsRef = React.createRef();
|
function toggleClick(toggleMode: string) {
|
||||||
|
if (toggleMode === VIEW_MODES.SUPERCHAT) {
|
||||||
|
toggleSuperChat();
|
||||||
|
} else {
|
||||||
|
setViewMode(VIEW_MODES.CHAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (discussionElement) {
|
||||||
|
discussionElement.scrollTop = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function toggleSuperChat() {
|
function toggleSuperChat() {
|
||||||
if (superChatsChannelUrls && superChatsChannelUrls.length > 0) {
|
const hasNewSuperchats = !superchatsAmount || superChatsChannelUrls.length !== superchatsAmount;
|
||||||
doResolveUris(superChatsChannelUrls, true);
|
|
||||||
|
|
||||||
if (superChatsByAmount.length > LARGE_SUPER_CHAT_LIST_THRESHOLD) {
|
if (superChatsChannelUrls && hasNewSuperchats) {
|
||||||
setResolvingSuperChats(true);
|
setSuperchatsAmount(superChatsChannelUrls.length);
|
||||||
}
|
doResolveUris(superChatsChannelUrls, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
setViewMode(VIEW_MODES.SUPERCHAT);
|
setViewMode(VIEW_MODES.SUPERCHAT);
|
||||||
if (setCustomViewMode) setCustomViewMode(VIEW_MODES.SUPERCHAT);
|
if (setCustomViewMode) setCustomViewMode(VIEW_MODES.SUPERCHAT);
|
||||||
}
|
}
|
||||||
|
@ -135,17 +149,21 @@ export default function LivestreamChatLayout(props: Props) {
|
||||||
}, [claimId, uri, doCommentList, doSuperChatList]);
|
}, [claimId, uri, doCommentList, doSuperChatList]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (isMobile && viewMode === VIEW_MODES.CHAT && !didInitialScroll) {
|
if (isMobile && !didInitialScroll) {
|
||||||
restoreScrollPos();
|
restoreScrollPos();
|
||||||
setDidInitialScroll(true);
|
setDidInitialScroll(true);
|
||||||
}
|
}
|
||||||
}, [didInitialScroll, isMobile, restoreScrollPos, viewMode]);
|
}, [didInitialScroll, isMobile, restoreScrollPos, viewMode]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (discussionElement && !openedPopoutWindow) setChatElement(discussionElement);
|
||||||
|
}, [discussionElement, openedPopoutWindow]);
|
||||||
|
|
||||||
// Register scroll handler (TODO: Should throttle/debounce)
|
// Register scroll handler (TODO: Should throttle/debounce)
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
function handleScroll() {
|
function handleScroll() {
|
||||||
if (discussionElement) {
|
if (chatElement) {
|
||||||
const scrollTop = discussionElement.scrollTop;
|
const scrollTop = chatElement.scrollTop;
|
||||||
|
|
||||||
if (scrollTop !== scrollPos) {
|
if (scrollTop !== scrollPos) {
|
||||||
setScrollPos(scrollTop);
|
setScrollPos(scrollTop);
|
||||||
|
@ -153,11 +171,12 @@ export default function LivestreamChatLayout(props: Props) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (discussionElement) {
|
if (chatElement) {
|
||||||
discussionElement.addEventListener('scroll', handleScroll);
|
chatElement.addEventListener('scroll', handleScroll);
|
||||||
return () => discussionElement.removeEventListener('scroll', handleScroll);
|
|
||||||
|
return () => chatElement.removeEventListener('scroll', handleScroll);
|
||||||
}
|
}
|
||||||
}, [discussionElement, scrollPos]);
|
}, [chatElement, scrollPos]);
|
||||||
|
|
||||||
// Retain scrollPos=0 when receiving new messages.
|
// Retain scrollPos=0 when receiving new messages.
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
@ -193,45 +212,7 @@ export default function LivestreamChatLayout(props: Props) {
|
||||||
}
|
}
|
||||||
}, [keyboardOpened, restoreScrollPos]);
|
}, [keyboardOpened, restoreScrollPos]);
|
||||||
|
|
||||||
// Stop spinner for resolving superchats
|
if (!claimId) return null;
|
||||||
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) => (
|
|
||||||
<Button
|
|
||||||
className={classnames('button-toggle', { 'button-toggle--active': viewMode === toggleMode })}
|
|
||||||
label={label}
|
|
||||||
onClick={() => {
|
|
||||||
if (toggleMode === VIEW_MODES.SUPERCHAT) {
|
|
||||||
toggleSuperChat();
|
|
||||||
} else {
|
|
||||||
setViewMode(VIEW_MODES.CHAT);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (discussionElement) {
|
|
||||||
const divHeight = discussionElement.scrollHeight;
|
|
||||||
discussionElement.scrollTop = toggleMode === VIEW_MODES.CHAT ? divHeight : divHeight * -1;
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (openedPopoutWindow || chatHidden) {
|
if (openedPopoutWindow || chatHidden) {
|
||||||
return (
|
return (
|
||||||
|
@ -266,6 +247,8 @@ export default function LivestreamChatLayout(props: Props) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const toggleProps = { viewMode, onClick: (toggleMode) => toggleClick(toggleMode) };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classnames('card livestream__chat', { 'livestream__chat--popout': isPopoutWindow })}>
|
<div className={classnames('card livestream__chat', { 'livestream__chat--popout': isPopoutWindow })}>
|
||||||
{!hideHeader && (
|
{!hideHeader && (
|
||||||
|
@ -281,37 +264,40 @@ export default function LivestreamChatLayout(props: Props) {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{superChatsByAmount && (
|
{superChatsByChronologicalOrder && (
|
||||||
<div className="recommended-content__toggles">
|
<div className="recommended-content__toggles">
|
||||||
{/* the superchats in chronological order button */}
|
{/* the superchats in chronological order button */}
|
||||||
{chatContentToggle(VIEW_MODES.CHAT, __('Chat'))}
|
<ChatContentToggle {...toggleProps} toggleMode={VIEW_MODES.CHAT} label={__('Chat')} />
|
||||||
|
|
||||||
{/* the button to show superchats listed by most to least support amount */}
|
{/* the button to show superchats listed by most to least support amount */}
|
||||||
{chatContentToggle(
|
<ChatContentToggle
|
||||||
VIEW_MODES.SUPERCHAT,
|
{...toggleProps}
|
||||||
<>
|
toggleMode={VIEW_MODES.SUPERCHAT}
|
||||||
<CreditAmount amount={superChatsLBCAmount || 0} size={8} /> /
|
label={
|
||||||
<CreditAmount amount={superChatsFiatAmount || 0} size={8} isFiat /> {__('Tipped')}
|
<>
|
||||||
</>
|
<CreditAmount amount={superChatsLBCAmount || 0} size={8} /> /
|
||||||
)}
|
<CreditAmount amount={superChatsFiatAmount || 0} size={8} isFiat /> {__('Tipped')}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div ref={commentsRef} className="livestreamComments__wrapper">
|
<div className="livestreamComments__wrapper">
|
||||||
<div
|
<div
|
||||||
className={classnames('livestream-comments__top-actions', {
|
className={classnames('livestream-comments__top-actions', {
|
||||||
'livestream-comments__top-actions--mobile': isMobile,
|
'livestream-comments__top-actions--mobile': isMobile,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{isMobile && ((pinnedComment && showPinned) || (superChatsByAmount && !superchatsHidden)) && (
|
{isMobile && ((pinnedComment && showPinned) || (superChatsByChronologicalOrder && !superchatsHidden)) && (
|
||||||
<MobileDrawerTopGradient theme={theme} />
|
<MobileDrawerTopGradient theme={theme} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{viewMode === VIEW_MODES.CHAT && superChatsByAmount && (
|
{viewMode === VIEW_MODES.CHAT && superChatsByChronologicalOrder && (
|
||||||
<LivestreamSuperchats
|
<LivestreamSuperchats
|
||||||
superChats={superChatsByAmount}
|
superChats={superChatsByChronologicalOrder}
|
||||||
toggleSuperChat={toggleSuperChat}
|
toggleSuperChat={toggleSuperChat}
|
||||||
superchatsHidden={superchatsHidden}
|
superchatsHidden={superchatsHidden}
|
||||||
isMobile={isMobile}
|
isMobile={isMobile}
|
||||||
|
@ -329,6 +315,7 @@ export default function LivestreamChatLayout(props: Props) {
|
||||||
uri={uri}
|
uri={uri}
|
||||||
handleDismissPin={() => setShowPinned(false)}
|
handleDismissPin={() => setShowPinned(false)}
|
||||||
isMobile
|
isMobile
|
||||||
|
setResolvingSuperChats={setResolvingSuperChats}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Slide>
|
</Slide>
|
||||||
|
@ -349,20 +336,15 @@ export default function LivestreamChatLayout(props: Props) {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{viewMode === VIEW_MODES.SUPERCHAT && resolvingSuperChats ? (
|
<LivestreamComments
|
||||||
<div className="main--empty">
|
uri={uri}
|
||||||
<Spinner />
|
viewMode={viewMode}
|
||||||
</div>
|
comments={commentsToDisplay}
|
||||||
) : (
|
isMobile={isMobile}
|
||||||
<LivestreamComments
|
restoreScrollPos={!scrolledPastRecent && isMobile && restoreScrollPos}
|
||||||
uri={uri}
|
/>
|
||||||
commentsToDisplay={commentsToDisplay}
|
|
||||||
isMobile={isMobile}
|
|
||||||
restoreScrollPos={!scrolledPastRecent && isMobile && restoreScrollPos}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{scrolledPastRecent ? (
|
{scrolledPastRecent && (
|
||||||
<Button
|
<Button
|
||||||
button="secondary"
|
button="secondary"
|
||||||
className="livestream-comments__scroll-to-recent"
|
className="livestream-comments__scroll-to-recent"
|
||||||
|
@ -370,7 +352,7 @@ export default function LivestreamChatLayout(props: Props) {
|
||||||
onClick={restoreScrollPos}
|
onClick={restoreScrollPos}
|
||||||
iconRight={ICONS.DOWN}
|
iconRight={ICONS.DOWN}
|
||||||
/>
|
/>
|
||||||
) : null}
|
)}
|
||||||
|
|
||||||
<div className="livestream__comment-create">
|
<div className="livestream__comment-create">
|
||||||
<CommentCreate
|
<CommentCreate
|
||||||
|
@ -387,6 +369,25 @@ export default function LivestreamChatLayout(props: Props) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ToggleProps = {
|
||||||
|
viewMode: string,
|
||||||
|
toggleMode: string,
|
||||||
|
label: string | any,
|
||||||
|
onClick: (string) => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ChatContentToggle = (props: ToggleProps) => {
|
||||||
|
const { viewMode, toggleMode, label, onClick } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
className={classnames('button-toggle', { 'button-toggle--active': viewMode === toggleMode })}
|
||||||
|
label={label}
|
||||||
|
onClick={() => onClick(toggleMode)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
type GradientProps = {
|
type GradientProps = {
|
||||||
theme: string,
|
theme: string,
|
||||||
};
|
};
|
||||||
|
@ -394,20 +395,12 @@ type GradientProps = {
|
||||||
const MobileDrawerTopGradient = (gradientProps: GradientProps) => {
|
const MobileDrawerTopGradient = (gradientProps: GradientProps) => {
|
||||||
const { theme } = gradientProps;
|
const { theme } = gradientProps;
|
||||||
|
|
||||||
const DrawerGlobalStyles = () => (
|
return (
|
||||||
<Global
|
<div
|
||||||
styles={{
|
style={{
|
||||||
'.livestream__top-gradient::after': {
|
background: `linear-gradient(180deg, ${theme === 'light' ? grey[300] : grey[900]} 0, transparent 65%)`,
|
||||||
background: `linear-gradient(180deg, ${theme === 'light' ? grey[300] : grey[900]} 0, transparent 65%)`,
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
|
className="livestream__top-gradient"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<DrawerGlobalStyles />
|
|
||||||
<div className="livestream__top-gradient" />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,9 +1,20 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { selectIsFetchingComments } from 'redux/selectors/comments';
|
import { selectIsFetchingComments } from 'redux/selectors/comments';
|
||||||
|
import { selectIsUriResolving } from 'redux/selectors/claims';
|
||||||
|
import { VIEW_MODES } from 'ui/component/livestreamChatLayout/view';
|
||||||
import LivestreamComments from './view';
|
import LivestreamComments from './view';
|
||||||
|
|
||||||
const select = (state) => ({
|
const select = (state, props) => {
|
||||||
fetchingComments: selectIsFetchingComments(state),
|
const { comments, viewMode } = props;
|
||||||
});
|
|
||||||
|
return {
|
||||||
|
fetchingComments: selectIsFetchingComments(state),
|
||||||
|
resolvingSuperchats: Boolean(
|
||||||
|
viewMode === VIEW_MODES.SUPERCHAT &&
|
||||||
|
comments &&
|
||||||
|
comments.some(({ channel_url }) => selectIsUriResolving(state, channel_url))
|
||||||
|
),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export default connect(select)(LivestreamComments);
|
export default connect(select)(LivestreamComments);
|
||||||
|
|
|
@ -1,29 +1,46 @@
|
||||||
// @flow
|
// @flow
|
||||||
import 'scss/component/_livestream-chat.scss';
|
import 'scss/component/_livestream-chat.scss';
|
||||||
|
|
||||||
import LivestreamComment from 'component/livestreamComment';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import LivestreamComment from 'component/livestreamComment';
|
||||||
|
import Spinner from 'component/spinner';
|
||||||
|
|
||||||
// 30 sec timestamp refresh timer
|
// 30 sec timestamp refresh timer
|
||||||
const UPDATE_TIMESTAMP_MS = 30 * 1000;
|
const UPDATE_TIMESTAMP_MS = 30 * 1000;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
commentsToDisplay: Array<Comment>,
|
comments: Array<Comment>,
|
||||||
fetchingComments: boolean,
|
|
||||||
uri: string,
|
uri: string,
|
||||||
isMobile?: boolean,
|
isMobile?: boolean,
|
||||||
|
viewMode: string,
|
||||||
restoreScrollPos?: () => void,
|
restoreScrollPos?: () => void,
|
||||||
|
setResolvingSuperChats?: (boolean) => void,
|
||||||
|
// redux
|
||||||
|
fetchingComments: boolean,
|
||||||
|
resolvingSuperchats: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function LivestreamComments(props: Props) {
|
export default function LivestreamComments(props: Props) {
|
||||||
const { commentsToDisplay, fetchingComments, uri, isMobile, restoreScrollPos } = props;
|
const {
|
||||||
|
comments,
|
||||||
|
uri,
|
||||||
|
isMobile,
|
||||||
|
restoreScrollPos,
|
||||||
|
setResolvingSuperChats,
|
||||||
|
fetchingComments,
|
||||||
|
resolvingSuperchats,
|
||||||
|
} = props;
|
||||||
|
|
||||||
const [forceUpdate, setForceUpdate] = React.useState(0);
|
const [forceUpdate, setForceUpdate] = React.useState(0);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (setResolvingSuperChats) setResolvingSuperChats(resolvingSuperchats);
|
||||||
|
}, [resolvingSuperchats, setResolvingSuperChats]);
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const shouldRefreshTimestamp =
|
const shouldRefreshTimestamp =
|
||||||
commentsToDisplay &&
|
comments &&
|
||||||
commentsToDisplay.some((comment) => {
|
comments.some((comment) => {
|
||||||
const { timestamp } = comment;
|
const { timestamp } = comment;
|
||||||
const timePosted = timestamp * 1000;
|
const timePosted = timestamp * 1000;
|
||||||
|
|
||||||
|
@ -43,19 +60,28 @@ export default function LivestreamComments(props: Props) {
|
||||||
// forceUpdate will re-activate the timer or else it will only refresh once
|
// forceUpdate will re-activate the timer or else it will only refresh once
|
||||||
}, [shouldRefreshTimestamp, forceUpdate]);
|
}, [shouldRefreshTimestamp, forceUpdate]);
|
||||||
|
|
||||||
|
if (resolvingSuperchats) {
|
||||||
|
return (
|
||||||
|
<div className="main--empty">
|
||||||
|
<Spinner />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/* top to bottom comment display */
|
/* top to bottom comment display */
|
||||||
if (!fetchingComments && commentsToDisplay && commentsToDisplay.length > 0) {
|
if (!fetchingComments && comments && comments.length > 0) {
|
||||||
|
const commentProps = { uri, forceUpdate };
|
||||||
|
|
||||||
return isMobile ? (
|
return isMobile ? (
|
||||||
<div className="livestream__comments--mobile">
|
<div className="livestream__comments--mobile">
|
||||||
{commentsToDisplay
|
{comments
|
||||||
.slice(0)
|
.slice(0)
|
||||||
.reverse()
|
.reverse()
|
||||||
.map((comment) => (
|
.map((comment) => (
|
||||||
<LivestreamComment
|
<LivestreamComment
|
||||||
|
{...commentProps}
|
||||||
comment={comment}
|
comment={comment}
|
||||||
key={comment.comment_id}
|
key={comment.comment_id}
|
||||||
uri={uri}
|
|
||||||
forceUpdate={forceUpdate}
|
|
||||||
isMobile
|
isMobile
|
||||||
restoreScrollPos={restoreScrollPos}
|
restoreScrollPos={restoreScrollPos}
|
||||||
/>
|
/>
|
||||||
|
@ -63,8 +89,8 @@ export default function LivestreamComments(props: Props) {
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="livestream__comments">
|
<div className="livestream__comments">
|
||||||
{commentsToDisplay.map((comment) => (
|
{comments.map((comment) => (
|
||||||
<LivestreamComment comment={comment} key={comment.comment_id} uri={uri} forceUpdate={forceUpdate} />
|
<LivestreamComment {...commentProps} comment={comment} key={comment.comment_id} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in a new issue