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
4969698e
Commit
4969698e
authored
May 28, 2021
by
Alejandro De Maria Antolinos
Browse files
Merge branch 'issue_491' into 'milestone-logbook-search-settings'
Issue 491 See merge request
!515
parents
06d5f2f6
934059ab
Pipeline
#47534
passed with stage
in 3 minutes and 1 second
Changes
20
Pipelines
1
Expand all
Hide whitespace changes
Inline
Side-by-side
package-lock.json
View file @
4969698e
This diff is collapsed.
Click to expand it.
src/api/icat-plus/logbook.js
View file @
4969698e
import
ICATPLUS
from
'
../../config/icatPlus
'
;
/**
* Get URL needed to retrieve events for a given investigation
* @param {String} sessionId the session identifier
* @param {String} investigationId the session identifier
* @return {String} the URL to get the requested events. Null if investigationId or sessionId is missing.
*/
export
function
getEventsByInvestigationId
(
sessionId
,
investigationId
)
{
return
`
${
ICATPLUS
.
server
}
/logbook/
${
sessionId
}
/investigation/id/
${
investigationId
}
/event/query`
;
}
/**
* Get URL needed to retrieve the event count for a given investigation
* @param {string} sessionId the session identifier
* @param {String} investigationId the investigation identifier
* @return {String} the URL to get the requested event count
*/
export
function
getEventCountByInvestigationId
(
sessionId
,
investigationId
)
{
return
`
${
ICATPLUS
.
server
}
/logbook/
${
sessionId
}
/investigation/id/
${
investigationId
}
/event/count`
;
}
/**
* Get URL used to create a new event for a given investigation on ICAT+
* @param {*} investigationId investigation indentifier
* @param {String} sessionId session identifier
* @return {String} URL to get the requested events
*/
export
function
createEvent
(
sessionId
,
investigationId
)
{
return
`
${
ICATPLUS
.
server
}
/logbook/
${
sessionId
}
/investigation/id/
${
investigationId
}
/event/create`
;
}
/**
* Get URL used to update an event on a given investigation on ICAT+
* @param {String} sessionId the session identifier
* @param {String} investigationId the investigation indentifier
* @return {String} the URL to get the requested events
*/
export
function
updateEvent
(
sessionId
,
investigationId
)
{
return
`
${
ICATPLUS
.
server
}
/logbook/
${
sessionId
}
/investigation/id/
${
investigationId
}
/event/update`
;
}
/**
* Get URL used to download a PDF file for a given investigation from the logbook
* @param {string} sessionId session identifier
* @param {*} investigationId investigation identifier
* @param {object} selectionFilter selection filter used to retrieve part of the logbook. This is URI encoded and passed as query string
*/
export
function
getPDF
(
sessionId
,
investigationId
,
selectionFilter
)
{
return
`
${
ICATPLUS
.
server
}
/logbook/
${
sessionId
}
/investigation/id/
${
investigationId
}
/event/pdf?find=&sort=&skip=&limit=`
.
replace
(
'
find=
'
,
()
=>
{
return
`find=
${
selectionFilter
&&
selectionFilter
.
find
?
JSON
.
stringify
(
selectionFilter
.
find
)
:
''
}
`
;
})
.
replace
(
'
&sort=
'
,
()
=>
{
return
`&sort=
${
selectionFilter
&&
selectionFilter
.
sort
?
JSON
.
stringify
(
selectionFilter
.
sort
)
:
''
}
`
;
})
.
replace
(
'
&skip=
'
,
()
=>
{
return
`&skip=
${
selectionFilter
&&
selectionFilter
.
skip
!==
undefined
?
JSON
.
stringify
(
selectionFilter
.
skip
)
:
''
}
`
;
})
.
replace
(
'
&limit=
'
,
()
=>
{
return
`&limit=
${
selectionFilter
&&
selectionFilter
.
limit
!==
undefined
?
JSON
.
stringify
(
selectionFilter
.
limit
)
:
''
}
`
;
});
export
function
getEventURL
(
sessionId
,
investigationId
,
skip
,
limit
,
sortOrder
,
sortBy
,
types
,
format
)
{
const
params
=
new
URLSearchParams
();
params
.
set
(
'
investigationId
'
,
investigationId
);
if
(
limit
)
params
.
set
(
'
limit
'
,
limit
);
if
(
sortBy
)
params
.
set
(
'
sortBy
'
,
sortBy
);
if
(
sortOrder
)
params
.
set
(
'
sortOrder
'
,
sortOrder
);
if
(
types
)
params
.
set
(
'
types
'
,
types
);
if
(
skip
)
params
.
set
(
'
skip
'
,
skip
);
if
(
format
)
params
.
set
(
'
format
'
,
format
);
return
`
${
ICATPLUS
.
server
}
/logbook/
${
sessionId
}
/event?
${
params
.
toString
()}
`
;
}
/** Get the tags associated to a given investigation
...
...
src/components/Logbook/List/Event.js
View file @
4969698e
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 @
4969698e
...
...
@@ -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 @
4969698e
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 @
4969698e
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
'
;
import
{
get
PDF
}
from
'
../../../api/icat-plus/logbook
'
;
import
{
get
EventURL
}
from
'
../../../api/icat-plus/logbook
'
;
import
{
NEW_EVENT_VISIBLE
}
from
'
../../../constants/eventTypes
'
;
import
LogbookPager
from
'
../LogbookPager
'
;
import
EventListMenuButton
from
'
./EventListMenuButton
'
;
...
...
@@ -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
);
...
...
@@ -20,16 +25,18 @@ function EventListMenu(props) {
eventCountBySelectionFilter
,
investigationId
,
isNewButtonEnabled
,
selectionFilter
,
sessionId
,
getEvents
,
periodicdata
,
setNewEventVisibility
,
logbookContext
,
isReleased
,
eventCountSinceLastRefresh
,
activePage
,
searchEvents
,
categoryTypes
,
skip
,
limit
,
sortOrder
,
sortBy
,
types
,
automaticCollapsing
,
automaticRefresh
,
isSortingLatestEventsFirst
,
...
...
@@ -42,10 +49,6 @@ function EventListMenu(props) {
setIsNavBarExpanded
(
!
isNavbarExpanded
);
};
const
setViewSettings
=
()
=>
{
setIsSettingsDisplayed
(
!
isSettingsDisplayed
);
};
const
getSettingsTooltip
=
()
=>
{
return
isSettingsDisplayed
?
'
Hide settings
'
:
'
Show settings
'
;
};
...
...
@@ -57,16 +60,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
(
...
...
@@ -96,7 +103,7 @@ function EventListMenu(props) {
glyph
=
"
plus
"
tooltipText
=
"
Create a new event
"
isEnabled
=
{
isNewButtonEnabled
}
isVisible
=
{
!
logbookContext
.
isReleased
}
isVisible
=
{
!
isReleased
}
/
>
<
/NavItem
>
...
...
@@ -111,7 +118,7 @@ function EventListMenu(props) {
glyph
=
"
camera
"
tooltipText
=
"
Take a photo
"
isEnabled
isVisible
=
{
!
logbookContext
.
isReleased
}
isVisible
=
{
!
isReleased
}
/
>
<
/NavItem
>
<
/Nav
>
...
...
@@ -133,7 +140,16 @@ function EventListMenu(props) {
href
=
{
!
isNewButtonEnabled
||
eventCountBySelectionFilter
===
0
?
null
:
getPDF
(
sessionId
,
investigationId
,
selectionFilter
)
:
getEventURL
(
sessionId
,
investigationId
,
skip
,
limit
,
sortOrder
,
sortBy
,
types
,
'
pdf
'
)
}
target
=
"
_blank
"
className
=
"
logbookNavItem
"
...
...
@@ -160,16 +176,18 @@ function EventListMenu(props) {
isEnabled
/>
<
/NavItem
>
<
NavItem
eventKey
=
{
5
}
className
=
"
logbookNavItem
"
>
<
div
className
=
"
hidden-xs hidden-sm
"
>
<
NewlyAvailableEventsDialogue
autorefreshEventList
=
{
automaticRefresh
}
eventCountByPeriodicRefresher
=
{
periodicdata
}
eventCountSinceLastRefresh
=
{
eventCountSinceLastRefresh
}
onIconClicked
=
{()
=>
getEvents
(
periodicdata
)}
/
>
<
/div
>
<
/NavItem
>
{
false
&&
(
<
NavItem
eventKey
=
{
5
}
className
=
"
logbookNavItem
"
>
<
div
className
=
"
hidden-xs hidden-sm
"
>
<
NewlyAvailableEventsDialogue
autorefreshEventList
=
{
false
}
//{isAutorefreshEnabled}
eventCountByPeriodicRefresher
=
{
periodicdata
}
eventCountSinceLastRefresh
=
{
eventCountSinceLastRefresh
}
onIconClicked
=
{()
=>
getEvents
(
periodicdata
)}
/
>
<
/div
>
<
/NavItem
>
)}
<
NavItem
className
=
"
logbookNavItem
"
>
<
div
className
=
"
hidden-xs hidden-sm hidden-md
"
>
...
...
@@ -215,7 +233,7 @@ function EventListMenu(props) {
{
isSettingsDisplayed
&&
(
<
SettingLogbookMenuPanel
categoryTypes
=
{
categoryTypes
}
logbookContext
=
{
logbookContext
}
isReleased
=
{
isReleased
}
automaticCollapsing
=
{
automaticCollapsing
}
automaticRefresh
=
{
automaticRefresh
}
isSortingLatestEventsFirst
=
{
isSortingLatestEventsFirst
}
...
...
@@ -238,10 +256,8 @@ EventListMenu.propTypes = {
investigationId
:
PropTypes
.
number
,
/** Whether the New button is enabled or not */
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
,
/** If investigation is relased */
isReleased
:
PropTypes
.
bool
,
/** Selection filter for mongo request (used for PDF request) */
selectionFilter
:
PropTypes
.
object
,
/** Session identifier */
...
...
src/components/Logbook/Menu/SettingLogbookMenuPanel.js
View file @
4969698e
...
...
@@ -58,14 +58,17 @@ const checkBoxes = [
export
default
function
SettingLogbookMenuPanel
(
props
)
{
const
dispatch
=
useDispatch
();
const
{
isReleased
,
automaticCollapsing
,
automaticRefresh
,
isSortingLatestEventsFirst
,
}
=
props
;
const
getValueByName
=
(
name
)
=>
checkBoxes
.
find
((
cb
)
=>
cb
.
name
===
name
).
value
;
const
logbookContext
=
props
.
logbookContext
;
const
toggleColor
=
'
#428bca
'
;
const
automaticCollapsing
=
props
.
automaticCollapsing
;
const
automaticRefresh
=
props
.
automaticRefresh
;
const
isSortingLatestEventsFirst
=
props
.
isSortingLatestEventsFirst
;
/** Checkbox will be checked if and only if its values are in the categoryTypes */
const
isChecked
=
(
values
)
=>
{
...
...
@@ -129,7 +132,7 @@ export default function SettingLogbookMenuPanel(props) {
<
/Col
>
<
Col
md
=
{
2
}
sm
=
{
6
}
xs
=
{
12
}
>
<
Grid
>
{
!
logbookContext
.
isReleased
&&
(
{
!
isReleased
&&
(
<
Row
>
<
Col
md
=
{
12
}
sm
=
{
12
}
xs
=
{
12
}
>
<
Switch
...
...
src/components/Logbook/NewOrEditEventPanel.js
View file @
4969698e
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,202 @@ 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
)
=>
{