Source: public/src/hooks/uploader.js

/** @module hooks/uploader */

import ReactDOM from 'react-dom';
import { hooks, findDeep, dataUriToBlob, ajax, i18n } from '../util';
import wp from 'wp';
import $ from 'jquery';
import store, { TreeNode } from '../store';
import { latestQueriedFolder } from '../AppTree';
import { message } from 'react-aiot';
import UploadMessage from '../components/UploadMessage';
import { Provider } from 'mobx-react';
import { BROWSER_SELECTOR } from '../others/mediaViews';
import rmlOpts from 'rmlopts';

const UniqueUploadMessage = <Provider store={ store }><UploadMessage /></Provider>,
    CLASS_NAME = 'ant-message-bottom';
let uploaderFetchCountsTimeout, currentMessageHide;

/**
 * Show the uploading message.
 */
function showMessage() {
    currentMessageHide = message.loading(UniqueUploadMessage, 0);
}

/**
 * Hide the uploading message.
 */
function hideMessage() {
    currentMessageHide && currentMessageHide();
}

/**
 * Toggle the placement of the unique uploader message.
 */
function togglePlacement() {
    $(this).parents('.ant-message').toggleClass(CLASS_NAME);
    setTimeout(() => $(document).one('mouseenter', '.rml-upload', togglePlacement), 10);
}

/**
 * Get the current selected node id.
 * 
 * @returns {object}
 */
function getNodeId() {
    const selectVal = $(".attachments-filter-preUploadUi:visible:first"),
        value = selectVal.val();
    if (value) {
        const option = selectVal.find('option[value="' + value + '"]'),
            { type, name } = option.data(),
            id = parseInt(value, 10);
        return store.getTreeItemById(value, false) || TreeNode.create({
            id, title: name, properties: { type },
            isQueried: false
        });
    }else{
        return latestQueriedFolder.node;
    }
}

/**
 * When a file is added do general checks.
 */
hooks.register('uploader/add', function(file, node) {
    if (node.id === 'all') {
        this.node = store.getTreeItemById(+rmlOpts.rootId, false);
    }
});

/**
 * The media-new.php page. Adds the property to the asyn-upload.php file and
 * modifies the output row while uploading a new file. 
 * 
 * @see wp-includes/js/plupload/handlers.js
 */
hooks.register('general', () => {
    if (!$('body').hasClass('media-new-php')) {
        return;
    }
    
    // When the file is uploaded, then the original filename is overwritten. Now we
    // must add it again to the row after the filename.
    if (window.prepareMediaItemInit) {
        const copyPrepareMediaItemInit = window.prepareMediaItemInit;
        window.prepareMediaItemInit = function(file) {
            copyPrepareMediaItemInit.apply(this, arguments);
            if (file.rmlFolderHTML) {
                const mediaRowFilename = $('#media-item-' + file.id).find(".filename");
                if (mediaRowFilename.size()) {
                    mediaRowFilename.after(file.rmlFolderHTML);
                }
            }
        };
    }

    // Add event to the uploader so the parameter for the folder id is sent
    setTimeout(() => window.uploader && window.uploader.bind('BeforeUpload', function(up, file) {
        const rmlFolderNode = getNodeId();

        // Set server-side-readable rmlfolder id
        if (rmlFolderNode && !isNaN(+rmlFolderNode.id)) {
            up.settings.multipart_params.rmlFolder = rmlFolderNode.id;
            
            // Get title as string
            const div = document.createElement('div');
            let { title } = rmlFolderNode;
            typeof title === 'string' ? div.innerText = title : ReactDOM.render(title, div);
            title = div.innerText;
            
            const mediaRowFilename = $('#media-item-' + file.id).find('.filename');
            if (mediaRowFilename.length > 0) {
                file.rmlFolderHTML = '<div class="media-item-rml-folder">' + title + '</div>';
                mediaRowFilename.after(file.rmlFolderHTML);
            }
	    }
    }), 500);
});

/**
 * Load data to a dropdown or show label that the folder is inherited from the AppTree.
 * This RFC is placed in the upload UI where you can select your files.
 */
hooks.register('wprfc/preUploadUi', async function(data) {
    const attachmentsBrowser = $(this).parents('.attachments-browser');
    if (attachmentsBrowser.size()) {
        $(this).parent().hide().prev().html(rmlOpts.lang.uploaderUsesLeftTree);
    }else{
        const { html } = await ajax('tree/dropdown');
        $(this).addClass('attachments-filter-preUploadUi').html(html);
    }
});

/**
 * The default backbone uploader.
 */
hooks.register('general', () => {
    if (!findDeep(window, 'wp.media') || !findDeep(window, 'wp.Uploader')) {
        return;
    }
    $(document).one('mouseenter', '.rml-upload', togglePlacement);
    
    // Initialize
    const oldP = wp.Uploader.prototype, oldInit = oldP.init, oldSucess = oldP.success;
    oldP.init = function() {
        oldInit.apply(this, arguments);
        
        /**
         * The uploader gets initialized.
         * 
         * @event module:util/hooks#uploader/init
         * @this wp.Uploader
         */
        hooks.call('uploader/init', [], this);
        
        // Bind the last selected node to the uploaded file
        this.uploader.bind('FileFiltered', function(up, file) {
            file.rmlFolderNode = getNodeId();
        });
        
        // Remove files from queue if upload is a folder
        /*this.uploader.bind('FilesAdded', function(up, files) {
            files.forEach(file => {
                console.log(file.getSource().relativePath);
            	up.removeFile(file);
            });
            console.log(files);
            console.log(up);
            
            // TODO: Disable upload until here and show dialog with folder preview
        }, undefined, 10);*/
        
        // A new file is added, add it to the store so it can be rendered
        this.uploader.bind('FilesAdded', function(up, files) {
            showMessage();
            files.forEach(file => {
                const { attachment: { cid }, name, percent, loaded, size, rmlFolderNode } = file,
                    previewObj = {
                        cid, name, percent, loaded, size,
                        node: rmlFolderNode
                    };
                    
                /**
                 * A new file is added.
                 * 
                 * @event module:util/hooks#uploader/add
                 * @param {object} file The file
                 * @param {module:store/TreeNode~TreeNode} folder The folder node
                 * @param {module:store~Store} store The store
                 * @this object
                 */
                hooks.call('uploader/add', [ file, rmlFolderNode, store ], previewObj);
                const upload = file.rmlUpload = store.addUploading(previewObj);
                
            	// Generate preview url
            	const preloader = new window.mOxie.Image();
            	preloader.onload = () => {
            	    preloader.downsize(89, 89);
            	    let finalUrl;
            	    
            	    try {
            	        finalUrl = preloader.getAsDataURL();
            	        finalUrl = dataUriToBlob(finalUrl);
            	        finalUrl = window.URL.createObjectURL(finalUrl);
            	        finalUrl && upload.setter(u => u.previewSrc = finalUrl);
            	    }catch (e) {
            	        // Silence is golden.
            	    }
            	};
            	preloader.load(file.getSource());
            });
		});
		
		// Set server-side-readable RML folder id
		this.uploader.bind('BeforeUpload', function(up, file) {
		    const { multipart_params } = up.settings;
		    let { rmlFolderNode } = file;
		    
            !rmlFolderNode && (rmlFolderNode = getNodeId()); // Lazy node
		    if (rmlFolderNode && !isNaN(+rmlFolderNode.id)) {
                multipart_params.rmlFolder = rmlFolderNode.id;
		    }
		});
		
		// The upload progress
		this.uploader.bind('UploadProgress', function({ total }, { rmlUpload, percent, loaded }) {
		    rmlUpload.setter(u => {
		        u.percent = percent;
		        u.loaded = loaded;
		    });
		    
		    store.setUploadTotal(total);
		});
		
		// All files are completed
		this.uploader.bind('UploadComplete', function(up, files) {
		    // Update queue and counter
		    up.splice();
		    up.total.reset();
		    clearTimeout(uploaderFetchCountsTimeout);
		    uploaderFetchCountsTimeout = setTimeout(() => store.fetchCounts(), 500); // Avoid too many requests
		    
		    // Hide uploader message
		    hideMessage();
		});
    };
    
    /**
     * A single file is completed successfully.
     */
    oldP.success = function(file_attachment) {
        oldSucess.apply(this, arguments);
        
        // Remove file from queue
        const upload = store.removeUploading(file_attachment.cid),
            uploadId = upload.node;
        store.addFoldersNeedsRefresh(uploadId);
        
        // Update all available backbone view
        const rmlGalleryOrder = file_attachment.get('rmlGalleryOrder'),
            at = rmlGalleryOrder === -1 ? 0 : rmlGalleryOrder;
        $(BROWSER_SELECTOR).each(function() {
            const backboneView = $(this).data('backboneView');
            if (backboneView) {
                const { $RmlAppTree } = backboneView.controller,
                    activeNode = $RmlAppTree.getTreeItemById(undefined, false);
                if (uploadId === activeNode.id || activeNode.id === 'all') {
                    backboneView.collection.add(file_attachment, { at: activeNode.id === 'all' ? 0 : at });
                }
            }
        });
    };
});

const GALLERY_ALLOWED_EXT = ['jpg', 'jpeg', 'jpe', 'gif', 'png'];

/**
 * Checks, if the uploading folder is a collection or gallery and restrict the upload,
 * move the file to unorganized folder.
 */
hooks.register('uploader/add', function({ name }, { properties }, store) {
    // May only contain image files
    if (properties && properties.type) {
        const ext = name.substr(name.lastIndexOf('.') + 1).toLowerCase(),
            isCollection = +properties.type === 1;
        if ($.inArray(ext, GALLERY_ALLOWED_EXT) === -1 || isCollection) {
            this.node = store.getTreeItemById(+rmlOpts.rootId, false);
            this.deny = i18n(isCollection ? 'uploadingCollection' : 'uploadingGallery');
        }
    }
});