Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
ICAT
Datahub
Commits
5616310d
Commit
5616310d
authored
May 26, 2021
by
Alejandro De Maria Antolinos
Browse files
Refactoring, making it simple and coverting to functional the logbook
parent
16afc2bf
Changes
10
Hide whitespace changes
Inline
Side-by-side
src/components/Logbook/List/Event.js
View file @
5616310d
import
React
from
'
react
'
;
import
React
,
{
useState
}
from
'
react
'
;
import
moment
from
'
moment
'
;
import
PropTypes
from
'
prop-types
'
;
import
{
useHistory
}
from
'
react-router
'
;
import
{
useQuery
}
from
'
../../../helpers/hooks
'
;
import
{
Button
,
Glyphicon
,
Label
}
from
'
react-bootstrap
'
;
import
{
getOriginalEvent
,
getPreviousVersionNumber
,
}
from
'
../../../helpers/eventHelpers
'
;
import
{
getOriginalEvent
}
from
'
../../../helpers/eventHelpers
'
;
import
TagListInLine
from
'
../Tag/TagListInLine
'
;
import
styles
from
'
./EventList.module.css
'
;
import
EventTextBox
from
'
./EventTextBox
'
;
...
...
@@ -13,124 +11,97 @@ import EventTextBox from './EventTextBox';
/** React component which renders an event. Here 'event can be the classical event as found in the logbook but
* could also be a list of event corresponding to a collapsed line containing several events
*/
class
Event
extends
React
.
Component
{
constructor
(
props
)
{
super
(
props
);
this
.
state
=
{
collapsed
:
true
,
};
this
.
handleClick
=
this
.
handleClick
.
bind
(
this
);
}
render
()
{
let
events
=
[
this
.
props
.
event
];
function
Event
(
props
)
{
let
events
=
[
props
.
event
];
const
{
isReleased
}
=
props
;
if
(
this
.
props
.
event
.
events
&&
!
this
.
state
.
collapsed
)
{
events
=
events
.
concat
(
this
.
props
.
event
.
events
);
}
const
history
=
useHistory
();
const
query
=
useQuery
();
const
getButtonIcon
=
(
event
)
=>
{
if
(
this
.
props
.
logbookContext
.
isReleased
)
{
return
<
Glyphicon
glyph
=
"
eye-open
"
/>
;
}
if
(
getPreviousVersionNumber
(
event
)
===
0
)
{
return
<
Glyphicon
glyph
=
"
pencil
"
style
=
{{
width
:
10
}}
/>
;
}
return
<
Glyphicon
glyph
=
"
pencil
"
style
=
{{
width
:
10
}}
/>
;
};
const
[
collapsed
,
setCollapsed
]
=
useState
(
true
);
const
getTimeComponent
=
(
event
)
=>
{
return
(
<
a
id
=
{
event
.
_id
}
href
=
{
`events?page=
${
event
.
meta
.
page
.
currentPage
}
#
${
event
.
_id
}
`
}
style
=
{{
fontWeight
:
'
bold
'
}}
>
{
moment
(
getOriginalEvent
(
event
).
creationDate
).
format
(
moment
.
HTML5_FMT
.
TIME_SECONDS
)}
<
/a
>
);
};
return
events
.
map
((
event
,
index
)
=>
(
<
tr
key
=
{
index
}
style
=
{{
backgroundColor
:
'
#f0f0f6
'
}}
>
<
td
style
=
{{
width
:
16
}}
className
=
{
styles
.
borderTopSeparatorBetweenEvents
}
>
<
Button
bsStyle
=
"
default
"
bsSize
=
"
small
"
style
=
{{
width
:
25
,
position
:
'
static
'
,
padding
:
0
}}
onClick
=
{()
=>
this
.
props
.
onEventClicked
(
event
)}
>
{
getButtonIcon
(
event
)}
<
/Button
>
<
/td
>
if
(
props
.
event
.
events
&&
!
collapsed
)
{
events
=
events
.
concat
(
props
.
event
.
events
);
}
<
td
className
=
{
styles
.
borderTopSeparatorBetweenEvents
}
style
=
{{
width
:
16
,
backgroundColor
:
'
white
'
}}
>
{
getTimeComponent
(
event
)}
<
/td
>
const
getButtonIcon
=
()
=>
{
if
(
isReleased
)
{
return
<
Glyphicon
glyph
=
"
eye-open
"
/>
;
}
return
<
Glyphicon
glyph
=
"
pencil
"
style
=
{{
width
:
10
}}
/>
;
};
<
td
className
=
{
styles
.
borderTopSeparatorBetweenEvents
}
style
=
{{
paddingBottom
:
0
,
backgroundColor
:
'
white
'
,
const
getTimeComponent
=
(
event
)
=>
{
return
(
<
a
id
=
{
event
.
_id
}
href
=
{
`events?page=
${
event
.
meta
.
page
.
currentPage
}
#
${
event
.
_id
}
`
}
style
=
{{
fontWeight
:
'
bold
'
}}
>
{
moment
(
getOriginalEvent
(
event
).
creationDate
).
format
(
moment
.
HTML5_FMT
.
TIME_SECONDS
)}
<
/a
>
);
};
return
events
.
map
((
event
,
index
)
=>
(
<
tr
key
=
{
index
}
style
=
{{
backgroundColor
:
'
#f0f0f6
'
}}
>
<
td
style
=
{{
width
:
16
}}
className
=
{
styles
.
borderTopSeparatorBetweenEvents
}
>
<
Button
bsStyle
=
"
default
"
bsSize
=
"
small
"
style
=
{{
width
:
25
,
position
:
'
static
'
,
padding
:
0
}}
onClick
=
{()
=>
{
query
.
set
(
'
edit
'
,
event
.
_id
);
history
.
push
({
search
:
query
.
toString
()
});
}}
>
<
div
style
=
{{
marginLeft
:
5
,
backgroundColor
:
'
white
'
}}
>
<
EventTextBox
event
=
{
event
}
/
>
{
getButtonIcon
(
event
)}
<
/Button
>
<
/td
>
{
event
.
events
&&
this
.
state
.
collapsed
&&
(
<
Label
style
=
{{
color
:
'
blue
'
,
backgroundColor
:
'
#f8f8f8
'
,
cursor
:
'
pointer
'
,
}}
onClick
=
{
this
.
handleClick
}
>
....
{
event
.
events
.
length
}
command
lines
more
<
/Label
>
)}
<
/div
>
<
/td
>
<
td
className
=
{
styles
.
borderTopSeparatorBetweenEvents
}
style
=
{{
width
:
50
,
backgroundColor
:
'
white
'
}}
>
<
TagListInLine
tags
=
{
event
.
tag
}
/
>
<
/td
>
<
/tr
>
));
}
<
td
className
=
{
styles
.
borderTopSeparatorBetweenEvents
}
style
=
{{
width
:
16
,
backgroundColor
:
'
white
'
}}
>
{
getTimeComponent
(
event
)}
<
/td
>
handleClick
()
{
this
.
setState
({
collapsed
:
!
this
.
state
.
collapsed
});
}
<
td
className
=
{
styles
.
borderTopSeparatorBetweenEvents
}
style
=
{{
paddingBottom
:
0
,
backgroundColor
:
'
white
'
,
}}
>
<
div
style
=
{{
marginLeft
:
5
,
backgroundColor
:
'
white
'
}}
>
<
EventTextBox
event
=
{
event
}
/
>
getUncollapsedEvents
()
{
return
(
<
tbody
>
{
this
.
props
.
event
.
events
.
map
((
event
)
=>
this
.
getEventContentBody
(
event
)
)}
<
/tbody
>
);
}
{
event
.
events
&&
collapsed
&&
(
<
Label
style
=
{{
color
:
'
blue
'
,
backgroundColor
:
'
#f8f8f8
'
,
cursor
:
'
pointer
'
,
}}
onClick
=
{
setCollapsed
(
!
collapsed
)}
>
....
{
event
.
events
.
length
}
command
lines
more
<
/Label
>
)}
<
/div
>
<
/td
>
<
td
className
=
{
styles
.
borderTopSeparatorBetweenEvents
}
style
=
{{
width
:
50
,
backgroundColor
:
'
white
'
}}
>
<
TagListInLine
tags
=
{
event
.
tag
}
/
>
<
/td
>
<
/tr
>
));
}
Event
.
protypes
=
{
/** A classical event or a structure representing a collapsed line containing several similar events */
event
:
PropTypes
.
object
,
/** Context in which the logbook is run */
logbookContext
:
PropTypes
.
object
,
/** Callback function triggered which the user clicks a link to edit/consult the detailed event */
onEventClicked
:
PropTypes
.
func
,
};
export
default
Event
;
src/components/Logbook/List/EventList.js
View file @
5616310d
...
...
@@ -76,7 +76,7 @@ function getItems(events, automaticCollapsing) {
* The list of the all events
*/
function
EventList
(
props
)
{
const
{
events
}
=
props
;
const
{
events
,
isReleased
}
=
props
;
if
(
!
events
)
{
return
null
;
...
...
@@ -121,7 +121,7 @@ function EventList(props) {
<
Event
key
=
{
index
}
event
=
{
event
}
logbookContext
=
{
props
.
logbookContext
}
isReleased
=
{
isReleased
}
onEventClicked
=
{
props
.
onEventClicked
}
/
>
);
...
...
src/components/Logbook/LogbookPager.js
View file @
5616310d
import
React
from
'
react
'
;
import
{
useHistory
}
from
'
react-router
'
;
import
{
useQuery
}
from
'
../../helpers/hooks
'
;
import
PropTypes
from
'
prop-types
'
;
import
ReactPaginate
from
'
react-paginate
'
;
import
UI
from
'
../../config/ui
'
;
...
...
@@ -24,6 +25,7 @@ const getTotalPageNumber = (eventCount) => {
/* This class handles the display of pages for the logbook */
function
LogbookPager
(
props
)
{
const
history
=
useHistory
();
const
query
=
useQuery
();
const
{
eventCount
=
0
,
isCentered
,
activePage
}
=
props
;
if
(
getTotalPageNumber
(
eventCount
)
<=
1
||
eventCount
===
0
)
{
...
...
@@ -50,7 +52,8 @@ function LogbookPager(props) {
subContainerClassName
=
{
'
pages pagination pagination-sm
'
}
activeClassName
=
{
'
active
'
}
onPageChange
=
{(
data
)
=>
{
history
.
push
(
`
${
window
.
location
.
pathname
}
?page=
${
data
.
selected
+
1
}
`
);
query
.
set
(
'
page
'
,
data
.
selected
+
1
);
history
.
push
({
search
:
query
.
toString
()
});
}}
/
>
<
/div
>
...
...
src/components/Logbook/Menu/EventListMenu.js
View file @
5616310d
import
PropTypes
from
'
prop-types
'
;
import
{
useHistory
}
from
'
react-router
'
;
import
{
useQuery
}
from
'
../../../helpers/hooks
'
;
import
React
,
{
useState
}
from
'
react
'
;
import
{
Nav
,
Navbar
,
NavItem
,
Well
}
from
'
react-bootstrap
'
;
import
{
ComboSearch
}
from
'
react-combo-search
'
;
...
...
@@ -13,6 +15,9 @@ import SettingLogbookMenuPanel from './SettingLogbookMenuPanel';
* The menu displayed above the event list
*/
function
EventListMenu
(
props
)
{
const
history
=
useHistory
();
const
query
=
useQuery
();
const
[
isNavbarExpanded
,
setIsNavBarExpanded
]
=
useState
(
false
);
const
[
isSettingsDisplayed
,
setIsSettingsDisplayed
]
=
useState
(
false
);
...
...
@@ -24,11 +29,9 @@ function EventListMenu(props) {
sessionId
,
getEvents
,
periodicdata
,
setNewEventVisibility
,
logbookContext
,
eventCountSinceLastRefresh
,
activePage
,
searchEvents
,
categoryTypes
,
automaticCollapsing
,
automaticRefresh
,
...
...
@@ -42,10 +45,6 @@ function EventListMenu(props) {
setIsNavBarExpanded
(
!
isNavbarExpanded
);
};
const
setViewSettings
=
()
=>
{
setIsSettingsDisplayed
(
!
isSettingsDisplayed
);
};
const
getSettingsTooltip
=
()
=>
{
return
isSettingsDisplayed
?
'
Hide settings
'
:
'
Show settings
'
;
};
...
...
@@ -57,16 +56,20 @@ function EventListMenu(props) {
const
onSelectNavbar
=
(
eventKey
)
=>
{
if
(
eventKey
===
1
)
{
if
(
isNewButtonEnabled
)
{
setNewEventVisibility
(
NEW_EVENT_VISIBLE
);
query
.
set
(
'
edit
'
,
NEW_EVENT_VISIBLE
);
history
.
push
({
search
:
query
.
toString
()
});
}
}
if
(
eventKey
===
3
)
{
set
ViewSettings
(
);
set
IsSettingsDisplayed
(
!
isSettingsDisplayed
);
}
};
const
onSearch
=
(
data
)
=>
{
return
searchEvents
(
data
);
if
(
data
?.
length
>
0
)
{
query
.
set
(
'
search
'
,
data
[
0
].
search
);
history
.
push
({
search
:
query
.
toString
()
});
}
};
return
(
...
...
@@ -242,8 +245,6 @@ EventListMenu.propTypes = {
isNewButtonEnabled
:
PropTypes
.
bool
,
/** Context in which the logbook is run */
logbookContext
:
PropTypes
.
object
,
/** Callback function which reloads the events based on search criteria*/
searchEvents
:
PropTypes
.
func
,
/** Selection filter for mongo request (used for PDF request) */
selectionFilter
:
PropTypes
.
object
,
/** Session identifier */
...
...
src/components/Logbook/NewOrEditEventPanel.js
View file @
5616310d
import
React
from
'
react
'
;
import
PropTypes
from
'
prop-types
'
;
import
React
,
{
useState
}
from
'
react
'
;
import
{
useFetcher
}
from
'
rest-hooks
'
;
import
{
useHistory
}
from
'
react-router
'
;
import
{
useQuery
}
from
'
../../helpers/hooks
'
;
import
Loader
from
'
../Loader
'
;
import
{
Button
,
Glyphicon
,
...
...
@@ -7,6 +10,7 @@ import {
OverlayTrigger
,
Panel
,
Tooltip
,
Alert
,
}
from
'
react-bootstrap
'
;
import
TimeAgo
from
'
react-timeago
'
;
import
{
...
...
@@ -17,57 +21,200 @@ import {
LOCALSTORAGE_KEY_NEW_EVENT_CONTENT_IN_HTML_FORMAT
,
LOCALSTORAGE_KEY_NEW_EVENT_CONTENT_IN_PLAINTEXT_FORMAT
,
}
from
'
../../constants/eventTypes
'
;
import
{
isEqual
}
from
'
lodash-es
'
;
import
TagsSelectorContainer
from
'
../../containers/Logbook/Tags/TagsSelectorContainer
'
;
import
{
getText
}
from
'
../../helpers/eventHelpers
'
;
import
EditorWrapper
from
'
./Editor/EditorWrapper
'
;
import
EventFooter
from
'
./EventFooter
'
;
import
EventVersions
from
'
./EventVersions
'
;
import
EventResource
from
'
../../resources/events
'
;
/** Render author and time */
const
AuthorAndTime
=
(
props
)
=>
{
if
(
props
.
event
)
{
return
(
<
h5
style
=
{{
marginTop
:
0
,
marginBottom
:
0
}}
>
<
small
style
=
{{
color
:
'
white
'
}}
>
{
'
'
}
<
em
>
@{
props
.
event
.
username
}
-
{
'
'
}
<
TimeAgo
date
=
{
props
.
event
.
creationDate
}
/
>
<
OverlayTrigger
trigger
=
"
click
"
placement
=
"
bottom
"
overlay
=
{
EventVersions
(
props
.
event
)}
rootClose
>
<
div
style
=
{{
display
:
'
inline-block
'
}}
>
<
Button
bsStyle
=
"
link
"
bsSize
=
"
xs
"
>
{
'
'
}
<
Glyphicon
glyph
=
"
cog
"
style
=
{{
color
:
'
white
'
}}
/>{' '
}
<
/Button
>
<
/div
>
<
/OverlayTrigger
>
<
/em>{' '
}
<
/small
>
<
/h5
>
);
}
return
null
;
};
/**
*
* React component which renders a Panel for creating of editing an event
*/
class
NewOrEditEventPanel
extends
React
.
Component
{
constructor
(
props
)
{
super
(
props
);
function
NewOrEditEventPanel
(
props
)
{
const
{
event
,
user
,
investigationId
}
=
props
;
this
.
state
=
{
isSaveButtonEnabled
:
false
,
//whether the editor contains some text or not
isEditorContentValid
:
false
,
optionPanelDecollaped
:
false
,
selectedTags
:
[],
// tags which are currently selected
};
const
history
=
useHistory
();
const
query
=
useQuery
();
this
.
isSaveButtonEnabled
=
this
.
isSaveButtonEnabled
.
bind
(
this
);
this
.
onCancelButtonClicked
=
this
.
onCancelButtonClicked
.
bind
(
this
);
this
.
onEventContentChanged
=
this
.
onEventContentChanged
.
bind
(
this
);
this
.
onSaveButtonClicked
=
this
.
onSaveButtonClicked
.
bind
(
this
);
this
.
onTagSelectionChanged
=
this
.
onTagSelectionChanged
.
bind
(
this
);
}
/** State */
const
[
selectedTags
,
setSelectedTags
]
=
useState
([]);
const
[
error
,
setError
]
=
useState
();
const
[
loading
,
setLoading
]
=
useState
(
false
);
render
()
{
const
{
event
,
user
,
investigationId
}
=
this
.
props
;
/* Fetchers */
const
createEvent
=
useFetcher
(
EventResource
.
createShape
());
const
updateEvent
=
useFetcher
(
EventResource
.
updateShape
());
return
(
/* Not sure if we should kept this code, now it is useless but it might be needed **/
const
onEventContentChanged
=
()
=>
{
let
isEditorContentValid
=
false
;
if
(
event
)
{
//editing an event
const
currentTextInEditionMode
=
localStorage
.
getItem
(
LOCALSTORAGE_KEY_EDITED_EVENT_CONTENT_IN_PLAINTEXT_FORMAT
+
event
.
_id
);
isEditorContentValid
=
currentTextInEditionMode
!==
''
&&
currentTextInEditionMode
!==
getText
(
event
.
content
,
'
plainText
'
);
}
else
{
//creating an event
const
currentTextInCreationMode
=
localStorage
.
getItem
(
LOCALSTORAGE_KEY_NEW_EVENT_CONTENT_IN_PLAINTEXT_FORMAT
);
isEditorContentValid
=
currentTextInCreationMode
!==
''
;
}
return
isEditorContentValid
;
};
/**
* Callback function triggered when the user click the save button while an event is being created or updated.
*/
const
onSaveButtonClicked
=
async
()
=>
{
try
{
setLoading
(
true
);
const
currentTagIds
=
selectedTags
.
map
((
tag
)
=>
tag
.
_id
);
if
(
event
)
{
await
updateEvent
(
{
investigationId
},
{
_id
:
event
.
_id
,
category
:
event
.
category
,
content
:
[
{
format
:
'
plainText
'
,
text
:
localStorage
.
getItem
(
LOCALSTORAGE_KEY_EDITED_EVENT_CONTENT_IN_PLAINTEXT_FORMAT
+
event
.
_id
),
},
{
format
:
'
html
'
,
text
:
localStorage
.
getItem
(
LOCALSTORAGE_KEY_EDITED_EVENT_CONTENT_IN_HTML_FORMAT
+
event
.
_id
),
},
],
creationDate
:
Date
(),
type
:
event
.
type
,
tag
:
currentTagIds
,
title
:
null
,
previousVersionEvent
:
event
.
_id
,
}
);
}
else
{
// creation of an event
await
createEvent
(
{
investigationId
},
{
category
:
EVENT_CATEGORY_COMMENT
,
content
:
[
{
format
:
'
plainText
'
,
text
:
localStorage
.
getItem
(
LOCALSTORAGE_KEY_NEW_EVENT_CONTENT_IN_PLAINTEXT_FORMAT
),
},
{
format
:
'
html
'
,
text
:
localStorage
.
getItem
(
LOCALSTORAGE_KEY_NEW_EVENT_CONTENT_IN_HTML_FORMAT
),
},
],
creationDate
:
Date
(),
investigationId
,
title
:
null
,
tag
:
currentTagIds
,
type
:
ANNOTATION
,
}
);
}
query
.
delete
(
'
edit
'
);
history
.
push
({
search
:
query
.
toString
()
});
}
catch
(
e
)
{
setError
(
`
${
e
.
status
}
:
${
e
.
message
}
`
);
}
};
const
onCancelButtonClicked
=
()
=>
{
localStorage
.
removeItem
(
'
plainText
'
);
localStorage
.
removeItem
(
'
HTMLText
'
);
query
.
delete
(
'
edit
'
);
history
.
push
({
search
:
query
.
toString
()
});
};
return
(
<>
<
div
style
=
{{
display
:
'
flex
'
,
flexDirection
:
'
column
'
,
height
:
'
100%
'
}}
>
<
div
style
=
{{
flex
:
'
0 0 33px
'
}}
>
<
Panel
bsStyle
=
"
primary
"
style
=
{{
marginBottom
:
0
}}
>
<
Panel
.
Heading
style
=
{{
padding
:
'
5px 15px
'
}}
>
<
b
>
{
this
.
props
.
event
?
'
Edit
'
:
'
New comment
'
}
<
/b
>
<
b
>
{
event
?
'
Edit
'
:
'
New comment
'
}
<
/b
>
<
div
className
=
"
pull-right
"
>
<
AuthorAndTime
event
=
{
event
}
/
>
<
/div
>
<
/Panel.Heading
>
<
/Panel
>
<
/div
>
{
error
&&
(
<
div
style
=
{{
padding
:
2
}}
>
<
Alert
bsStyle
=
"
danger
"
>
<
strong
>
Oops
<
/strong> {error
}
<
/Alert
>
<
/div
>
)}
<
div
style
=
{{
flex
:
'
2 2 70%
'
}}
>
<
EditorWrapper
event
=
{
event
}
investigationId
=
{
investigationId
}
onContentChanged
=
{
this
.
onEventContentChanged
}
user
=
{
user
}
/
>
{
loading
&&
(
<>
<
br
/>
<
Loader
message
=
"
Saving...
"
><
/Loader
>
<
/
>
)}
{
!
loading
&&
(
<
EditorWrapper
event
=
{
event
}
investigationId
=
{
investigationId
}
onContentChanged
=
{
onEventContentChanged
}
user
=
{
user
}
/
>
)}
<
/div
>
<
div
style
=
{{
flex
:
'
1 1 32px
'
,
padding
:
'
5px 10px 0 10px
'
}}
>
...
...
@@ -88,7 +235,7 @@ class NewOrEditEventPanel extends React.Component {
}
>
<
a
href
=
{
`/investigation/
${
this
.
props
.
investigationId
}
/events/tagManager`
}
href
=
{
`/investigation/
${
investigationId
}
/events/tagManager`
}
target
=
"
_blank
"
rel
=
"
noopener noreferrer
"
>
...
...
@@ -100,9 +247,9 @@ class NewOrEditEventPanel extends React.Component {
<
/div
>
<
div
style
=
{{
flex
:
'
1 1 100px
'
}}
>
<
TagsSelectorContainer
investigationId
=
{
this
.
props
.
investigationId
}
onChange
=
{
this
.
onTagSelectionChanged
}
tags
=
{
this