HTMLEditor.js.ori 7.74 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
import React, { Component } from 'react';
import _ from 'lodash'
import axios from 'axios';
import PropTypes from 'prop-types'

import { uploadFile } from '../../api/icat/icatPlus.js'

// tinymce editor
import { Editor } from '@tinymce/tinymce-react'
import tinymce from 'tinymce/tinymce';
import { EditionModeConfig, ViewModeConfig } from '../../config/tinymce/tinymce.js'
import { GUI_CONFIG } from '../../config/gui.config.js';
import { getFileByEventId } from "../../api/icat/icatPlus"

/**
 * The HTML editor used to read and write the logbook's annotations.
 */
class HTMLEditor extends Component {
    constructor(props) {
        super(props);
        this.originalText = props.text; // Stores original text provided in props, could be undefined
        this.state = {
            editorContent: this.originalText
        }

        this.imagesUploadHandler = this.imagesUploadHandler.bind(this);
        this.onEditorChange = this.onEditorChange.bind(this);
        this.storeToLocalStorage = this.storeToLocalStorage.bind(this);
        this.setImageHeightByCSSRule = this.setImageHeightByCSSRule.bind(this);
        this.onImageLoaded = this.onImageLoaded.bind(this);
    }

    render() {
        let isEditionMode = this.props.isEditionMode;

        const { editorContent } = this.state;
        const config = (isEditionMode === true) ? new EditionModeConfig() : new ViewModeConfig();

        return (
            <div >
                <Editor
                    init={{
                        plugins: config.plugins,
                        skin_url: config.skin_url,
                        branding: config.branding,
                        readonly: config.readonly,
                        toolbar: config.toolbar,
                        menubar: config.menubar,
                        statusbar: config.statusbar,
                        images_upload_handler: this.imagesUploadHandler,
                        paste_data_images: config.paste_data_images,
                        autoresize_min_height: config.autoresize_min_height,
                        autoresize_max_height: config.autoresize_max_height,
                        formats: config.formats,
                        content_css: config.content_css,
                    }}
                    value={editorContent}
                    onEditorChange={this.onEditorChange}
                />
            </div>
        );
    }

    componentDidMount() {
        this.storeToLocalStorage()
    }

    /**
     * Callback function triggered when the image has been downloaded
     * @param {*} event the event
     */
    onImageLoaded(event) {
        let element = tinymce.activeEditor.selection.select(event.target);
        tinymce.activeEditor.dom.setStyles(element, { 'max-width': '100%', 'height': 'auto' });
    }

    /**
     * Sets a default image height using a CSS rule. This is a trick to make sure the image height 
     * is applied in the editor (for scrollabar propose) especially because the image is not necessary 
     * yet downloaded at this step. The height is changed to auto after the image is fully downloaded 
     * such that image ratio is kept. 
     */
    setImageHeightByCSSRule() {
        if (tinymce && tinymce.activeEditor) {
            let selectedNode = tinymce.activeEditor.selection.getNode();
            if (selectedNode.nodeName === 'IMG' && selectedNode.style.height !== 'auto') {
                let nxElement = selectedNode;
                nxElement.style.height = GUI_CONFIG().NEW_EVENT_MAX_HEIGHT;
                nxElement.style.width = 'auto'; // a css trick for some browsers IE8 and old iceweasel
                nxElement.onload = this.onImageLoaded;
                tinymce.activeEditor.dom.replace(nxElement, selectedNode);
            }
        }
    }

    /**
     * The function executed when the editor state changes (mouse click, key press for example )
     * @param {string} editorContent the editor content in html format
     */
    onEditorChange(editorContent) {
        this.setImageHeightByCSSRule();

        // Inform parent component that the current text equals the original text as provided in the props                
        if (this.props.canEnableSaveButton) {
            let hasText = editorContent.length !== 0 ? true : false;
            let isCurrentTextEqualsOriginal;
            if (this.originalText) {
                isCurrentTextEqualsOriginal = (_.isEqual(editorContent, this.originalText)) ? true : false;
            }
            this.props.canEnableSaveButton({ hasText: hasText, currentTextEqualsOriginal: isCurrentTextEqualsOriginal });
        }

        this.setState({ editorContent: editorContent });
        this.storeToLocalStorage(editorContent);
    }

    /**
     * Defines what to do when the user drag an image onto the dropzone of the editor image plugin. This function must return a promise. 
     * The value of a fullfilled promise must be an array of the form { data: { link: url } } where url value is the link to the image which 
     * has just been uoloaded to the ICAT+ server.
     * @param {*} file : the image which has just been dropped on the drop zone.
     */

    imagesUploadHandler(blobInfo, success, failure) {
        let { investigationId } = this.props;
        var sessionId = this.props.user.sessionId;


        let data = new FormData();
        data.append('file', blobInfo.blob(), blobInfo.filename());
        data.append('investigationId', investigationId);
        data.append('creationDate', Date());
        data.append('type', 'attachment');
        data.append('category', 'file');
        data.append('username', this.props.user.username);

        axios({
            method: "post",
            url: uploadFile(sessionId, investigationId),
            data: data,
        })
            .then(function (value) {
                let eventId = value.data._id;
                success(getFileByEventId(sessionId, investigationId, eventId));
            }, function (error) {
                console.log("[ERROR] Retrieval of the image you have just upladed into the editor failed ! ");
                failure(error);
            });
    }

    /**
     * Store the editor content to localStorage.
     * @param {*} editorContent the editor content in HTML format
            */
    storeToLocalStorage(editorContent) {
        if (editorContent) {
            // Editor content has been modified by the user. Save the update to localStorage
            if (tinymce && tinymce.activeEditor) {
                // Save the plain text format to localstorage
                localStorage.setItem('plainText', tinymce.activeEditor.getContent({ format: 'text' }));
            }
            // Save the HTML format to localstorage
            localStorage.setItem('HTMLText', editorContent);
        }
        else {
            // The following is executed on componentDidMount. Usefull when the event title is changed only while the editor content was not modified
            localStorage.setItem('HTMLText', this.originalText);
        }
    }
}


HTMLEditor.defaultProps = {
    /** by default, the editor is not in editing mode */
    isEditionMode: false
};


HTMLEditor.propTypes = {
    /** Determines whether the editor is in editing mode or not. */
    isEditionMode: PropTypes.bool,
    /** The text provided to the editor. No text indicates that HTMLEditor is begin used for the creation of a new event. */
    text: PropTypes.string,
    /** the investigationId of the event being edited. */
    investigationId: PropTypes.string,
    /** the user who is currently logged in */
    user: PropTypes.object.isRequired,
    /** callback function called when editor content changed : from no text to text or vice versa, or  when the current text is identical to the original text provided to the editor*/
    canEnableSaveButton: PropTypes.func,
}

export default HTMLEditor;