application/components/common/dropzone.js
import React from 'react';
import Dropzone from 'react-dropzone';
import {connect} from 'react-redux';
import Immutable from 'immutable';
import _ from 'lodash';
import FlatButton from 'material-ui/FlatButton';
import RaisedButton from 'material-ui/RaisedButton';
import Dialog from 'material-ui/Dialog';
import LoadingIndicator from './loading-indicator';
import {ContentService} from './../../services/content-service';
import {ErrorService} from './../../services/error-service';
import Events from './../../util/events';
import {Row, Col} from './../flexbox';
import {snackbarShowMessage} from './../../redux/actions/snackbar-actions';
import {
alertShowMessage
} from './../../redux/actions/alert-actions';
import {language as dropzoneLanguage} from '../../lang/en/dropzone';
import {uploadCartSetItems} from './../../redux/actions/upload-cart-actions';
/**
* Drag and drop upload component. This is initiated by
* the root application on drag-in.
*
* Fires events
* <ul>
* <li>dropzoneUploadStarted</li>
* <li>dropzoneUploadFinished</li>
* </ul>
*/
class DropzoneContainer extends React.Component {
constructor(props) {
super(props);
this.onDrop = this.onDrop.bind(this);
this.onUpload = this.onUpload.bind(this);
this.onRemoveFile = this.onRemoveFile.bind(this);
this.uploadReady = this.uploadReady.bind(this);
this.onError = this.onError.bind(this);
this.onUploadStart = this.onUploadStart.bind(this);
this.onUploadFinish = this.onUploadFinish.bind(this);
this.onCancel = this.onCancel.bind(this);
this.state = {
files: [],
isUploading: false,
};
}
/**
* When files are dropped on the container, just add them to the state
*/
onDrop(dropped) {
let files = this.state.files;
let i = 0;
dropped.forEach((file) => {
files.push(file);
i++;
});
this.setState({files: files}, () => {
setTimeout(() => {
this.forceUpdate();
}, 10*i); // delays the update based on the number of items
});
}
onRemoveFile(file, e) {
e.stopPropagation();
let files = this.state.files;
let newFiles = [];
files.forEach((f) => {
if (f.name === file.name) {
return;
}
newFiles.push(f);
});
this.setState({files: newFiles});
}
/**
* Callback that is fired when the upload is ready to be processed. Converts
* each file into a API ready hash and pushes them into the create action
*
* @param {array} files upload files
* @param {array} errors files that won't be uploaded
*/
uploadReady(files, errors) {
this.onUploadStart(files);
if (errors.length) {
this.onError(errors);
}
const total = files.length;
let processed = 0;
let itemStack = [];
// loop through each processed file and create
// a new content item for each
const complete = () => {
processed++;
if (processed === total) {
this.onUploadFinish(itemStack);
if (errors.length) {
this.onError(errors);
}
}
};
files.forEach((file) => {
const data = {
'slug': file.name,
'title': file.name,
'file': file.file
};
ContentService.upload(data)
.then((item) => {
console.log('GOOD', data);
itemStack.push(item.first());
complete();
})
.catch(() => {
console.log('BAD', data);
errors.push(file);
complete();
});
});
}
/**
* Upload action. Processes each file to determine if it can be processed. Converts
* binary data into base64 encoded string and passes to upload handler.
*/
onUpload(e) {
e.preventDefault();
e.stopPropagation();
this.setState({'isUploading': true});
const files = this.state.files;
const total = files.length;
let processed = 0;
let toUpload = [];
let isError = [];
// loop through each file and read it into base64
// if it errors, push it into an error stack
// when the loop count matches the total, we call the start upload method
files.forEach((file) => {
toUpload.push({
name: file.name,
file: file
});
processed++;
if (processed === total) {
this.uploadReady(toUpload, isError);
}
});
}
onCancel(e) {
e.preventDefault();
e.stopPropagation();
if (this.props.onCancel) {
this.props.onCancel();
}
Events.emit('dropzoneUploadCancelled');
}
uploadFromDropbox(files) {
this.setState({'isUploading': true, 'files': files});
const total = files.length;
let processed = 0;
let itemStack = [];
let errors = [];
// loop through each processed file and create
// a new content item for each
const complete = () => {
processed++;
if (processed === total) {
this.onUploadFinish(itemStack);
if (errors.length) {
this.onError(errors);
}
}
};
files.forEach((file, i) => {
const data = {
'slug': file.name,
'title': file.name,
'attachment': {
'link': file.link,
'name': file.name
}
};
ContentService.create(data)
.then((item) => {
console.log('GOOD', data);
itemStack.push(item.first());
complete();
})
.catch(() => {
console.log('BAD', data);
errors.push(file);
complete();
});
});
}
uploadFromBox(files) {
this.setState({'isUploading': true, 'files': files});
const total = files.length;
let processed = 0;
let itemStack = [];
let errors = [];
// loop through each processed file and create
// a new content item for each
const complete = () => {
processed++;
if (processed === total) {
this.onUploadFinish(itemStack);
if (errors.length) {
this.onError(errors);
}
}
};
files.forEach((file, i) => {
const data = {
'slug': file.name,
'title': file.name,
'attachment': {
'link': file.url,
'name': file.name
}
};
ContentService.create(data)
.then((item) => {
console.log('GOOD', data);
itemStack.push(item.first());
complete();
})
.catch(() => {
console.log('BAD', data);
errors.push(file);
complete();
});
});
}
handleUploadFrom(where) {
if (where == 'box') {
let boxSelect = new window.BoxSelect({
clientId: 630673,
linkType: 'direct',
multiselect: 'true'
});
boxSelect.success(this.uploadFromBox.bind(this));
boxSelect.launchPopup();
} else if (where == 'dropbox') {
window.Dropbox.choose({
success: this.uploadFromDropbox.bind(this),
linkType: 'direct',
multiselect: true,
folderselect: false
});
} else if (where == 'drive') {
const picker = new GooglePicker;
}
// if (this.props.onCancel) {
// this.props.onCancel();
// }
// Events.emit('dropzoneUploadCancelled');
// Events.emit('dropzoneOpenUploadFrom');
}
/**
* Fires 'dropzoneUploadStarted' and calls onUpload prop
*/
onUploadStart(files) {
if (this.props.onUpload) {
this.props.onUpload(files);
}
Events.emit('dropzoneUploadStarted');
}
/**
* Fires 'dropzoneUploadFinished' and calls onFinish prop
*/
onUploadFinish(items = null) {
if (this.props.onFinish) {
this.props.onFinish();
}
this.props.dispatch(snackbarShowMessage(dropzoneLanguage.complete()));
if (items) {
this.props.dispatch(uploadCartSetItems(Immutable.fromJS(items)));
}
Events.emit('dropzoneUploadFinished');
}
/**
* Calls onError prop if files could not be uploaded
*/
onError(files) {
if (this.props.onError) {
this.props.onError(files);
} else {
let filenames = [];
for (let file of files) {
filenames.push(file.name);
}
// ErrorService.add('error', 'Unable to upload some files: ' + filenames.join(', '));
this.props.dispatch(alertShowMessage({
'title': 'Upload error',
'message': dropzoneLanguage.failed(filenames)
}));
}
}
render() {
const componentConfig = {
showFiletypeIcon: true
};
let content = ''
if (this.state.isUploading) {
content = (
<div className='dropzone-loader'>
<LoadingIndicator />
{dropzoneLanguage.uploading(this.state.files.length)}
</div>
);
} else {
content = (
<div className='dropzone-default'>
{dropzoneLanguage.placeholder()}
</div>
);
if (this.state.files.length) {
content = [];
this.state.files.forEach((file, i) => {
let size = {
size: file.size,
label: 'B'
};
if (file.size > 1000 && file.size < 100000) {
size.size = file.size/1000;
size.label = 'KB'
} else if (file.size >= 100000) {
size.size = file.size/1000000;
size.label = 'MB'
}
let previewClass = 'fa fa-file-text-o';
if (file.type.indexOf('pdf') !== -1) {
previewClass = 'fa fa-file-pdf-o'
} else if (file.type.indexOf('video') !== -1) {
previewClass = 'fa fa-file-video-o'
} else if (file.type.indexOf('audio') !== -1) {
previewClass = 'fa fa-file-audio-o'
}
content.push(
<Col xs={3} className='dropzone-file' key={i} onClick={this.onRemoveFile.bind(this, file)}>
{
file.type.indexOf('image') !== -1
? <img src={file.preview} />
: <div><i className={previewClass}></i></div>
}
<strong className='truncate'>{file.name}</strong>
{size.size.toPrecision(3)}{size.label}
</Col>
);
});
}
}
const actions = [
<FlatButton
label="Cancel"
onClick={this.onCancel}
/>,
<FlatButton
label="Upload Files"
className='secondary-button'
keyboardFocused={true}
disabled={this.state.files.length ? false : true}
onClick={this.onUpload}
/>,
];
return (
<Dialog
actions={actions}
autoScrollBodyContent={true}
className='dropzone-root'
open={true}
repositionOnUpdate={true}
style={{maxHeight: '60%'}}
>
<Dropzone config={componentConfig} onDrop={this.onDrop} activeClassName="dropzone-container active" className="dropzone-container">
<Row className='dropzone-inner-content'>
{content}
</Row>
</Dropzone>
{
this.state.files.length
? ''
: (
<Row center='xs'>
<Col xs={12} middle='xs' center='xs'>
<strong>Or, upload from</strong>
<RaisedButton
onClick={this.handleUploadFrom.bind(this, 'box')}
label="Box"
primary={true}
style={{'marginLeft': '10px'}}
/>
<RaisedButton
onClick={this.handleUploadFrom.bind(this, 'dropbox')}
label="Dropbox"
primary={true}
style={{'marginLeft': '10px'}}
/>
</Col>
{/*<li><span className="faux-link" onClick={this.handleUploadFrom.bind(this, 'drive')}>Google Drive</span></li>*/}
</Row>
)
}
</Dialog>
);
}
}
// class GooglePicker {
// constructor() {
// this.apiKey = 'AIzaSyDrQh7-yY7IU2VsHc6uOILaCNOx4qylDtk';
// this.clientId = '267620822619-vfj3kft5qedqcdeccusuv7tfqfoo8moq.apps.googleusercontent.com';
// this.scope = 'https://www.googleapis.com/auth/drive.readonly';
// this.appId = '267620822619';
// this.pickerApiLoaded = false;
// this.oauthToken = false;
// window.gapi.load('auth2', this.onAuthApiLoad.bind(this));
// window.gapi.load('picker', this.onPickerApiLoad.bind(this));
// window.gapi.load('client:oauth2', this.onDriveApiLoad.bind(this));
// }
// onAuthApiLoad() {
// window.gapi.auth2.authorize({
// client_id: this.clientId,
// scope: this.scope
// }, this.handleAuthResult.bind(this));
// }
// onPickerApiLoad() {
// this.pickerApiLoaded = true;
// this.createPicker.call(this);
// }
// onDriveApiLoad() {
// window.gapi.client.load('drive', () => {});
// }
// handleAuthResult(result) {
// if (result && !result.error) {
// this.oauthToken = result.access_token;
// this.createPicker.call(this);
// }
// }
// createPicker() {
// if (!this.pickerApiLoaded || !this.oauthToken) {
// return;
// }
// const picker = new window.google.picker.PickerBuilder()
// .addView(window.google.picker.ViewId.DOCS)
// .setOAuthToken(this.oauthToken)
// .setAppId(this.appId)
// .setDeveloperKey(this.apiKey)
// .enableFeature(window.google.picker.Feature.MULTISELECT_ENABLED)
// .setCallback(this.handleSelection.bind(this))
// .build();
// picker.setVisible(true);
// }
// handleSelection(files) {
// if (files.docs) {
// files.docs.forEach((item, i) => {
// const fileId = item.id;
// console.log(fileId);
// const request = window.gapi.client.drive.files.get({
// 'fileId': fileId
// });
// console.log(request);
// });
// }
// }
// }
export default connect()(DropzoneContainer);