Rename FileReactions index import

Fix fileAction undefined logic

Fix live comment menu

Fix superchats
This commit is contained in:
Rafael 2022-02-02 09:45:16 -03:00 committed by Thomas Zarebczan
parent baf51f9c32
commit c1b84368a9
33 changed files with 443 additions and 176 deletions

View file

@ -24,10 +24,10 @@ export default function CollectionAddButton(props: Props) {
return !isPlayable ? null : ( return !isPlayable ? null : (
<Tooltip title={__('Add this claim to a list')} arrow={false}> <Tooltip title={__('Add this claim to a list')} arrow={false}>
<Button <Button
button={!fileAction && 'alt'} button={!fileAction ? 'alt' : undefined}
className={classnames({ 'button--file-action': fileAction })} className={classnames({ 'button--file-action': fileAction })}
icon={fileAction ? (!isSaved ? ICONS.ADD : ICONS.STACK) : ICONS.LIBRARY} icon={fileAction ? (!isSaved ? ICONS.ADD : ICONS.STACK) : ICONS.LIBRARY}
iconSize={fileAction && 22} iconSize={fileAction ? 22 : undefined}
label={uri ? (!isSaved ? __('Save') : __('Saved')) : __('New List')} label={uri ? (!isSaved ? __('Save') : __('Saved')) : __('New List')}
requiresAuth requiresAuth
onClick={(e) => { onClick={(e) => {

View file

@ -6,7 +6,6 @@ import {
makeSelectClaimIsPending, makeSelectClaimIsPending,
makeSelectReflectingClaimForUri, makeSelectReflectingClaimForUri,
makeSelectClaimWasPurchased, makeSelectClaimWasPurchased,
isStreamPlaceholderClaim,
selectTitleForUri, selectTitleForUri,
selectDateForUri, selectDateForUri,
} from 'redux/selectors/claims'; } from 'redux/selectors/claims';
@ -20,7 +19,7 @@ import { selectIsActiveLivestreamForUri } from 'redux/selectors/livestream';
import { selectLanguage, selectShowMatureContent } from 'redux/selectors/settings'; import { selectLanguage, selectShowMatureContent } from 'redux/selectors/settings';
import { makeSelectHasVisitedUri } from 'redux/selectors/content'; import { makeSelectHasVisitedUri } from 'redux/selectors/content';
import { selectIsSubscribedForUri } from 'redux/selectors/subscriptions'; import { selectIsSubscribedForUri } from 'redux/selectors/subscriptions';
import { isClaimNsfw } from 'util/claim'; import { isClaimNsfw, isStreamPlaceholderClaim } from 'util/claim';
import ClaimPreview from './view'; import ClaimPreview from './view';
import formatMediaDuration from 'util/formatMediaDuration'; import formatMediaDuration from 'util/formatMediaDuration';

View file

@ -1,10 +1,11 @@
import * as PAGES from 'constants/pages'; import * as PAGES from 'constants/pages';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectClaimForUri, makeSelectClaimIsPending, isStreamPlaceholderClaim } from 'redux/selectors/claims'; import { selectClaimForUri, makeSelectClaimIsPending } from 'redux/selectors/claims';
import { doClearPublish, doPrepareEdit } from 'redux/actions/publish'; import { doClearPublish, doPrepareEdit } from 'redux/actions/publish';
import { push } from 'connected-react-router'; import { push } from 'connected-react-router';
import ClaimPreviewSubtitle from './view'; import ClaimPreviewSubtitle from './view';
import { doFetchSubCount, selectSubCountForUri } from 'lbryinc'; import { doFetchSubCount, selectSubCountForUri } from 'lbryinc';
import { isStreamPlaceholderClaim } from 'util/claim';
const select = (state, props) => { const select = (state, props) => {
const claim = selectClaimForUri(state, props.uri); const claim = selectClaimForUri(state, props.uri);

View file

@ -4,7 +4,6 @@ import {
selectIsUriResolving, selectIsUriResolving,
getThumbnailFromClaim, getThumbnailFromClaim,
selectTitleForUri, selectTitleForUri,
isStreamPlaceholderClaim,
selectDateForUri, selectDateForUri,
} from 'redux/selectors/claims'; } from 'redux/selectors/claims';
import { doFileGet } from 'redux/actions/file'; import { doFileGet } from 'redux/actions/file';
@ -12,7 +11,7 @@ import { doResolveUri } from 'redux/actions/claims';
import { selectViewCountForUri, selectBanStateForUri } from 'lbryinc'; import { selectViewCountForUri, selectBanStateForUri } from 'lbryinc';
import { selectIsActiveLivestreamForUri } from 'redux/selectors/livestream'; import { selectIsActiveLivestreamForUri } from 'redux/selectors/livestream';
import { selectShowMatureContent } from 'redux/selectors/settings'; import { selectShowMatureContent } from 'redux/selectors/settings';
import { isClaimNsfw } from 'util/claim'; import { isClaimNsfw, isStreamPlaceholderClaim } from 'util/claim';
import ClaimPreviewTile from './view'; import ClaimPreviewTile from './view';
import formatMediaDuration from 'util/formatMediaDuration'; import formatMediaDuration from 'util/formatMediaDuration';

View file

@ -21,10 +21,10 @@ export default function ClaimSupportButton(props: Props) {
return disableSupport ? null : ( return disableSupport ? null : (
<Tooltip title={__('Support this claim')} arrow={false}> <Tooltip title={__('Support this claim')} arrow={false}>
<Button <Button
button={!fileAction && 'alt'} button={!fileAction ? 'alt' : undefined}
className={classnames({ 'button--file-action': fileAction })} className={classnames({ 'button--file-action': fileAction })}
icon={ICONS.LBC} icon={ICONS.LBC}
iconSize={fileAction && 22} iconSize={fileAction ? 22 : undefined}
label={isRepost ? __('Support Repost') : __('Support --[button to support a claim]--')} label={isRepost ? __('Support Repost') : __('Support --[button to support a claim]--')}
requiresAuth requiresAuth
onClick={() => doOpenModal(MODALS.SEND_TIP, { uri, isSupport: true })} onClick={() => doOpenModal(MODALS.SEND_TIP, { uri, isSupport: true })}

View file

@ -36,6 +36,7 @@ type Props = {
pinComment: (string, string, boolean) => Promise<any>, pinComment: (string, string, boolean) => Promise<any>,
commentModAddDelegate: (string, string, ChannelClaim) => void, commentModAddDelegate: (string, string, ChannelClaim) => void,
setQuickReply: (any) => void, setQuickReply: (any) => void,
handleDismissPin?: () => void,
}; };
function CommentMenuList(props: Props) { function CommentMenuList(props: Props) {
@ -63,6 +64,7 @@ function CommentMenuList(props: Props) {
pinComment, pinComment,
commentModAddDelegate, commentModAddDelegate,
setQuickReply, setQuickReply,
handleDismissPin,
} = props; } = props;
const { const {
@ -251,6 +253,13 @@ function CommentMenuList(props: Props) {
</MenuItem> </MenuItem>
)} )}
{isPinned && (
<MenuItem className="comment__menu-option menu__link" onSelect={handleDismissPin}>
<Icon aria-hidden icon={ICONS.DISMISS_ALL} />
{__('Dismiss Pin')}
</MenuItem>
)}
{activeChannelClaim && ( {activeChannelClaim && (
<div className="comment__menu-active"> <div className="comment__menu-active">
<ChannelThumbnail xsmall noLazyLoad uri={activeChannelClaim.permanent_url} /> <ChannelThumbnail xsmall noLazyLoad uri={activeChannelClaim.permanent_url} />

View file

@ -3,6 +3,7 @@ import { COMMENT_PAGE_SIZE_TOP_LEVEL, SORT_BY } from 'constants/comment';
import { ENABLE_COMMENT_REACTIONS } from 'config'; import { ENABLE_COMMENT_REACTIONS } from 'config';
import { getChannelIdFromClaim } from 'util/claim'; import { getChannelIdFromClaim } from 'util/claim';
import { useIsMobile, useIsMediumScreen } from 'effects/use-screensize'; import { useIsMobile, useIsMediumScreen } from 'effects/use-screensize';
import { getCommentsListTitle } from 'util/comments';
import * as ICONS from 'constants/icons'; import * as ICONS from 'constants/icons';
import * as REACTION_TYPES from 'constants/reactions'; import * as REACTION_TYPES from 'constants/reactions';
import Button from 'component/button'; import Button from 'component/button';
@ -49,7 +50,6 @@ type Props = {
commentsAreExpanded?: boolean, commentsAreExpanded?: boolean,
fetchReacts: (Array<string>) => Promise<any>, fetchReacts: (Array<string>) => Promise<any>,
doResolveUris: (Array<string>) => void, doResolveUris: (Array<string>) => void,
setCommentListTitle?: (string) => void,
fetchTopLevelComments: (string, number, number, number) => void, fetchTopLevelComments: (string, number, number, number) => void,
fetchComment: (string) => void, fetchComment: (string) => void,
resetComments: (string) => void, resetComments: (string) => void,
@ -78,7 +78,6 @@ function CommentList(props: Props) {
commentsAreExpanded, commentsAreExpanded,
fetchReacts, fetchReacts,
doResolveUris, doResolveUris,
setCommentListTitle,
fetchTopLevelComments, fetchTopLevelComments,
fetchComment, fetchComment,
resetComments, resetComments,
@ -101,11 +100,7 @@ function CommentList(props: Props) {
const isResolvingComments = topLevelComments && resolvedComments.length !== topLevelComments.length; const isResolvingComments = topLevelComments && resolvedComments.length !== topLevelComments.length;
const alreadyResolved = !isResolvingComments && resolvedComments.length !== 0; const alreadyResolved = !isResolvingComments && resolvedComments.length !== 0;
const canDisplayComments = commentsToDisplay && commentsToDisplay.length === topLevelComments.length; const canDisplayComments = commentsToDisplay && commentsToDisplay.length === topLevelComments.length;
const title = const title = getCommentsListTitle(totalComments);
(totalComments === 0 && __('Leave a comment')) ||
(totalComments === 1 && __('1 comment')) ||
__('%total_comments% comments', { total_comments: totalComments });
if (setCommentListTitle) setCommentListTitle(title);
// Display comments immediately if not fetching reactions // Display comments immediately if not fetching reactions
// If not, wait to show comments until reactions are fetched // If not, wait to show comments until reactions are fetched

View file

@ -2745,4 +2745,24 @@ export const icons = {
</svg> </svg>
); );
}, },
[ICONS.DISMISS_ALL]: (props: IconProps) => {
const { size = 24, color = 'currentColor', ...otherProps } = props;
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width={size}
height={size}
fill={color}
stroke={color}
strokeWidth="1"
strokeLinecap="round"
strokeLinejoin="round"
{...otherProps}
>
<path d="M5 13h14v-2H5v2zm-2 4h14v-2H3v2zM7 7v2h14V7H7z" />
</svg>
);
},
}; };

View file

@ -3,7 +3,6 @@ import {
selectClaimIsMine, selectClaimIsMine,
selectClaimForUri, selectClaimForUri,
selectHasChannels, selectHasChannels,
selectIsStreamPlaceholderForUri,
makeSelectTagInClaimOrChannelForUri, makeSelectTagInClaimOrChannelForUri,
} from 'redux/selectors/claims'; } from 'redux/selectors/claims';
import { makeSelectStreamingUrlForUri } from 'redux/selectors/file_info'; import { makeSelectStreamingUrlForUri } from 'redux/selectors/file_info';
@ -15,6 +14,7 @@ import { doOpenModal } from 'redux/actions/app';
import FileActions from './view'; import FileActions from './view';
import { makeSelectFileRenderModeForUri } from 'redux/selectors/content'; import { makeSelectFileRenderModeForUri } from 'redux/selectors/content';
import { DISABLE_DOWNLOAD_BUTTON_TAG } from 'constants/tags'; import { DISABLE_DOWNLOAD_BUTTON_TAG } from 'constants/tags';
import { isStreamPlaceholderClaim } from 'util/claim';
const select = (state, props) => { const select = (state, props) => {
const { uri } = props; const { uri } = props;
@ -27,7 +27,7 @@ const select = (state, props) => {
renderMode: makeSelectFileRenderModeForUri(uri)(state), renderMode: makeSelectFileRenderModeForUri(uri)(state),
costInfo: selectCostInfoForUri(state, uri), costInfo: selectCostInfoForUri(state, uri),
hasChannels: selectHasChannels(state), hasChannels: selectHasChannels(state),
isLivestreamClaim: selectIsStreamPlaceholderForUri(state, uri), isLivestreamClaim: isStreamPlaceholderClaim(claim),
streamingUrl: makeSelectStreamingUrlForUri(uri)(state), streamingUrl: makeSelectStreamingUrlForUri(uri)(state),
disableDownloadButton: makeSelectTagInClaimOrChannelForUri(props.uri, DISABLE_DOWNLOAD_BUTTON_TAG)(state), disableDownloadButton: makeSelectTagInClaimOrChannelForUri(props.uri, DISABLE_DOWNLOAD_BUTTON_TAG)(state),
}; };

View file

@ -119,7 +119,7 @@ export default function FileActions(props: Props) {
<ClaimCollectionAddButton uri={uri} fileAction /> <ClaimCollectionAddButton uri={uri} fileAction />
{!hideRepost && !isMobile && ( {!hideRepost && !isMobile && !isLivestreamClaim && (
<Tooltip title={__('Repost')} arrow={false}> <Tooltip title={__('Repost')} arrow={false}>
<Button <Button
button="alt" button="alt"
@ -183,7 +183,7 @@ export default function FileActions(props: Props) {
<MenuList className="menu__list"> <MenuList className="menu__list">
{isMobile && ( {isMobile && (
<> <>
{!hideRepost && ( {!hideRepost && !isLivestreamClaim && (
<MenuItem className="comment__menu-option" onSelect={handleRepostClick}> <MenuItem className="comment__menu-option" onSelect={handleRepostClick}>
<div className="menu__link"> <div className="menu__link">
<Icon aria-hidden icon={ICONS.REPOST} /> <Icon aria-hidden icon={ICONS.REPOST} />

View file

@ -10,8 +10,6 @@ import Button from 'component/button';
import LbcSymbol from 'component/common/lbc-symbol'; import LbcSymbol from 'component/common/lbc-symbol';
import FileDetails from 'component/fileDetails'; import FileDetails from 'component/fileDetails';
import FileValues from 'component/fileValues'; import FileValues from 'component/fileValues';
import { useIsMobile } from 'effects/use-screensize';
type Props = { type Props = {
uri: string, uri: string,
expandOverride: boolean, expandOverride: boolean,
@ -28,8 +26,6 @@ type Props = {
export default function FileDescription(props: Props) { export default function FileDescription(props: Props) {
const { uri, description, amount, hasSupport, isEmpty, doOpenModal, claimIsMine, expandOverride } = props; const { uri, description, amount, hasSupport, isEmpty, doOpenModal, claimIsMine, expandOverride } = props;
const isMobile = useIsMobile();
const [expanded, setExpanded] = React.useState(false); const [expanded, setExpanded] = React.useState(false);
const [showCreditDetails, setShowCreditDetails] = React.useState(false); const [showCreditDetails, setShowCreditDetails] = React.useState(false);
@ -47,13 +43,11 @@ export default function FileDescription(props: Props) {
'media__info-text--expanded': expanded, 'media__info-text--expanded': expanded,
})} })}
> >
{isMobile && <ClaimTags uri={uri} type="large" />}
<div className="mediaInfo__description"> <div className="mediaInfo__description">
{description && ( {description && (
<MarkdownPreview className="markdown-preview--description" content={description} simpleLinks /> <MarkdownPreview className="markdown-preview--description" content={description} simpleLinks />
)} )}
{!isMobile && <ClaimTags uri={uri} type="large" />} <ClaimTags uri={uri} type="large" />
<FileDetails uri={uri} /> <FileDetails uri={uri} />
</div> </div>
</div> </div>

View file

@ -5,7 +5,7 @@ import {
makeSelectDislikeCountForUri, makeSelectDislikeCountForUri,
} from 'redux/selectors/reactions'; } from 'redux/selectors/reactions';
import { doFetchReactions, doReactionLike, doReactionDislike } from 'redux/actions/reactions'; import { doFetchReactions, doReactionLike, doReactionDislike } from 'redux/actions/reactions';
import FileViewCount from './view'; import FileReactions from './view';
import { selectClaimForUri, selectIsStreamPlaceholderForUri } from 'redux/selectors/claims'; import { selectClaimForUri, selectIsStreamPlaceholderForUri } from 'redux/selectors/claims';
const select = (state, props) => { const select = (state, props) => {
@ -34,4 +34,4 @@ const perform = {
doReactionDislike, doReactionDislike,
}; };
export default connect(select, perform)(FileViewCount); export default connect(select, perform)(FileReactions);

View file

@ -138,7 +138,7 @@ const FileReaction = (reactionProps: ReactionProps) => {
return ( return (
<Tooltip title={title} arrow={false}> <Tooltip title={title} arrow={false}>
<div style={{ margin: '0' }}> <div className="file-reaction__tooltip-inner">
<Button <Button
requiresAuth requiresAuth
authSrc="filereaction_like" authSrc="filereaction_like"

View file

@ -0,0 +1,105 @@
// @flow
// $FlowFixMe
import { Global } from '@emotion/react';
import { Menu, MenuButton, MenuList, MenuItem } from '@reach/menu-button';
import { useHistory } from 'react-router-dom';
import { useIsMobile } from 'effects/use-screensize';
import usePersistedState from 'effects/use-persisted-state';
import * as ICONS from 'constants/icons';
import Icon from 'component/common/icon';
import React from 'react';
type Props = {
isPopoutWindow?: boolean,
superchatsHidden?: boolean,
noSuperchats?: boolean,
hideChat?: () => void,
setPopoutWindow?: (any) => void,
toggleSuperchats?: () => void,
};
export default function LivestreamMenu(props: Props) {
const { isPopoutWindow, superchatsHidden, noSuperchats, hideChat, setPopoutWindow, toggleSuperchats } = props;
const {
location: { pathname },
} = useHistory();
const isMobile = useIsMobile();
const [showTimestamps, setShowTimestamps] = usePersistedState('live-timestamps', false);
function handlePopout() {
if (setPopoutWindow) {
const newWindow = window.open('/$/popout' + pathname, 'Popout Chat', 'height=700,width=400');
// Add function to newWindow when closed (either manually or from button component)
newWindow.onbeforeunload = () => setPopoutWindow(undefined);
if (window.focus) newWindow.focus();
setPopoutWindow(newWindow);
}
}
const MenuGlobalStyles = () => (
<Global
styles={{
':root': {
'--live-timestamp-opacity': showTimestamps ? '0.5' : '0',
},
}}
/>
);
return (
<>
<MenuGlobalStyles />
<Menu>
<MenuButton className="menu__button">
<Icon size={isMobile ? 16 : 18} icon={ICONS.SETTINGS} />
</MenuButton>
<MenuList className="menu__list">
<MenuItem className="comment__menu-option" onSelect={() => setShowTimestamps(!showTimestamps)}>
<span className="menu__link">
<Icon aria-hidden icon={ICONS.TIME} />
{__('Toggle Timestamps')}
</span>
</MenuItem>
{!isMobile ? (
<>
{/* No need for Hide Chat on mobile with the expand/collapse drawer */}
<MenuItem className="comment__menu-option" onSelect={hideChat}>
<span className="menu__link">
<Icon aria-hidden icon={ICONS.EYE} />
{__('Hide Chat')}
</span>
</MenuItem>
{!isPopoutWindow && (
<MenuItem className="comment__menu-option" onSelect={handlePopout}>
<span className="menu__link">
<Icon aria-hidden icon={ICONS.EXTERNAL} />
{__('Popout Chat')}
</span>
</MenuItem>
)}
</>
) : (
!noSuperchats && (
<MenuItem className="comment__menu-option" onSelect={toggleSuperchats}>
<span className="menu__link">
<Icon aria-hidden icon={superchatsHidden ? ICONS.EYE : ICONS.DISMISS_ALL} size={18} />
{superchatsHidden ? __('Display Superchats') : __('Dismiss Superchats')}
</span>
</MenuItem>
)
)}
</MenuList>
</Menu>
</>
);
}

View file

@ -2,29 +2,20 @@
import 'scss/component/_livestream-chat.scss'; import 'scss/component/_livestream-chat.scss';
import { formatLbryUrlForWeb } from 'util/url'; import { formatLbryUrlForWeb } from 'util/url';
import { Menu, MenuButton, MenuList, MenuItem } from '@reach/menu-button';
import { useHistory } from 'react-router-dom';
import { useIsMobile } from 'effects/use-screensize'; import { useIsMobile } from 'effects/use-screensize';
import * as ICONS from 'constants/icons'; import * as ICONS from 'constants/icons';
import Button from 'component/button'; import Button from 'component/button';
import classnames from 'classnames'; import classnames from 'classnames';
import CommentCreate from 'component/commentCreate'; import CommentCreate from 'component/commentCreate';
import CreditAmount from 'component/common/credit-amount'; import CreditAmount from 'component/common/credit-amount';
import Icon from 'component/common/icon';
import LivestreamComment from 'component/livestreamComment'; import LivestreamComment from 'component/livestreamComment';
import LivestreamComments from 'component/livestreamComments'; import LivestreamComments from 'component/livestreamComments';
import LivestreamSuperchats from './livestream-superchats'; import LivestreamSuperchats from './livestream-superchats';
import LivestreamMenu from './livestream-menu';
import React from 'react'; import React from 'react';
import Spinner from 'component/spinner'; import Spinner from 'component/spinner';
import Yrbl from 'component/yrbl'; import Yrbl from 'component/yrbl';
import { getTipValues } from 'util/livestream';
const IS_TIMESTAMP_VISIBLE = () =>
// $FlowFixMe
document.documentElement.style.getPropertyValue('--live-timestamp-opacity') === '0.5';
const TOGGLE_TIMESTAMP_OPACITY = () =>
// $FlowFixMe
document.documentElement.style.setProperty('--live-timestamp-opacity', IS_TIMESTAMP_VISIBLE() ? '0' : '0.5');
const VIEW_MODES = { const VIEW_MODES = {
CHAT: 'chat', CHAT: 'chat',
@ -42,6 +33,8 @@ type Props = {
superChats: Array<Comment>, superChats: Array<Comment>,
uri: string, uri: string,
hideHeader?: boolean, hideHeader?: boolean,
superchatsHidden?: boolean,
customViewMode?: string,
doCommentList: (string, string, number, number) => void, doCommentList: (string, string, number, number) => void,
doResolveUris: (Array<string>, boolean) => void, doResolveUris: (Array<string>, boolean) => void,
doSuperChatList: (string) => void, doSuperChatList: (string) => void,
@ -57,15 +50,13 @@ export default function LivestreamChatLayout(props: Props) {
superChats: superChatsByAmount, superChats: superChatsByAmount,
uri, uri,
hideHeader, hideHeader,
superchatsHidden,
customViewMode,
doCommentList, doCommentList,
doResolveUris, doResolveUris,
doSuperChatList, doSuperChatList,
} = props; } = props;
const {
location: { pathname },
} = useHistory();
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const discussionElement = document.querySelector('.livestream__comments'); const discussionElement = document.querySelector('.livestream__comments');
@ -90,22 +81,7 @@ export default function LivestreamChatLayout(props: Props) {
const commentsToDisplay = viewMode === VIEW_MODES.CHAT ? commentsByChronologicalOrder : superChatsByAmount; const commentsToDisplay = viewMode === VIEW_MODES.CHAT ? commentsByChronologicalOrder : superChatsByAmount;
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);
let superChatsChannelUrls = [];
let superChatsFiatAmount = 0;
let superChatsLBCAmount = 0;
if (superChatsByAmount) {
superChatsByAmount.forEach((superChat) => {
const { is_fiat: isFiat, support_amount: tipAmount, channel_url: uri } = superChat;
if (isFiat) {
superChatsFiatAmount = superChatsFiatAmount + tipAmount;
} else {
superChatsLBCAmount = superChatsLBCAmount + tipAmount;
}
superChatsChannelUrls.push(uri || '0');
});
}
function toggleSuperChat() { function toggleSuperChat() {
if (superChatsChannelUrls && superChatsChannelUrls.length > 0) { if (superChatsChannelUrls && superChatsChannelUrls.length > 0) {
@ -118,15 +94,11 @@ export default function LivestreamChatLayout(props: Props) {
setViewMode(VIEW_MODES.SUPERCHAT); setViewMode(VIEW_MODES.SUPERCHAT);
} }
function handlePopout() { React.useEffect(() => {
const newWindow = window.open('/$/popout' + pathname, 'Popout Chat', 'height=700,width=400'); if (customViewMode && customViewMode !== viewMode) {
setViewMode(customViewMode);
// Add function to newWindow when closed (either manually or from button component) }
newWindow.onbeforeunload = () => setPopoutWindow(undefined); }, [customViewMode, viewMode]);
if (window.focus) newWindow.focus();
setPopoutWindow(newWindow);
}
React.useEffect(() => { React.useEffect(() => {
if (claimId) { if (claimId) {
@ -249,38 +221,11 @@ export default function LivestreamChatLayout(props: Props) {
<div className="card__title-section--small livestreamDiscussion__title"> <div className="card__title-section--small livestreamDiscussion__title">
{__('Live Chat')} {__('Live Chat')}
<Menu> <LivestreamMenu
<MenuButton className="menu__button"> isPopoutWindow={isPopoutWindow}
<Icon size={18} icon={ICONS.SETTINGS} /> hideChat={() => setChatHidden(true)}
</MenuButton> setPopoutWindow={(v) => setPopoutWindow(v)}
/>
<MenuList className="menu__list">
<MenuItem className="comment__menu-option" onSelect={TOGGLE_TIMESTAMP_OPACITY}>
<span className="menu__link">
<Icon aria-hidden icon={ICONS.TIME} />
{__('Toggle Timestamps')}
</span>
</MenuItem>
<MenuItem className="comment__menu-option" onSelect={() => setChatHidden(true)}>
<span className="menu__link">
<Icon aria-hidden icon={ICONS.EYE} />
{__('Hide Chat')}
</span>
</MenuItem>
{!isPopoutWindow && !isMobile && (
<>
<MenuItem className="comment__menu-option" onSelect={handlePopout}>
<span className="menu__link">
<Icon aria-hidden icon={ICONS.EXTERNAL} />
{__('Popout Chat')}
</span>
</MenuItem>
</>
)}
</MenuList>
</Menu>
</div> </div>
{superChatsByAmount && ( {superChatsByAmount && (
@ -302,28 +247,33 @@ export default function LivestreamChatLayout(props: Props) {
)} )}
<div ref={commentsRef} className="livestreamComments__wrapper"> <div ref={commentsRef} className="livestreamComments__wrapper">
{viewMode === VIEW_MODES.CHAT && superChatsByAmount && ( <div className="livestream-comments__top-actions">
<LivestreamSuperchats superChats={superChatsByAmount} toggleSuperChat={toggleSuperChat} /> {viewMode === VIEW_MODES.CHAT && superChatsByAmount && !superchatsHidden && (
)} <LivestreamSuperchats superChats={superChatsByAmount} toggleSuperChat={toggleSuperChat} />
)}
{pinnedComment && showPinned && viewMode === VIEW_MODES.CHAT && ( {pinnedComment && showPinned && viewMode === VIEW_MODES.CHAT && (
<div className="livestreamPinned__wrapper"> <div className="livestreamPinned__wrapper">
<LivestreamComment <LivestreamComment
comment={pinnedComment} comment={pinnedComment}
key={pinnedComment.comment_id} key={pinnedComment.comment_id}
uri={uri} uri={uri}
pushMention={setMention} pushMention={setMention}
/> handleDismissPin={() => setShowPinned(false)}
/>
<Button {!isMobile && (
title={__('Dismiss pinned comment')} <Button
button="inverse" title={__('Dismiss pinned comment')}
className="close-button" button="inverse"
onClick={() => setShowPinned(false)} className="close-button"
icon={ICONS.REMOVE} onClick={() => setShowPinned(false)}
/> icon={ICONS.REMOVE}
</div> />
)} )}
</div>
)}
</div>
{viewMode === VIEW_MODES.SUPERCHAT && resolvingSuperChats ? ( {viewMode === VIEW_MODES.SUPERCHAT && resolvingSuperChats ? (
<div className="main--empty"> <div className="main--empty">

View file

@ -26,10 +26,11 @@ type Props = {
claim: StreamClaim, claim: StreamClaim,
myChannelIds: ?Array<string>, myChannelIds: ?Array<string>,
stakedLevel: number, stakedLevel: number,
handleDismissPin?: () => void,
}; };
export default function LivestreamComment(props: Props) { export default function LivestreamComment(props: Props) {
const { comment, forceUpdate, uri, claim, myChannelIds, stakedLevel } = props; const { comment, forceUpdate, uri, claim, myChannelIds, stakedLevel, handleDismissPin } = props;
const { const {
channel_url: authorUri, channel_url: authorUri,
@ -137,6 +138,7 @@ export default function LivestreamComment(props: Props) {
disableEdit disableEdit
disableRemove={comment.removed} disableRemove={comment.removed}
isLiveComment isLiveComment
handleDismissPin={handleDismissPin}
/> />
</Menu> </Menu>
</div> </div>

View file

@ -1,5 +1,6 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectClaimForUri, makeSelectTagInClaimOrChannelForUri, selectThumbnailForUri } from 'redux/selectors/claims'; import { selectClaimForUri, makeSelectTagInClaimOrChannelForUri, selectThumbnailForUri } from 'redux/selectors/claims';
import { selectSuperChatsForUri } from 'redux/selectors/comments';
import LivestreamLayout from './view'; import LivestreamLayout from './view';
import { DISABLE_COMMENTS_TAG } from 'constants/tags'; import { DISABLE_COMMENTS_TAG } from 'constants/tags';
@ -10,6 +11,7 @@ const select = (state, props) => {
claim: selectClaimForUri(state, uri), claim: selectClaimForUri(state, uri),
thumbnail: selectThumbnailForUri(state, uri), thumbnail: selectThumbnailForUri(state, uri),
chatDisabled: makeSelectTagInClaimOrChannelForUri(uri, DISABLE_COMMENTS_TAG)(state), chatDisabled: makeSelectTagInClaimOrChannelForUri(uri, DISABLE_COMMENTS_TAG)(state),
superChats: selectSuperChatsForUri(state, uri),
}; };
}; };

View file

@ -1,6 +1,7 @@
// @flow // @flow
import { lazyImport } from 'util/lazyImport'; import { lazyImport } from 'util/lazyImport';
import { useIsMobile } from 'effects/use-screensize'; import { useIsMobile } from 'effects/use-screensize';
import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button';
import FileTitleSection from 'component/fileTitleSection'; import FileTitleSection from 'component/fileTitleSection';
import LivestreamLink from 'component/livestreamLink'; import LivestreamLink from 'component/livestreamLink';
import React from 'react'; import React from 'react';
@ -10,9 +11,18 @@ import LivestreamIframeRender from './iframe-render';
import Button from 'component/button'; import Button from 'component/button';
import * as ICONS from 'constants/icons'; import * as ICONS from 'constants/icons';
import SwipeableDrawer from 'component/swipeableDrawer'; import SwipeableDrawer from 'component/swipeableDrawer';
import LivestreamMenu from 'component/livestreamChatLayout/livestream-menu';
import Icon from 'component/common/icon';
import CreditAmount from 'component/common/credit-amount';
import { getTipValues } from 'util/livestream';
const LivestreamChatLayout = lazyImport(() => import('component/livestreamChatLayout' /* webpackChunkName: "chat" */)); const LivestreamChatLayout = lazyImport(() => import('component/livestreamChatLayout' /* webpackChunkName: "chat" */));
const VIEW_MODES = {
CHAT: 'chat',
SUPERCHAT: 'sc',
};
type Props = { type Props = {
activeStreamUri: boolean | string, activeStreamUri: boolean | string,
claim: ?StreamClaim, claim: ?StreamClaim,
@ -22,6 +32,7 @@ type Props = {
showLivestream: boolean, showLivestream: boolean,
showScheduledInfo: boolean, showScheduledInfo: boolean,
uri: string, uri: string,
superChats: Array<Comment>,
}; };
export default function LivestreamLayout(props: Props) { export default function LivestreamLayout(props: Props) {
@ -34,16 +45,43 @@ export default function LivestreamLayout(props: Props) {
showLivestream, showLivestream,
showScheduledInfo, showScheduledInfo,
uri, uri,
superChats,
} = props; } = props;
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const [showChat, setShowChat] = React.useState(undefined); const [showChat, setShowChat] = React.useState(undefined);
const drawerWasToggled = showChat !== undefined; const [superchatsHidden, setSuperchatsHidden] = React.useState(false);
const [chatViewMode, setChatViewMode] = React.useState(VIEW_MODES.CHAT);
if (!claim || !claim.signing_channel) return null; if (!claim || !claim.signing_channel) return null;
const { name: channelName, claim_id: channelClaimId } = claim.signing_channel; const { name: channelName, claim_id: channelClaimId } = claim.signing_channel;
const { superChatsFiatAmount, superChatsLBCAmount } = getTipValues(superChats);
const ChatModeSelector = () => (
<Menu>
<MenuButton>
<span className="swipeable-drawer__title-menu">
{chatViewMode === VIEW_MODES.CHAT ? __('Live Chat') : __('Super Chats')}
<Icon icon={ICONS.DOWN} />
</span>
</MenuButton>
<MenuList className="menu__list--header">
<MenuItem className="menu__link" onSelect={() => setChatViewMode(VIEW_MODES.CHAT)}>
{__('Live Chat')}
</MenuItem>
<MenuItem className="menu__link" onSelect={() => setChatViewMode(VIEW_MODES.SUPERCHAT)}>
<div className="recommended-content__toggles">
<CreditAmount amount={superChatsLBCAmount || 0} size={8} /> /
<CreditAmount amount={superChatsFiatAmount || 0} size={8} isFiat /> {__('Tipped')}
</div>
</MenuItem>
</MenuList>
</Menu>
);
return ( return (
<> <>
@ -94,10 +132,21 @@ export default function LivestreamLayout(props: Props) {
<SwipeableDrawer <SwipeableDrawer
open={Boolean(showChat)} open={Boolean(showChat)}
toggleDrawer={() => setShowChat(!showChat)} toggleDrawer={() => setShowChat(!showChat)}
title={__('Live Chat')} title={<ChatModeSelector />}
didInitialDisplay={drawerWasToggled} actions={
<LivestreamMenu
noSuperchats={!superChats || superChats.length === 0}
superchatsHidden={superchatsHidden}
toggleSuperchats={() => setSuperchatsHidden(!superchatsHidden)}
/>
}
> >
<LivestreamChatLayout uri={uri} hideHeader /> <LivestreamChatLayout
uri={uri}
hideHeader
superchatsHidden={superchatsHidden}
customViewMode={chatViewMode}
/>
</SwipeableDrawer> </SwipeableDrawer>
</React.Suspense> </React.Suspense>
)} )}

View file

@ -3,15 +3,12 @@
import { Global } from '@emotion/react'; import { Global } from '@emotion/react';
// $FlowFixMe // $FlowFixMe
import { grey } from '@mui/material/colors'; import { grey } from '@mui/material/colors';
// $FlowFixMe
import Typography from '@mui/material/Typography';
import { HEADER_HEIGHT_MOBILE } from 'component/fileRenderMobile/view'; import { HEADER_HEIGHT_MOBILE } from 'component/fileRenderMobile/view';
import { SwipeableDrawer as MUIDrawer } from '@mui/material'; import { SwipeableDrawer as MUIDrawer } from '@mui/material';
import * as ICONS from 'constants/icons'; import * as ICONS from 'constants/icons';
import * as React from 'react'; import * as React from 'react';
import Button from 'component/button'; import Button from 'component/button';
import Icon from 'component/common/icon';
import Portal from '@mui/material/Portal';
const DRAWER_PULLER_HEIGHT = 42; const DRAWER_PULLER_HEIGHT = 42;
@ -20,20 +17,20 @@ type Props = {
open: Boolean, open: Boolean,
theme: string, theme: string,
mobilePlayerDimensions?: { height: number }, mobilePlayerDimensions?: { height: number },
title: string, title: any,
didInitialDisplay?: boolean, actions?: any,
toggleDrawer: () => void, toggleDrawer: () => void,
}; };
export default function SwipeableDrawer(props: Props) { export default function SwipeableDrawer(props: Props) {
const { mobilePlayerDimensions, title, children, open, theme, didInitialDisplay, toggleDrawer } = props; const { mobilePlayerDimensions, title, children, open, theme, actions, toggleDrawer } = props;
const [coverHeight, setCoverHeight] = React.useState(); const [coverHeight, setCoverHeight] = React.useState();
const videoHeight = coverHeight || (mobilePlayerDimensions ? mobilePlayerDimensions.height : 0); const videoHeight = coverHeight || (mobilePlayerDimensions ? mobilePlayerDimensions.height : 0);
React.useEffect(() => { React.useEffect(() => {
if (open && !mobilePlayerDimensions) { if (open && !mobilePlayerDimensions && !coverHeight) {
const element = document.querySelector(`.file-page__video-container`); const element = document.querySelector(`.file-page__video-container`);
if (element) { if (element) {
@ -41,15 +38,14 @@ export default function SwipeableDrawer(props: Props) {
setCoverHeight(rect.height); setCoverHeight(rect.height);
} }
} }
}, [mobilePlayerDimensions, open]); }, [coverHeight, mobilePlayerDimensions, open]);
const drawerGlobalStyles = ( const DrawerGlobalStyles = () => (
<Global <Global
styles={{ styles={{
'.main-wrapper__inner--filepage': { '.main-wrapper__inner--filepage': {
'padding-bottom': didInitialDisplay ? `${DRAWER_PULLER_HEIGHT}px !important` : 'inherit',
overflow: open ? 'hidden' : 'unset', overflow: open ? 'hidden' : 'unset',
'max-height': open ? '100vh' : 'unset', maxHeight: open ? '100vh' : 'unset',
}, },
'.MuiDrawer-root': { '.MuiDrawer-root': {
top: `calc(${HEADER_HEIGHT_MOBILE}px + ${videoHeight}px) !important`, top: `calc(${HEADER_HEIGHT_MOBILE}px + ${videoHeight}px) !important`,
@ -68,9 +64,21 @@ export default function SwipeableDrawer(props: Props) {
<span className="swipeable-drawer__puller" style={{ backgroundColor: theme === 'light' ? grey[300] : grey[800] }} /> <span className="swipeable-drawer__puller" style={{ backgroundColor: theme === 'light' ? grey[300] : grey[800] }} />
); );
const HeaderContents = () => (
<div className="swipeable-drawer__header-content">
{title}
<div className="swipeable-drawer__header-actions">
{actions}
<Button icon={ICONS.REMOVE} iconSize={16} onClick={toggleDrawer} />
</div>
</div>
);
return ( return (
<> <>
{drawerGlobalStyles} <DrawerGlobalStyles />
<MUIDrawer <MUIDrawer
anchor="bottom" anchor="bottom"
@ -83,22 +91,10 @@ export default function SwipeableDrawer(props: Props) {
disableSwipeToOpen disableSwipeToOpen
ModalProps={{ keepMounted: true }} ModalProps={{ keepMounted: true }}
> >
{didInitialDisplay && ( {open && (
<div className="swipeable-drawer__header" style={{ top: -DRAWER_PULLER_HEIGHT }}> <div className="swipeable-drawer__header" style={{ top: -DRAWER_PULLER_HEIGHT }}>
{open ? ( <Puller />
<> <HeaderContents />
<Puller />
<Typography sx={{ p: 1.5, color: 'var(--color-text)', display: 'flex' }}>{title}</Typography>
<Button button="close" icon={ICONS.REMOVE} onClick={toggleDrawer} />
</>
) : (
<Portal>
<div className="swipeable-drawer__expand" onClick={toggleDrawer}>
<Typography sx={{ p: 1.5, color: 'var(--color-text)' }}>{title}</Typography>
<Icon icon={ICONS.UP} />
</div>
</Portal>
)}
</div> </div>
)} )}

View file

@ -185,8 +185,9 @@ export const TECHNOLOGY = 'Technology';
export const EMOJI = 'Emoji'; export const EMOJI = 'Emoji';
export const STICKER = 'Sticker'; export const STICKER = 'Sticker';
export const EDUCATION = 'Education'; export const EDUCATION = 'Education';
export const POP_CULTURE = 'Pop Culture'; export const POP_CULTURE = 'PopCulture';
export const ODYSEE_LOGO = 'OdyseeLogo'; export const ODYSEE_LOGO = 'OdyseeLogo';
export const ODYSEE_WHITE_TEXT = 'OdyseeLogoWhiteText'; export const ODYSEE_WHITE_TEXT = 'OdyseeLogoWhiteText';
export const ODYSEE_DARK_TEXT = 'OdyseeLogoDarkText'; export const ODYSEE_DARK_TEXT = 'OdyseeLogoDarkText';
export const FEATURED = 'Featured'; export const FEATURED = 'Featured';
export const DISMISS_ALL = 'DismissAll';

View file

@ -13,6 +13,7 @@ import * as SETTINGS from 'constants/settings';
import { selectCostInfoForUri, doFetchCostInfoForUri } from 'lbryinc'; import { selectCostInfoForUri, doFetchCostInfoForUri } from 'lbryinc';
import { selectShowMatureContent, selectClientSetting } from 'redux/selectors/settings'; import { selectShowMatureContent, selectClientSetting } from 'redux/selectors/settings';
import { makeSelectFileRenderModeForUri, makeSelectContentPositionForUri } from 'redux/selectors/content'; import { makeSelectFileRenderModeForUri, makeSelectContentPositionForUri } from 'redux/selectors/content';
import { makeSelectCommentsListTitleForUri } from 'redux/selectors/comments';
import { DISABLE_COMMENTS_TAG } from 'constants/tags'; import { DISABLE_COMMENTS_TAG } from 'constants/tags';
import { doSetMobilePlayerDimensions } from 'redux/actions/app'; import { doSetMobilePlayerDimensions } from 'redux/actions/app';
@ -38,6 +39,7 @@ const select = (state, props) => {
hasCollectionById: Boolean(makeSelectCollectionForId(collectionId)(state)), hasCollectionById: Boolean(makeSelectCollectionForId(collectionId)(state)),
collectionId, collectionId,
position: makeSelectContentPositionForUri(uri)(state), position: makeSelectContentPositionForUri(uri)(state),
commentsListTitle: makeSelectCommentsListTitleForUri(uri)(state),
}; };
}; };

View file

@ -39,6 +39,7 @@ type Props = {
commentsDisabled: boolean, commentsDisabled: boolean,
isLivestream: boolean, isLivestream: boolean,
position: number, position: number,
commentsListTitle: string,
doFetchCostInfoForUri: (uri: string) => void, doFetchCostInfoForUri: (uri: string) => void,
doSetContentHistoryItem: (uri: string) => void, doSetContentHistoryItem: (uri: string) => void,
doSetPrimaryUri: (uri: ?string) => void, doSetPrimaryUri: (uri: ?string) => void,
@ -62,6 +63,7 @@ export default function FilePage(props: Props) {
collectionId, collectionId,
isLivestream, isLivestream,
position, position,
commentsListTitle,
doFetchCostInfoForUri, doFetchCostInfoForUri,
doSetContentHistoryItem, doSetContentHistoryItem,
doSetPrimaryUri, doSetPrimaryUri,
@ -71,9 +73,7 @@ export default function FilePage(props: Props) {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const [showComments, setShowComments] = React.useState(undefined); const [showComments, setShowComments] = React.useState(undefined);
const [commentListTitle, setCommentListTitle] = React.useState();
const drawerWasToggled = showComments !== undefined;
const cost = costInfo ? costInfo.cost : null; const cost = costInfo ? costInfo.cost : null;
const hasFileInfo = fileInfo !== undefined; const hasFileInfo = fileInfo !== undefined;
const isMarkdown = renderMode === RENDER_MODES.MARKDOWN; const isMarkdown = renderMode === RENDER_MODES.MARKDOWN;
@ -189,7 +189,7 @@ export default function FilePage(props: Props) {
const commentsListElement = commentsDisabled ? ( const commentsListElement = commentsDisabled ? (
<Empty text={__('The creator of this content has disabled comments.')} /> <Empty text={__('The creator of this content has disabled comments.')} />
) : ( ) : (
<CommentsList uri={uri} linkedCommentId={linkedCommentId} setCommentListTitle={setCommentListTitle} /> <CommentsList uri={uri} linkedCommentId={linkedCommentId} />
); );
return ( return (
@ -219,8 +219,7 @@ export default function FilePage(props: Props) {
<SwipeableDrawer <SwipeableDrawer
open={Boolean(showComments)} open={Boolean(showComments)}
toggleDrawer={() => setShowComments(!showComments)} toggleDrawer={() => setShowComments(!showComments)}
title={commentListTitle} title={commentsListTitle}
didInitialDisplay={drawerWasToggled}
> >
{commentsListElement} {commentsListElement}
</SwipeableDrawer> </SwipeableDrawer>
@ -232,7 +231,7 @@ export default function FilePage(props: Props) {
{isMobile && ( {isMobile && (
<Button <Button
className="swipeable-drawer__expand-button" className="swipeable-drawer__expand-button"
label={commentListTitle} label={commentsListTitle}
button="primary" button="primary"
icon={ICONS.CHAT} icon={ICONS.CHAT}
onClick={() => setShowComments(!showComments)} onClick={() => setShowComments(!showComments)}

View file

@ -3,7 +3,7 @@ import { normalizeURI, parseURI, isURIValid } from 'util/lbryURI';
import { selectSupportsByOutpoint } from 'redux/selectors/wallet'; import { selectSupportsByOutpoint } from 'redux/selectors/wallet';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { createCachedSelector } from 're-reselect'; import { createCachedSelector } from 're-reselect';
import { isClaimNsfw, filterClaims, getChannelIdFromClaim } from 'util/claim'; import { isClaimNsfw, filterClaims, getChannelIdFromClaim, isStreamPlaceholderClaim } from 'util/claim';
import * as CLAIM from 'constants/claim'; import * as CLAIM from 'constants/claim';
import { INTERNAL_TAGS } from 'constants/tags'; import { INTERNAL_TAGS } from 'constants/tags';
@ -592,7 +592,7 @@ export const selectChannelForClaimUri = createCachedSelector(
return includePrefix ? permanentUrl : permanentUrl.slice('lbry://'.length); return includePrefix ? permanentUrl : permanentUrl.slice('lbry://'.length);
} }
} }
)((state, uri) => String(uri)); )((state, uri, includePrefix) => `${String(uri)}:${String(includePrefix)}`);
// Returns the associated channel uri for a given claim uri // Returns the associated channel uri for a given claim uri
// accepts a regular claim uri lbry://something // accepts a regular claim uri lbry://something
@ -716,10 +716,6 @@ export const makeSelectClaimHasSource = (uri: string) =>
return Boolean(claim.value.source); return Boolean(claim.value.source);
}); });
export const isStreamPlaceholderClaim = (claim: ?StreamClaim) => {
return claim ? Boolean(claim.value_type === 'stream' && !claim.value.source) : false;
};
export const selectIsStreamPlaceholderForUri = (state: State, uri: string) => { export const selectIsStreamPlaceholderForUri = (state: State, uri: string) => {
const claim = selectClaimForUri(state, uri); const claim = selectClaimForUri(state, uri);
return isStreamPlaceholderClaim(claim); return isStreamPlaceholderClaim(claim);

View file

@ -14,6 +14,7 @@ import {
} from 'redux/selectors/claims'; } from 'redux/selectors/claims';
import { isClaimNsfw, getChannelFromClaim } from 'util/claim'; import { isClaimNsfw, getChannelFromClaim } from 'util/claim';
import { selectSubscriptionUris } from 'redux/selectors/subscriptions'; import { selectSubscriptionUris } from 'redux/selectors/subscriptions';
import { getCommentsListTitle } from 'util/comments';
type State = { claims: any, comments: CommentsState }; type State = { claims: any, comments: CommentsState };
@ -338,9 +339,15 @@ export const makeSelectTotalReplyPagesForParentId = (parentId: string) =>
export const makeSelectTotalCommentsCountForUri = (uri: string) => export const makeSelectTotalCommentsCountForUri = (uri: string) =>
createSelector(selectState, selectCommentsByUri, (state, byUri) => { createSelector(selectState, selectCommentsByUri, (state, byUri) => {
const claimId = byUri[uri]; const claimId = byUri[uri];
return state.totalCommentsById[claimId] || 0; return state.totalCommentsById[claimId] || 0;
}); });
export const makeSelectCommentsListTitleForUri = (uri: string) =>
createSelector(makeSelectTotalCommentsCountForUri(uri), (totalComments) => {
return getCommentsListTitle(totalComments);
});
// Personal list // Personal list
export const makeSelectChannelIsBlocked = (uri: string) => export const makeSelectChannelIsBlocked = (uri: string) =>
createSelector(selectModerationBlockList, (blockedChannelUris) => { createSelector(selectModerationBlockList, (blockedChannelUris) => {

View file

@ -756,3 +756,7 @@ svg + .button__label {
.button--hash-id { .button--hash-id {
@include font-mono; @include font-mono;
} }
.file-reaction__tooltip-inner {
margin: 0px !important;
}

View file

@ -169,13 +169,10 @@ $recent-msg-button__height: 2rem;
} }
@media (max-width: $breakpoint-small) { @media (max-width: $breakpoint-small) {
position: absolute;
z-index: 1300; z-index: 1300;
width: 100%; width: 100%;
background-color: transparent; background-color: transparent;
padding: 0px; padding: 0px;
padding-left: var(--spacing-xxs);
padding-top: var(--spacing-xxs);
border-bottom: none; border-bottom: none;
scrollbar-width: 0px; scrollbar-width: 0px;
@ -186,10 +183,23 @@ $recent-msg-button__height: 2rem;
} }
} }
.livestream-comments__top-actions {
@media (max-width: $breakpoint-small) {
position: absolute;
display: grid;
padding: var(--spacing-xxs);
padding-right: var(--spacing-m);
> div:not(:first-child) {
margin-top: var(--spacing-xxs);
}
}
}
.livestreamPinned__wrapper { .livestreamPinned__wrapper {
@extend .livestreamSuperchats__wrapper;
display: flex; display: flex;
flex-shrink: 0; flex-shrink: 0;
position: relative;
padding: var(--spacing-s) var(--spacing-xs); padding: var(--spacing-s) var(--spacing-xs);
border-bottom: 1px solid var(--color-border); border-bottom: 1px solid var(--color-border);
font-size: var(--font-small); font-size: var(--font-small);
@ -213,6 +223,46 @@ $recent-msg-button__height: 2rem;
padding: var(--spacing-xs); padding: var(--spacing-xs);
width: var(--livestream-comments-width); width: var(--livestream-comments-width);
} }
@media (max-width: $breakpoint-small) {
max-width: 100%;
padding: 0;
padding-left: var(--spacing-xxs);
border-radius: var(--border-radius);
border: 1px solid var(--color-border);
.livestream__comment {
overflow: unset;
.livestreamComment__body {
margin: 0px;
width: 100%;
.markdown-preview {
p,
.button__label {
white-space: nowrap !important;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
}
a {
pointer-events: none;
}
}
}
}
span {
font-size: var(--font-xxsmall) !important;
}
.close-button {
padding: 0;
padding-left: var(--spacing-xxs);
}
}
} }
.livestreamSuperchat__amount--large { .livestreamSuperchat__amount--large {
@ -266,6 +316,10 @@ $recent-msg-button__height: 2rem;
} }
&:nth-of-type(3) { &:nth-of-type(3) {
background-color: var(--color-superchat-3); background-color: var(--color-superchat-3);
@media (max-width: $breakpoint-small) {
background-color: #fff;
}
} }
&:nth-of-type(-n + 3) { &:nth-of-type(-n + 3) {
@ -280,6 +334,7 @@ $recent-msg-button__height: 2rem;
} }
@media (max-width: $breakpoint-small) { @media (max-width: $breakpoint-small) {
background-color: #fff;
padding: 5px; padding: 5px;
padding-bottom: 2px; padding-bottom: 2px;

View file

@ -30,6 +30,22 @@
.channel-name { .channel-name {
font-size: var(--font-xsmall); font-size: var(--font-xsmall);
} }
@media (max-width: $breakpoint-small) {
display: flex;
justify-content: space-between;
.livestreamComment__menu {
position: relative;
right: 0;
top: 0;
}
span,
p {
font-size: var(--font-xxsmall) !important;
}
}
} }
.livestream__comment--mentioned { .livestream__comment--mentioned {
@ -42,6 +58,7 @@
.livestream__comment--superchat { .livestream__comment--superchat {
background-color: var(--color-card-background-highlighted); background-color: var(--color-card-background-highlighted);
display: unset;
+ .livestream__comment--superchat { + .livestream__comment--superchat {
margin-bottom: var(--spacing-xxs); margin-bottom: var(--spacing-xxs);
@ -58,6 +75,10 @@
margin-top: calc(var(--spacing-xxs) / 2); margin-top: calc(var(--spacing-xxs) / 2);
} }
.livestreamComment__menu {
position: absolute;
}
&::before { &::before {
position: absolute; position: absolute;
left: 0; left: 0;

View file

@ -87,11 +87,13 @@
z-index: 0; z-index: 0;
margin-right: auto; margin-right: auto;
margin-left: auto; margin-left: auto;
min-height: calc(100vh - var(--header-height));
@media (min-width: $breakpoint-small) {
min-height: calc(100vh - var(--header-height));
}
@media (max-width: $breakpoint-small) { @media (max-width: $breakpoint-small) {
width: 100%; width: 100%;
min-height: calc(100vh - var(--header-height-mobile));
} }
} }
@ -237,7 +239,7 @@
} }
.file-page__recommended { .file-page__recommended {
margin-top: var(--spacing-xxs) !important; margin-top: 0px !important;
} }
} }
} }

View file

@ -142,7 +142,7 @@
margin: 0 !important; margin: 0 !important;
padding: var(--spacing-xxs); padding: var(--spacing-xxs);
width: 3.1rem; width: 3.1rem;
height: 3.1rem; height: 3.5rem;
.button__content { .button__content {
flex-direction: column; flex-direction: column;

View file

@ -20,6 +20,31 @@
left: calc(50% - 15px); left: calc(50% - 15px);
} }
.swipeable-drawer__header-content {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--spacing-s) var(--spacing-xxs);
font-size: var(--font-small);
}
.swipeable-drawer__title-menu {
display: flex;
align-items: center;
svg {
margin-left: var(--spacing-xxs);
}
}
.swipeable-drawer__header-actions {
display: flex;
button:not(:last-child) {
margin-right: var(--spacing-xxs);
}
}
.swipeable-drawer__expand-button { .swipeable-drawer__expand-button {
width: 100%; width: 100%;
margin: var(--spacing-xxs) 0; margin: var(--spacing-xxs) 0;

View file

@ -123,3 +123,7 @@ export function getClaimTitle(claim: ?Claim) {
const metadata = getClaimMetadata(claim); const metadata = getClaimMetadata(claim);
return metadata && metadata.title; return metadata && metadata.title;
} }
export const isStreamPlaceholderClaim = (claim: ?StreamClaim) => {
return claim ? Boolean(claim.value_type === 'stream' && !claim.value.source) : false;
};

View file

@ -108,3 +108,12 @@ export function getStickerUrl(comment: string) {
const stickerFromComment = parseSticker(comment); const stickerFromComment = parseSticker(comment);
return stickerFromComment && stickerFromComment.url; return stickerFromComment && stickerFromComment.url;
} }
export function getCommentsListTitle(totalComments: number) {
const title =
(totalComments === 0 && __('Leave a comment')) ||
(totalComments === 1 && __('1 comment')) ||
__('%total_comments% comments', { total_comments: totalComments });
return title;
}

View file

@ -22,3 +22,24 @@ export function getLivestreamUris(activeLivestreams: ?LivestreamInfo, channelIds
// $FlowFixMe // $FlowFixMe
return values.map((v) => v.claimUri); return values.map((v) => v.claimUri);
} }
export function getTipValues(superChatsByAmount: Array<Comment>) {
let superChatsChannelUrls = [];
let superChatsFiatAmount = 0;
let superChatsLBCAmount = 0;
if (superChatsByAmount) {
superChatsByAmount.forEach((superChat) => {
const { is_fiat: isFiat, support_amount: tipAmount, channel_url: uri } = superChat;
if (isFiat) {
superChatsFiatAmount = superChatsFiatAmount + tipAmount;
} else {
superChatsLBCAmount = superChatsLBCAmount + tipAmount;
}
superChatsChannelUrls.push(uri || '0');
});
}
return { superChatsChannelUrls, superChatsFiatAmount, superChatsLBCAmount };
}