Home Reference Source

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);