Fixes
Rename FileReactions index import Fix fileAction undefined logic Fix live comment menu Fix superchats
This commit is contained in:
parent
baf51f9c32
commit
c1b84368a9
33 changed files with 443 additions and 176 deletions
|
@ -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) => {
|
||||||
|
|
|
@ -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';
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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';
|
||||||
|
|
||||||
|
|
|
@ -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 })}
|
||||||
|
|
|
@ -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} />
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -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),
|
||||||
};
|
};
|
||||||
|
|
|
@ -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} />
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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"
|
||||||
|
|
105
ui/component/livestreamChatLayout/livestream-menu.jsx
Normal file
105
ui/component/livestreamChatLayout/livestream-menu.jsx
Normal 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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -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">
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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)}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
};
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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 };
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue