ES6 Plato on Github
Report Home
Summary Display
components/Event/DetailedEvent.js
Maintainability
63.97
Lines of code
557
Difficulty
55.89
Estimated Errors
3.84
Function weight
By Complexity
By SLOC
import React from 'react'; import { Row, Col, OverlayTrigger, Tooltip, Popover, Button, Well, Label, FormControl, InputGroup, Glyphicon } from 'react-bootstrap'; import { getEventCreationDate, getEventHistoryCreationDate, getOriginalEvent, getPreviousVersionNumber, getEventIcon } from '../../helpers/EventHelpers'; import EventFooter from './EventFooter'; import { ANNOTATION, NOTIFICATION, READ_MODE, DETAILED_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 DetailedEvent 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; } } DetailedEvent.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}> × </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}> × </a> <div className='content'> <CompleteEventHistory event={event} /> </div> </div> )} </Popup> </div> </Popover>); } /** * React component which represents the content of an annotation * @param {*} props the props passed to this component */ class AnnotationContent extends React.Component { constructor(props) { super(props); this.state = { inputTitleValue: this.props.event.title || '' }; this.onChangeInputValue = this.onChangeInputValue.bind(this); } onChangeInputValue(e) { this.setState({ inputTitleValue: e.target.value }); } render() { let { event, investigationId, user } = this.props; if (event.type && event.type === ANNOTATION) { return ( <div> <EventContentDisplayer content={event.content} useRichTextEditor={true} isEditionMode={true} user={user} investigationId={investigationId} canEnableSaveButton={this.props.canEnableSaveButton} /> <div style={{ paddingTop: '8px' }}> <InputGroup> <InputGroup.Addon style={{ backgroundColor: 'transparent', border: 'none' }}> <Label> Title </Label> </InputGroup.Addon> <FormControl type='text' value={this.state.inputTitleValue} onChange={this.onChangeInputValue} placeholder='Optional title here' ref={this.props.setTitleInput} /> </InputGroup> </div> </div> ); }; } componentDidUpdate() { this.props.canEnableSaveButton(); } } AnnotationContent.propTypes = { /* the callback function which activates the save button */ canEnableSaveButton: PropTypes.func.isRequired, /* The event to be shown */ event: PropTypes.object.isRequired, /* the investigationId */ investigationId: PropTypes.string.isRequired, /* the callback function which adds a ref to this */ setTitleInput: PropTypes.func.isRequired, /* the user */ user: PropTypes.object.isRequired }; /** * React component which represents the content of an notification * @param {*} props the props passed to this component */ const NotificationContent = (props) => { let { event, investigationId, user } = props; let notificationMessage = getOriginalEvent(event); if (event.type && event.type === NOTIFICATION) { let editorContent; if (getPreviousVersionNumber(event) === 0) { let fakeContent = [{ format: 'html', text: '<p> </p>' }]; editorContent = (<EventContentDisplayer content={fakeContent} useRichTextEditor={true} isEditionMode={true} user={user} investigationId={investigationId} canEnableSaveButton={props.canEnableSaveButton} />); } else { editorContent = (<EventContentDisplayer content={event.content} useRichTextEditor={true} isEditionMode={true} user={user} investigationId={investigationId} canEnableSaveButton={props.canEnableSaveButton} />); }; return ( <div> <div style={{ marginLeft: '10px' }} > <div id='divContainingHTMLNotificationInDocView'> <EventContentDisplayer content={notificationMessage.content} useRichTextEditor={false} isEditionMode={false} /> </div> </div> {editorContent} </div> ); }; }; NotificationContent.propTypes = { /* the callback function which activates the save button */ canEnableSaveButton: PropTypes.func.isRequired, /* The event to be shown */ event: PropTypes.object.isRequired, /* the investigationId */ investigationId: PropTypes.string.isRequired, /* the user */ user: PropTypes.object.isRequired }; /** * 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 DetailedEvent;