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 : (
<Tooltip title={__('Add this claim to a list')} arrow={false}>
<Button
button={!fileAction && 'alt'}
button={!fileAction ? 'alt' : undefined}
className={classnames({ 'button--file-action': fileAction })}
icon={fileAction ? (!isSaved ? ICONS.ADD : ICONS.STACK) : ICONS.LIBRARY}
iconSize={fileAction && 22}
iconSize={fileAction ? 22 : undefined}
label={uri ? (!isSaved ? __('Save') : __('Saved')) : __('New List')}
requiresAuth
onClick={(e) => {

View file

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

View file

@ -1,10 +1,11 @@
import * as PAGES from 'constants/pages';
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 { push } from 'connected-react-router';
import ClaimPreviewSubtitle from './view';
import { doFetchSubCount, selectSubCountForUri } from 'lbryinc';
import { isStreamPlaceholderClaim } from 'util/claim';
const select = (state, props) => {
const claim = selectClaimForUri(state, props.uri);

View file

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

View file

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

View file

@ -36,6 +36,7 @@ type Props = {
pinComment: (string, string, boolean) => Promise<any>,
commentModAddDelegate: (string, string, ChannelClaim) => void,
setQuickReply: (any) => void,
handleDismissPin?: () => void,
};
function CommentMenuList(props: Props) {
@ -63,6 +64,7 @@ function CommentMenuList(props: Props) {
pinComment,
commentModAddDelegate,
setQuickReply,
handleDismissPin,
} = props;
const {
@ -251,6 +253,13 @@ function CommentMenuList(props: Props) {
</MenuItem>
)}
{isPinned && (
<MenuItem className="comment__menu-option menu__link" onSelect={handleDismissPin}>
<Icon aria-hidden icon={ICONS.DISMISS_ALL} />
{__('Dismiss Pin')}
</MenuItem>
)}
{activeChannelClaim && (
<div className="comment__menu-active">
<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 { getChannelIdFromClaim } from 'util/claim';
import { useIsMobile, useIsMediumScreen } from 'effects/use-screensize';
import { getCommentsListTitle } from 'util/comments';
import * as ICONS from 'constants/icons';
import * as REACTION_TYPES from 'constants/reactions';
import Button from 'component/button';
@ -49,7 +50,6 @@ type Props = {
commentsAreExpanded?: boolean,
fetchReacts: (Array<string>) => Promise<any>,
doResolveUris: (Array<string>) => void,
setCommentListTitle?: (string) => void,
fetchTopLevelComments: (string, number, number, number) => void,
fetchComment: (string) => void,
resetComments: (string) => void,
@ -78,7 +78,6 @@ function CommentList(props: Props) {
commentsAreExpanded,
fetchReacts,
doResolveUris,
setCommentListTitle,
fetchTopLevelComments,
fetchComment,
resetComments,
@ -101,11 +100,7 @@ function CommentList(props: Props) {
const isResolvingComments = topLevelComments && resolvedComments.length !== topLevelComments.length;
const alreadyResolved = !isResolvingComments && resolvedComments.length !== 0;
const canDisplayComments = commentsToDisplay && commentsToDisplay.length === topLevelComments.length;
const title =
(totalComments === 0 && __('Leave a comment')) ||
(totalComments === 1 && __('1 comment')) ||
__('%total_comments% comments', { total_comments: totalComments });
if (setCommentListTitle) setCommentListTitle(title);
const title = getCommentsListTitle(totalComments);
// Display comments immediately if not fetching reactions
// If not, wait to show comments until reactions are fetched

View file

@ -2745,4 +2745,24 @@ export const icons = {
</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,
selectClaimForUri,
selectHasChannels,
selectIsStreamPlaceholderForUri,
makeSelectTagInClaimOrChannelForUri,
} from 'redux/selectors/claims';
import { makeSelectStreamingUrlForUri } from 'redux/selectors/file_info';
@ -15,6 +14,7 @@ import { doOpenModal } from 'redux/actions/app';
import FileActions from './view';
import { makeSelectFileRenderModeForUri } from 'redux/selectors/content';
import { DISABLE_DOWNLOAD_BUTTON_TAG } from 'constants/tags';
import { isStreamPlaceholderClaim } from 'util/claim';
const select = (state, props) => {
const { uri } = props;
@ -27,7 +27,7 @@ const select = (state, props) => {
renderMode: makeSelectFileRenderModeForUri(uri)(state),
costInfo: selectCostInfoForUri(state, uri),
hasChannels: selectHasChannels(state),
isLivestreamClaim: selectIsStreamPlaceholderForUri(state, uri),
isLivestreamClaim: isStreamPlaceholderClaim(claim),
streamingUrl: makeSelectStreamingUrlForUri(uri)(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 />
{!hideRepost && !isMobile && (
{!hideRepost && !isMobile && !isLivestreamClaim && (
<Tooltip title={__('Repost')} arrow={false}>
<Button
button="alt"
@ -183,7 +183,7 @@ export default function FileActions(props: Props) {
<MenuList className="menu__list">
{isMobile && (
<>
{!hideRepost && (
{!hideRepost && !isLivestreamClaim && (
<MenuItem className="comment__menu-option" onSelect={handleRepostClick}>
<div className="menu__link">
<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 FileDetails from 'component/fileDetails';
import FileValues from 'component/fileValues';
import { useIsMobile } from 'effects/use-screensize';
type Props = {
uri: string,
expandOverride: boolean,
@ -28,8 +26,6 @@ type Props = {
export default function FileDescription(props: Props) {
const { uri, description, amount, hasSupport, isEmpty, doOpenModal, claimIsMine, expandOverride } = props;
const isMobile = useIsMobile();
const [expanded, setExpanded] = React.useState(false);
const [showCreditDetails, setShowCreditDetails] = React.useState(false);
@ -47,13 +43,11 @@ export default function FileDescription(props: Props) {
'media__info-text--expanded': expanded,
})}
>
{isMobile && <ClaimTags uri={uri} type="large" />}
<div className="mediaInfo__description">
{description && (
<MarkdownPreview className="markdown-preview--description" content={description} simpleLinks />
)}
{!isMobile && <ClaimTags uri={uri} type="large" />}
<ClaimTags uri={uri} type="large" />
<FileDetails uri={uri} />
</div>
</div>

View file

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

View file

@ -138,7 +138,7 @@ const FileReaction = (reactionProps: ReactionProps) => {
return (
<Tooltip title={title} arrow={false}>
<div style={{ margin: '0' }}>
<div className="file-reaction__tooltip-inner">
<Button
requiresAuth
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 { 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 * as ICONS from 'constants/icons';
import Button from 'component/button';
import classnames from 'classnames';
import CommentCreate from 'component/commentCreate';
import CreditAmount from 'component/common/credit-amount';
import Icon from 'component/common/icon';
import LivestreamComment from 'component/livestreamComment';
import LivestreamComments from 'component/livestreamComments';
import LivestreamSuperchats from './livestream-superchats';
import LivestreamMenu from './livestream-menu';
import React from 'react';
import Spinner from 'component/spinner';
import Yrbl from 'component/yrbl';
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');
import { getTipValues } from 'util/livestream';
const VIEW_MODES = {
CHAT: 'chat',
@ -42,6 +33,8 @@ type Props = {
superChats: Array<Comment>,
uri: string,
hideHeader?: boolean,
superchatsHidden?: boolean,
customViewMode?: string,
doCommentList: (string, string, number, number) => void,
doResolveUris: (Array<string>, boolean) => void,
doSuperChatList: (string) => void,
@ -57,15 +50,13 @@ export default function LivestreamChatLayout(props: Props) {
superChats: superChatsByAmount,
uri,
hideHeader,
superchatsHidden,
customViewMode,
doCommentList,
doResolveUris,
doSuperChatList,
} = props;
const {
location: { pathname },
} = useHistory();
const isMobile = useIsMobile();
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 commentsLength = commentsToDisplay && commentsToDisplay.length;
const pinnedComment = pinnedComments.length > 0 ? pinnedComments[0] : null;
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');
});
}
const { superChatsChannelUrls, superChatsFiatAmount, superChatsLBCAmount } = getTipValues(superChatsByAmount);
function toggleSuperChat() {
if (superChatsChannelUrls && superChatsChannelUrls.length > 0) {
@ -118,15 +94,11 @@ export default function LivestreamChatLayout(props: Props) {
setViewMode(VIEW_MODES.SUPERCHAT);
}
function handlePopout() {
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);
}
React.useEffect(() => {
if (customViewMode && customViewMode !== viewMode) {
setViewMode(customViewMode);
}
}, [customViewMode, viewMode]);
React.useEffect(() => {
if (claimId) {
@ -249,38 +221,11 @@ export default function LivestreamChatLayout(props: Props) {
<div className="card__title-section--small livestreamDiscussion__title">
{__('Live Chat')}
<Menu>
<MenuButton className="menu__button">
<Icon size={18} icon={ICONS.SETTINGS} />
</MenuButton>
<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>
<LivestreamMenu
isPopoutWindow={isPopoutWindow}
hideChat={() => setChatHidden(true)}
setPopoutWindow={(v) => setPopoutWindow(v)}
/>
</div>
{superChatsByAmount && (
@ -302,28 +247,33 @@ export default function LivestreamChatLayout(props: Props) {
)}
<div ref={commentsRef} className="livestreamComments__wrapper">
{viewMode === VIEW_MODES.CHAT && superChatsByAmount && (
<LivestreamSuperchats superChats={superChatsByAmount} toggleSuperChat={toggleSuperChat} />
)}
<div className="livestream-comments__top-actions">
{viewMode === VIEW_MODES.CHAT && superChatsByAmount && !superchatsHidden && (
<LivestreamSuperchats superChats={superChatsByAmount} toggleSuperChat={toggleSuperChat} />
)}
{pinnedComment && showPinned && viewMode === VIEW_MODES.CHAT && (
<div className="livestreamPinned__wrapper">
<LivestreamComment
comment={pinnedComment}
key={pinnedComment.comment_id}
uri={uri}
pushMention={setMention}
/>
{pinnedComment && showPinned && viewMode === VIEW_MODES.CHAT && (
<div className="livestreamPinned__wrapper">
<LivestreamComment
comment={pinnedComment}
key={pinnedComment.comment_id}
uri={uri}
pushMention={setMention}
handleDismissPin={() => setShowPinned(false)}
/>
<Button
title={__('Dismiss pinned comment')}
button="inverse"
className="close-button"
onClick={() => setShowPinned(false)}
icon={ICONS.REMOVE}
/>
</div>
)}
{!isMobile && (
<Button
title={__('Dismiss pinned comment')}
button="inverse"
className="close-button"
onClick={() => setShowPinned(false)}
icon={ICONS.REMOVE}
/>
)}
</div>
)}
</div>
{viewMode === VIEW_MODES.SUPERCHAT && resolvingSuperChats ? (
<div className="main--empty">

View file

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

View file

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

View file

@ -1,6 +1,7 @@
// @flow
import { lazyImport } from 'util/lazyImport';
import { useIsMobile } from 'effects/use-screensize';
import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button';
import FileTitleSection from 'component/fileTitleSection';
import LivestreamLink from 'component/livestreamLink';
import React from 'react';
@ -10,9 +11,18 @@ import LivestreamIframeRender from './iframe-render';
import Button from 'component/button';
import * as ICONS from 'constants/icons';
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 VIEW_MODES = {
CHAT: 'chat',
SUPERCHAT: 'sc',
};
type Props = {
activeStreamUri: boolean | string,
claim: ?StreamClaim,
@ -22,6 +32,7 @@ type Props = {
showLivestream: boolean,
showScheduledInfo: boolean,
uri: string,
superChats: Array<Comment>,
};
export default function LivestreamLayout(props: Props) {
@ -34,16 +45,43 @@ export default function LivestreamLayout(props: Props) {
showLivestream,
showScheduledInfo,
uri,
superChats,
} = props;
const isMobile = useIsMobile();
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;
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 (
<>
@ -94,10 +132,21 @@ export default function LivestreamLayout(props: Props) {
<SwipeableDrawer
open={Boolean(showChat)}
toggleDrawer={() => setShowChat(!showChat)}
title={__('Live Chat')}
didInitialDisplay={drawerWasToggled}
title={<ChatModeSelector />}
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>
</React.Suspense>
)}

View file

@ -3,15 +3,12 @@
import { Global } from '@emotion/react';
// $FlowFixMe
import { grey } from '@mui/material/colors';
// $FlowFixMe
import Typography from '@mui/material/Typography';
import { HEADER_HEIGHT_MOBILE } from 'component/fileRenderMobile/view';
import { SwipeableDrawer as MUIDrawer } from '@mui/material';
import * as ICONS from 'constants/icons';
import * as React from 'react';
import Button from 'component/button';
import Icon from 'component/common/icon';
import Portal from '@mui/material/Portal';
const DRAWER_PULLER_HEIGHT = 42;
@ -20,20 +17,20 @@ type Props = {
open: Boolean,
theme: string,
mobilePlayerDimensions?: { height: number },
title: string,
didInitialDisplay?: boolean,
title: any,
actions?: any,
toggleDrawer: () => void,
};
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 videoHeight = coverHeight || (mobilePlayerDimensions ? mobilePlayerDimensions.height : 0);
React.useEffect(() => {
if (open && !mobilePlayerDimensions) {
if (open && !mobilePlayerDimensions && !coverHeight) {
const element = document.querySelector(`.file-page__video-container`);
if (element) {
@ -41,15 +38,14 @@ export default function SwipeableDrawer(props: Props) {
setCoverHeight(rect.height);
}
}
}, [mobilePlayerDimensions, open]);
}, [coverHeight, mobilePlayerDimensions, open]);
const drawerGlobalStyles = (
const DrawerGlobalStyles = () => (
<Global
styles={{
'.main-wrapper__inner--filepage': {
'padding-bottom': didInitialDisplay ? `${DRAWER_PULLER_HEIGHT}px !important` : 'inherit',
overflow: open ? 'hidden' : 'unset',
'max-height': open ? '100vh' : 'unset',
maxHeight: open ? '100vh' : 'unset',
},
'.MuiDrawer-root': {
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] }} />
);
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 (
<>
{drawerGlobalStyles}
<DrawerGlobalStyles />
<MUIDrawer
anchor="bottom"
@ -83,22 +91,10 @@ export default function SwipeableDrawer(props: Props) {
disableSwipeToOpen
ModalProps={{ keepMounted: true }}
>
{didInitialDisplay && (
{open && (
<div className="swipeable-drawer__header" style={{ top: -DRAWER_PULLER_HEIGHT }}>
{open ? (
<>
<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>
)}
<Puller />
<HeaderContents />
</div>
)}

View file

@ -185,8 +185,9 @@ export const TECHNOLOGY = 'Technology';
export const EMOJI = 'Emoji';
export const STICKER = 'Sticker';
export const EDUCATION = 'Education';
export const POP_CULTURE = 'Pop Culture';
export const POP_CULTURE = 'PopCulture';
export const ODYSEE_LOGO = 'OdyseeLogo';
export const ODYSEE_WHITE_TEXT = 'OdyseeLogoWhiteText';
export const ODYSEE_DARK_TEXT = 'OdyseeLogoDarkText';
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 { selectShowMatureContent, selectClientSetting } from 'redux/selectors/settings';
import { makeSelectFileRenderModeForUri, makeSelectContentPositionForUri } from 'redux/selectors/content';
import { makeSelectCommentsListTitleForUri } from 'redux/selectors/comments';
import { DISABLE_COMMENTS_TAG } from 'constants/tags';
import { doSetMobilePlayerDimensions } from 'redux/actions/app';
@ -38,6 +39,7 @@ const select = (state, props) => {
hasCollectionById: Boolean(makeSelectCollectionForId(collectionId)(state)),
collectionId,
position: makeSelectContentPositionForUri(uri)(state),
commentsListTitle: makeSelectCommentsListTitleForUri(uri)(state),
};
};

View file

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

View file

@ -3,7 +3,7 @@ import { normalizeURI, parseURI, isURIValid } from 'util/lbryURI';
import { selectSupportsByOutpoint } from 'redux/selectors/wallet';
import { createSelector } from '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 { INTERNAL_TAGS } from 'constants/tags';
@ -592,7 +592,7 @@ export const selectChannelForClaimUri = createCachedSelector(
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
// accepts a regular claim uri lbry://something
@ -716,10 +716,6 @@ export const makeSelectClaimHasSource = (uri: string) =>
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) => {
const claim = selectClaimForUri(state, uri);
return isStreamPlaceholderClaim(claim);

View file

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

View file

@ -756,3 +756,7 @@ svg + .button__label {
.button--hash-id {
@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) {
position: absolute;
z-index: 1300;
width: 100%;
background-color: transparent;
padding: 0px;
padding-left: var(--spacing-xxs);
padding-top: var(--spacing-xxs);
border-bottom: none;
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 {
@extend .livestreamSuperchats__wrapper;
display: flex;
flex-shrink: 0;
position: relative;
padding: var(--spacing-s) var(--spacing-xs);
border-bottom: 1px solid var(--color-border);
font-size: var(--font-small);
@ -213,6 +223,46 @@ $recent-msg-button__height: 2rem;
padding: var(--spacing-xs);
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 {
@ -266,6 +316,10 @@ $recent-msg-button__height: 2rem;
}
&:nth-of-type(3) {
background-color: var(--color-superchat-3);
@media (max-width: $breakpoint-small) {
background-color: #fff;
}
}
&:nth-of-type(-n + 3) {
@ -280,6 +334,7 @@ $recent-msg-button__height: 2rem;
}
@media (max-width: $breakpoint-small) {
background-color: #fff;
padding: 5px;
padding-bottom: 2px;

View file

@ -30,6 +30,22 @@
.channel-name {
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 {
@ -42,6 +58,7 @@
.livestream__comment--superchat {
background-color: var(--color-card-background-highlighted);
display: unset;
+ .livestream__comment--superchat {
margin-bottom: var(--spacing-xxs);
@ -58,6 +75,10 @@
margin-top: calc(var(--spacing-xxs) / 2);
}
.livestreamComment__menu {
position: absolute;
}
&::before {
position: absolute;
left: 0;

View file

@ -87,11 +87,13 @@
z-index: 0;
margin-right: 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) {
width: 100%;
min-height: calc(100vh - var(--header-height-mobile));
}
}
@ -237,7 +239,7 @@
}
.file-page__recommended {
margin-top: var(--spacing-xxs) !important;
margin-top: 0px !important;
}
}
}

View file

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

View file

@ -20,6 +20,31 @@
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 {
width: 100%;
margin: var(--spacing-xxs) 0;

View file

@ -123,3 +123,7 @@ export function getClaimTitle(claim: ?Claim) {
const metadata = getClaimMetadata(claim);
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);
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
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 };
}