Better Portrait videos support (#892)
This commit is contained in:
commit
bab96276b6
25 changed files with 508 additions and 152 deletions
|
@ -1,18 +1,18 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { FLOATING_PLAYER_CLASS } from './view';
|
import { FLOATING_PLAYER_CLASS } from './view';
|
||||||
|
|
||||||
function getRootEl() {
|
export function getRootEl() {
|
||||||
return document && document.documentElement;
|
return document && document.documentElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getScreenWidth() {
|
export function getScreenWidth() {
|
||||||
const mainEl = getRootEl();
|
const rootEl = getRootEl();
|
||||||
return mainEl ? mainEl.clientWidth : window.innerWidth;
|
return rootEl ? rootEl.clientWidth : window.innerWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getScreenHeight() {
|
export function getScreenHeight() {
|
||||||
const mainEl = getRootEl();
|
const rootEl = getRootEl();
|
||||||
return mainEl ? mainEl.clientHeight : window.innerHeight;
|
return rootEl ? rootEl.clientHeight : window.innerHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getFloatingPlayerRect() {
|
export function getFloatingPlayerRect() {
|
||||||
|
@ -52,3 +52,53 @@ export function calculateRelativePos(x: number, y: number) {
|
||||||
y: y / getScreenHeight(),
|
y: y / getScreenHeight(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Max landscape height = calculates the maximum size the player would be at
|
||||||
|
// if it was at landscape aspect ratio
|
||||||
|
export function getMaxLandscapeHeight(width?: number) {
|
||||||
|
const windowWidth = width || getScreenWidth();
|
||||||
|
const maxLandscapeHeight = (windowWidth * 9) / 16;
|
||||||
|
|
||||||
|
return maxLandscapeHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a video is higher than landscape, this calculates how much is needed in order
|
||||||
|
// for the video to be centered in a container at the landscape height
|
||||||
|
export function getAmountNeededToCenterVideo(height: number, fromValue: number) {
|
||||||
|
const minVideoHeight = getMaxLandscapeHeight();
|
||||||
|
const timesHigherThanLandscape = height / minVideoHeight;
|
||||||
|
const amountNeededToCenter = (height - fromValue) / timesHigherThanLandscape;
|
||||||
|
|
||||||
|
return amountNeededToCenter * -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPossiblePlayerHeight(height: number, isMobile: boolean) {
|
||||||
|
// min player height = landscape size based on screen width (only for mobile, since
|
||||||
|
// comment expansion will default to landscape view height)
|
||||||
|
const minHeight = getMaxLandscapeHeight();
|
||||||
|
const maxPercentOfScreen = isMobile ? 70 : 80;
|
||||||
|
// max player height
|
||||||
|
const maxHeight = (getScreenHeight() * maxPercentOfScreen) / 100;
|
||||||
|
|
||||||
|
const forceMaxHeight = height < maxHeight ? height : maxHeight;
|
||||||
|
const forceMinHeight = isMobile && height < minHeight ? minHeight : forceMaxHeight;
|
||||||
|
|
||||||
|
return forceMinHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getWindowAngle(cb?: () => void) {
|
||||||
|
// iOS
|
||||||
|
if (typeof window.orientation === 'number') {
|
||||||
|
return window.orientation;
|
||||||
|
}
|
||||||
|
// Android
|
||||||
|
if (screen && screen.orientation && screen.orientation.angle) {
|
||||||
|
return window.orientation;
|
||||||
|
}
|
||||||
|
if (cb) cb();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isWindowLandscapeForAngle(angle: number) {
|
||||||
|
return angle === 90 || angle === 270 || angle === -90;
|
||||||
|
}
|
||||||
|
|
|
@ -22,11 +22,10 @@ import { selectCostInfoForUri } from 'lbryinc';
|
||||||
import { doUriInitiatePlay, doSetPlayingUri } from 'redux/actions/content';
|
import { doUriInitiatePlay, doSetPlayingUri } from 'redux/actions/content';
|
||||||
import { doFetchRecommendedContent } from 'redux/actions/search';
|
import { doFetchRecommendedContent } from 'redux/actions/search';
|
||||||
import { withRouter } from 'react-router';
|
import { withRouter } from 'react-router';
|
||||||
import { selectMobilePlayerDimensions } from 'redux/selectors/app';
|
import { selectAppDrawerOpen } from 'redux/selectors/app';
|
||||||
import { selectIsActiveLivestreamForUri, selectCommentSocketConnected } from 'redux/selectors/livestream';
|
import { selectIsActiveLivestreamForUri, selectCommentSocketConnected } from 'redux/selectors/livestream';
|
||||||
import { doSetMobilePlayerDimensions } from 'redux/actions/app';
|
|
||||||
import { doCommentSocketConnect, doCommentSocketDisconnect } from 'redux/actions/websocket';
|
import { doCommentSocketConnect, doCommentSocketDisconnect } from 'redux/actions/websocket';
|
||||||
import { isStreamPlaceholderClaim } from 'util/claim';
|
import { isStreamPlaceholderClaim, getVideoClaimAspectRatio } from 'util/claim';
|
||||||
import FileRenderFloating from './view';
|
import FileRenderFloating from './view';
|
||||||
|
|
||||||
const select = (state, props) => {
|
const select = (state, props) => {
|
||||||
|
@ -57,10 +56,11 @@ const select = (state, props) => {
|
||||||
previousListUri: collectionId && makeSelectPreviousUrlForCollectionAndUrl(collectionId, uri)(state),
|
previousListUri: collectionId && makeSelectPreviousUrlForCollectionAndUrl(collectionId, uri)(state),
|
||||||
collectionId,
|
collectionId,
|
||||||
isCurrentClaimLive: selectIsActiveLivestreamForUri(state, uri),
|
isCurrentClaimLive: selectIsActiveLivestreamForUri(state, uri),
|
||||||
mobilePlayerDimensions: selectMobilePlayerDimensions(state),
|
videoAspectRatio: getVideoClaimAspectRatio(claim),
|
||||||
socketConnected: selectCommentSocketConnected(state),
|
socketConnected: selectCommentSocketConnected(state),
|
||||||
isLivestreamClaim: isStreamPlaceholderClaim(claim),
|
isLivestreamClaim: isStreamPlaceholderClaim(claim),
|
||||||
geoRestriction: selectGeoRestrictionForUri(state, uri),
|
geoRestriction: selectGeoRestrictionForUri(state, uri),
|
||||||
|
appDrawerOpen: selectAppDrawerOpen(state),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -68,7 +68,6 @@ const perform = {
|
||||||
doFetchRecommendedContent,
|
doFetchRecommendedContent,
|
||||||
doUriInitiatePlay,
|
doUriInitiatePlay,
|
||||||
doSetPlayingUri,
|
doSetPlayingUri,
|
||||||
doSetMobilePlayerDimensions,
|
|
||||||
doCommentSocketConnect,
|
doCommentSocketConnect,
|
||||||
doCommentSocketDisconnect,
|
doCommentSocketDisconnect,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
|
// $FlowFixMe
|
||||||
|
import { Global } from '@emotion/react';
|
||||||
|
|
||||||
|
import type { ElementRef } from 'react';
|
||||||
import * as ICONS from 'constants/icons';
|
import * as ICONS from 'constants/icons';
|
||||||
import * as RENDER_MODES from 'constants/file_render_modes';
|
import * as RENDER_MODES from 'constants/file_render_modes';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
@ -12,13 +17,22 @@ import { PRIMARY_PLAYER_WRAPPER_CLASS } from 'page/file/view';
|
||||||
import Draggable from 'react-draggable';
|
import Draggable from 'react-draggable';
|
||||||
import { onFullscreenChange } from 'util/full-screen';
|
import { onFullscreenChange } from 'util/full-screen';
|
||||||
import { generateListSearchUrlParams, formatLbryChannelName } from 'util/url';
|
import { generateListSearchUrlParams, formatLbryChannelName } from 'util/url';
|
||||||
import { useIsMobile } from 'effects/use-screensize';
|
import { useIsMobile, useIsMobileLandscape, useIsLandscapeScreen } from 'effects/use-screensize';
|
||||||
import debounce from 'util/debounce';
|
import debounce from 'util/debounce';
|
||||||
import { useHistory } from 'react-router';
|
import { useHistory } from 'react-router';
|
||||||
import { isURIEqual } from 'util/lbryURI';
|
import { isURIEqual } from 'util/lbryURI';
|
||||||
import AutoplayCountdown from 'component/autoplayCountdown';
|
import AutoplayCountdown from 'component/autoplayCountdown';
|
||||||
import usePlayNext from 'effects/use-play-next';
|
import usePlayNext from 'effects/use-play-next';
|
||||||
import { getScreenWidth, getScreenHeight, clampFloatingPlayerToScreen, calculateRelativePos } from './helper-functions';
|
import {
|
||||||
|
getRootEl,
|
||||||
|
getScreenWidth,
|
||||||
|
getScreenHeight,
|
||||||
|
clampFloatingPlayerToScreen,
|
||||||
|
calculateRelativePos,
|
||||||
|
getMaxLandscapeHeight,
|
||||||
|
getAmountNeededToCenterVideo,
|
||||||
|
getPossiblePlayerHeight,
|
||||||
|
} from './helper-functions';
|
||||||
|
|
||||||
// scss/init/vars.scss
|
// scss/init/vars.scss
|
||||||
// --header-height
|
// --header-height
|
||||||
|
@ -26,9 +40,10 @@ const HEADER_HEIGHT = 60;
|
||||||
// --header-height-mobile
|
// --header-height-mobile
|
||||||
export const HEADER_HEIGHT_MOBILE = 56;
|
export const HEADER_HEIGHT_MOBILE = 56;
|
||||||
|
|
||||||
const IS_DESKTOP_MAC = typeof process === 'object' ? process.platform === 'darwin' : false;
|
|
||||||
const DEBOUNCE_WINDOW_RESIZE_HANDLER_MS = 100;
|
const DEBOUNCE_WINDOW_RESIZE_HANDLER_MS = 100;
|
||||||
|
|
||||||
export const INLINE_PLAYER_WRAPPER_CLASS = 'inline-player__wrapper';
|
export const INLINE_PLAYER_WRAPPER_CLASS = 'inline-player__wrapper';
|
||||||
|
export const CONTENT_VIEWER_CLASS = 'content__viewer';
|
||||||
export const FLOATING_PLAYER_CLASS = 'content__viewer--floating';
|
export const FLOATING_PLAYER_CLASS = 'content__viewer--floating';
|
||||||
|
|
||||||
// ****************************************************************************
|
// ****************************************************************************
|
||||||
|
@ -55,11 +70,11 @@ type Props = {
|
||||||
doUriInitiatePlay: (playingOptions: PlayingUri, isPlayable: ?boolean, isFloating: ?boolean) => void,
|
doUriInitiatePlay: (playingOptions: PlayingUri, isPlayable: ?boolean, isFloating: ?boolean) => void,
|
||||||
doSetPlayingUri: ({ uri?: ?string }) => void,
|
doSetPlayingUri: ({ uri?: ?string }) => void,
|
||||||
isCurrentClaimLive?: boolean,
|
isCurrentClaimLive?: boolean,
|
||||||
mobilePlayerDimensions?: any,
|
videoAspectRatio: number,
|
||||||
socketConnected: boolean,
|
socketConnected: boolean,
|
||||||
isLivestreamClaim: boolean,
|
isLivestreamClaim: boolean,
|
||||||
geoRestriction: ?GeoRestriction,
|
geoRestriction: ?GeoRestriction,
|
||||||
doSetMobilePlayerDimensions: ({ height?: ?number, width?: ?number }) => void,
|
appDrawerOpen: boolean,
|
||||||
doCommentSocketConnect: (string, string, string) => void,
|
doCommentSocketConnect: (string, string, string) => void,
|
||||||
doCommentSocketDisconnect: (string, string) => void,
|
doCommentSocketDisconnect: (string, string) => void,
|
||||||
};
|
};
|
||||||
|
@ -88,14 +103,20 @@ export default function FileRenderFloating(props: Props) {
|
||||||
doUriInitiatePlay,
|
doUriInitiatePlay,
|
||||||
doSetPlayingUri,
|
doSetPlayingUri,
|
||||||
isCurrentClaimLive,
|
isCurrentClaimLive,
|
||||||
mobilePlayerDimensions,
|
videoAspectRatio,
|
||||||
geoRestriction,
|
geoRestriction,
|
||||||
doSetMobilePlayerDimensions,
|
appDrawerOpen,
|
||||||
doCommentSocketConnect,
|
doCommentSocketConnect,
|
||||||
doCommentSocketDisconnect,
|
doCommentSocketDisconnect,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
const isTabletLandscape = useIsLandscapeScreen() && !isMobile;
|
||||||
|
const isLandscapeRotated = useIsMobileLandscape();
|
||||||
|
|
||||||
|
const initialMobileState = React.useRef(isMobile);
|
||||||
|
const initialPlayerHeight = React.useRef();
|
||||||
|
const resizedBetweenFloating = React.useRef();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
location: { state },
|
location: { state },
|
||||||
|
@ -103,8 +124,9 @@ export default function FileRenderFloating(props: Props) {
|
||||||
const hideFloatingPlayer = state && state.hideFloatingPlayer;
|
const hideFloatingPlayer = state && state.hideFloatingPlayer;
|
||||||
|
|
||||||
const { uri: playingUrl, source: playingUriSource, primaryUri: playingPrimaryUri } = playingUri;
|
const { uri: playingUrl, source: playingUriSource, primaryUri: playingPrimaryUri } = playingUri;
|
||||||
|
|
||||||
const isComment = playingUriSource === 'comment';
|
const isComment = playingUriSource === 'comment';
|
||||||
const mainFilePlaying = !isFloating && primaryUri && isURIEqual(uri, primaryUri);
|
const mainFilePlaying = Boolean(!isFloating && primaryUri && isURIEqual(uri, primaryUri));
|
||||||
const noFloatingPlayer = !isFloating || !floatingPlayerEnabled || hideFloatingPlayer;
|
const noFloatingPlayer = !isFloating || !floatingPlayerEnabled || hideFloatingPlayer;
|
||||||
|
|
||||||
const [fileViewerRect, setFileViewerRect] = React.useState();
|
const [fileViewerRect, setFileViewerRect] = React.useState();
|
||||||
|
@ -151,13 +173,18 @@ export default function FileRenderFloating(props: Props) {
|
||||||
x: rect.x,
|
x: rect.x,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// replace the initial value every time the window is resized if isMobile is true,
|
||||||
|
// since it could be a portrait -> landscape rotation switch, or if it was a mobile - desktop
|
||||||
|
// switch, so use the ref to compare the initial state
|
||||||
|
const resizedEnoughForMobileSwitch = isMobile !== initialMobileState.current;
|
||||||
|
if (videoAspectRatio && (!initialPlayerHeight.current || isMobile || resizedEnoughForMobileSwitch)) {
|
||||||
|
const heightForRect = getPossiblePlayerHeight(videoAspectRatio * rect.width, isMobile);
|
||||||
|
initialPlayerHeight.current = heightForRect;
|
||||||
|
}
|
||||||
|
|
||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
setFileViewerRect({ ...objectRect, windowOffset: window.pageYOffset });
|
setFileViewerRect({ ...objectRect, windowOffset: window.pageYOffset });
|
||||||
|
}, [isMobile, mainFilePlaying, videoAspectRatio]);
|
||||||
if (!mobilePlayerDimensions || mobilePlayerDimensions.height !== rect.height) {
|
|
||||||
doSetMobilePlayerDimensions({ height: rect.height, width: getScreenWidth() });
|
|
||||||
}
|
|
||||||
}, [doSetMobilePlayerDimensions, mainFilePlaying, mobilePlayerDimensions]);
|
|
||||||
|
|
||||||
const restoreToRelativePosition = React.useCallback(() => {
|
const restoreToRelativePosition = React.useCallback(() => {
|
||||||
const SCROLL_BAR_PX = 12; // root: --body-scrollbar-width
|
const SCROLL_BAR_PX = 12; // root: --body-scrollbar-width
|
||||||
|
@ -222,11 +249,16 @@ export default function FileRenderFloating(props: Props) {
|
||||||
|
|
||||||
// Listen to main-window resizing and adjust the floating player position accordingly:
|
// Listen to main-window resizing and adjust the floating player position accordingly:
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
// intended to only run once: when floating player switches between true - false
|
||||||
|
// otherwise handleResize() can run twice when this effect re-runs, so use
|
||||||
|
// resizedBetweenFloating ref
|
||||||
if (isFloating) {
|
if (isFloating) {
|
||||||
// Ensure player is within screen when 'isFloating' changes.
|
// Ensure player is within screen when 'isFloating' changes.
|
||||||
restoreToRelativePosition();
|
restoreToRelativePosition();
|
||||||
} else {
|
resizedBetweenFloating.current = false;
|
||||||
|
} else if (!resizedBetweenFloating.current) {
|
||||||
handleResize();
|
handleResize();
|
||||||
|
resizedBetweenFloating.current = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onWindowResize() {
|
function onWindowResize() {
|
||||||
|
@ -234,14 +266,13 @@ export default function FileRenderFloating(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('resize', onWindowResize);
|
window.addEventListener('resize', onWindowResize);
|
||||||
if (!isFloating) onFullscreenChange(window, 'add', handleResize);
|
if (!isFloating && !isMobile) onFullscreenChange(window, 'add', handleResize);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('resize', onWindowResize);
|
window.removeEventListener('resize', onWindowResize);
|
||||||
if (!isFloating) onFullscreenChange(window, 'remove', handleResize);
|
if (!isFloating && !isMobile) onFullscreenChange(window, 'remove', handleResize);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Only listen to these and avoid infinite loops
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [clampToScreenOnResize, handleResize, isFloating]);
|
}, [clampToScreenOnResize, handleResize, isFloating]);
|
||||||
|
|
||||||
|
@ -267,10 +298,14 @@ export default function FileRenderFloating(props: Props) {
|
||||||
}, [doFetchRecommendedContent, isFloating, uri]);
|
}, [doFetchRecommendedContent, isFloating, uri]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (isFloating && isMobile) {
|
return () => {
|
||||||
doSetMobilePlayerDimensions({ height: null, width: null });
|
// basically if switched videos (playingUrl change or unmount),
|
||||||
|
// erase the data so it can be re-calculated
|
||||||
|
if (playingUrl) {
|
||||||
|
initialPlayerHeight.current = undefined;
|
||||||
}
|
}
|
||||||
}, [doSetMobilePlayerDimensions, doSetPlayingUri, isFloating, isMobile]);
|
};
|
||||||
|
}, [playingUrl]);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
geoRestriction ||
|
geoRestriction ||
|
||||||
|
@ -329,30 +364,42 @@ export default function FileRenderFloating(props: Props) {
|
||||||
cancel=".button"
|
cancel=".button"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={classnames('content__viewer', {
|
className={classnames([CONTENT_VIEWER_CLASS], {
|
||||||
[FLOATING_PLAYER_CLASS]: isFloating,
|
[FLOATING_PLAYER_CLASS]: isFloating,
|
||||||
'content__viewer--inline': !isFloating,
|
'content__viewer--inline': !isFloating,
|
||||||
'content__viewer--secondary': isComment,
|
'content__viewer--secondary': isComment,
|
||||||
'content__viewer--theater-mode': videoTheaterMode && mainFilePlaying && !isCurrentClaimLive && !isMobile,
|
'content__viewer--theater-mode': videoTheaterMode && mainFilePlaying && !isCurrentClaimLive && !isMobile,
|
||||||
'content__viewer--disable-click': wasDragging,
|
'content__viewer--disable-click': wasDragging,
|
||||||
'content__viewer--mobile': isMobile && !playingUriSource,
|
'content__viewer--mobile': isMobile && !isLandscapeRotated && !playingUriSource,
|
||||||
})}
|
})}
|
||||||
style={
|
style={
|
||||||
!isFloating && fileViewerRect
|
!isFloating && fileViewerRect
|
||||||
? {
|
? {
|
||||||
width: fileViewerRect.width,
|
width: fileViewerRect.width,
|
||||||
height: fileViewerRect.height,
|
height: appDrawerOpen ? `${getMaxLandscapeHeight()}px` : fileViewerRect.height,
|
||||||
left: fileViewerRect.x,
|
left: fileViewerRect.x,
|
||||||
top:
|
top:
|
||||||
isMobile && !playingUriSource
|
isMobile && !playingUriSource
|
||||||
? HEADER_HEIGHT_MOBILE
|
? HEADER_HEIGHT_MOBILE
|
||||||
: fileViewerRect.windowOffset +
|
: fileViewerRect.windowOffset + fileViewerRect.top - HEADER_HEIGHT,
|
||||||
fileViewerRect.top -
|
|
||||||
(!isMobile ? HEADER_HEIGHT - (IS_DESKTOP_MAC ? 24 : 0) : 0),
|
|
||||||
}
|
}
|
||||||
: {}
|
: {}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
{uri && videoAspectRatio && fileViewerRect ? (
|
||||||
|
<PlayerGlobalStyles
|
||||||
|
videoAspectRatio={videoAspectRatio}
|
||||||
|
videoTheaterMode={videoTheaterMode}
|
||||||
|
appDrawerOpen={appDrawerOpen && !isLandscapeRotated && !isTabletLandscape}
|
||||||
|
initialPlayerHeight={initialPlayerHeight}
|
||||||
|
isFloating={isFloating}
|
||||||
|
fileViewerRect={fileViewerRect}
|
||||||
|
mainFilePlaying={mainFilePlaying}
|
||||||
|
isLandscapeRotated={isLandscapeRotated}
|
||||||
|
isTabletLandscape={isTabletLandscape}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<div className={classnames('content__wrapper', { 'content__wrapper--floating': isFloating })}>
|
<div className={classnames('content__wrapper', { 'content__wrapper--floating': isFloating })}>
|
||||||
{isFloating && (
|
{isFloating && (
|
||||||
<Button
|
<Button
|
||||||
|
@ -398,3 +445,194 @@ export default function FileRenderFloating(props: Props) {
|
||||||
</Draggable>
|
</Draggable>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GlobalStylesProps = {
|
||||||
|
videoAspectRatio: number,
|
||||||
|
videoTheaterMode: boolean,
|
||||||
|
appDrawerOpen: boolean,
|
||||||
|
initialPlayerHeight: ElementRef<any>,
|
||||||
|
isFloating: boolean,
|
||||||
|
fileViewerRect: any,
|
||||||
|
mainFilePlaying: boolean,
|
||||||
|
isLandscapeRotated: boolean,
|
||||||
|
isTabletLandscape: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
|
const PlayerGlobalStyles = (props: GlobalStylesProps) => {
|
||||||
|
const {
|
||||||
|
videoAspectRatio,
|
||||||
|
videoTheaterMode,
|
||||||
|
appDrawerOpen,
|
||||||
|
initialPlayerHeight,
|
||||||
|
isFloating,
|
||||||
|
fileViewerRect,
|
||||||
|
mainFilePlaying,
|
||||||
|
isLandscapeRotated,
|
||||||
|
isTabletLandscape,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const isMobile = useIsMobile();
|
||||||
|
const isMobilePlayer = isMobile && !isFloating; // to avoid miniplayer -> file page only
|
||||||
|
|
||||||
|
const heightForViewer = getPossiblePlayerHeight(videoAspectRatio * fileViewerRect.width, isMobile);
|
||||||
|
const widthForViewer = heightForViewer / videoAspectRatio;
|
||||||
|
const maxLandscapeHeight = getMaxLandscapeHeight(isMobile ? undefined : widthForViewer);
|
||||||
|
const heightResult = appDrawerOpen ? `${maxLandscapeHeight}px` : `${heightForViewer}px`;
|
||||||
|
const amountNeededToCenter = getAmountNeededToCenterVideo(heightForViewer, maxLandscapeHeight);
|
||||||
|
|
||||||
|
// forceDefaults = no styles should be applied to any of these conditions
|
||||||
|
// !mainFilePlaying = embeds on markdown (comments or posts)
|
||||||
|
const forceDefaults = !mainFilePlaying || videoTheaterMode || isFloating || isMobile;
|
||||||
|
|
||||||
|
const videoGreaterThanLandscape = heightForViewer > maxLandscapeHeight;
|
||||||
|
|
||||||
|
// Handles video shrink + center on mobile view
|
||||||
|
// direct DOM manipulation due to performance for every scroll
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!isMobilePlayer || !mainFilePlaying || appDrawerOpen || isLandscapeRotated || isTabletLandscape) return;
|
||||||
|
|
||||||
|
const viewer = document.querySelector(`.${CONTENT_VIEWER_CLASS}`);
|
||||||
|
if (viewer) viewer.style.height = `${heightForViewer}px`;
|
||||||
|
|
||||||
|
function handleScroll() {
|
||||||
|
const rootEl = getRootEl();
|
||||||
|
|
||||||
|
const viewer = document.querySelector(`.${CONTENT_VIEWER_CLASS}`);
|
||||||
|
const videoNode = document.querySelector('.vjs-tech');
|
||||||
|
const touchOverlay = document.querySelector('.vjs-touch-overlay');
|
||||||
|
|
||||||
|
if (rootEl && viewer) {
|
||||||
|
const scrollTop = window.pageYOffset || rootEl.scrollTop;
|
||||||
|
const isHigherThanLandscape = scrollTop < initialPlayerHeight.current - maxLandscapeHeight;
|
||||||
|
|
||||||
|
if (videoNode) {
|
||||||
|
if (isHigherThanLandscape) {
|
||||||
|
if (initialPlayerHeight.current > maxLandscapeHeight) {
|
||||||
|
const result = initialPlayerHeight.current - scrollTop;
|
||||||
|
const amountNeededToCenter = getAmountNeededToCenterVideo(videoNode.offsetHeight, result);
|
||||||
|
|
||||||
|
videoNode.style.top = `${amountNeededToCenter}px`;
|
||||||
|
if (touchOverlay) touchOverlay.style.height = `${result}px`;
|
||||||
|
viewer.style.height = `${result}px`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (touchOverlay) touchOverlay.style.height = `${maxLandscapeHeight}px`;
|
||||||
|
viewer.style.height = `${maxLandscapeHeight}px`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('scroll', handleScroll);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
// clear the added styles on unmount
|
||||||
|
const viewer = document.querySelector(`.${CONTENT_VIEWER_CLASS}`);
|
||||||
|
// $FlowFixMe
|
||||||
|
if (viewer) viewer.style.height = undefined;
|
||||||
|
const touchOverlay = document.querySelector('.vjs-touch-overlay');
|
||||||
|
if (touchOverlay) touchOverlay.removeAttribute('style');
|
||||||
|
|
||||||
|
window.removeEventListener('scroll', handleScroll);
|
||||||
|
};
|
||||||
|
}, [
|
||||||
|
appDrawerOpen,
|
||||||
|
heightForViewer,
|
||||||
|
isMobilePlayer,
|
||||||
|
mainFilePlaying,
|
||||||
|
maxLandscapeHeight,
|
||||||
|
initialPlayerHeight,
|
||||||
|
isLandscapeRotated,
|
||||||
|
isTabletLandscape,
|
||||||
|
]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (appDrawerOpen && videoGreaterThanLandscape && isMobilePlayer) {
|
||||||
|
const videoNode = document.querySelector('.vjs-tech');
|
||||||
|
if (videoNode) videoNode.style.top = `${amountNeededToCenter}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isMobile && isFloating) {
|
||||||
|
const viewer = document.querySelector(`.${CONTENT_VIEWER_CLASS}`);
|
||||||
|
if (viewer) viewer.removeAttribute('style');
|
||||||
|
const touchOverlay = document.querySelector('.vjs-touch-overlay');
|
||||||
|
if (touchOverlay) touchOverlay.removeAttribute('style');
|
||||||
|
const videoNode = document.querySelector('.vjs-tech');
|
||||||
|
if (videoNode) videoNode.removeAttribute('style');
|
||||||
|
}
|
||||||
|
}, [amountNeededToCenter, appDrawerOpen, isFloating, isMobile, isMobilePlayer, videoGreaterThanLandscape]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (isTabletLandscape) {
|
||||||
|
const videoNode = document.querySelector('.vjs-tech');
|
||||||
|
if (videoNode) videoNode.removeAttribute('style');
|
||||||
|
const touchOverlay = document.querySelector('.vjs-touch-overlay');
|
||||||
|
if (touchOverlay) touchOverlay.removeAttribute('style');
|
||||||
|
}
|
||||||
|
}, [isTabletLandscape]);
|
||||||
|
|
||||||
|
// -- render styles --
|
||||||
|
|
||||||
|
// declaring some style objects as variables makes it easier for repeated cases
|
||||||
|
const transparentBackground = {
|
||||||
|
background: videoGreaterThanLandscape && mainFilePlaying && !forceDefaults ? 'transparent !important' : undefined,
|
||||||
|
};
|
||||||
|
const maxHeight = {
|
||||||
|
maxHeight: !videoTheaterMode && !isMobile ? 'var(--desktop-portrait-player-max-height)' : undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Global
|
||||||
|
styles={{
|
||||||
|
[`.${PRIMARY_PLAYER_WRAPPER_CLASS}`]: {
|
||||||
|
height: !videoTheaterMode && mainFilePlaying ? `${heightResult} !important` : undefined,
|
||||||
|
opacity: !videoTheaterMode && mainFilePlaying ? '0 !important' : undefined,
|
||||||
|
},
|
||||||
|
|
||||||
|
'.file-render--video': {
|
||||||
|
...transparentBackground,
|
||||||
|
...maxHeight,
|
||||||
|
|
||||||
|
video: maxHeight,
|
||||||
|
},
|
||||||
|
'.content__wrapper': transparentBackground,
|
||||||
|
'.video-js': {
|
||||||
|
...transparentBackground,
|
||||||
|
|
||||||
|
'.vjs-touch-overlay': {
|
||||||
|
maxHeight: isTabletLandscape ? 'var(--desktop-portrait-player-max-height) !important' : undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
'.vjs-fullscreen': {
|
||||||
|
video: {
|
||||||
|
top: 'unset !important',
|
||||||
|
height: '100% !important',
|
||||||
|
},
|
||||||
|
'.vjs-touch-overlay': {
|
||||||
|
height: '100% !important',
|
||||||
|
maxHeight: 'unset !important',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
'.vjs-tech': {
|
||||||
|
opacity: '1',
|
||||||
|
height:
|
||||||
|
isMobilePlayer && ((appDrawerOpen && videoGreaterThanLandscape) || videoGreaterThanLandscape)
|
||||||
|
? 'unset !important'
|
||||||
|
: '100%',
|
||||||
|
position: 'absolute',
|
||||||
|
top: isFloating ? '0px !important' : undefined,
|
||||||
|
},
|
||||||
|
|
||||||
|
[`.${CONTENT_VIEWER_CLASS}`]: {
|
||||||
|
height:
|
||||||
|
(!forceDefaults || isLandscapeRotated) && (!isMobile || isMobilePlayer)
|
||||||
|
? `${heightResult} !important`
|
||||||
|
: undefined,
|
||||||
|
...maxHeight,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
@ -133,7 +133,8 @@ export default function FileRenderInitiator(props: Props) {
|
||||||
}, [collectionId, doUriInitiatePlay, isMarkdownPost, isPlayable, parentCommentId, pathname, uri]);
|
}, [collectionId, doUriInitiatePlay, isMarkdownPost, isPlayable, parentCommentId, pathname, uri]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const videoOnPage = document.querySelector('video');
|
// avoid selecting 'video' anymore -> can cause conflicts with Ad popup videos
|
||||||
|
const videoOnPage = document.querySelector('.vjs-tech');
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(canViewFile || forceAutoplayParam) &&
|
(canViewFile || forceAutoplayParam) &&
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import 'scss/component/_swipeable-drawer.scss';
|
import 'scss/component/_swipeable-drawer.scss';
|
||||||
|
|
||||||
import { lazyImport } from 'util/lazyImport';
|
import { lazyImport } from 'util/lazyImport';
|
||||||
import { useIsMobile } from 'effects/use-screensize';
|
import { useIsMobile, useIsMobileLandscape } from 'effects/use-screensize';
|
||||||
import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button';
|
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';
|
||||||
|
@ -12,7 +12,7 @@ import FileRenderInitiator from 'component/fileRenderInitiator';
|
||||||
import LivestreamScheduledInfo from 'component/livestreamScheduledInfo';
|
import LivestreamScheduledInfo from 'component/livestreamScheduledInfo';
|
||||||
import * as ICONS from 'constants/icons';
|
import * as ICONS from 'constants/icons';
|
||||||
import SwipeableDrawer from 'component/swipeableDrawer';
|
import SwipeableDrawer from 'component/swipeableDrawer';
|
||||||
import { DrawerExpandButton } from 'component/swipeableDrawer/view';
|
import DrawerExpandButton from 'component/swipeableDrawerExpand';
|
||||||
import LivestreamMenu from 'component/livestreamChatLayout/livestream-menu';
|
import LivestreamMenu from 'component/livestreamChatLayout/livestream-menu';
|
||||||
import Icon from 'component/common/icon';
|
import Icon from 'component/common/icon';
|
||||||
import CreditAmount from 'component/common/credit-amount';
|
import CreditAmount from 'component/common/credit-amount';
|
||||||
|
@ -53,8 +53,8 @@ export default function LivestreamLayout(props: Props) {
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
const isLandscapeRotated = useIsMobileLandscape();
|
||||||
|
|
||||||
const [showChat, setShowChat] = React.useState(undefined);
|
|
||||||
const [superchatsHidden, setSuperchatsHidden] = React.useState(false);
|
const [superchatsHidden, setSuperchatsHidden] = React.useState(false);
|
||||||
const [chatViewMode, setChatViewMode] = React.useState(VIEW_MODES.CHAT);
|
const [chatViewMode, setChatViewMode] = React.useState(VIEW_MODES.CHAT);
|
||||||
|
|
||||||
|
@ -101,11 +101,9 @@ export default function LivestreamLayout(props: Props) {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isMobile && !hideComments && (
|
{isMobile && !isLandscapeRotated && !hideComments && (
|
||||||
<React.Suspense fallback={null}>
|
<React.Suspense fallback={null}>
|
||||||
<SwipeableDrawer
|
<SwipeableDrawer
|
||||||
open={Boolean(showChat)}
|
|
||||||
toggleDrawer={() => setShowChat(!showChat)}
|
|
||||||
title={
|
title={
|
||||||
<ChatModeSelector
|
<ChatModeSelector
|
||||||
superChats={superChats}
|
superChats={superChats}
|
||||||
|
@ -133,7 +131,7 @@ export default function LivestreamLayout(props: Props) {
|
||||||
/>
|
/>
|
||||||
</SwipeableDrawer>
|
</SwipeableDrawer>
|
||||||
|
|
||||||
<DrawerExpandButton label={__('Open Live Chat')} toggleDrawer={() => setShowChat(!showChat)} />
|
<DrawerExpandButton label={__('Open Live Chat')} />
|
||||||
</React.Suspense>
|
</React.Suspense>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { lazyImport } from 'util/lazyImport';
|
||||||
import { MAIN_CLASS } from 'constants/classnames';
|
import { MAIN_CLASS } from 'constants/classnames';
|
||||||
import { parseURI } from 'util/lbryURI';
|
import { parseURI } from 'util/lbryURI';
|
||||||
import { useHistory } from 'react-router';
|
import { useHistory } from 'react-router';
|
||||||
import { useIsMobile, useIsMediumScreen } from 'effects/use-screensize';
|
import { useIsMobile, useIsMediumScreen, useIsMobileLandscape } from 'effects/use-screensize';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import Header from 'component/header';
|
import Header from 'component/header';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
@ -65,6 +65,7 @@ function Page(props: Props) {
|
||||||
|
|
||||||
const isMediumScreen = useIsMediumScreen();
|
const isMediumScreen = useIsMediumScreen();
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
const isLandscapeRotated = useIsMobileLandscape();
|
||||||
const [sidebarOpen, setSidebarOpen] = usePersistedState('sidebar', false);
|
const [sidebarOpen, setSidebarOpen] = usePersistedState('sidebar', false);
|
||||||
|
|
||||||
const url = pathname.slice(1).replace(/:/g, '#');
|
const url = pathname.slice(1).replace(/:/g, '#');
|
||||||
|
@ -143,7 +144,7 @@ function Page(props: Props) {
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|
||||||
{!isMobile && (!livestream || !chatDisabled) && rightSide}
|
{(!isMobile || isLandscapeRotated) && (!livestream || !chatDisabled) && rightSide}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
{!noFooter && (
|
{!noFooter && (
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import SwipeableDrawer from './view';
|
import SwipeableDrawer from './view';
|
||||||
import { selectTheme } from 'redux/selectors/settings';
|
import { selectTheme } from 'redux/selectors/settings';
|
||||||
import { selectMobilePlayerDimensions } from 'redux/selectors/app';
|
import { selectAppDrawerOpen } from 'redux/selectors/app';
|
||||||
|
import { doToggleAppDrawer } from 'redux/actions/app';
|
||||||
|
|
||||||
const select = (state) => ({
|
const select = (state) => ({
|
||||||
|
open: selectAppDrawerOpen(state),
|
||||||
theme: selectTheme(state),
|
theme: selectTheme(state),
|
||||||
mobilePlayerDimensions: selectMobilePlayerDimensions(state),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select)(SwipeableDrawer);
|
const perform = {
|
||||||
|
toggleDrawer: doToggleAppDrawer,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(select, perform)(SwipeableDrawer);
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { grey } from '@mui/material/colors';
|
||||||
|
|
||||||
import { HEADER_HEIGHT_MOBILE } from 'component/fileRenderFloating/view';
|
import { HEADER_HEIGHT_MOBILE } from 'component/fileRenderFloating/view';
|
||||||
import { PRIMARY_PLAYER_WRAPPER_CLASS, PRIMARY_IMAGE_WRAPPER_CLASS } from 'page/file/view';
|
import { PRIMARY_PLAYER_WRAPPER_CLASS, PRIMARY_IMAGE_WRAPPER_CLASS } from 'page/file/view';
|
||||||
|
import { getMaxLandscapeHeight } from 'component/fileRenderFloating/helper-functions';
|
||||||
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';
|
||||||
|
@ -18,21 +19,21 @@ const DRAWER_PULLER_HEIGHT = 42;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children: Node,
|
children: Node,
|
||||||
open: boolean,
|
|
||||||
theme: string,
|
|
||||||
mobilePlayerDimensions?: { height: number },
|
|
||||||
title: any,
|
title: any,
|
||||||
hasSubtitle?: boolean,
|
hasSubtitle?: boolean,
|
||||||
actions?: any,
|
actions?: any,
|
||||||
|
// -- redux --
|
||||||
|
open: boolean,
|
||||||
|
theme: string,
|
||||||
toggleDrawer: () => void,
|
toggleDrawer: () => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function SwipeableDrawer(props: Props) {
|
export default function SwipeableDrawer(props: Props) {
|
||||||
const { mobilePlayerDimensions, title, hasSubtitle, children, open, theme, actions, toggleDrawer } = props;
|
const { title, hasSubtitle, children, open, theme, actions, toggleDrawer } = props;
|
||||||
|
|
||||||
const [coverHeight, setCoverHeight] = React.useState();
|
const [coverHeight, setCoverHeight] = React.useState();
|
||||||
|
|
||||||
const videoHeight = (mobilePlayerDimensions && mobilePlayerDimensions.height) || coverHeight || 0;
|
const videoHeight = coverHeight || getMaxLandscapeHeight() || 0;
|
||||||
|
|
||||||
const handleResize = React.useCallback(() => {
|
const handleResize = React.useCallback(() => {
|
||||||
const element =
|
const element =
|
||||||
|
@ -47,14 +48,14 @@ export default function SwipeableDrawer(props: Props) {
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
// Drawer will follow the cover image on resize, so it's always visible
|
// Drawer will follow the cover image on resize, so it's always visible
|
||||||
if (open && (!mobilePlayerDimensions || !mobilePlayerDimensions.height)) {
|
if (open) {
|
||||||
handleResize();
|
handleResize();
|
||||||
|
|
||||||
window.addEventListener('resize', handleResize);
|
window.addEventListener('resize', handleResize);
|
||||||
|
|
||||||
return () => window.removeEventListener('resize', handleResize);
|
return () => window.removeEventListener('resize', handleResize);
|
||||||
}
|
}
|
||||||
}, [handleResize, mobilePlayerDimensions, open]);
|
}, [handleResize, open]);
|
||||||
|
|
||||||
// Reset scroll position when opening: avoid broken position where
|
// Reset scroll position when opening: avoid broken position where
|
||||||
// the drawer is lower than the video
|
// the drawer is lower than the video
|
||||||
|
@ -94,12 +95,12 @@ export default function SwipeableDrawer(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type GlobalStylesProps = {
|
type GlobalStylesProps = {
|
||||||
open?: boolean,
|
open: boolean,
|
||||||
videoHeight: number,
|
videoHeight: number,
|
||||||
};
|
};
|
||||||
|
|
||||||
const DrawerGlobalStyles = (globalStylesProps: GlobalStylesProps) => {
|
const DrawerGlobalStyles = (props: GlobalStylesProps) => {
|
||||||
const { open, videoHeight } = globalStylesProps;
|
const { open, videoHeight } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Global
|
<Global
|
||||||
|
@ -126,8 +127,8 @@ type PullerProps = {
|
||||||
theme: string,
|
theme: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Puller = (pullerProps: PullerProps) => {
|
const Puller = (props: PullerProps) => {
|
||||||
const { theme } = pullerProps;
|
const { theme } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<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] }} />
|
||||||
|
@ -141,8 +142,8 @@ type HeaderProps = {
|
||||||
toggleDrawer: () => void,
|
toggleDrawer: () => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
const HeaderContents = (headerProps: HeaderProps) => {
|
const HeaderContents = (props: HeaderProps) => {
|
||||||
const { title, hasSubtitle, actions, toggleDrawer } = headerProps;
|
const { title, hasSubtitle, actions, toggleDrawer } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -160,22 +161,3 @@ const HeaderContents = (headerProps: HeaderProps) => {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
type ExpandButtonProps = {
|
|
||||||
label: any,
|
|
||||||
toggleDrawer: () => void,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DrawerExpandButton = (expandButtonProps: ExpandButtonProps) => {
|
|
||||||
const { label, toggleDrawer } = expandButtonProps;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
className="swipeable-drawer__expand-button"
|
|
||||||
label={label}
|
|
||||||
button="primary"
|
|
||||||
icon={ICONS.CHAT}
|
|
||||||
onClick={toggleDrawer}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
9
ui/component/swipeableDrawerExpand/index.js
Normal file
9
ui/component/swipeableDrawerExpand/index.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { doToggleAppDrawer } from 'redux/actions/app';
|
||||||
|
import DrawerExpandButton from './view';
|
||||||
|
|
||||||
|
const perform = {
|
||||||
|
onClick: doToggleAppDrawer,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(null, perform)(DrawerExpandButton);
|
15
ui/component/swipeableDrawerExpand/view.jsx
Normal file
15
ui/component/swipeableDrawerExpand/view.jsx
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
// @flow
|
||||||
|
import 'scss/component/_swipeable-drawer.scss';
|
||||||
|
import * as ICONS from 'constants/icons';
|
||||||
|
import * as React from 'react';
|
||||||
|
import Button from 'component/button';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
label: any,
|
||||||
|
// -- redux --
|
||||||
|
onClick: () => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function DrawerExpandButton(buttonProps: Props) {
|
||||||
|
return <Button className="swipeable-drawer__expand-button" button="primary" icon={ICONS.CHAT} {...buttonProps} />;
|
||||||
|
}
|
|
@ -61,9 +61,7 @@ const onPlayerReady = (player, options) => {
|
||||||
!player.el_.ownerDocument.querySelector('.bc-iframe')
|
!player.el_.ownerDocument.querySelector('.bc-iframe')
|
||||||
) {
|
) {
|
||||||
player.tech_.el_.setAttribute('playsinline', 'playsinline');
|
player.tech_.el_.setAttribute('playsinline', 'playsinline');
|
||||||
player.tech_.supportsFullScreen = function() {
|
player.tech_.supportsFullScreen = () => false;
|
||||||
return false;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const controlBar = player.getChild('ControlBar');
|
const controlBar = player.getChild('ControlBar');
|
||||||
|
@ -115,7 +113,7 @@ const onPlayerReady = (player, options) => {
|
||||||
screen.orientation.onchange = rotationHandler;
|
screen.orientation.onchange = rotationHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
player.on('ended', _ => {
|
player.on('ended', (_) => {
|
||||||
if (locked === true) {
|
if (locked === true) {
|
||||||
screen.orientation.unlock();
|
screen.orientation.unlock();
|
||||||
locked = false;
|
locked = false;
|
||||||
|
@ -150,14 +148,12 @@ const onPlayerReady = (player, options) => {
|
||||||
* Whether to disable when the video ends (e.g., if there is an endscreen)
|
* Whether to disable when the video ends (e.g., if there is an endscreen)
|
||||||
* Never shows if the endscreen plugin is present
|
* Never shows if the endscreen plugin is present
|
||||||
*/
|
*/
|
||||||
const mobileUi = function(options) {
|
function mobileUi(options) {
|
||||||
// if (videojs.browser.IS_ANDROID || videojs.browser.IS_IOS) {
|
// if (videojs.browser.IS_ANDROID || videojs.browser.IS_IOS) {
|
||||||
if (videojs.browser.IS_ANDROID) {
|
if (videojs.browser.IS_ANDROID) {
|
||||||
this.ready(() => {
|
this.ready(() => onPlayerReady(this, videojs.mergeOptions(defaults, options)));
|
||||||
onPlayerReady(this, videojs.mergeOptions(defaults, options));
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// Register the plugin with video.js.
|
// Register the plugin with video.js.
|
||||||
registerPlugin('mobileUi', mobileUi);
|
registerPlugin('mobileUi', mobileUi);
|
||||||
|
|
|
@ -32,7 +32,6 @@ export const TOGGLE_YOUTUBE_SYNC_INTEREST = 'TOGGLE_YOUTUBE_SYNC_INTEREST';
|
||||||
export const TOGGLE_SPLASH_ANIMATION = 'TOGGLE_SPLASH_ANIMATION';
|
export const TOGGLE_SPLASH_ANIMATION = 'TOGGLE_SPLASH_ANIMATION';
|
||||||
export const SET_ACTIVE_CHANNEL = 'SET_ACTIVE_CHANNEL';
|
export const SET_ACTIVE_CHANNEL = 'SET_ACTIVE_CHANNEL';
|
||||||
export const SET_INCOGNITO = 'SET_INCOGNITO';
|
export const SET_INCOGNITO = 'SET_INCOGNITO';
|
||||||
export const SET_MOBILE_PLAYER_DIMENSIONS = 'SET_MOBILE_PLAYER_DIMENSIONS';
|
|
||||||
export const SET_AD_BLOCKER_FOUND = 'SET_AD_BLOCKER_FOUND';
|
export const SET_AD_BLOCKER_FOUND = 'SET_AD_BLOCKER_FOUND';
|
||||||
export const RELOAD_REQUIRED = 'RELOAD_REQUIRED';
|
export const RELOAD_REQUIRED = 'RELOAD_REQUIRED';
|
||||||
|
|
||||||
|
@ -40,6 +39,7 @@ export const RELOAD_REQUIRED = 'RELOAD_REQUIRED';
|
||||||
export const CHANGE_AFTER_AUTH_PATH = 'CHANGE_AFTER_AUTH_PATH';
|
export const CHANGE_AFTER_AUTH_PATH = 'CHANGE_AFTER_AUTH_PATH';
|
||||||
export const WINDOW_SCROLLED = 'WINDOW_SCROLLED';
|
export const WINDOW_SCROLLED = 'WINDOW_SCROLLED';
|
||||||
export const HISTORY_NAVIGATE = 'HISTORY_NAVIGATE';
|
export const HISTORY_NAVIGATE = 'HISTORY_NAVIGATE';
|
||||||
|
export const DRAWER_OPENED = 'DRAWER_OPENED';
|
||||||
|
|
||||||
// Upgrades
|
// Upgrades
|
||||||
export const UPGRADE_CANCELLED = 'UPGRADE_CANCELLED';
|
export const UPGRADE_CANCELLED = 'UPGRADE_CANCELLED';
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// @flow
|
// @flow
|
||||||
// Widths are taken from "ui/scss/init/vars.scss"
|
// Widths are taken from "ui/scss/init/vars.scss"
|
||||||
import React, { useRef } from 'react';
|
import React, { useRef } from 'react';
|
||||||
|
import { getWindowAngle, isWindowLandscapeForAngle } from 'component/fileRenderFloating/helper-functions';
|
||||||
const DEFAULT_SCREEN_SIZE = 1080;
|
const DEFAULT_SCREEN_SIZE = 1080;
|
||||||
|
|
||||||
export function useWindowSize() {
|
export function useWindowSize() {
|
||||||
|
@ -51,6 +52,38 @@ export function useIsMobile() {
|
||||||
return useHasWindowWidthChangedEnough((windowSize) => windowSize < 901);
|
return useHasWindowWidthChangedEnough((windowSize) => windowSize < 901);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useIsMobileLandscape() {
|
||||||
|
const isMobile = useIsMobile();
|
||||||
|
const isLandscapeScreen = useIsLandscapeScreen();
|
||||||
|
return isMobile && isLandscapeScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useIsLandscapeScreen() {
|
||||||
|
const isWindowClient = typeof window === 'object';
|
||||||
|
|
||||||
|
const windowAngle = getWindowAngle();
|
||||||
|
const isLandscape = isWindowLandscapeForAngle(windowAngle);
|
||||||
|
const [landscape, setLandscape] = React.useState<boolean>(isLandscape);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
function handleResize() {
|
||||||
|
const currAngle = getWindowAngle();
|
||||||
|
const isCurrLandscape = isWindowLandscapeForAngle(currAngle);
|
||||||
|
if (landscape !== isCurrLandscape) {
|
||||||
|
setLandscape(isCurrLandscape);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isWindowClient) {
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
|
||||||
|
return () => window.removeEventListener('resize', handleResize);
|
||||||
|
}
|
||||||
|
}, [isWindowClient, landscape]);
|
||||||
|
|
||||||
|
return landscape;
|
||||||
|
}
|
||||||
|
|
||||||
export function useIsMediumScreen() {
|
export function useIsMediumScreen() {
|
||||||
return useHasWindowWidthChangedEnough((windowSize) => windowSize < 1151);
|
return useHasWindowWidthChangedEnough((windowSize) => windowSize < 1151);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { selectShowMatureContent, selectClientSetting } from 'redux/selectors/se
|
||||||
import { makeSelectFileRenderModeForUri, selectContentPositionForUri } from 'redux/selectors/content';
|
import { makeSelectFileRenderModeForUri, selectContentPositionForUri } from 'redux/selectors/content';
|
||||||
import { selectCommentsListTitleForUri, selectSettingsByChannelId } from 'redux/selectors/comments';
|
import { selectCommentsListTitleForUri, selectSettingsByChannelId } from 'redux/selectors/comments';
|
||||||
import { DISABLE_COMMENTS_TAG } from 'constants/tags';
|
import { DISABLE_COMMENTS_TAG } from 'constants/tags';
|
||||||
|
import { doToggleAppDrawer } from 'redux/actions/app';
|
||||||
import { getChannelIdFromClaim } from 'util/claim';
|
import { getChannelIdFromClaim } from 'util/claim';
|
||||||
|
|
||||||
import FilePage from './view';
|
import FilePage from './view';
|
||||||
|
@ -53,6 +54,7 @@ const perform = {
|
||||||
doSetContentHistoryItem,
|
doSetContentHistoryItem,
|
||||||
doSetPrimaryUri,
|
doSetPrimaryUri,
|
||||||
clearPosition,
|
clearPosition,
|
||||||
|
doToggleAppDrawer,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withRouter(connect(select, perform)(FilePage));
|
export default withRouter(connect(select, perform)(FilePage));
|
||||||
|
|
|
@ -15,8 +15,8 @@ import CollectionContent from 'component/collectionContentSidebar';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import Empty from 'component/common/empty';
|
import Empty from 'component/common/empty';
|
||||||
import SwipeableDrawer from 'component/swipeableDrawer';
|
import SwipeableDrawer from 'component/swipeableDrawer';
|
||||||
import { DrawerExpandButton } from 'component/swipeableDrawer/view';
|
import DrawerExpandButton from 'component/swipeableDrawerExpand';
|
||||||
import { useIsMobile } from 'effects/use-screensize';
|
import { useIsMobile, useIsMobileLandscape } from 'effects/use-screensize';
|
||||||
|
|
||||||
const CommentsList = lazyImport(() => import('component/commentsList' /* webpackChunkName: "comments" */));
|
const CommentsList = lazyImport(() => import('component/commentsList' /* webpackChunkName: "comments" */));
|
||||||
const PostViewer = lazyImport(() => import('component/postViewer' /* webpackChunkName: "postViewer" */));
|
const PostViewer = lazyImport(() => import('component/postViewer' /* webpackChunkName: "postViewer" */));
|
||||||
|
@ -49,6 +49,7 @@ type Props = {
|
||||||
doSetPrimaryUri: (uri: ?string) => void,
|
doSetPrimaryUri: (uri: ?string) => void,
|
||||||
clearPosition: (uri: string) => void,
|
clearPosition: (uri: string) => void,
|
||||||
doClearPlayingUri: () => void,
|
doClearPlayingUri: () => void,
|
||||||
|
doToggleAppDrawer: () => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function FilePage(props: Props) {
|
export default function FilePage(props: Props) {
|
||||||
|
@ -76,12 +77,11 @@ export default function FilePage(props: Props) {
|
||||||
doSetContentHistoryItem,
|
doSetContentHistoryItem,
|
||||||
doSetPrimaryUri,
|
doSetPrimaryUri,
|
||||||
clearPosition,
|
clearPosition,
|
||||||
|
doToggleAppDrawer,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
const isLandscapeRotated = useIsMobileLandscape();
|
||||||
// Auto-open the drawer on Mobile view if there is a linked comment
|
|
||||||
const [showComments, setShowComments] = React.useState(linkedCommentId);
|
|
||||||
|
|
||||||
const channelSettings = channelId ? settingsByChannelId[channelId] : undefined;
|
const channelSettings = channelId ? settingsByChannelId[channelId] : undefined;
|
||||||
const commentSettingDisabled = channelSettings && !channelSettings.comments_enabled;
|
const commentSettingDisabled = channelSettings && !channelSettings.comments_enabled;
|
||||||
|
@ -99,6 +99,15 @@ export default function FilePage(props: Props) {
|
||||||
return durationInSecs ? isVideoTooShort || almostFinishedPlaying : false;
|
return durationInSecs ? isVideoTooShort || almostFinishedPlaying : false;
|
||||||
}, [audioVideoDuration, fileInfo, position]);
|
}, [audioVideoDuration, fileInfo, position]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (linkedCommentId && isMobile) {
|
||||||
|
doToggleAppDrawer();
|
||||||
|
}
|
||||||
|
// only on mount, otherwise clicking on a comments timestamp and linking it
|
||||||
|
// would trigger the drawer
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
// always refresh file info when entering file page to see if we have the file
|
// always refresh file info when entering file page to see if we have the file
|
||||||
// this could probably be refactored into more direct components now
|
// this could probably be refactored into more direct components now
|
||||||
|
@ -229,17 +238,13 @@ export default function FilePage(props: Props) {
|
||||||
<Empty {...emptyMsgProps} text={__('The creator of this content has disabled comments.')} />
|
<Empty {...emptyMsgProps} text={__('The creator of this content has disabled comments.')} />
|
||||||
) : commentSettingDisabled ? (
|
) : commentSettingDisabled ? (
|
||||||
<Empty {...emptyMsgProps} text={__('This channel has disabled comments on their page.')} />
|
<Empty {...emptyMsgProps} text={__('This channel has disabled comments on their page.')} />
|
||||||
) : isMobile ? (
|
) : isMobile && !isLandscapeRotated ? (
|
||||||
<>
|
<>
|
||||||
<SwipeableDrawer
|
<SwipeableDrawer title={commentsListTitle}>
|
||||||
open={Boolean(showComments)}
|
|
||||||
toggleDrawer={() => setShowComments(!showComments)}
|
|
||||||
title={commentsListTitle}
|
|
||||||
>
|
|
||||||
<CommentsList {...commentsListProps} />
|
<CommentsList {...commentsListProps} />
|
||||||
</SwipeableDrawer>
|
</SwipeableDrawer>
|
||||||
|
|
||||||
<DrawerExpandButton label={commentsListTitle} toggleDrawer={() => setShowComments(!showComments)} />
|
<DrawerExpandButton label={commentsListTitle} />
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<CommentsList {...commentsListProps} />
|
<CommentsList {...commentsListProps} />
|
||||||
|
|
|
@ -38,6 +38,7 @@ import {
|
||||||
selectUpgradeTimer,
|
selectUpgradeTimer,
|
||||||
selectModal,
|
selectModal,
|
||||||
selectAllowAnalytics,
|
selectAllowAnalytics,
|
||||||
|
selectAppDrawerOpen,
|
||||||
} from 'redux/selectors/app';
|
} from 'redux/selectors/app';
|
||||||
import { selectDaemonSettings, selectClientSetting } from 'redux/selectors/settings';
|
import { selectDaemonSettings, selectClientSetting } from 'redux/selectors/settings';
|
||||||
import { selectUser, selectUserVerifiedEmail } from 'redux/selectors/user';
|
import { selectUser, selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||||
|
@ -669,6 +670,7 @@ export function doHandleSyncComplete(error, hasNewData, syncId) {
|
||||||
dispatch(doGetAndPopulatePreferences(syncId));
|
dispatch(doGetAndPopulatePreferences(syncId));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.error('Error in doHandleSyncComplete', error);
|
console.error('Error in doHandleSyncComplete', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -737,14 +739,21 @@ export function doSetIncognito(incognitoEnabled) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const doSetMobilePlayerDimensions = ({ height, width }) => ({
|
|
||||||
type: ACTIONS.SET_MOBILE_PLAYER_DIMENSIONS,
|
|
||||||
data: { heightWidth: { height, width } },
|
|
||||||
});
|
|
||||||
|
|
||||||
export function doSetAdBlockerFound(found) {
|
export function doSetAdBlockerFound(found) {
|
||||||
return {
|
return {
|
||||||
type: ACTIONS.SET_AD_BLOCKER_FOUND,
|
type: ACTIONS.SET_AD_BLOCKER_FOUND,
|
||||||
data: found,
|
data: found,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function doToggleAppDrawer(open) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const state = getState();
|
||||||
|
const isOpen = selectAppDrawerOpen(state);
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: ACTIONS.DRAWER_OPENED,
|
||||||
|
data: !isOpen,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -46,8 +46,8 @@ export type AppState = {
|
||||||
interestedInYoutubeSync: boolean,
|
interestedInYoutubeSync: boolean,
|
||||||
activeChannel: ?string,
|
activeChannel: ?string,
|
||||||
incognito: boolean,
|
incognito: boolean,
|
||||||
mobilePlayerDimensions?: { height: number, width: number },
|
|
||||||
adBlockerFound: ?boolean, // undefined = unknown; true/false = yes/no;
|
adBlockerFound: ?boolean, // undefined = unknown; true/false = yes/no;
|
||||||
|
appDrawerOpen: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultState: AppState = {
|
const defaultState: AppState = {
|
||||||
|
@ -87,8 +87,8 @@ const defaultState: AppState = {
|
||||||
interestedInYoutubeSync: false,
|
interestedInYoutubeSync: false,
|
||||||
activeChannel: undefined,
|
activeChannel: undefined,
|
||||||
incognito: false,
|
incognito: false,
|
||||||
mobilePlayerDimensions: undefined,
|
|
||||||
adBlockerFound: undefined,
|
adBlockerFound: undefined,
|
||||||
|
appDrawerOpen: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// @@router comes from react-router
|
// @@router comes from react-router
|
||||||
|
@ -328,13 +328,6 @@ reducers[ACTIONS.SET_INCOGNITO] = (state, action) => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
reducers[ACTIONS.SET_MOBILE_PLAYER_DIMENSIONS] = (state, action) => {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
mobilePlayerDimensions: action.data.heightWidth,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
reducers[ACTIONS.SET_AD_BLOCKER_FOUND] = (state, action) => {
|
reducers[ACTIONS.SET_AD_BLOCKER_FOUND] = (state, action) => {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
@ -342,6 +335,13 @@ reducers[ACTIONS.SET_AD_BLOCKER_FOUND] = (state, action) => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
reducers[ACTIONS.DRAWER_OPENED] = (state, action) => {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
appDrawerOpen: action.data,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
reducers[ACTIONS.USER_STATE_POPULATE] = (state, action) => {
|
reducers[ACTIONS.USER_STATE_POPULATE] = (state, action) => {
|
||||||
const { welcomeVersion, allowAnalytics } = action.data;
|
const { welcomeVersion, allowAnalytics } = action.data;
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -107,5 +107,5 @@ export const selectActiveChannelStakedLevel = (state) => {
|
||||||
|
|
||||||
export const selectIncognito = (state) => selectState(state).incognito;
|
export const selectIncognito = (state) => selectState(state).incognito;
|
||||||
|
|
||||||
export const selectMobilePlayerDimensions = (state) => selectState(state).mobilePlayerDimensions;
|
|
||||||
export const selectAdBlockerFound = (state) => selectState(state).adBlockerFound;
|
export const selectAdBlockerFound = (state) => selectState(state).adBlockerFound;
|
||||||
|
export const selectAppDrawerOpen = (state) => selectState(state).appDrawerOpen;
|
||||||
|
|
|
@ -22,6 +22,12 @@
|
||||||
@media (max-width: $breakpoint-small) {
|
@media (max-width: $breakpoint-small) {
|
||||||
max-height: var(--mobile-player-max-height);
|
max-height: var(--mobile-player-max-height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
video {
|
||||||
|
@media (max-width: $breakpoint-small) {
|
||||||
|
max-height: var(--mobile-player-max-height);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.content__viewer--secondary {
|
.content__viewer--secondary {
|
||||||
|
@ -330,7 +336,6 @@
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
border: none;
|
border: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
max-height: var(--mobile-player-max-height);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -124,10 +124,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: $breakpoint-small) {
|
@media (max-width: $breakpoint-small) {
|
||||||
.card__main-actions {
|
|
||||||
// padding: var(--spacing-s) var(--spacing-xxs) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.claim-preview--inline {
|
.claim-preview--inline {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
|
@ -203,12 +199,19 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.file-page__video-container {
|
||||||
|
max-height: var(--desktop-portrait-player-max-height);
|
||||||
|
}
|
||||||
|
|
||||||
.file-render {
|
.file-render {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
max-height: var(--inline-player-max-height);
|
|
||||||
|
@media (max-width: $breakpoint-small) {
|
||||||
|
max-height: var(--mobile-player-max-height);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-render--video {
|
.file-render--video {
|
||||||
|
@ -724,6 +727,8 @@ $control-bar-icon-size: 0.8rem;
|
||||||
&.skip {
|
&.skip {
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
max-height: var(--mobile-player-max-height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -761,6 +766,10 @@ $control-bar-icon-size: 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-js.vjs-fullscreen {
|
.video-js.vjs-fullscreen {
|
||||||
|
video {
|
||||||
|
max-height: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
.vjs-button--theater-mode {
|
.vjs-button--theater-mode {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
@ -804,12 +813,6 @@ $control-bar-icon-size: 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-render {
|
.file-render {
|
||||||
.video-js {
|
|
||||||
/*display: flex;*/
|
|
||||||
/*align-items: center;*/
|
|
||||||
/*justify-content: center;*/
|
|
||||||
}
|
|
||||||
|
|
||||||
.vjs-big-play-button {
|
.vjs-big-play-button {
|
||||||
@extend .button--icon;
|
@extend .button--icon;
|
||||||
@extend .button--play;
|
@extend .button--play;
|
||||||
|
|
|
@ -566,6 +566,10 @@ body {
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.file-page__video-container {
|
||||||
|
max-height: unset;
|
||||||
|
}
|
||||||
|
|
||||||
.file-page__recommended {
|
.file-page__recommended {
|
||||||
@media (max-width: $breakpoint-medium) {
|
@media (max-width: $breakpoint-medium) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
backdrop-filter: blur(4px);
|
backdrop-filter: blur(4px);
|
||||||
background-color: var(--mui-background);
|
background-color: var(--mui-background);
|
||||||
border-top: 1px solid var(--color-border);
|
border-top: 1px solid var(--color-border);
|
||||||
|
border-top-left-radius: 12px;
|
||||||
|
border-top-right-radius: 12px;
|
||||||
|
|
||||||
.button--close {
|
.button--close {
|
||||||
top: 2px !important;
|
top: 2px !important;
|
||||||
|
@ -28,6 +30,12 @@
|
||||||
left: calc(50% - 15px);
|
left: calc(50% - 15px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.MuiDrawer-root > .MuiPaper-root {
|
||||||
|
overflow: visible;
|
||||||
|
color: var(--color-text);
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
.swipeable-drawer__header-content {
|
.swipeable-drawer__header-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -102,16 +110,3 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
|
|
|
@ -140,11 +140,6 @@ $control-bar-icon-size: 0.8rem;
|
||||||
transition: 0.1s;
|
transition: 0.1s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.vjs-user-active.vjs-playing {
|
|
||||||
.vjs-control-bar {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Button glow
|
// Button glow
|
||||||
|
|
|
@ -76,9 +76,9 @@
|
||||||
--header-height-mobile: 56px;
|
--header-height-mobile: 56px;
|
||||||
|
|
||||||
// Inline Player
|
// Inline Player
|
||||||
// --inline-player-max-height: calc(100vh - var(--header-height) - var(--spacing-l) * 2);
|
|
||||||
--inline-player-max-height: calc(100vh - var(--header-height) - var(--spacing-l) * 4);
|
--inline-player-max-height: calc(100vh - var(--header-height) - var(--spacing-l) * 4);
|
||||||
--mobile-player-max-height: 50vh;
|
--mobile-player-max-height: 70vh;
|
||||||
|
--desktop-portrait-player-max-height: 80vh;
|
||||||
|
|
||||||
// Card
|
// Card
|
||||||
--card-radius: var(--border-radius);
|
--card-radius: var(--border-radius);
|
||||||
|
|
|
@ -124,6 +124,17 @@ export function getClaimTitle(claim: ?Claim) {
|
||||||
return metadata && metadata.title;
|
return metadata && metadata.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getClaimVideoInfo(claim: ?Claim) {
|
||||||
|
const metadata = getClaimMetadata(claim);
|
||||||
|
// $FlowFixMe
|
||||||
|
return metadata && metadata.video;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getVideoClaimAspectRatio(claim: ?Claim) {
|
||||||
|
const { width, height } = getClaimVideoInfo(claim) || {};
|
||||||
|
return width && height ? height / width : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export const isStreamPlaceholderClaim = (claim: ?StreamClaim) => {
|
export const isStreamPlaceholderClaim = (claim: ?StreamClaim) => {
|
||||||
return claim ? Boolean(claim.value_type === 'stream' && !claim.value.source) : false;
|
return claim ? Boolean(claim.value_type === 'stream' && !claim.value.source) : false;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue