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:
Rafael 2022-02-01 17:29:00 -03:00 committed by Thomas Zarebczan
parent a84ebbc68f
commit 0c47f1daa9
4 changed files with 97 additions and 114 deletions

View file

@ -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));

View file

@ -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}

View file

@ -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);
};

View file

@ -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,