diff --git a/public/assets/css/general.css b/public/assets/css/general.css index 629a999c..9625166f 100644 --- a/public/assets/css/general.css +++ b/public/assets/css/general.css @@ -483,26 +483,25 @@ table { cursor: pointer; } -#primary-dropzone-instructions, #dropbzone-dragover { - z-index: -1; -} - -.position-absolute { +#dropzone-text-holder { position: absolute; - top: 0px; - left: 0px; - height: 100%; + left: 0; + top: 0; width: 100%; + height: 100%; } -#asset-preview-holder { - position: relative; +#dropzone-dragover, #dropzone-instructions { + padding: 1em; } #asset-preview { display: block; - padding: 0.5rem; - width: calc(100% - 1rem); + width: 100%; +} + +.dim { + opacity: 0.2; } /* Assets */ diff --git a/public/assets/js/dropzoneFunctions.js b/public/assets/js/dropzoneFunctions.js index 57b8c47f..139597f9 100644 --- a/public/assets/js/dropzoneFunctions.js +++ b/public/assets/js/dropzoneFunctions.js @@ -1,10 +1,2 @@ -function preview_onmouseenter_handler () { - document.getElementById('asset-preview-dropzone-instructions').setAttribute('class', 'flex-container--column flex-container--center-center position-absolute'); - document.getElementById('asset-preview').style.opacity = 0.2; -} -function preview_onmouseleave_handler () { - document.getElementById('asset-preview-dropzone-instructions').setAttribute('class', 'hidden'); - document.getElementById('asset-preview').style.opacity = 1; -} diff --git a/react/components/PublishDropzone.jsx b/react/components/Dropzone.jsx similarity index 77% rename from react/components/PublishDropzone.jsx rename to react/components/Dropzone.jsx index d1a1b534..ef789061 100644 --- a/react/components/PublishDropzone.jsx +++ b/react/components/Dropzone.jsx @@ -3,12 +3,13 @@ import React from 'react'; import { selectFile } from '../actions'; import { connect } from 'react-redux'; -class PublishDropzone extends React.Component { +class Dropzone extends React.Component { constructor (props) { super(props); this.state = { fileError: null, dragOver : false, + } this.handleDrop = this.handleDrop.bind(this); this.handleDragOver = this.handleDragOver.bind(this); @@ -115,19 +116,21 @@ class PublishDropzone extends React.Component {
-
- { this.state.dragOver ? ( -
-

Drop it.

-
- ) : ( -
-

{this.state.fileError}

-

Drag & drop image or video here to publish

-

OR

-

CHOOSE FILE

-
- )} +
+
+ { this.state.dragOver ? ( +
+

Drop it.

+
+ ) : ( +
+

{this.state.fileError}

+

Drag & drop image or video here to publish

+

OR

+

CHOOSE FILE

+
+ )} +
); @@ -148,4 +151,4 @@ const mapDispatchToProps = dispatch => { }; } -export default connect(mapStateToProps, mapDispatchToProps)(PublishDropzone); +export default connect(mapStateToProps, mapDispatchToProps)(Dropzone); diff --git a/react/components/Preview.jsx b/react/components/Preview.jsx new file mode 100644 index 00000000..da800820 --- /dev/null +++ b/react/components/Preview.jsx @@ -0,0 +1,53 @@ +import React from 'react'; +import {connect} from 'react-redux'; + +class Preview extends React.Component { + constructor (props) { + super(props); + this.state = { + previewSource: '', + } + this.previewFile = this.previewFile.bind(this); + } + componentWillMount () { + console.log('Preview will mount'); + if (this.props.file) { + this.previewFile(this.props.file); + } + } + componentWillReceiveProps ({ file }) { + console.log('Preview will receive props'); + this.previewFile(file); + } + previewFile (file) { + console.log('previewFile', file) + const that = this; + if (file.type !== 'video/mp4') { + const previewReader = new FileReader(); + previewReader.readAsDataURL(file); + previewReader.onloadend = function () { + that.setState({previewSource: previewReader.result}); + }; + } else { + that.setState({previewSource: '/assets/img/video_thumb_default.png'}); + } + } + render () { + return ( + publish preview + ); + } +}; + +const mapStateToProps = state => { + return { + file: state.file, + }; +}; + +export default connect(mapStateToProps, null)(Preview); diff --git a/react/components/PreviewDropzone.jsx b/react/components/PreviewDropzone.jsx index 44c38379..49432db1 100644 --- a/react/components/PreviewDropzone.jsx +++ b/react/components/PreviewDropzone.jsx @@ -1,40 +1,138 @@ import React from 'react'; -import {connect} from 'react-redux'; +// import PropTypes from 'prop-types'; +import { selectFile } from '../actions'; +import { connect } from 'react-redux'; +import Preview from './Preview.jsx'; + +import { validateFile } from '../utils/file.js'; class PreviewDropzone extends React.Component { constructor (props) { super(props); this.state = { - previewSource: '', + fileError : null, + dragOver : false, + mouseOver : false, + dimPreview: false, } - this.previewFile = this.previewFile.bind(this); + this.handleDrop = this.handleDrop.bind(this); + this.handleDragOver = this.handleDragOver.bind(this); + this.handleDragEnd = this.handleDragEnd.bind(this); + this.handleDragEnter = this.handleDragEnter.bind(this); + this.handleDragLeave = this.handleDragLeave.bind(this); + this.handleMouseEnter = this.handleMouseEnter.bind(this); + this.handleMouseLeave = this.handleMouseLeave.bind(this); + this.handleClick = this.handleClick.bind(this); + this.handleFileInput = this.handleFileInput.bind(this); + this.selectFile = this.selectFile.bind(this); } - componentDidMount () { - console.log('props after mount', this.props); - this.previewFile(this.props.file); + handleDrop (event) { + event.preventDefault(); + this.setState({dragOver: false}); + // if dropped items aren't files, reject them + const dt = event.dataTransfer; + console.log('dt', dt); + if (dt.items) { + if (dt.items[0].kind == 'file') { + const droppedFile = dt.items[0].getAsFile(); + this.selectFile(droppedFile); + } + } } - previewFile (file) { - console.log('previewFile', file) - const that = this; - if (file.type !== 'video/mp4') { - const previewReader = new FileReader(); - previewReader.readAsDataURL(file); - previewReader.onloadend = function () { - that.setState({previewSource: previewReader.result}); - }; + handleDragOver (event) { + event.preventDefault(); + } + handleDragEnd (event) { + var dt = event.dataTransfer; + if (dt.items) { + for (var i = 0; i < dt.items.length; i++) { + dt.items.remove(i); + } } else { - that.setState({previewSource: '/assets/img/video_thumb_default.png'}); + event.dataTransfer.clearData(); + } + } + handleDragEnter () { + this.setState({dragOver: true, dimPreview: true}); + } + handleDragLeave () { + this.setState({dragOver: false, dimPreview: false}); + } + handleMouseEnter () { + this.setState({mouseOver: true, dimPreview: true}); + } + handleMouseLeave () { + this.setState({mouseOver: false, dimPreview: false}); + } + handleClick (event) { + event.preventDefault(); + // trigger file input + document.getElementById('file_input').click(); + } + handleFileInput (event) { + event.preventDefault(); + const fileList = event.target.files; + this.selectFile(fileList[0]); + } + selectFile (file) { + if (file) { + try { + validateFile(file); // validate the file's name, type, and size + } catch (error) { + return this.setState({fileError: error.message}); + } + // stage it so it will be ready when the publish button is clicked + this.setState({fileError: null}); + this.props.onFileSelect(file); } } render () { return ( -
-
-

Drag & drop image or video here

-

OR

-

CHOOSE FILE

+
+
+ +
+
+ {this.props.file ? ( +
+ +
+ { this.state.dragOver ? ( +
+

Drop it.

+
+ ) : ( + null + )} + { this.state.mouseOver ? ( +
+

{this.state.fileError}

+

Drag & drop image or video here to publish

+

OR

+

CHOOSE FILE

+
+ ) : ( + null + )} +
+
+ ) : ( +
+ { this.state.dragOver ? ( +
+

Drop it.

+
+ ) : ( +
+

{this.state.fileError}

+

Drag & drop image or video here to publish

+

OR

+

CHOOSE FILE

+
+ )} +
+ )}
- publish preview
); } @@ -46,4 +144,12 @@ const mapStateToProps = state => { }; }; -export default connect(mapStateToProps, null)(PreviewDropzone); +const mapDispatchToProps = dispatch => { + return { + onFileSelect: (file) => { + dispatch(selectFile(file)); + }, + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(PreviewDropzone); diff --git a/react/components/PublishTool.jsx b/react/components/PublishTool.jsx index 92f862c8..9e7f0687 100644 --- a/react/components/PublishTool.jsx +++ b/react/components/PublishTool.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import PublishDropzone from './PublishDropzone.jsx'; +import PreviewDropzone from './PreviewDropzone.jsx'; import PublishForm from './PublishForm.jsx'; import PublishStatus from './PublishStatus.jsx'; import {connect} from 'react-redux'; @@ -11,7 +11,7 @@ class PublishTool extends React.Component { render () { return (
- { !this.props.file && } + { !this.props.file && } { this.props.file && } { this.props.publishStatus && }
diff --git a/react/utils/file.js b/react/utils/file.js new file mode 100644 index 00000000..4a054f33 --- /dev/null +++ b/react/utils/file.js @@ -0,0 +1,38 @@ +module.exports = { + validateFile (file) { + if (!file) { + console.log('no file found'); + throw new Error('no file provided'); + } + if (/'/.test(file.name)) { + console.log('file name had apostrophe in it'); + throw new Error('apostrophes are not allowed in the file name'); + } + // validate size and type + switch (file.type) { + case 'image/jpeg': + case 'image/jpg': + case 'image/png': + if (file.size > 10000000) { + console.log('file was too big'); + throw new Error('Sorry, images are limited to 10 megabytes.'); + } + break; + case 'image/gif': + if (file.size > 50000000) { + console.log('file was too big'); + throw new Error('Sorry, .gifs are limited to 50 megabytes.'); + } + break; + case 'video/mp4': + if (file.size > 50000000) { + console.log('file was too big'); + throw new Error('Sorry, videos are limited to 50 megabytes.'); + } + break; + default: + console.log('file type is not supported'); + throw new Error(file.type + ' is not a supported file type. Only, .jpeg, .png, .gif, and .mp4 files are currently supported.'); + } + }, +}