Commit c2f8df4e authored by Maxime Chaillet's avatar Maxime Chaillet

Merge branch 'issue149' into 'master'

Issue149

Closes #149

See merge request !149
parents 86b376a3 750176ef
Pipeline #11497 passed with stages
in 6 minutes and 30 seconds
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
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 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>
)}