Commit a0ba1195 authored by Maxime Chaillet's avatar Maxime Chaillet
Browse files

clean the code. It fixes #149.

parent edb08063
import React from 'react';
import { Row, Col, OverlayTrigger, Tooltip, Popover, Button, Well, Label, FormControl, InputGroup, Glyphicon, Panel } from 'react-bootstrap';
import { getEventCreationDate, getEventHistoryCreationDate, getOriginalEvent, getPreviousVersionNumber, getEventIcon } from '../../helpers/EventHelpers';
import EventFooter from './EventFooter';
import EventHeader from './EventHeader';
import { ANNOTATION, NOTIFICATION, READ_MODE, DETAILED_EVENT_CONTEXT, EDIT_EVENT_CONTEXT } from '../../constants/EventTypes';
import PropTypes from 'prop-types';
import EventContentDisplayer from './EventContentDisplayer';
import Popup from 'reactjs-popup';
import Moment from 'moment';
import EventVersion from './EventVersion';
import _ from 'lodash';
import TagContainer from '../../containers/TagContainer';
/**
* A detailed event of the logbook.
*/
class EditEvent extends React.Component {
constructor(props) {
super(props);
// Initialize state
this.state = {
isSaveButtonEnabled: false, // whether the event is valid ie can be saved
isEditorContentValid: false
};
this.canEnableSaveButton = this.canEnableSaveButton.bind(this);
this.onCancelButtonClicked = this.onCancelButtonClicked.bind(this);
this.onSuccessfullyUpdated = this.onSuccessfullyUpdated.bind(this);
this.setTitleInput = this.setTitleInput.bind(this);
this.setTagContainer = this.setTagContainer.bind(this);
this.updateEvent = this.updateEvent.bind(this);
}
render() {
let { event, user } = this.props;
if (event.type === ANNOTATION || event.type === NOTIFICATION) {
return (
<div id='editionBox' style={{ display: 'flex' }} >
{/* the left panel */}
<div style={{ flexGrow: '0', maxWidth: '155px' }}>
<div className='pull-right' style={{ paddingRight: '8px' }}>
{getEventIcon(event.category, '20')}
</div>
<div style={{ paddingLeft: '5px' }} >
<div style={{ height: '50px' }} />
{/* <OverlayTrigger
placement='top'
overlay={<Tooltip id='tooltip'> <p> Manage tags </p> </Tooltip>}>
<a href={'/investigation/' + this.props.investigationId + '/events/tagManager'}
target='_blank'
style={{ float: 'right', paddingRight: '10px', color: '#777', marginTop: '2px' }}>
<Glyphicon glyph='cog' />
</a>
</OverlayTrigger> */}
{/* <Label> Tags </Label>
<div style={{ paddingTop: '5px', marginRight: '10px' }}>
<TagContainer
canEnableSaveButton={this.canEnableSaveButton}
context={DETAILED_EVENT_CONTEXT}
event={event}
investigationId={this.props.investigationId}
setTagContainer={this.setTagContainer}
/>
</div> */}
{/* <hr id='hrEvents' /> */}
{/* <Label> Creation date </Label>
<OverlayTrigger
placement='right'
overlay={
<Tooltip id='tooltip'>
<p> Event created on {getEventHistoryCreationDate(getOriginalEvent(event))} </p>
</Tooltip>
}>
<div>
<div style={{ paddingTop: '5px', paddingLeft: '10px', color: '#666666' }}>
{getEventCreationDate(getOriginalEvent(event))}
</div>
</div>
</OverlayTrigger> */}
<hr id='hrEvents' />
{/* <CommentBy event={event} /> */}
{/* <div style={{ position: 'relative' }}>
<div >
<OverlayTrigger
trigger='click'
placement='top'
overlay={eventVersionsPopover(event)}
rootClose={true}
>
<Label> History <div className='arrow-down' /> </Label>
</OverlayTrigger>
</div>
</div> */}
</div>
</div>
{/* <div
style={{ flexGrow: '1' }}>
{(event.type === ANNOTATION) ? <AnnotationContent
canEnableSaveButton={this.canEnableSaveButton}
event={event}
investigationId={this.props.investigationId}
setTitleInput={this.setTitleInput}
user={user}
/> : null}
{(event.type === NOTIFICATION) ? <NotificationContent
canEnableSaveButton={this.canEnableSaveButton}
event={event}
investigationId={this.props.investigationId}
user={user}
/> : null}
<EventFooter
onCancelButtonClicked={this.onCancelButtonClicked}
onSaveButtonClicked={this.updateEvent}
isSaveButtonEnabled={this.state.isSaveButtonEnabled} />
</div> */}
</div >);
} else {
console.log('[ERROR] Event (' + event._id + ') not shown because its type is not annotation and not notification. ');
return null;
};
}
// updateEvent() {
// let { investigationId, event, user } = this.props;
// // get tags
// let currentTagIds = this.tagContainer.state.selectedTags.map((tag) => tag._id);
// let updatedEvent = {
// _id: event._id,
// category: event.category,
// content: [
// {
// format: 'plainText',
// text: localStorage.getItem('plainText')
// },
// {
// format: 'html',
// text: localStorage.getItem('HTMLText')
// }
// ],
// creationDate: Date(),
// type: event.type,
// tags: currentTagIds,
// title: (this.inputTitle && this.inputTitle.props) ? this.inputTitle.props.value : null,
// username: user.username,
// previousVersionEvent: event._id
// };
// this.props.updateEvent(updatedEvent, user.sessionId, investigationId, this.onSuccessfullyUpdated);
// }
// /** Callback executed when the event has been successfully updated on the server side*/
// onSuccessfullyUpdated(updatedEvent) {
// this.props.toggleMode(READ_MODE); //this makes sure the mode is changed in case the event is updated.
// this.props.onEventUpdated(updatedEvent);
// }
// /**
// * Callback function triggered when the user click the cancel button
// * @param {*} e the browser's event
// */
// onCancelButtonClicked(e) {
// e.stopPropagation();
// this.props.toggleMode(READ_MODE);
// }
/**
* Callback function called when the text of the editor changed from empty to something or vice versa
* @param {object} change an object containing the properties:
* -1- hasText boolean which indicates whether the editor content has text or not; not null
* -2- currentTextEqualsOriginal, boolean, which indicates whether the current content is the same as the original content; not null
*/
// canEnableSaveButton(change) {
// let hasEventTitleChangedFromItsOriginalValue =
// this.inputTitle &&
// this.props.event &&
// this.inputTitle.props.value !== this.props.event.title;
// let hasEventTagsChangedFromItsOriginalValue =
// this.tagContainer &&
// this.props.event &&
// !(_.isEqual(this.tagContainer.state.selectedTags.map((tag) => tag._id), this.props.event.tag));
// if (hasEventTitleChangedFromItsOriginalValue || hasEventTagsChangedFromItsOriginalValue) {
// /* title is not the same as original. can save (even with no content) */
// if (this.state.isSaveButtonEnabled !== true) { this.setState({ isSaveButtonEnabled: true }); }
// } else {
// /* title is the same as original */
// if (change) {
// /* triggered from the editor */
// let hasText = null;
// let currentTextEqualsOriginal = null;
// if ('hasText' in change) { hasText = change.hasText; };
// if ('currentTextEqualsOriginal' in change) {
// currentTextEqualsOriginal = change.currentTextEqualsOriginal;
// };
// if (!hasText) {
// // there is no text in the editor
// if (this.state.isEditorContentValid !== false) {
// this.setState({
// isEditorContentValid: false,
// isSaveButtonEnabled: false
// });
// };
// } else {
// // there is text in the editor
// if (currentTextEqualsOriginal === undefined || currentTextEqualsOriginal === null) {
// if (this.state.isEditorContentValid !== false) {
// this.setState({
// isEditorContentValid: false,
// isSaveButtonEnabled: false
// });
// };
// } else if (currentTextEqualsOriginal) {
// // current text in the editor equals the original text
// if (this.state.isEditorContentValid !== false) {
// this.setState({
// isEditorContentValid: false,
// isSaveButtonEnabled: false
// });
// };
// } else {
// if (this.state.isEditorContentValid !== true) {
// this.setState({
// isEditorContentValid: true,
// isSaveButtonEnabled: !hasEventTitleChangedFromItsOriginalValue
// });
// };
// };
// }
// } else {
// if (!this.state.isEditorContentValid) {
// /* Editor content is not valid. Can not save */
// if (this.state.isSaveButtonEnabled !== false) { this.setState({ isSaveButtonEnabled: false }); }
// };
// };
// };
// }
setTitleInput(element) {
this.inputTitle = element;
}
// setTagContainer(element) {
// this.tagContainer = element;
// }
}
EditEvent.propTypes = {
/** the event object as received from the ICAT+ server */
event: PropTypes.object.isRequired,
/** the investigationId which the events belong to. */
investigationId: PropTypes.string.isRequired,
/**Callback function triggered when the user updated an existing event */
onEventUpdated: PropTypes.func,
/* Callback function used to change from one mode to the other READ_MODE vs EDIT_MODE*/
toggleMode: PropTypes.func.isRequired,
/** the user who is currently logged in */
user: PropTypes.object.isRequired,
};
/**
* Render the popup which displays all event versions of the event
* @param {*} event the event under investigation
*/
// function eventVersionsPopover(event) {
// let e = event;
// /* build the different lines representing the previous versions */
// let lines = [];
// while (e.previousVersionEvent != null) {
// if (lines.length === 0) {
// lines.push(<EventVersionItem event={e} type='edition' mostRecent={true} />);
// } else {
// lines.push(<EventVersionItem event={e} type='edition' mostRecent={false} />);
// };
// e = e.previousVersionEvent;
// }
// lines.push(<EventVersionItem event={e} type='creation' />);
// /* build the popover */
// return (<Popover id='mypopover'>
// <div>
// <p style={{ marginBottom: '5px' }}> This log has {getPreviousVersionNumber(event) + 1} versions. </p>
// <hr style={{ marginTop: '5px' }} />
// {lines.map((item, index) => <Popup
// key={index}
// trigger={<div> {item} </div>}
// modal
// closeOnDocumentClick={false}
// contentStyle={{ width: '90%', height: '90%' }}
// >
// {close => (
// <div className='fullPage'>
// <a className='close' onClick={close}> &times; </a>
// <div className='content'>
// <EventVersion
// event={item.props.event}
// isLatestVersion={(index === 1) ? true : false}
// isOriginalVersion={(index === lines.length - 1) ? true : false}
// />
// </div>
// </div>
// )}
// </Popup>
// )}
// <hr style={{ marginBottom: '5px' }} />
// <Popup
// trigger={<div style={{ display: 'inline' }}> <Button bsStyle='link'> See all versions </Button> </div>}
// modal
// closeOnDocumentClick={false}
// contentStyle={{ width: '90%', height: '90%' }}
// >
// {close => (
// <div className='fullPage'>
// <a className='close' onClick={close}> &times; </a>
// <div className='content'>
// <CompleteEventHistory event={event} />
// </div>
// </div>
// )}
// </Popup>
// </div>
// </Popover>);
// }
// /**
// * React component which represents the commentBy part.
// * @param {*} props
// */
// const CommentBy = (props) => {
// let { event } = props;
// if (getPreviousVersionNumber(event) === 0) {
// return (
// <div>
// <Label> Created by </Label>
// <div style={{ paddingTop: '5px', paddingLeft: '10px' }}>
// <p style={{ color: '#666666' }}> {props.event.username} </p>
// </div>
// <hr id='hrEvents' />
// </div>
// );
// };
// if (getPreviousVersionNumber(event) !== 0) {
// return (
// <div>
// <Label> Commented by </Label>
// <div style={{ paddingTop: '5px', paddingLeft: '10px' }}>
// <p style={{ color: '#666666' }}> {props.event.username} </p>
// </div>
// <hr id='hrEvents' />
// </div>
// );
// };
// return null;
// };
// /**
// * React component which represents on item of the event history
// * @param {*} props the props passed to this component
// */
// class EventVersionItem extends React.Component {
// render() {
// function buildTheDisplay(event, type, mostRecent) {
// return (
// <Well bsSize='small' style={{ marginBottom: '5px', cursor: 'pointer' }}>
// <b> {event.username} </b>
// {type === 'creation' ? 'created on ' : ''}
// {type === 'edition' ? 'edited on ' : ''}
// {Moment(event.creationDate).format('MMMM DD HH:mm')}
// {mostRecent === true ? ' (most recent)' : ''}
// {event.machine ? <span style={{ color: '#777777', fontStyle: 'italic' }}> from {event.machine} </span> : ''}
// </Well>
// );
// };
// return (
// buildTheDisplay(this.props.event, this.props.type, this.props.mostRecent)
// );
// };
// };
// /**
// * React component which displays the complete event history
// */
// const CompleteEventHistory = (props) => {
// let { event } = props;
// let eventVersions = [];
// /* first push the current event which is the latest version of the event*/
// //eventHistoryItems.push(<EventListViewExpanded event={event} isFullPage={true} />)
// eventVersions.push(<EventVersion event={event} isLatestVersion={true} />);
// /* then display the previous version of this event */
// while (event.previousVersionEvent != null) {
// event = event.previousVersionEvent;
// eventVersions.push(<div class='margin-bottom-10'>
// <Row>
// <Col xs={1}> </Col>
// <Col xs={11}>
// <EventVersion event={event} isOriginalVersion={event.previousVersionEvent === null ? true : false} />
// </Col>
// </Row>
// </div>);
// };
// return eventVersions;
// };
export default EditEvent;
\ No newline at end of file
This diff is collapsed.
import React from 'react'
import PropTypes from 'prop-types'
import HTMLEditor from './HTMLEditor';
import { getContent } from '../../helpers/EventHelpers';
/**
* This component takes care about displaying the provided content
* using a rich text editor or not
* in edition mode or in view mode
*
*/
class EventContentDisplayer extends React.Component {
render() {
let { isEditionMode, user, content, useRichTextEditor, investigationId, canEnableSaveButton } = this.props;
if (content && content instanceof Array && content.length !== 0) {
if (useRichTextEditor !== null && useRichTextEditor !== undefined) {
let HTMLText = this.getContent(content); // can be null
if (useRichTextEditor === true) {
// save the event plainText format to localstorage. This is usefull in case the editor is not changed (when only title is updated)
localStorage.setItem('plainText', getContent(content, 'plainText'));
return (<HTMLEditor
user={user}
text={HTMLText}
isEditionMode={isEditionMode}
investigationId={investigationId}
canEnableSaveButton={canEnableSaveButton}
/>)
} else if (useRichTextEditor === false) {
return (<div dangerouslySetInnerHTML={{ __html: HTMLText }} />)
}
}
}
return null
}
/**
* Gets the content of the event as an html string.
* @param {array} the event content, not null
* @returns {string} the html formatted content if it exists in the content. The plaintext content surrounded by <p> </p> if it does not exists. Null if none of these exist.
*/
getContent(content) {
let HTMLText = getContent(content, 'html');
if (HTMLText) {
return HTMLText;
} else {
let plainText = getContent(content, 'plainText');
if (plainText) {
return "<p>" + plainText + "</p>"
}
}
return null;
}
}
EventContentDisplayer.propTypes = {
/* the event content which will be displayed. An array containing different usable formats of the content */
content: PropTypes.array.isRequired,
/* whether a rich text editor should be used to view the content or not */
useRichTextEditor: PropTypes.bool.isRequired,
/* true when the display prupose is edition or creation of a new event. False when the display purpose is viewing an existing event content. */
isEditionMode: PropTypes.bool.isRequired,
/* the user currently logged in */
user: PropTypes.object,
/** the investigationId of the event being edited. */
investigationId: PropTypes.string,
/** 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 EventContentDisplayer;
\ No newline at end of file
......@@ -41,7 +41,6 @@ class EventListMenu extends React.Component {
}
}
//let defaultSortingOrder = (GUI_CONFIG().DEFAULT_SORTING_FILTER) ? GUI_CONFIG().DEFAULT_SORTING_FILTER.createdAt : 1;
return (
<Navbar
fluid
......@@ -171,4 +170,4 @@ EventListMenu.propTypes = {
sortingFilter: PropTypes.object.isRequired
};
export default EventListMenu;
export default EventListMenu;
\ No newline at end of file
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
*/