Merge pull request #2492 from zxawry/thumbnail-slider

Thumbnail slider
This commit is contained in:
Sean Yesmunt 2019-05-21 18:00:29 -04:00 committed by GitHub
commit 957446ddad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 135 additions and 6 deletions

View file

@ -407,6 +407,7 @@ class PublishForm extends React.PureComponent<Props> {
</header>
<SelectThumbnail
filePath={filePath}
thumbnailPath={thumbnailPath}
thumbnail={thumbnail}
uploadThumbnailStatus={uploadThumbnailStatus}

View file

@ -2,6 +2,7 @@
import * as MODALS from 'constants/modal_types';
import { THUMBNAIL_STATUSES } from 'lbry-redux';
import * as React from 'react';
import getMediaType from 'util/get-media-type';
import { FormField } from 'component/common/form';
import FileSelector from 'component/common/file-selector';
import Button from 'component/button';
@ -9,6 +10,7 @@ import ThumbnailMissingImage from './thumbnail-missing.png';
import ThumbnailBrokenImage from './thumbnail-broken.png';
type Props = {
filePath: ?string,
thumbnail: ?string,
formDisabled: boolean,
uploadThumbnailStatus: string,
@ -50,6 +52,7 @@ class SelectThumbnail extends React.PureComponent<Props, State> {
render() {
const {
filePath,
thumbnail,
formDisabled,
uploadThumbnailStatus: status,
@ -61,6 +64,8 @@ class SelectThumbnail extends React.PureComponent<Props, State> {
const { thumbnailError } = this.state;
const isSupportedVideo = getMediaType(null, filePath) === 'video';
let thumbnailSrc;
if (!thumbnail) {
thumbnailSrc = ThumbnailMissingImage;
@ -141,9 +146,16 @@ class SelectThumbnail extends React.PureComponent<Props, State> {
<div className="card__actions">
<Button
button="link"
label={__('Or enter a URL manually')}
label={__('Enter a thumbnail URL')}
onClick={() => updatePublishForm({ uploadThumbnailStatus: THUMBNAIL_STATUSES.MANUAL })}
/>
{isSupportedVideo && (
<Button
button="link"
label={__('Take a snapshot from your video')}
onClick={() => openModal(MODALS.AUTO_GENERATE_THUMBNAIL, { filePath })}
/>
)}
</div>
)}

View file

@ -3,6 +3,7 @@ export const CONFIRM_EXTERNAL_RESOURCE = 'confirm_external_resource';
export const INCOMPATIBLE_DAEMON = 'incompatible_daemon';
export const FILE_TIMEOUT = 'file_timeout';
export const DOWNLOADING = 'downloading';
export const AUTO_GENERATE_THUMBNAIL = 'auto_generate_thumbnail';
export const AUTO_UPDATE_DOWNLOADED = 'auto_update_downloaded';
export const AUTO_UPDATE_CONFIRM = 'auto_update_confirm';
export const ERROR = 'error';

View file

@ -0,0 +1,16 @@
import { connect } from 'react-redux';
import { doHideModal } from 'redux/actions/app';
import { doUploadThumbnail } from 'redux/actions/publish';
import { doToast } from 'lbry-redux';
import ModalAutoGenerateThumbnail from './view';
const perform = dispatch => ({
closeModal: () => dispatch(doHideModal()),
upload: buffer => dispatch(doUploadThumbnail(null, buffer)),
showToast: options => dispatch(doToast(options)),
});
export default connect(
null,
perform
)(ModalAutoGenerateThumbnail);

View file

@ -0,0 +1,84 @@
// @flow
import React, { useRef } from 'react';
import { Modal } from 'modal/modal';
type Props = {
upload: Buffer => void,
filePath: string,
closeModal: () => void,
showToast: ({}) => void,
};
function ModalAutoGenerateThumbnail(props: Props) {
const { closeModal, filePath, upload, showToast } = props;
const playerRef = useRef();
let src = filePath.replace(/\\/g, '/');
src = src[0] !== '/' ? `/${src}` : src;
src = encodeURI(`file://${src}`).replace(/[?#]/g, encodeURIComponent);
function uploadImage() {
const imageBuffer = captureSnapshot();
if (imageBuffer) {
upload(imageBuffer);
closeModal();
} else {
onError();
}
}
function captureSnapshot(): ?Buffer {
const player = playerRef.current;
if (!player) {
return;
}
const canvas = document.createElement('canvas');
canvas.width = player.videoWidth;
canvas.height = player.videoHeight;
const context = canvas.getContext('2d');
context.drawImage(player, 0, 0, canvas.width, canvas.height);
const dataURL = canvas.toDataURL();
const rawData = dataURL.replace(/data:image\/\w+;base64,/i, '');
canvas.remove();
return Buffer.from(rawData, 'base64');
}
function resize(): void {
const player = playerRef.current;
if (!player) {
return;
}
console.log('resized');
const fixedWidth = 450;
const videoWidth = player.videoWidth;
const videoHeight = player.videoHeight;
player.width = fixedWidth;
player.height = Math.floor(videoHeight * (fixedWidth / videoWidth));
}
function onError(): void {
showToast({ isError: true, message: __("Something didn't work. Please try again.") });
}
return (
<Modal
isOpen
title={__('Upload Thumbnail')}
contentLabel={__('Confirm Thumbnail Upload')}
type="confirm"
confirmButtonLabel={__('Upload')}
onConfirmed={uploadImage}
onAborted={closeModal}
>
<section className="card__content">
<p className="card__subtitle">{__('Pause at any time to select a thumbnail from your video')}.</p>
<video ref={playerRef} src={src} onLoadedMetadata={resize} onError={onError} controls />
</section>
</Modal>
);
}
export default ModalAutoGenerateThumbnail;

View file

@ -4,6 +4,7 @@ import * as MODALS from 'constants/modal_types';
import ModalError from 'modal/modalError';
import ModalAuthFailure from 'modal/modalAuthFailure';
import ModalDownloading from 'modal/modalDownloading';
import ModalAutoGenerateThumbnail from 'modal/modalAutoGenerateThumbnail';
import ModalAutoUpdateDownloaded from 'modal/modalAutoUpdateDownloaded';
import ModalAutoUpdateConfirm from 'modal/modalAutoUpdateConfirm';
import ModalUpgrade from 'modal/modalUpgrade';
@ -51,6 +52,8 @@ function ModalRouter(props: Props) {
return <ModalUpgrade {...modalProps} />;
case MODALS.DOWNLOADING:
return <ModalDownloading {...modalProps} />;
case MODALS.AUTO_GENERATE_THUMBNAIL:
return <ModalAutoGenerateThumbnail {...modalProps} />;
case MODALS.AUTO_UPDATE_DOWNLOADED:
return <ModalAutoUpdateDownloaded {...modalProps} />;
case MODALS.AUTO_UPDATE_CONFIRM:

View file

@ -68,10 +68,22 @@ export const doUpdatePublishForm = (publishFormValue: UpdatePublishFormData) =>
data: { ...publishFormValue },
});
export const doUploadThumbnail = (filePath: string) => (dispatch: Dispatch) => {
const thumbnail = fs.readFileSync(filePath);
const fileExt = path.extname(filePath);
const fileName = path.basename(filePath);
export const doUploadThumbnail = (filePath: string, thumbnailBuffer: Uint8Array) => (dispatch: Dispatch) => {
let thumbnail, fileExt, fileName, fileType;
if (filePath) {
thumbnail = fs.readFileSync(filePath);
fileExt = path.extname(filePath);
fileName = path.basename(filePath);
fileType = `image/${fileExt.slice(1)}`;
} else if (thumbnailBuffer) {
thumbnail = thumbnailBuffer;
fileExt = '.png';
fileName = 'thumbnail.png';
fileType = 'image/png';
} else {
return null;
}
const makeid = () => {
let text = '';
@ -102,7 +114,7 @@ export const doUploadThumbnail = (filePath: string) => (dispatch: Dispatch) => {
const data = new FormData();
const name = makeid();
const file = new File([thumbnail], fileName, { type: `image/${fileExt.slice(1)}` });
const file = new File([thumbnail], fileName, { type: fileType });
data.append('name', name);
data.append('file', file);