diff --git a/react/containers/PublishThumbnailInput/index.js b/react/containers/PublishThumbnailInput/index.js
index babc1303..6614ed7a 100644
--- a/react/containers/PublishThumbnailInput/index.js
+++ b/react/containers/PublishThumbnailInput/index.js
@@ -1,17 +1,32 @@
-import {connect} from 'react-redux';
-import {updateMetadata} from 'actions/publish';
+import { connect } from 'react-redux';
+import { updateThumbnailClaim, updateThumbnailFileOptions, updateThumbnailSelectedFile } from 'actions/publish';
import View from './view';
-const mapStateToProps = ({ publish }) => {
+const mapStateToProps = ({ publish, site }) => {
return {
- thumbnail: publish.metadata.thumbnail,
+ host : site.host,
+ // file props
+ publishFile : publish.file,
+ publishClaim : publish.claim,
+ // channel props
+ channel : publish.thumbnail.channel,
+ claim : publish.thumbnail.claim,
+ url : publish.thumbnail.url,
+ potentialFiles: publish.thumbnail.potentialFiles,
+ selectedFile : publish.thumbnail.selectedFile,
};
};
const mapDispatchToProps = dispatch => {
return {
- onThumbnailChange: (name, value) => {
- dispatch(updateMetadata(name, value));
+ onThumbnailClaimChange: (claim, url) => {
+ dispatch(updateThumbnailClaim(claim, url));
+ },
+ onThumbnailFileOptionsChange: (fileOne, fileTwo, fileThree) => {
+ dispatch(updateThumbnailFileOptions(fileOne, fileTwo, fileThree));
+ },
+ onThumbnailFileSelect: (file) => {
+ dispatch(updateThumbnailSelectedFile(file));
},
};
};
diff --git a/react/containers/PublishThumbnailInput/view.jsx b/react/containers/PublishThumbnailInput/view.jsx
index 2ddddb6e..05096ebc 100644
--- a/react/containers/PublishThumbnailInput/view.jsx
+++ b/react/containers/PublishThumbnailInput/view.jsx
@@ -1,79 +1,122 @@
import React from 'react';
+const ThumbnailPreview = ({dataUrl}) => {
+ const thumbnailPreviewStyle = {
+ width : '30%',
+ padding: '1%',
+ display: 'inline-block',
+ }
+ return (
+
+ { dataUrl ? (
+
+ ) : (
+
loading...
+ )
+ }
+
+ );
+}
+
class PublishThumbnailInput extends React.Component {
constructor (props) {
super(props);
this.state = {
- videoPreviewSrc: null,
- thumbnailError : null,
- thumbnailInput : '',
+ error: null,
+ };
+ }
+ componentDidMount () {
+ this.setClaimAndThumbnail(this.props.publishClaim);
+ this.createThreePotentialThumbnails();
+ }
+ componentWillReceiveProps (nextProps) {
+ if (nextProps.publishFile !== this.publishFile) {
+ // this.createThreePotentialThumbnails();
+ }
+ if (nextProps.publishClaim !== this.props.publishClaim) {
+ console.log(nextProps.publishClaim, this.props.publishClaim);
+ this.setClaimAndThumbnail(nextProps.publishClaim);
}
- this.handleInput = this.handleInput.bind(this);
- this.urlIsAnImage = this.urlIsAnImage.bind(this);
- this.testImage = this.testImage.bind(this);
- this.updateVideoThumb = this.updateVideoThumb.bind(this);
}
- handleInput (event) {
- const value = event.target.value;
- this.setState({thumbnailInput: value});
+ createThreePotentialThumbnails () {
+ const videoFile = this.props.publishFile;
+ console.log('video file', videoFile);
+ Promise.all([this.createThumbnail(videoFile), this.createThumbnail(videoFile), this.createThumbnail(videoFile)])
+ .then(([thumbOne, thumbTwo, thumbThree]) => {
+ // set the potential thumbnails
+ console.log([thumbOne, thumbTwo, thumbThree]);
+ this.selectVideoThumb(thumbOne);
+ this.setPossibleThumbnailFiles(thumbOne, thumbTwo, thumbThree);
+ })
+ .catch(error => {
+ this.setState({error: error.message});
+ });
}
- urlIsAnImage (url) {
- return (url.match(/\.(jpeg|jpg|gif|png)$/) != null);
- }
- testImage (url) {
+ createThumbnail (file) {
return new Promise((resolve, reject) => {
- const xhttp = new XMLHttpRequest();
- xhttp.open('HEAD', url, true);
- xhttp.onreadystatechange = () => {
- if (xhttp.readyState === 4) {
- if (xhttp.status === 200) {
- resolve();
- } else {
- reject();
+ console.log('creating a thumbnail');
+ var fileReader = new FileReader();
+ fileReader.onload = () => {
+ console.log('thumbnail loaded');
+ const blob = new Blob([fileReader.result], {type: file.type});
+ const url = URL.createObjectURL(blob);
+ let video = document.createElement('video');
+ const timeupdate = () => {
+ if (snapImage()) {
+ video.removeEventListener('timeupdate', timeupdate);
+ video.pause();
}
- }
+ };
+ const snapImage = () => {
+ 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();
+ // console.log('imageDataUrl', imageDataUrl);
+ const success = imageDataUrl.length > 1000;
+ if (success) { // do something with the image
+ return resolve(imageDataUrl);
+ // URL.revokeObjectURL(url);
+ }
+ reject(success);
+ };
+ video.addEventListener('loadeddata', () => {
+ if (snapImage()) {
+ video.removeEventListener('timeupdate', timeupdate);
+ }
+ });
+ video.addEventListener('timeupdate', timeupdate);
+ video.preload = 'metadata';
+ video.src = url;
+ // Load video in Safari / IE11
+ video.muted = true;
+ video.playsInline = true;
+ video.play();
};
- xhttp.send();
+ fileReader.readAsArrayBuffer(file);
});
}
- updateVideoThumb (event) {
- const imageUrl = event.target.value;
- const that = this;
- if (this.urlIsAnImage(imageUrl)) {
- this.testImage(imageUrl, 3000)
- .then(() => {
- console.log('thumbnail is a valid image');
- that.props.onThumbnailChange('thumbnail', imageUrl);
- that.setState({thumbnailError: null});
- })
- .catch(error => {
- console.log('encountered an error loading thumbnail image url:', error);
- that.props.onThumbnailChange('thumbnail', null);
- that.setState({thumbnailError: 'That is an invalid image url'});
- });
- } else {
- that.props.onThumbnailChange('thumbnail', null);
- that.setState({thumbnailError: null});
- }
+ selectVideoThumb (dataUrl) {
+ // update this.props.selectedFile
+ this.props.onThumbnailFileSelect(dataUrl);
+ }
+ setPossibleThumbnailFiles (fileOne, fileTwo, fileThree) {
+ console.log('updating thumbnail file options');
+ this.props.onThumbnailFileOptionsChange(fileOne, fileTwo, fileThree);
+ }
+ setClaimAndThumbnail (claim) {
+ // set thumbnail claim based on publish claim name
+ const url = `${this.props.host}/${this.props.channel}/${claim}.jpeg`;
+ this.props.onThumbnailClaimChange(claim, url);
}
render () {
return (
-
-
-
-
-
{this.state.thumbnailError}
-
{
- this.handleInput(event);
- this.updateVideoThumb(event);
- }} />
-
+
+
+
{this.state.error}
+ {this.props.potentialFiles.map((file, index) =>
)}
);
diff --git a/react/reducers/index.js b/react/reducers/index.js
index c80273f3..2fce4d7a 100644
--- a/react/reducers/index.js
+++ b/react/reducers/index.js
@@ -2,9 +2,11 @@ import { combineReducers } from 'redux';
import PublishReducer from 'reducers/publish';
import ChannelReducer from 'reducers/channel';
import ShowReducer from 'reducers/show';
+import SiteReducer from 'reducers/site';
export default combineReducers({
channel: ChannelReducer,
publish: PublishReducer,
show : ShowReducer,
+ site : SiteReducer,
});
diff --git a/react/reducers/publish.js b/react/reducers/publish.js
index 1195a37d..3bba5d21 100644
--- a/react/reducers/publish.js
+++ b/react/reducers/publish.js
@@ -1,5 +1,6 @@
import * as actions from 'constants/publish_action_types';
import { LOGIN } from 'constants/publish_channel_select_states';
+const { publish } = require('../../config/speechConfig.js');
const initialState = {
publishInChannel : false,
@@ -24,6 +25,13 @@ const initialState = {
license : '',
nsfw : false,
},
+ thumbnail: {
+ channel : publish.thumbnailChannel,
+ claim : null,
+ url : null,
+ potentialFiles: [], // should be named 'thumbnailFiles' or something
+ selectedFile : null,
+ },
};
/*
@@ -73,6 +81,25 @@ export default function (state = initialState, action) {
return Object.assign({}, state, {
showMetadataInputs: action.value,
});
+ case actions.THUMBNAIL_CLAIM_UPDATE:
+ return Object.assign({}, state, {
+ thumbnail: Object.assign({}, state.thumbnail, {
+ claim: action.claim,
+ url : action.url,
+ }),
+ });
+ case actions.THUMBNAIL_FILES_UPDATE:
+ return Object.assign({}, state, {
+ thumbnail: Object.assign({}, state.thumbnail, {
+ potentialFiles: [action.fileOne, action.fileTwo, action.fileThree],
+ }),
+ });
+ case actions.THUMBNAIL_FILE_SELECT:
+ return Object.assign({}, state, {
+ thumbnail: Object.assign({}, state.thumbnail, {
+ selectedFile: action.file,
+ }),
+ });
default:
return state;
}
diff --git a/react/reducers/site.js b/react/reducers/site.js
new file mode 100644
index 00000000..aa215467
--- /dev/null
+++ b/react/reducers/site.js
@@ -0,0 +1,12 @@
+const { site } = require('../../config/speechConfig.js');
+
+const initialState = {
+ host: site.host,
+};
+
+export default function (state = initialState, action) {
+ switch (action.type) {
+ default:
+ return state;
+ }
+}