added slider to select thumb
This commit is contained in:
parent
d3dc86fd1f
commit
41b5a05a55
7 changed files with 112 additions and 164 deletions
|
@ -283,11 +283,12 @@ a, a:visited {
|
|||
|
||||
/* ERROR MESSAGES */
|
||||
|
||||
.info-message--success, .info-message--failure {
|
||||
.info-message, .info-message--success, .info-message--failure {
|
||||
|
||||
font-size: medium;
|
||||
margin: 0px;
|
||||
padding: 0.3em;
|
||||
color: #9b9b9b;
|
||||
}
|
||||
|
||||
.info-message--success {
|
||||
|
@ -603,33 +604,6 @@ table {
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
/* ---- grid items ---- */
|
||||
|
||||
.grid-item {
|
||||
width: calc(33% - 2rem);
|
||||
padding: 0px;
|
||||
margin: 1rem;
|
||||
float: left;
|
||||
border: 0.5px solid white;
|
||||
}
|
||||
|
||||
.grid-item-image {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.grid-item-details {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.grid-item-details-text {
|
||||
font-size: medium;
|
||||
margin: 0px;
|
||||
text-align: center;
|
||||
padding: 1em 0px 1em 0px;
|
||||
width: 100%;
|
||||
.slider {
|
||||
width: 100%
|
||||
}
|
||||
|
|
|
@ -75,23 +75,13 @@ export function toggleMetadataInputs (showMetadataInputs) {
|
|||
export function updateThumbnailClaim (claim, url) {
|
||||
return {
|
||||
type: actions.THUMBNAIL_CLAIM_UPDATE,
|
||||
claim,
|
||||
url,
|
||||
};
|
||||
};
|
||||
|
||||
export function updateThumbnailFileOptions (fileOne, fileTwo, fileThree) {
|
||||
return {
|
||||
type: actions.THUMBNAIL_FILES_UPDATE,
|
||||
fileOne,
|
||||
fileTwo,
|
||||
fileThree,
|
||||
data: { claim, url },
|
||||
};
|
||||
};
|
||||
|
||||
export function updateThumbnailSelectedFile (file) {
|
||||
return {
|
||||
type: actions.THUMBNAIL_FILE_SELECT,
|
||||
file,
|
||||
data: file,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -34,10 +34,10 @@ class Preview extends React.Component {
|
|||
render () {
|
||||
return (
|
||||
<img
|
||||
id="dropzone-preview"
|
||||
id='dropzone-preview'
|
||||
src={this.state.imgSource}
|
||||
className={this.props.dimPreview ? 'dim' : ''}
|
||||
alt="publish preview"
|
||||
alt='publish preview'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,5 +8,4 @@ export const ERROR_UPDATE = 'ERROR_UPDATE';
|
|||
export const SELECTED_CHANNEL_UPDATE = 'SELECTED_CHANNEL_UPDATE';
|
||||
export const TOGGLE_METADATA_INPUTS = 'TOGGLE_METADATA_INPUTS';
|
||||
export const THUMBNAIL_CLAIM_UPDATE = 'THUMBNAIL_CLAIM_UPDATE';
|
||||
export const THUMBNAIL_FILES_UPDATE = 'THUMBNAIL_FILES_UPDATE';
|
||||
export const THUMBNAIL_FILE_SELECT = 'THUMBNAIL_FILE_SELECT';
|
||||
|
|
|
@ -1,30 +1,25 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { updateThumbnailClaim, updateThumbnailFileOptions, updateThumbnailSelectedFile } from 'actions/publish';
|
||||
import { updateThumbnailClaim, updateThumbnailSelectedFile } from 'actions/publish';
|
||||
import View from './view';
|
||||
|
||||
const mapStateToProps = ({ publish, site }) => {
|
||||
return {
|
||||
host : site.host,
|
||||
host : site.host,
|
||||
// file props
|
||||
publishFile : publish.file,
|
||||
publishClaim : publish.claim,
|
||||
file : publish.file,
|
||||
claim : publish.claim,
|
||||
// channel props
|
||||
channel : publish.thumbnail.channel,
|
||||
claim : publish.thumbnail.claim,
|
||||
url : publish.thumbnail.url,
|
||||
potentialFiles: publish.thumbnail.potentialFiles,
|
||||
selectedFile : publish.thumbnail.selectedFile,
|
||||
thumbnailChannel: publish.thumbnail.channel,
|
||||
thumbnailClaim : publish.thumbnail.claim,
|
||||
thumbnailFile : publish.thumbnail.selectedFile,
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
onThumbnailClaimChange: (claim, url) => {
|
||||
onThumbnailChange: (claim, url) => {
|
||||
dispatch(updateThumbnailClaim(claim, url));
|
||||
},
|
||||
onThumbnailFileOptionsChange: (fileOne, fileTwo, fileThree) => {
|
||||
dispatch(updateThumbnailFileOptions(fileOne, fileTwo, fileThree));
|
||||
},
|
||||
onThumbnailFileSelect: (file) => {
|
||||
dispatch(updateThumbnailSelectedFile(file));
|
||||
},
|
||||
|
|
|
@ -1,117 +1,113 @@
|
|||
import React from 'react';
|
||||
|
||||
const ThumbnailPreview = ({dataUrl}) => {
|
||||
const divStyle = {
|
||||
width : '30%',
|
||||
margin : '1%',
|
||||
display: 'inline-block',
|
||||
border : 'solid 1px black',
|
||||
};
|
||||
const imageStyle = {
|
||||
width : '100%',
|
||||
display: 'block',
|
||||
};
|
||||
return (
|
||||
<div style={divStyle}>
|
||||
{ dataUrl ? (
|
||||
<img style={imageStyle} src={dataUrl} alt='image preview here' />
|
||||
) : (
|
||||
<p>loading...</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
class PublishThumbnailInput extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
videoPreviewSrc: null,
|
||||
thumbnailError : null,
|
||||
thumbnailInput : '',
|
||||
videoSource : null,
|
||||
error : null,
|
||||
sliderMinRange: 1,
|
||||
sliderMaxRange: null,
|
||||
sliderValue : null,
|
||||
};
|
||||
this.handleSliderChange = this.handleSliderChange.bind(this);
|
||||
this.handleVideoLoadedData = this.handleVideoLoadedData.bind(this);
|
||||
this.setThumbnailWithSnapshot = this.setThumbnailWithSnapshot.bind(this);
|
||||
}
|
||||
componentDidMount () {
|
||||
this.setClaimAndThumbailUrl(this.props.publishClaim);
|
||||
this.previewThumbnails(this.props.publishFile);
|
||||
this.setThumbnailClaimAndUrl();
|
||||
this.setVideoSource();
|
||||
}
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (nextProps.publishClaim !== this.props.publishClaim) {
|
||||
console.log(nextProps.publishClaim, this.props.publishClaim);
|
||||
this.setClaimAndThumbailUrl(nextProps.publishClaim);
|
||||
}
|
||||
setThumbnailClaimAndUrl () {
|
||||
const { claim, host, thumbnailChannel } = this.props;
|
||||
const url = `${host}/${thumbnailChannel}/${claim}-thumb.png`;
|
||||
this.props.onThumbnailChange(`${claim}-thumb`, url);
|
||||
}
|
||||
previewThumbnails (videoFile) {
|
||||
console.log('video file:', videoFile);
|
||||
this.loadFileAndReturnThumbnails(videoFile)
|
||||
.then((thumbnail) => {
|
||||
this.selectVideoThumb(thumbnail);
|
||||
this.setPossibleThumbnailFiles(thumbnail, thumbnail, thumbnail);
|
||||
})
|
||||
.catch(error => {
|
||||
console.log('error:', error);
|
||||
this.setState({error: error.message});
|
||||
});
|
||||
setVideoSource () {
|
||||
const { file } = this.props;
|
||||
const previewReader = new FileReader();
|
||||
previewReader.readAsDataURL(file);
|
||||
previewReader.onloadend = () => {
|
||||
this.setState({videoSource: previewReader.result});
|
||||
};
|
||||
}
|
||||
loadFileAndReturnThumbnails (file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var fileReader = new FileReader();
|
||||
fileReader.onload = () => {
|
||||
console.log('file loaded');
|
||||
const blob = new Blob([fileReader.result], {type: file.type});
|
||||
const url = URL.createObjectURL(blob);
|
||||
let video = document.createElement('video');
|
||||
const snapShot = () => {
|
||||
let canvas = document.createElement('canvas');
|
||||
canvas.width = video.videoWidth;
|
||||
canvas.height = video.videoHeight;
|
||||
canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);
|
||||
const imageDataUrl = canvas.toDataURL();
|
||||
const success = imageDataUrl.length > 1000;
|
||||
if (success) {
|
||||
return imageDataUrl;
|
||||
}
|
||||
return success;
|
||||
};
|
||||
const loadedata = () => {
|
||||
console.log('loadeddata');
|
||||
console.log('readyState', video.readyState);
|
||||
const duration = video.duration;
|
||||
console.log('readyState', duration);
|
||||
const thumb = snapShot();
|
||||
resolve(thumb);
|
||||
};
|
||||
video.addEventListener('loadeddata', loadedata);
|
||||
video.preload = 'metadata';
|
||||
video.src = url;
|
||||
// Load video in Safari / IE11
|
||||
video.muted = true;
|
||||
video.playsInline = true;
|
||||
video.play();
|
||||
};
|
||||
fileReader.readAsArrayBuffer(file);
|
||||
handleVideoLoadedData (event) {
|
||||
const duration = event.target.duration;
|
||||
// set the slider
|
||||
this.setState({
|
||||
sliderMaxRange: duration * 100,
|
||||
sliderValue : duration * 100 / 2,
|
||||
});
|
||||
// update the current time of the video
|
||||
let video = document.getElementById('video-thumb-player');
|
||||
video.currentTime = duration / 2;
|
||||
}
|
||||
selectVideoThumb (dataUrl) {
|
||||
// update this.props.selectedFile
|
||||
this.props.onThumbnailFileSelect(dataUrl);
|
||||
handleSliderChange (event) {
|
||||
const value = parseInt(event.target.value);
|
||||
// update the slider value
|
||||
this.setState({
|
||||
sliderValue: value,
|
||||
});
|
||||
// update the current time of the video
|
||||
let video = document.getElementById('video-thumb-player');
|
||||
video.currentTime = value / 100;
|
||||
}
|
||||
setPossibleThumbnailFiles (fileOne, fileTwo, fileThree) {
|
||||
console.log('updating thumbnail file options');
|
||||
this.props.onThumbnailFileOptionsChange(fileOne, fileTwo, fileThree);
|
||||
setThumbnailWithSnapshot () {
|
||||
// take a snapshot
|
||||
const snapshot = this.takeSnapShot();
|
||||
// set the thumbnail in redux store
|
||||
if (snapshot) this.props.onThumbnailFileSelect(snapshot);
|
||||
}
|
||||
setClaimAndThumbailUrl (claim) {
|
||||
// set thumbnail claim based on publish claim name
|
||||
const url = `${this.props.host}/${this.props.channel}/${claim}.jpeg`;
|
||||
this.props.onThumbnailClaimChange(claim, url);
|
||||
takeSnapShot () {
|
||||
let video = document.getElementById('video-thumb-player');
|
||||
let canvas = document.createElement('canvas');
|
||||
canvas.width = video.videoWidth;
|
||||
canvas.height = video.videoHeight;
|
||||
canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);
|
||||
const imageDataUrl = canvas.toDataURL();
|
||||
const success = imageDataUrl.length > 1000;
|
||||
if (!success) {
|
||||
this.setState({error: 'error taking snapshot'});
|
||||
return false;
|
||||
}
|
||||
return imageDataUrl;
|
||||
}
|
||||
render () {
|
||||
const { error, videoSource, sliderMinRange, sliderMaxRange, sliderValue } = this.state;
|
||||
return (
|
||||
<div>
|
||||
<label className='label'>Thumbnail:</label>
|
||||
<div>
|
||||
<p className='info-message-placeholder info-message--failure'>{this.state.error}</p>
|
||||
{this.props.potentialFiles.map((file, index) => <ThumbnailPreview dataUrl={file} key={index} />)}
|
||||
{ error ? (
|
||||
<p className='info-message--failure'>{error}</p>
|
||||
) : (
|
||||
<p className='info-message'>Use slider to set thumbnail:</p>
|
||||
)}
|
||||
<video
|
||||
id='video-thumb-player'
|
||||
preload='metadata'
|
||||
muted
|
||||
style={{display: 'none'}}
|
||||
playsInline
|
||||
onLoadedData={this.handleVideoLoadedData}
|
||||
src={videoSource}
|
||||
onTimeUpdate={this.setThumbnailWithSnapshot}
|
||||
/>
|
||||
{
|
||||
sliderValue ? (
|
||||
<div className='slide-container'>
|
||||
<input
|
||||
type='range'
|
||||
min={sliderMinRange}
|
||||
max={sliderMaxRange}
|
||||
value={sliderValue}
|
||||
className='slider'
|
||||
onChange={this.handleSliderChange}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<p>loading slider... </p>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -26,11 +26,9 @@ const initialState = {
|
|||
nsfw : false,
|
||||
},
|
||||
thumbnail: {
|
||||
channel : publish.thumbnailChannel,
|
||||
claim : null,
|
||||
url : null,
|
||||
potentialFiles: [], // should be named 'thumbnailFiles' or something
|
||||
selectedFile : null,
|
||||
channel : publish.thumbnailChannel,
|
||||
claim : null,
|
||||
selectedFile: null,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -76,21 +74,17 @@ export default function (state = initialState, action) {
|
|||
});
|
||||
case actions.THUMBNAIL_CLAIM_UPDATE:
|
||||
return Object.assign({}, state, {
|
||||
thumbnail: Object.assign({}, state.thumbnail, {
|
||||
claim: action.claim,
|
||||
url : action.url,
|
||||
metadata: Object.assign({}, state.metadata, {
|
||||
thumbnail: action.data.url,
|
||||
}),
|
||||
});
|
||||
case actions.THUMBNAIL_FILES_UPDATE:
|
||||
return Object.assign({}, state, {
|
||||
thumbnail: Object.assign({}, state.thumbnail, {
|
||||
potentialFiles: [action.fileOne, action.fileTwo, action.fileThree],
|
||||
claim: action.data.claim,
|
||||
}),
|
||||
});
|
||||
case actions.THUMBNAIL_FILE_SELECT:
|
||||
return Object.assign({}, state, {
|
||||
thumbnail: Object.assign({}, state.thumbnail, {
|
||||
selectedFile: action.file,
|
||||
selectedFile: action.data,
|
||||
}),
|
||||
});
|
||||
default:
|
||||
|
|
Loading…
Reference in a new issue