Create swipeableDrawer component
- CommentsList needs to return a title with comment amounts - mobile player dimensions needed to fill in the cover - hid livestream header for now until figuring out a better presentation - ~colum-reverse~ was causing problems with MUI's drawer scrolling, so reversed the chat order and made it ~column~ by default - Hid bottom expand navigation if component not yet opened - some other style changes in the middle
This commit is contained in:
parent
ce11a4b9c1
commit
baf51f9c32
21 changed files with 432 additions and 96 deletions
|
@ -4,6 +4,7 @@
|
|||
.*/node_modules/react-plastic/.*
|
||||
.*/node_modules/raf-schd/.*
|
||||
.*/node_modules/react-beautiful-dnd/.*
|
||||
.*/node_modules/@emotion/.*
|
||||
|
||||
[include]
|
||||
|
||||
|
|
|
@ -49,6 +49,7 @@ 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,
|
||||
|
@ -77,6 +78,7 @@ function CommentList(props: Props) {
|
|||
commentsAreExpanded,
|
||||
fetchReacts,
|
||||
doResolveUris,
|
||||
setCommentListTitle,
|
||||
fetchTopLevelComments,
|
||||
fetchComment,
|
||||
resetComments,
|
||||
|
@ -84,13 +86,13 @@ function CommentList(props: Props) {
|
|||
|
||||
const isMobile = useIsMobile();
|
||||
const isMediumScreen = useIsMediumScreen();
|
||||
const desktopView = !isMobile && !isMediumScreen;
|
||||
|
||||
const spinnerRef = React.useRef();
|
||||
const DEFAULT_SORT = ENABLE_COMMENT_REACTIONS ? SORT_BY.POPULARITY : SORT_BY.NEWEST;
|
||||
const [sort, setSort] = usePersistedState('comment-sort-by', DEFAULT_SORT);
|
||||
const [page, setPage] = React.useState(0);
|
||||
const [commentsToDisplay, setCommentsToDisplay] = React.useState(topLevelComments);
|
||||
const hasDefaultExpansion = commentsAreExpanded || desktopView;
|
||||
const hasDefaultExpansion = commentsAreExpanded || !isMediumScreen || isMobile;
|
||||
const [expandedComments, setExpandedComments] = React.useState(hasDefaultExpansion);
|
||||
const totalFetchedComments = allCommentIds ? allCommentIds.length : 0;
|
||||
const channelId = getChannelIdFromClaim(claim);
|
||||
|
@ -99,6 +101,11 @@ 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);
|
||||
|
||||
// Display comments immediately if not fetching reactions
|
||||
// If not, wait to show comments until reactions are fetched
|
||||
|
@ -283,11 +290,7 @@ function CommentList(props: Props) {
|
|||
return (
|
||||
<Card
|
||||
className="card--enable-overflow"
|
||||
title={
|
||||
(totalComments === 0 && __('Leave a comment')) ||
|
||||
(totalComments === 1 && __('1 comment')) ||
|
||||
__('%total_comments% comments', { total_comments: totalComments })
|
||||
}
|
||||
title={!isMobile && title}
|
||||
titleActions={
|
||||
<>
|
||||
{totalComments > 1 && ENABLE_COMMENT_REACTIONS && (
|
||||
|
@ -310,8 +313,8 @@ function CommentList(props: Props) {
|
|||
|
||||
<ul
|
||||
className={classnames({
|
||||
comments: desktopView || expandedComments,
|
||||
'comments--contracted': !desktopView && !expandedComments,
|
||||
comments: isMediumScreen || expandedComments,
|
||||
'comments--contracted': !isMediumScreen && !expandedComments,
|
||||
})}
|
||||
>
|
||||
{readyToDisplayComments && pinnedComments && getCommentElems(pinnedComments)}
|
||||
|
|
|
@ -8,10 +8,12 @@ import {
|
|||
import { selectPlayingUri, makeSelectFileRenderModeForUri } from 'redux/selectors/content';
|
||||
import { selectCostInfoForUri } from 'lbryinc';
|
||||
import { doPlayUri } from 'redux/actions/content';
|
||||
import { doSetMobilePlayerDimensions } from 'redux/actions/app';
|
||||
import { withRouter } from 'react-router';
|
||||
import { getChannelIdFromClaim } from 'util/claim';
|
||||
import { selectActiveLivestreamForChannel } from 'redux/selectors/livestream';
|
||||
import FileRenderMobile from './view';
|
||||
import { selectMobilePlayerDimensions } from 'redux/selectors/app';
|
||||
|
||||
const select = (state, props) => {
|
||||
const playingUri = selectPlayingUri(state);
|
||||
|
@ -34,11 +36,14 @@ const select = (state, props) => {
|
|||
activeLivestreamForChannel: channelClaimId && selectActiveLivestreamForChannel(state, channelClaimId),
|
||||
claimId,
|
||||
channelClaimId,
|
||||
mobilePlayerDimensions: selectMobilePlayerDimensions(state),
|
||||
playingUri,
|
||||
};
|
||||
};
|
||||
|
||||
const perform = {
|
||||
doPlayUri,
|
||||
doSetMobilePlayerDimensions,
|
||||
};
|
||||
|
||||
export default withRouter(connect(select, perform)(FileRenderMobile));
|
||||
|
|
|
@ -11,6 +11,7 @@ import LivestreamIframeRender from 'component/livestreamLayout/iframe-render';
|
|||
|
||||
const PRIMARY_PLAYER_WRAPPER_CLASS = 'file-page__video-container';
|
||||
export const INLINE_PLAYER_WRAPPER_CLASS = 'inline-player__wrapper';
|
||||
export const HEADER_HEIGHT_MOBILE = 56;
|
||||
|
||||
// ****************************************************************************
|
||||
// ****************************************************************************
|
||||
|
@ -27,7 +28,10 @@ type Props = {
|
|||
previousListUri: string,
|
||||
activeLivestreamForChannel?: any,
|
||||
channelClaimId?: any,
|
||||
playingUri?: PlayingUri,
|
||||
mobilePlayerDimensions?: any,
|
||||
doPlayUri: (string) => void,
|
||||
doSetMobilePlayerDimensions: (height: number, width: number) => void,
|
||||
};
|
||||
|
||||
export default function FileRenderMobile(props: Props) {
|
||||
|
@ -43,7 +47,10 @@ export default function FileRenderMobile(props: Props) {
|
|||
previousListUri,
|
||||
activeLivestreamForChannel,
|
||||
channelClaimId,
|
||||
playingUri,
|
||||
mobilePlayerDimensions,
|
||||
doPlayUri,
|
||||
doSetMobilePlayerDimensions,
|
||||
} = props;
|
||||
|
||||
const { push } = useHistory();
|
||||
|
@ -58,6 +65,7 @@ export default function FileRenderMobile(props: Props) {
|
|||
const canViewFile = isFree || claimWasPurchased;
|
||||
const isPlayable = RENDER_MODES.FLOATING_MODES.includes(renderMode) || activeLivestreamForChannel;
|
||||
const isReadyToPlay = isPlayable && streamingUrl;
|
||||
const isCurrentMediaPlaying = playingUri && playingUri.uri === uri;
|
||||
|
||||
const handleResize = React.useCallback(() => {
|
||||
const element = document.querySelector(`.${PRIMARY_PLAYER_WRAPPER_CLASS}`);
|
||||
|
@ -80,7 +88,11 @@ export default function FileRenderMobile(props: Props) {
|
|||
|
||||
// $FlowFixMe
|
||||
setFileViewerRect({ ...objectRect });
|
||||
}, []);
|
||||
|
||||
if (doSetMobilePlayerDimensions && (!mobilePlayerDimensions || mobilePlayerDimensions.height !== rect.height)) {
|
||||
doSetMobilePlayerDimensions(rect.height, rect.width);
|
||||
}
|
||||
}, [doSetMobilePlayerDimensions, mobilePlayerDimensions]);
|
||||
|
||||
// Initial resize, will place the player correctly above the cover when starts playing
|
||||
// (remember the URI here is from playingUri). The cover then keeps on the page and kind of serves as a placeholder
|
||||
|
@ -128,7 +140,13 @@ export default function FileRenderMobile(props: Props) {
|
|||
setPlayNextUrl(true);
|
||||
}, [doNavigate, doPlay, nextListUri, playNextUrl, previousListUri]);
|
||||
|
||||
if (!isPlayable || !uri || countdownCanceled || (collectionId && !canViewFile && !nextListUri)) {
|
||||
if (
|
||||
!isCurrentMediaPlaying ||
|
||||
!isPlayable ||
|
||||
!uri ||
|
||||
countdownCanceled ||
|
||||
(collectionId && !canViewFile && !nextListUri)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -146,32 +164,28 @@ export default function FileRenderMobile(props: Props) {
|
|||
}
|
||||
>
|
||||
<div className="content__wrapper">
|
||||
<React.Suspense fallback={<Loading />}>
|
||||
{isCurrentClaimLive && channelClaimId ? (
|
||||
<LivestreamIframeRender channelClaimId={channelClaimId} showLivestream mobileVersion />
|
||||
) : isReadyToPlay ? (
|
||||
<FileRender uri={uri} />
|
||||
) : !canViewFile ? (
|
||||
<div className="content__loading">
|
||||
<AutoplayCountdown
|
||||
nextRecommendedUri={nextListUri}
|
||||
doNavigate={() => setDoNavigate(true)}
|
||||
doReplay={() => doPlayUri(uri)}
|
||||
doPrevious={() => {
|
||||
setPlayNextUrl(false);
|
||||
setDoNavigate(true);
|
||||
}}
|
||||
onCanceled={() => setCountdownCanceled(true)}
|
||||
skipPaid
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Loading />
|
||||
)}
|
||||
</React.Suspense>
|
||||
{isCurrentClaimLive && channelClaimId ? (
|
||||
<LivestreamIframeRender channelClaimId={channelClaimId} showLivestream mobileVersion />
|
||||
) : isReadyToPlay ? (
|
||||
<FileRender uri={uri} />
|
||||
) : !canViewFile ? (
|
||||
<div className="content__loading">
|
||||
<AutoplayCountdown
|
||||
nextRecommendedUri={nextListUri}
|
||||
doNavigate={() => setDoNavigate(true)}
|
||||
doReplay={() => doPlayUri(uri)}
|
||||
doPrevious={() => {
|
||||
setPlayNextUrl(false);
|
||||
setDoNavigate(true);
|
||||
}}
|
||||
onCanceled={() => setCountdownCanceled(true)}
|
||||
skipPaid
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<LoadingScreen status={__('Loading')} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const Loading = () => <LoadingScreen status={__('Loading')} />;
|
||||
|
|
|
@ -41,6 +41,7 @@ type Props = {
|
|||
pinnedComments: Array<Comment>,
|
||||
superChats: Array<Comment>,
|
||||
uri: string,
|
||||
hideHeader?: boolean,
|
||||
doCommentList: (string, string, number, number) => void,
|
||||
doResolveUris: (Array<string>, boolean) => void,
|
||||
doSuperChatList: (string) => void,
|
||||
|
@ -55,6 +56,7 @@ export default function LivestreamChatLayout(props: Props) {
|
|||
pinnedComments,
|
||||
superChats: superChatsByAmount,
|
||||
uri,
|
||||
hideHeader,
|
||||
doCommentList,
|
||||
doResolveUris,
|
||||
doSuperChatList,
|
||||
|
@ -242,60 +244,62 @@ export default function LivestreamChatLayout(props: Props) {
|
|||
|
||||
return (
|
||||
<div className={classnames('card livestream__chat', { 'livestream__chat--popout': isPopoutWindow })}>
|
||||
<div className="card__header--between livestreamDiscussion__header">
|
||||
<div className="card__title-section--small livestreamDiscussion__title">
|
||||
{__('Live Chat')}
|
||||
{!hideHeader && (
|
||||
<div className="card__header--between livestreamDiscussion__header">
|
||||
<div className="card__title-section--small livestreamDiscussion__title">
|
||||
{__('Live Chat')}
|
||||
|
||||
<Menu>
|
||||
<MenuButton className="menu__button">
|
||||
<Icon size={18} icon={ICONS.SETTINGS} />
|
||||
</MenuButton>
|
||||
<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>
|
||||
<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>
|
||||
<MenuItem className="comment__menu-option" onSelect={() => setChatHidden(true)}>
|
||||
<span className="menu__link">
|
||||
<Icon aria-hidden icon={ICONS.EYE} />
|
||||
{__('Hide Chat')}
|
||||
</span>
|
||||
</MenuItem>
|
||||
|
||||
{!isPopoutWindow && !isMobile && (
|
||||
{!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>
|
||||
|
||||
{superChatsByAmount && (
|
||||
<div className="recommended-content__toggles">
|
||||
{/* the superchats in chronological order button */}
|
||||
{chatContentToggle(VIEW_MODES.CHAT, __('Chat'))}
|
||||
|
||||
{/* the button to show superchats listed by most to least support amount */}
|
||||
{chatContentToggle(
|
||||
VIEW_MODES.SUPERCHAT,
|
||||
<>
|
||||
<MenuItem className="comment__menu-option" onSelect={handlePopout}>
|
||||
<span className="menu__link">
|
||||
<Icon aria-hidden icon={ICONS.EXTERNAL} />
|
||||
{__('Popout Chat')}
|
||||
</span>
|
||||
</MenuItem>
|
||||
<CreditAmount amount={superChatsLBCAmount || 0} size={8} /> /
|
||||
<CreditAmount amount={superChatsFiatAmount || 0} size={8} isFiat /> {__('Tipped')}
|
||||
</>
|
||||
)}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{superChatsByAmount && (
|
||||
<div className="recommended-content__toggles">
|
||||
{/* the superchats in chronological order button */}
|
||||
{chatContentToggle(VIEW_MODES.CHAT, __('Chat'))}
|
||||
|
||||
{/* the button to show superchats listed by most to least support amount */}
|
||||
{chatContentToggle(
|
||||
VIEW_MODES.SUPERCHAT,
|
||||
<>
|
||||
<CreditAmount amount={superChatsLBCAmount || 0} size={8} /> /
|
||||
<CreditAmount amount={superChatsFiatAmount || 0} size={8} isFiat /> {__('Tipped')}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div ref={commentsRef} className="livestreamComments__wrapper">
|
||||
{viewMode === VIEW_MODES.CHAT && superChatsByAmount && (
|
||||
|
|
|
@ -45,9 +45,12 @@ export default function LivestreamComments(props: Props) {
|
|||
if (!fetchingComments && commentsToDisplay && commentsToDisplay.length > 0) {
|
||||
return (
|
||||
<div className="livestream__comments">
|
||||
{commentsToDisplay.map((comment) => (
|
||||
<LivestreamComment comment={comment} key={comment.comment_id} uri={uri} forceUpdate={forceUpdate} />
|
||||
))}
|
||||
{commentsToDisplay
|
||||
.slice(0)
|
||||
.reverse()
|
||||
.map((comment) => (
|
||||
<LivestreamComment comment={comment} key={comment.comment_id} uri={uri} forceUpdate={forceUpdate} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,6 +7,9 @@ import React from 'react';
|
|||
import { PRIMARY_PLAYER_WRAPPER_CLASS } from 'page/file/view';
|
||||
import FileRenderInitiator from 'component/fileRenderInitiator';
|
||||
import LivestreamIframeRender from './iframe-render';
|
||||
import Button from 'component/button';
|
||||
import * as ICONS from 'constants/icons';
|
||||
import SwipeableDrawer from 'component/swipeableDrawer';
|
||||
|
||||
const LivestreamChatLayout = lazyImport(() => import('component/livestreamChatLayout' /* webpackChunkName: "chat" */));
|
||||
|
||||
|
@ -35,6 +38,9 @@ export default function LivestreamLayout(props: Props) {
|
|||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const [showChat, setShowChat] = React.useState(undefined);
|
||||
const drawerWasToggled = showChat !== undefined;
|
||||
|
||||
if (!claim || !claim.signing_channel) return null;
|
||||
|
||||
const { name: channelName, claim_id: channelClaimId } = claim.signing_channel;
|
||||
|
@ -85,10 +91,27 @@ export default function LivestreamLayout(props: Props) {
|
|||
|
||||
{isMobile && !hideComments && (
|
||||
<React.Suspense fallback={null}>
|
||||
<LivestreamChatLayout uri={uri} />
|
||||
<SwipeableDrawer
|
||||
open={Boolean(showChat)}
|
||||
toggleDrawer={() => setShowChat(!showChat)}
|
||||
title={__('Live Chat')}
|
||||
didInitialDisplay={drawerWasToggled}
|
||||
>
|
||||
<LivestreamChatLayout uri={uri} hideHeader />
|
||||
</SwipeableDrawer>
|
||||
</React.Suspense>
|
||||
)}
|
||||
|
||||
{isMobile && (
|
||||
<Button
|
||||
className="swipeable-drawer__expand-button"
|
||||
label="Open Live Chat"
|
||||
button="primary"
|
||||
icon={ICONS.CHAT}
|
||||
onClick={() => setShowChat(!showChat)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<FileTitleSection uri={uri} livestream isLive={showLivestream} />
|
||||
</div>
|
||||
</>
|
||||
|
|
11
ui/component/swipeableDrawer/index.js
Normal file
11
ui/component/swipeableDrawer/index.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { connect } from 'react-redux';
|
||||
import LivestreamLayout from './view';
|
||||
import { selectTheme } from 'redux/selectors/settings';
|
||||
import { selectMobilePlayerDimensions } from 'redux/selectors/app';
|
||||
|
||||
const select = (state) => ({
|
||||
theme: selectTheme(state),
|
||||
mobilePlayerDimensions: selectMobilePlayerDimensions(state),
|
||||
});
|
||||
|
||||
export default connect(select)(LivestreamLayout);
|
109
ui/component/swipeableDrawer/view.jsx
Normal file
109
ui/component/swipeableDrawer/view.jsx
Normal file
|
@ -0,0 +1,109 @@
|
|||
// @flow
|
||||
// $FlowFixMe
|
||||
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;
|
||||
|
||||
type Props = {
|
||||
children: Node,
|
||||
open: Boolean,
|
||||
theme: string,
|
||||
mobilePlayerDimensions?: { height: number },
|
||||
title: string,
|
||||
didInitialDisplay?: boolean,
|
||||
toggleDrawer: () => void,
|
||||
};
|
||||
|
||||
export default function SwipeableDrawer(props: Props) {
|
||||
const { mobilePlayerDimensions, title, children, open, theme, didInitialDisplay, toggleDrawer } = props;
|
||||
|
||||
const [coverHeight, setCoverHeight] = React.useState();
|
||||
|
||||
const videoHeight = coverHeight || (mobilePlayerDimensions ? mobilePlayerDimensions.height : 0);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (open && !mobilePlayerDimensions) {
|
||||
const element = document.querySelector(`.file-page__video-container`);
|
||||
|
||||
if (element) {
|
||||
const rect = element.getBoundingClientRect();
|
||||
setCoverHeight(rect.height);
|
||||
}
|
||||
}
|
||||
}, [mobilePlayerDimensions, open]);
|
||||
|
||||
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',
|
||||
},
|
||||
'.MuiDrawer-root': {
|
||||
top: `calc(${HEADER_HEIGHT_MOBILE}px + ${videoHeight}px) !important`,
|
||||
},
|
||||
'.MuiDrawer-root > .MuiPaper-root': {
|
||||
overflow: 'visible',
|
||||
color: 'var(--color-text)',
|
||||
position: 'absolute',
|
||||
height: `calc(100% - ${DRAWER_PULLER_HEIGHT}px)`,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const Puller = () => (
|
||||
<span className="swipeable-drawer__puller" style={{ backgroundColor: theme === 'light' ? grey[300] : grey[800] }} />
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{drawerGlobalStyles}
|
||||
|
||||
<MUIDrawer
|
||||
anchor="bottom"
|
||||
open={open}
|
||||
onClose={toggleDrawer}
|
||||
onOpen={toggleDrawer}
|
||||
hideBackdrop
|
||||
disableEnforceFocus
|
||||
disablePortal
|
||||
disableSwipeToOpen
|
||||
ModalProps={{ keepMounted: true }}
|
||||
>
|
||||
{didInitialDisplay && (
|
||||
<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>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{children}
|
||||
</MUIDrawer>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -32,6 +32,7 @@ export const TOGGLE_YOUTUBE_SYNC_INTEREST = 'TOGGLE_YOUTUBE_SYNC_INTEREST';
|
|||
export const TOGGLE_SPLASH_ANIMATION = 'TOGGLE_SPLASH_ANIMATION';
|
||||
export const SET_ACTIVE_CHANNEL = 'SET_ACTIVE_CHANNEL';
|
||||
export const SET_INCOGNITO = 'SET_INCOGNITO';
|
||||
export const SET_MOBILE_PLAYER_DIMENSIONS = 'SET_MOBILE_PLAYER_DIMENSIONS';
|
||||
export const RELOAD_REQUIRED = 'RELOAD_REQUIRED';
|
||||
|
||||
// Navigation
|
||||
|
|
|
@ -14,6 +14,7 @@ import { selectCostInfoForUri, doFetchCostInfoForUri } from 'lbryinc';
|
|||
import { selectShowMatureContent, selectClientSetting } from 'redux/selectors/settings';
|
||||
import { makeSelectFileRenderModeForUri, makeSelectContentPositionForUri } from 'redux/selectors/content';
|
||||
import { DISABLE_COMMENTS_TAG } from 'constants/tags';
|
||||
import { doSetMobilePlayerDimensions } from 'redux/actions/app';
|
||||
|
||||
import FilePage from './view';
|
||||
|
||||
|
@ -45,6 +46,7 @@ const perform = {
|
|||
doSetContentHistoryItem,
|
||||
doSetPrimaryUri,
|
||||
clearPosition,
|
||||
doSetMobilePlayerDimensions,
|
||||
};
|
||||
|
||||
export default withRouter(connect(select, perform)(FilePage));
|
||||
|
|
|
@ -15,6 +15,9 @@ import CollectionContent from 'component/collectionContentSidebar';
|
|||
import Button from 'component/button';
|
||||
import I18nMessage from 'component/i18nMessage';
|
||||
import Empty from 'component/common/empty';
|
||||
import SwipeableDrawer from 'component/swipeableDrawer';
|
||||
import * as ICONS from 'constants/icons';
|
||||
import { useIsMobile } from 'effects/use-screensize';
|
||||
|
||||
const CommentsList = lazyImport(() => import('component/commentsList' /* webpackChunkName: "comments" */));
|
||||
const PostViewer = lazyImport(() => import('component/postViewer' /* webpackChunkName: "postViewer" */));
|
||||
|
@ -65,6 +68,12 @@ export default function FilePage(props: Props) {
|
|||
clearPosition,
|
||||
} = 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;
|
||||
|
@ -177,6 +186,12 @@ 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} />
|
||||
);
|
||||
|
||||
return (
|
||||
<Page className="file-page" filePage isMarkdown={isMarkdown}>
|
||||
<div className={classnames('section card-stack', `file-page__${renderMode}`)}>
|
||||
|
@ -200,15 +215,30 @@ export default function FilePage(props: Props) {
|
|||
|
||||
{RENDER_MODES.FLOATING_MODES.includes(renderMode) && <FileTitleSection uri={uri} />}
|
||||
|
||||
{commentsDisabled ? (
|
||||
<Empty text={__('The creator of this content has disabled comments.')} />
|
||||
{isMobile ? (
|
||||
<SwipeableDrawer
|
||||
open={Boolean(showComments)}
|
||||
toggleDrawer={() => setShowComments(!showComments)}
|
||||
title={commentListTitle}
|
||||
didInitialDisplay={drawerWasToggled}
|
||||
>
|
||||
{commentsListElement}
|
||||
</SwipeableDrawer>
|
||||
) : (
|
||||
<React.Suspense fallback={null}>
|
||||
<CommentsList uri={uri} linkedCommentId={linkedCommentId} />
|
||||
</React.Suspense>
|
||||
commentsListElement
|
||||
)}
|
||||
</>
|
||||
|
||||
{isMobile && (
|
||||
<Button
|
||||
className="swipeable-drawer__expand-button"
|
||||
label={commentListTitle}
|
||||
button="primary"
|
||||
icon={ICONS.CHAT}
|
||||
onClick={() => setShowComments(!showComments)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!isMarkdown && videoTheaterMode && <RightSideContent {...rightSideProps} />}
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -736,3 +736,8 @@ export function doSetIncognito(incognitoEnabled) {
|
|||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const doSetMobilePlayerDimensions = (height, width) => ({
|
||||
type: ACTIONS.SET_MOBILE_PLAYER_DIMENSIONS,
|
||||
data: { heightWidth: { height, width } },
|
||||
});
|
||||
|
|
|
@ -324,6 +324,13 @@ reducers[ACTIONS.SET_INCOGNITO] = (state, action) => {
|
|||
};
|
||||
};
|
||||
|
||||
reducers[ACTIONS.SET_MOBILE_PLAYER_DIMENSIONS] = (state, action) => {
|
||||
return {
|
||||
...state,
|
||||
mobilePlayerDimensions: action.data.heightWidth,
|
||||
};
|
||||
};
|
||||
|
||||
reducers[ACTIONS.USER_STATE_POPULATE] = (state, action) => {
|
||||
const { welcomeVersion, allowAnalytics } = action.data;
|
||||
return {
|
||||
|
|
|
@ -106,3 +106,5 @@ export const selectActiveChannelStakedLevel = (state) => {
|
|||
};
|
||||
|
||||
export const selectIncognito = (state) => selectState(state).incognito;
|
||||
|
||||
export const selectMobilePlayerDimensions = (state) => selectState(state).mobilePlayerDimensions;
|
||||
|
|
|
@ -68,4 +68,5 @@
|
|||
@import 'component/stripe-card';
|
||||
@import 'component/wallet-tip-send';
|
||||
@import 'component/swipe-list';
|
||||
@import 'component/swipeable-drawer';
|
||||
@import 'component/utils';
|
||||
|
|
|
@ -13,6 +13,15 @@
|
|||
.card--enable-overflow {
|
||||
overflow: visible;
|
||||
margin-bottom: var(--spacing-m);
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
overflow-y: scroll;
|
||||
height: 100%;
|
||||
|
||||
.card__main-actions {
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card--disabled {
|
||||
|
|
|
@ -27,6 +27,8 @@ $recent-msg-button__height: 2rem;
|
|||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
margin: 0 !important;
|
||||
height: 100%;
|
||||
margin-bottom: 0px !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,7 +99,6 @@ $recent-msg-button__height: 2rem;
|
|||
.livestreamComments__wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - var(--header-height) - #{$discussion-header__height});
|
||||
|
||||
.main--empty {
|
||||
.yrbl__wrap {
|
||||
|
@ -109,11 +110,20 @@ $recent-msg-button__height: 2rem;
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: $breakpoint-small) {
|
||||
height: calc(100% - var(--header-height) - #{$discussion-header__height});
|
||||
}
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.livestream__comments {
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
flex-direction: column;
|
||||
font-size: var(--font-small);
|
||||
overflow-y: scroll;
|
||||
overflow-x: visible;
|
||||
|
@ -138,6 +148,10 @@ $recent-msg-button__height: 2rem;
|
|||
padding: var(--spacing-s);
|
||||
border-top: 1px solid var(--color-border);
|
||||
margin-top: auto;
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
padding: var(--spacing-xxs);
|
||||
}
|
||||
}
|
||||
|
||||
.livestreamSuperchats__wrapper {
|
||||
|
@ -153,6 +167,23 @@ $recent-msg-button__height: 2rem;
|
|||
padding: var(--spacing-xs);
|
||||
width: var(--livestream-comments-width);
|
||||
}
|
||||
|
||||
@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;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.livestreamPinned__wrapper {
|
||||
|
@ -209,6 +240,17 @@ $recent-msg-button__height: 2rem;
|
|||
.channel-thumbnail {
|
||||
margin-right: var(--spacing-xs);
|
||||
@include handleChannelGif(2rem);
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
margin-right: var(--spacing-xxs);
|
||||
@include handleChannelGif(1.5rem);
|
||||
|
||||
.channel-staked__wrapper {
|
||||
padding: 0px;
|
||||
left: -0.4rem;
|
||||
bottom: -0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:first-of-type {
|
||||
|
@ -236,6 +278,15 @@ $recent-msg-button__height: 2rem;
|
|||
.channel-name {
|
||||
max-width: 5rem;
|
||||
}
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
padding: 5px;
|
||||
padding-bottom: 2px;
|
||||
|
||||
span {
|
||||
font-size: var(--font-xxsmall);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.livestreamSuperchat__info {
|
||||
|
@ -247,6 +298,10 @@ $recent-msg-button__height: 2rem;
|
|||
.button {
|
||||
margin-top: calc(var(--spacing-xxs) / 2);
|
||||
}
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
max-height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.livestreamSuperchat__info--sticker {
|
||||
|
|
|
@ -36,6 +36,11 @@
|
|||
|
||||
.main-wrapper__inner--filepage {
|
||||
padding: 0;
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
margin-top: 0px;
|
||||
padding-top: var(--header-height-mobile);
|
||||
}
|
||||
}
|
||||
|
||||
.main-wrapper__inner--theater-mode {
|
||||
|
@ -227,9 +232,13 @@
|
|||
|
||||
.card {
|
||||
border-radius: 0;
|
||||
margin-bottom: var(--spacing-xxs) !important;
|
||||
margin-bottom: 0px !important;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.file-page__recommended {
|
||||
margin-top: var(--spacing-xxs) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -284,13 +293,13 @@
|
|||
margin-top: var(--spacing-m);
|
||||
margin-bottom: var(--spacing-m);
|
||||
|
||||
@media (min-width: $breakpoint-large + 300px) {
|
||||
@media (min-width: ($breakpoint-large + 300px)) {
|
||||
max-width: calc(var(--page-max-width--filepage) / 1.25);
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
@media (min-width: $breakpoint-medium) and (max-width: $breakpoint-large + 300px) {
|
||||
@media (min-width: $breakpoint-medium) and (max-width: ($breakpoint-large + 300px)) {
|
||||
max-width: calc(100vw - var(--livestream-comments-width) - var(--spacing-m) * 3);
|
||||
margin-left: var(--spacing-m);
|
||||
margin-right: var(--spacing-m);
|
||||
|
|
|
@ -68,6 +68,8 @@
|
|||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
font-size: var(--font-xsmall);
|
||||
word-wrap: break-word;
|
||||
overflow: hidden;
|
||||
|
||||
a {
|
||||
.button__content {
|
||||
|
|
40
ui/scss/component/_swipeable-drawer.scss
Normal file
40
ui/scss/component/_swipeable-drawer.scss
Normal file
|
@ -0,0 +1,40 @@
|
|||
.swipeable-drawer__header {
|
||||
position: absolute;
|
||||
visibility: visible;
|
||||
right: 0;
|
||||
left: 0;
|
||||
background-color: var(--color-card-background);
|
||||
|
||||
.button--close {
|
||||
top: 2px !important;
|
||||
right: 2px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.swipeable-drawer__puller {
|
||||
width: 30px;
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: calc(50% - 15px);
|
||||
}
|
||||
|
||||
.swipeable-drawer__expand-button {
|
||||
width: 100%;
|
||||
margin: var(--spacing-xxs) 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.swipeable-drawer__expand {
|
||||
border-top: 1px solid var(--color-border);
|
||||
position: fixed;
|
||||
background-color: var(--color-card-background);
|
||||
visibility: visible;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
Loading…
Reference in a new issue