Refactor fileRenderIninitiator component
- Only pass necessary props - Created new initialize play redux action - Removed deprecated ~file app download~ keyboard functionality, moved preventDefault to videojs keyboard events to prevent ~space~ moving down the page
This commit is contained in:
parent
a84ebbc68f
commit
0c47f1daa9
4 changed files with 97 additions and 114 deletions
|
@ -1,9 +1,8 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { doPlayUri, doSetPlayingUri, doSetPrimaryUri } from 'redux/actions/content';
|
||||
import { selectThumbnailForUri, makeSelectClaimForUri, makeSelectClaimWasPurchased } from 'redux/selectors/claims';
|
||||
import { doUriInitiatePlay } from 'redux/actions/content';
|
||||
import { selectThumbnailForUri, makeSelectClaimWasPurchased } from 'redux/selectors/claims';
|
||||
import { makeSelectFileInfoForUri } from 'redux/selectors/file_info';
|
||||
import * as SETTINGS from 'constants/settings';
|
||||
import * as COLLECTIONS_CONSTS from 'constants/collections';
|
||||
import { selectCostInfoForUri } from 'lbryinc';
|
||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||
import { selectClientSetting } from 'redux/selectors/settings';
|
||||
|
@ -15,35 +14,26 @@ import {
|
|||
makeSelectFileRenderModeForUri,
|
||||
} from 'redux/selectors/content';
|
||||
import FileRenderInitiator from './view';
|
||||
import { doAnaltyicsPurchaseEvent } from 'redux/actions/app';
|
||||
|
||||
const select = (state, props) => {
|
||||
const { search } = props.location;
|
||||
const urlParams = new URLSearchParams(search);
|
||||
const collectionId = urlParams.get(COLLECTIONS_CONSTS.COLLECTION_ID);
|
||||
const { uri } = props;
|
||||
|
||||
return {
|
||||
claimThumbnail: selectThumbnailForUri(state, props.uri),
|
||||
fileInfo: makeSelectFileInfoForUri(props.uri)(state),
|
||||
obscurePreview: selectShouldObscurePreviewForUri(state, props.uri),
|
||||
isPlaying: makeSelectIsPlaying(props.uri)(state),
|
||||
insufficientCredits: selectInsufficientCreditsForUri(state, props.uri),
|
||||
claimThumbnail: selectThumbnailForUri(state, uri),
|
||||
fileInfo: makeSelectFileInfoForUri(uri)(state),
|
||||
obscurePreview: selectShouldObscurePreviewForUri(state, uri),
|
||||
isPlaying: makeSelectIsPlaying(uri)(state),
|
||||
insufficientCredits: selectInsufficientCreditsForUri(state, uri),
|
||||
autoplay: selectClientSetting(state, SETTINGS.AUTOPLAY_MEDIA),
|
||||
costInfo: selectCostInfoForUri(state, props.uri),
|
||||
renderMode: makeSelectFileRenderModeForUri(props.uri)(state),
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
claimWasPurchased: makeSelectClaimWasPurchased(props.uri)(state),
|
||||
costInfo: selectCostInfoForUri(state, uri),
|
||||
renderMode: makeSelectFileRenderModeForUri(uri)(state),
|
||||
claimWasPurchased: makeSelectClaimWasPurchased(uri)(state),
|
||||
authenticated: selectUserVerifiedEmail(state),
|
||||
collectionId,
|
||||
};
|
||||
};
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
play: (uri, collectionId, isPlayable) => {
|
||||
dispatch(doSetPrimaryUri(uri));
|
||||
if (isPlayable) dispatch(doSetPlayingUri({ uri, collectionId }));
|
||||
dispatch(doPlayUri(uri, undefined, undefined, (fileInfo) => dispatch(doAnaltyicsPurchaseEvent(fileInfo))));
|
||||
},
|
||||
});
|
||||
const perform = {
|
||||
doUriInitiatePlay,
|
||||
};
|
||||
|
||||
export default withRouter(connect(select, perform)(FileRenderInitiator));
|
||||
|
|
|
@ -3,20 +3,18 @@
|
|||
// The actual viewer for a file exists in TextViewer and FileRenderFloating
|
||||
// They can't exist in one component because we need to handle/listen for the start of a new file view
|
||||
// while a file is currently being viewed
|
||||
import React, { useEffect, useCallback } from 'react';
|
||||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
import * as PAGES from 'constants/pages';
|
||||
import * as RENDER_MODES from 'constants/file_render_modes';
|
||||
import * as KEYCODES from 'constants/keycodes';
|
||||
import Button from 'component/button';
|
||||
import isUserTyping from 'util/detect-typing';
|
||||
import { getThumbnailCdnUrl } from 'util/thumbnail';
|
||||
import Nag from 'component/common/nag';
|
||||
// $FlowFixMe cannot resolve ...
|
||||
import FileRenderPlaceholder from 'static/img/fileRenderPlaceholder.png';
|
||||
import * as COLLECTIONS_CONSTS from 'constants/collections';
|
||||
|
||||
type Props = {
|
||||
play: (string, string, boolean) => void,
|
||||
isPlaying: boolean,
|
||||
fileInfo: FileListItem,
|
||||
uri: string,
|
||||
|
@ -29,16 +27,14 @@ type Props = {
|
|||
costInfo: any,
|
||||
inline: boolean,
|
||||
renderMode: string,
|
||||
claim: StreamClaim,
|
||||
claimWasPurchased: boolean,
|
||||
authenticated: boolean,
|
||||
videoTheaterMode: boolean,
|
||||
collectionId: string,
|
||||
doUriInitiatePlay: (uri: string, collectionId: ?string, isPlayable: boolean) => void,
|
||||
};
|
||||
|
||||
export default function FileRenderInitiator(props: Props) {
|
||||
const {
|
||||
play,
|
||||
isPlaying,
|
||||
fileInfo,
|
||||
uri,
|
||||
|
@ -47,111 +43,88 @@ export default function FileRenderInitiator(props: Props) {
|
|||
history,
|
||||
location,
|
||||
claimThumbnail,
|
||||
autoplay,
|
||||
renderMode,
|
||||
costInfo,
|
||||
claimWasPurchased,
|
||||
authenticated,
|
||||
videoTheaterMode,
|
||||
collectionId,
|
||||
doUriInitiatePlay,
|
||||
} = props;
|
||||
|
||||
const containerRef = React.useRef<any>();
|
||||
|
||||
const [thumbnail, setThumbnail] = React.useState(FileRenderPlaceholder);
|
||||
|
||||
const { search, href, state: locationState } = location;
|
||||
const urlParams = search && new URLSearchParams(search);
|
||||
const collectionId = urlParams && urlParams.get(COLLECTIONS_CONSTS.COLLECTION_ID);
|
||||
|
||||
// check if there is a time or autoplay parameter, if so force autoplay
|
||||
const urlTimeParam = location && location.href && location.href.indexOf('t=') > -1;
|
||||
const forceAutoplayParam = location && location.state && location.state.forceAutoplay;
|
||||
const autoplay = forceAutoplayParam || urlTimeParam || props.autoplay;
|
||||
const urlTimeParam = href && href.indexOf('t=') > -1;
|
||||
const forceAutoplayParam = locationState && locationState.forceAutoplay;
|
||||
const shouldAutoplay = forceAutoplayParam || urlTimeParam || autoplay;
|
||||
|
||||
const isFree = costInfo && costInfo.cost === 0;
|
||||
const canViewFile = isFree || claimWasPurchased;
|
||||
const fileStatus = fileInfo && fileInfo.status;
|
||||
const isPlayable = RENDER_MODES.FLOATING_MODES.includes(renderMode);
|
||||
const isText = RENDER_MODES.TEXT_MODES.includes(renderMode);
|
||||
const [thumbnail, setThumbnail] = React.useState(FileRenderPlaceholder);
|
||||
const containerRef = React.useRef<any>();
|
||||
|
||||
useEffect(() => {
|
||||
if (claimThumbnail) {
|
||||
setTimeout(() => {
|
||||
let newThumbnail = claimThumbnail;
|
||||
|
||||
// @if TARGET='web'
|
||||
if (
|
||||
containerRef.current &&
|
||||
containerRef.current.parentElement &&
|
||||
containerRef.current.parentElement.offsetWidth
|
||||
) {
|
||||
const w = containerRef.current.parentElement.offsetWidth;
|
||||
newThumbnail = getThumbnailCdnUrl({ thumbnail: newThumbnail, width: w, height: w });
|
||||
}
|
||||
// @endif
|
||||
|
||||
if (newThumbnail !== thumbnail) {
|
||||
setThumbnail(newThumbnail);
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
}, [claimThumbnail, thumbnail]);
|
||||
const renderUnsupported = RENDER_MODES.UNSUPPORTED_IN_THIS_APP.includes(renderMode);
|
||||
const disabled = renderUnsupported || (!fileInfo && insufficientCredits && !claimWasPurchased);
|
||||
const shouldRedirect = !authenticated && !isFree;
|
||||
|
||||
function doAuthRedirect() {
|
||||
history.push(`/$/${PAGES.AUTH}?redirect=${encodeURIComponent(location.pathname)}`);
|
||||
}
|
||||
|
||||
// Wrap this in useCallback because we need to use it to the keyboard effect
|
||||
React.useEffect(() => {
|
||||
if (!claimThumbnail) return;
|
||||
|
||||
setTimeout(() => {
|
||||
let newThumbnail = claimThumbnail;
|
||||
|
||||
if (
|
||||
containerRef.current &&
|
||||
containerRef.current.parentElement &&
|
||||
containerRef.current.parentElement.offsetWidth
|
||||
) {
|
||||
const w = containerRef.current.parentElement.offsetWidth;
|
||||
newThumbnail = getThumbnailCdnUrl({ thumbnail: newThumbnail, width: w, height: w });
|
||||
}
|
||||
|
||||
if (newThumbnail !== thumbnail) {
|
||||
setThumbnail(newThumbnail);
|
||||
}
|
||||
}, 200);
|
||||
}, [claimThumbnail, thumbnail]);
|
||||
|
||||
// Wrap this in useCallback because we need to use it to the view effect
|
||||
// If we don't a new instance will be created for every render and react will think the dependencies have changed, which will add/remove the listener for every render
|
||||
const viewFile = useCallback(
|
||||
(e?: SyntheticInputEvent<*> | KeyboardEvent) => {
|
||||
if (e) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
const viewFile = React.useCallback(() => {
|
||||
doUriInitiatePlay(uri, collectionId, isPlayable);
|
||||
}, [collectionId, doUriInitiatePlay, isPlayable, uri]);
|
||||
|
||||
play(uri, collectionId, isPlayable);
|
||||
},
|
||||
[play, uri, isPlayable, collectionId]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// This is just for beginning to download a file
|
||||
// Play/Pause/Fullscreen will be handled by the respective viewers because not every file type should behave the same
|
||||
function handleKeyDown(e: KeyboardEvent) {
|
||||
if (!isUserTyping() && e.keyCode === KEYCODES.SPACEBAR) {
|
||||
e.preventDefault();
|
||||
|
||||
if (!isPlaying || fileStatus === 'stopped') {
|
||||
viewFile(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => {
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
}, [isPlaying, fileStatus, viewFile]);
|
||||
|
||||
useEffect(() => {
|
||||
React.useEffect(() => {
|
||||
const videoOnPage = document.querySelector('video');
|
||||
|
||||
if (
|
||||
(canViewFile || forceAutoplayParam) &&
|
||||
((autoplay && (!videoOnPage || forceAutoplayParam) && isPlayable) ||
|
||||
((shouldAutoplay && (!videoOnPage || forceAutoplayParam) && isPlayable) ||
|
||||
RENDER_MODES.AUTO_RENDER_MODES.includes(renderMode))
|
||||
) {
|
||||
viewFile();
|
||||
}
|
||||
}, [autoplay, canViewFile, forceAutoplayParam, isPlayable, renderMode, viewFile]);
|
||||
}, [canViewFile, forceAutoplayParam, isPlayable, renderMode, shouldAutoplay, viewFile]);
|
||||
|
||||
/*
|
||||
once content is playing, let the appropriate <FileRender> take care of it...
|
||||
but for playables, always render so area can be used to fill with floating player
|
||||
*/
|
||||
if (isPlaying && !isPlayable) {
|
||||
if (canViewFile && !collectionId) {
|
||||
return null;
|
||||
}
|
||||
if (isPlaying && !isPlayable && canViewFile && !collectionId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const showAppNag = IS_WEB && RENDER_MODES.UNSUPPORTED_IN_THIS_APP.includes(renderMode);
|
||||
const disabled = showAppNag || (!fileInfo && insufficientCredits && !claimWasPurchased);
|
||||
const shouldRedirect = IS_WEB && !authenticated && !isFree;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
|
@ -164,7 +137,7 @@ export default function FileRenderInitiator(props: Props) {
|
|||
'card__media--nsfw': obscurePreview,
|
||||
})}
|
||||
>
|
||||
{showAppNag && (
|
||||
{renderUnsupported ? (
|
||||
<Nag
|
||||
type="helpful"
|
||||
inline
|
||||
|
@ -172,16 +145,19 @@ export default function FileRenderInitiator(props: Props) {
|
|||
actionText={__('Get the App')}
|
||||
href="https://lbry.com/get"
|
||||
/>
|
||||
) : (
|
||||
!claimWasPurchased &&
|
||||
insufficientCredits && (
|
||||
<Nag
|
||||
type="helpful"
|
||||
inline
|
||||
message={__('You need more Credits to purchase this.')}
|
||||
actionText={__('Open Rewards')}
|
||||
onClick={() => history.push(`/$/${PAGES.REWARDS}`)}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
{!claimWasPurchased && insufficientCredits && !showAppNag && (
|
||||
<Nag
|
||||
type="helpful"
|
||||
inline
|
||||
message={__('You need more Credits to purchase this.')}
|
||||
actionText={__('Open Rewards')}
|
||||
onClick={() => history.push(`/$/${PAGES.REWARDS}`)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!disabled && (
|
||||
<Button
|
||||
requiresAuth={shouldRedirect}
|
||||
|
|
|
@ -131,7 +131,12 @@ const VideoJsKeyboardShorcuts = ({
|
|||
// eslint-disable-next-line flowtype/no-types-missing-file-annotation
|
||||
function handleSingleKeyActions(e: KeyboardEvent, playerRef, containerRef) {
|
||||
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) return;
|
||||
if (e.keyCode === KEYCODES.SPACEBAR || e.keyCode === KEYCODES.K) togglePlay(containerRef);
|
||||
|
||||
if (e.keyCode === KEYCODES.SPACEBAR || e.keyCode === KEYCODES.K) {
|
||||
e.preventDefault();
|
||||
togglePlay(containerRef);
|
||||
}
|
||||
|
||||
if (e.keyCode === KEYCODES.F) toggleFullscreen(playerRef);
|
||||
if (e.keyCode === KEYCODES.M) toggleMute(containerRef);
|
||||
if (e.keyCode === KEYCODES.UP) volumeUp(e, playerRef);
|
||||
|
@ -143,7 +148,7 @@ const VideoJsKeyboardShorcuts = ({
|
|||
if (e.keyCode === KEYCODES.LEFT) seekVideo(-SEEK_STEP_5, playerRef, containerRef);
|
||||
}
|
||||
|
||||
var curried_function = function(playerRef: any, containerRef: any) {
|
||||
var curried_function = function (playerRef: any, containerRef: any) {
|
||||
return function curried_func(e: any) {
|
||||
handleKeyDown(e, playerRef, containerRef);
|
||||
};
|
||||
|
|
|
@ -4,7 +4,7 @@ import * as MODALS from 'constants/modal_types';
|
|||
// @if TARGET='app'
|
||||
import { ipcRenderer } from 'electron';
|
||||
// @endif
|
||||
import { doOpenModal, doAnalyticsView } from 'redux/actions/app';
|
||||
import { doOpenModal, doAnalyticsView, doAnaltyicsPurchaseEvent } from 'redux/actions/app';
|
||||
import { makeSelectClaimForUri, selectClaimIsMineForUri, makeSelectClaimWasPurchased } from 'redux/selectors/claims';
|
||||
import {
|
||||
makeSelectFileInfoForUri,
|
||||
|
@ -148,6 +148,18 @@ export function doDownloadUri(uri: string) {
|
|||
return (dispatch: Dispatch) => dispatch(doPlayUri(uri, false, true, () => dispatch(doAnalyticsView(uri))));
|
||||
}
|
||||
|
||||
export function doUriInitiatePlay(uri: string, collectionId?: string, isPlayable?: boolean) {
|
||||
return (dispatch: Dispatch) => {
|
||||
dispatch(doSetPrimaryUri(uri));
|
||||
|
||||
if (isPlayable) {
|
||||
dispatch(doSetPlayingUri({ uri, collectionId }));
|
||||
}
|
||||
|
||||
dispatch(doPlayUri(uri, false, true, (fileInfo) => dispatch(doAnaltyicsPurchaseEvent(fileInfo))));
|
||||
};
|
||||
}
|
||||
|
||||
export function doPlayUri(
|
||||
uri: string,
|
||||
skipCostCheck: boolean = false,
|
||||
|
|
Loading…
Reference in a new issue