Source: public/src/components/MetaBox.js

/** @module components/MetaBox */
 
import React from 'react';
import ReactDOM from 'react-dom';
import $ from 'jquery';
import { observer, inject } from 'mobx-react';
import { Icon, Spin } from 'react-aiot';
import { ajax, hooks, rmlOpts } from '../util';

/**
 * Show a meta box for the selected folder id. It also supports
 * user settings.
 * 
 * @property {string|int} id The id of the folder or 'usersettings'
 * @property {module:components/MetaBox~MetaBox~patcher} patcher
 * @extends React.Component
 */
@inject('store')
@observer
class MetaBox extends React.Component {
    constructor(props) {
        super(props);
        
        this.state = {
            id: 0, // The current visible id
            html: '' // The html
        };
    }
    
    componentWillMount() {
        this.handleAjax(this.props, this.state);
    }
    
    componentWillUpdate(...args) {
        this.handleAjax(...args);
    }
    
    handleAjax(nextProps, nextState) {
        // Loading phase
        if (nextProps.id !== this.state.id) {
            this.state.id = nextProps.id; // Avoid rerendering
            this.state.html = '';
            const url = nextProps.id === 'usersettings' ? 'usersettings' : 'folders/' + nextProps.id + '/meta';
            ajax(url).then(({ html }) => {
                this.setState({ html });
            }, () => { // An error occured
                this.setState({
                    html: ''
                });
            });
        }
    }
    
    handleRef = ref => {
        this.refSpan = ref;
        
        /**
         * The MetaBox ref element is ready and created.
         * 
         * @event module:util/hooks#folder/meta
         * @param {HTMLElement} ref The reference
         * @param {string|id} id The folder id or 'usersettings'
         * @param {module:store~Store} store The store
         */
        hooks.call('folder/meta', [ ref, this.state.id, this.props.store ]);
        const { patcher } = this.props;
        
        /**
         * This callback is fired when the ref is created
         * 
         * @callback module:components/MetaBox~MetaBox~patcher
         * @param {function} handleSave Fire this function if you want to serialize the data and save to server (async)
         * @param {HTMLElement} ref The meta box container
         */
        patcher && patcher(this.handleSave, ref);
    }
    
    handleSave = async () => {
        const form = $(this.refSpan).children('form'),
            serialize = form.serializeArray(),
            data = {};
        $.each(serialize, (key, value) => (data[value.name] = value.value));
        
        /**
         * The MetaBox is serialized and ready to send.
         * 
         * @event module:util/hooks#folder/meta/serialize
         * @param {string|id} id The folder id or 'usersettings'
         * @param {module:store~Store} store The store
         * @param {object} data The data prepared for the server so you can perhaps modify it
         * @param {HTMLElement} form The form container
         */
        hooks.call('folder/meta/serialize', [ this.state.id, this.props.store, data, form ]);
        const url = this.state.id === 'usersettings' ? 'usersettings' : 'folders/' + this.state.id + '/meta';
        const response = await ajax(url, {
            method: 'PUT',
            data
        });
        
        /**
         * The MetaBox is saved successfully.
         * 
         * @event module:util/hooks#folder/meta/saved
         * @param {string|id} id The folder id or 'usersettings'
         * @param {object} response The server response
         * @param {object} data The data sent to the server
         */
        hooks.call('folder/meta/saved', [ this.state.id, response, data ]);
    }
    
    render() {
        let selected;
        if (this.props.id === 'usersettings') {
            selected = {
                icon: <Icon type="setting" />,
                title: rmlOpts.lang.userSettingsToolTipTitle
            };
        }else{
            selected = this.props.store.getTreeItemById(this.props.id, false);
        }
        const { html } = this.state,
            { busy = false, errors = [] } = this.props;
        if (!selected) {
            return null;
        }
        
        return (<Spin spinning={ !html || busy } size="small">
            <div className="rml-postbox">
                <h2><Icon type="ellipsis" /> { selected.icon } { selected.title }</h2>
                { errors.length > 0 && (<ul>{ errors.map((e, i) => (<li key={i}>{e}</li>)) }</ul>) }
                { html && (<div className="inside">
                    <span dangerouslySetInnerHTML={{ __html: html }} style={{ display: html ? 'block' : 'none' }}
                        ref={ this.handleRef } />
            	</div>) }
            </div>
        </Spin>);
    }
}

/**
 * Wait for the input field for the cover image and create a media picker.
 * 
 * @see https://wordpress.stackexchange.com/questions/190987/how-do-i-create-a-custom-add-media-button-modal
 */
hooks.register('wprfc/metaCoverImage', function(data) {
    const inputFilename = $(this),
        inputId = $(this).prev();
    $('<div class="rml-drop-zone">' + rmlOpts.lang.coverImageDropHere + '</div>').insertAfter($(this)).droppable({
        tolerance: 'pointer',
        drop: function(event, ui) {
            const { draggable } = ui,
                id = draggable.data('id'),
                filename = draggable.parents('.attachments-browser').data('backboneView').collection.get(id).get('filename');
            inputFilename.val(filename);
            inputId.val(id);
        }
    });
});

export default MetaBox;