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
On Thursday, 7th July from 1 to 3 pm there will be a maintenance with a short downtime of GitLab.
Open sidebar
nomad-lab
nomad-FAIR
Commits
9c1d8fcf
Commit
9c1d8fcf
authored
Apr 23, 2021
by
Markus Scheidgen
Browse files
Updated README.md, added breadcrumb navigation.
parent
8a89eec3
Pipeline
#100187
passed with stages
in 25 minutes and 30 seconds
Changes
8
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
README.md
View file @
9c1d8fcf
...
...
@@ -48,8 +48,9 @@ Omitted versions are plain bugfix releases with only minor changes and fixes.
### v0.10.3
-
fixes in the VASP parser
-
new tubemole parser
-
new tu
r
bemole parser
-
property placeholders while loading entry page
-
improved UI navigation with breadcrumbs
### v0.10.2
-
fixes small parser and normalizer issues
...
...
gui/src/components/UserdataPage.js
View file @
9c1d8fcf
...
...
@@ -77,7 +77,10 @@ function UserdataPage() {
initialRequest
=
{{
order_by
:
'
upload_time
'
,
uploads_grouped
:
true
}}
initialResultTab
=
"
uploads
"
availableResultTabs
=
{[
'
uploads
'
,
'
datasets
'
,
'
entries
'
,
...(
encyclopediaEnabled
?
[
'
materials
'
]
:
[])]}
resultListProps
=
{{
selectedColumnsKey
:
'
userEntries
'
,
selectedColumns
:
[
'
formula
'
,
'
upload_time
'
,
'
mainfile
'
,
'
published
'
,
'
co_authors
'
,
'
references
'
,
'
datasets
'
]}}
resultListProps
=
{{
selectedColumnsKey
:
'
userEntries
'
,
entryPagePathPrefix
:
'
/userdata
'
,
selectedColumns
:
[
'
formula
'
,
'
upload_time
'
,
'
mainfile
'
,
'
published
'
,
'
co_authors
'
,
'
references
'
,
'
datasets
'
]}}
/
>
}
...
...
gui/src/components/nav/AppBar.js
View file @
9c1d8fcf
...
...
@@ -17,10 +17,10 @@
*/
import
React
,
{
useCallback
,
useContext
,
useEffect
,
useState
}
from
'
react
'
import
{
useLocation
}
from
'
react-router-dom
'
import
{
Link
as
RouterLink
}
from
'
react-router-dom
'
import
{
Typography
,
AppBar
as
MuiAppBar
,
Toolbar
,
Link
,
LinearProgress
,
makeStyles
}
from
'
@material-ui/core
'
import
{
allRoutes
as
r
oute
s
}
from
'
./Routes
'
import
{
useR
oute
}
from
'
./Routes
'
import
LoginLogout
from
'
../LoginLogout
'
import
HelpDialog
from
'
../Help
'
import
MainMenu
from
'
./MainMenu
'
...
...
@@ -77,14 +77,7 @@ const useAppBarStyles = makeStyles(theme => ({
export
default
function
AppBar
()
{
const
classes
=
useAppBarStyles
()
const
{
pathname
}
=
useLocation
()
const
selectedRouteKey
=
Object
.
keys
(
routes
).
find
(
key
=>
{
const
route
=
routes
[
key
]
return
pathname
.
startsWith
(
route
.
path
)
})
const
selectedRoute
=
routes
[
selectedRouteKey
]
const
help
=
selectedRoute
.
appBarHelp
const
title
=
selectedRoute
.
appBarTitle
const
{
help
,
title
,
breadCrumbs
}
=
useRoute
()
return
<
MuiAppBar
position
=
"
fixed
"
className
=
{
classes
.
root
}
>
<
Toolbar
classes
=
{{
root
:
classes
.
toolbar
}}
disableGutters
>
...
...
@@ -93,6 +86,11 @@ export default function AppBar() {
<
img
alt
=
"
The NOMAD logo
"
className
=
{
classes
.
logo
}
src
=
{
`
${
guiBase
}
/nomad.png`
}
><
/img
>
<
/Link
>
<
Typography
variant
=
"
h6
"
color
=
"
inherit
"
noWrap
>
{
breadCrumbs
&&
breadCrumbs
.
map
((
breadCrumb
,
index
)
=>
<
React
.
Fragment
key
=
{
index
}
>
<
Link
component
=
{
RouterLink
}
to
=
{
breadCrumb
.
path
}
>
{
breadCrumb
.
title
}
<
/Link> › 
;
<
/React.Fragment>
)
}
{
title
}
<
/Typography
>
{
help
?
<
HelpDialog
color
=
"
inherit
"
maxWidth
=
"
md
"
classes
=
{{
root
:
classes
.
helpButton
}}
{...
help
}
/> : ''
}
...
...
gui/src/components/nav/MainMenu.js
View file @
9c1d8fcf
...
...
@@ -19,10 +19,9 @@
import
{
Button
,
Dialog
,
DialogActions
,
DialogContent
,
DialogTitle
,
FormControlLabel
,
FormGroup
,
Switch
}
from
'
@material-ui/core
'
import
React
,
{
useEffect
,
useMemo
,
useRef
,
useState
}
from
'
react
'
import
{
useLocation
}
from
'
react-router-dom
'
import
React
,
{
useEffect
,
useMemo
,
useState
}
from
'
react
'
import
Markdown
from
'
../Markdown
'
import
{
allRoutes
as
r
oute
s
}
from
'
./Routes
'
import
{
useR
oute
}
from
'
./Routes
'
import
{
matomo
}
from
'
../App
'
import
{
useCookies
}
from
'
react-cookie
'
import
{
MenuBar
,
MenuBarItem
,
MenuBarMenu
}
from
'
./MenuBar
'
...
...
@@ -118,27 +117,8 @@ function Consent(moreProps) {
}
export
default
function
MainMenu
()
{
// We keep the URL of those path where components keep meaningful state in the URL.
// If the menu is used to comeback, the old URL is used. Therefore, it appears as
// if the same component instance with the same state is still there.
const
{
pathname
,
search
}
=
useLocation
()
const
historyRef
=
useRef
({
search
:
'
/search
'
,
userdata
:
'
/userdata
'
})
const
history
=
{...
historyRef
.
current
}
Object
.
keys
(
historyRef
.
current
).
forEach
(
key
=>
{
if
(
pathname
.
startsWith
(
'
/
'
+
key
))
{
historyRef
.
current
[
key
]
=
pathname
+
(
search
||
''
)
history
[
key
]
=
'
/
'
+
key
}
})
const
route
=
Object
.
keys
(
routes
).
find
(
routeKey
=>
pathname
.
startsWith
(
routes
[
routeKey
].
path
))
const
routeNavPath
=
route
&&
routes
[
route
].
navPath
if
(
routeNavPath
)
{
historyRef
.
current
.
navPath
=
routeNavPath
}
const
selected
=
(
route
&&
routes
[
route
].
navPath
)
||
historyRef
.
current
.
navPath
||
(
route
&&
routes
[
route
].
defaultNavPath
)
||
'
publish/uploads
'
const
route
=
useRoute
()
const
selected
=
(
route
?.
navPath
)
||
'
publish/uploads
'
return
<
MenuBar
selected
=
{
selected
}
>
<
MenuBarMenu
name
=
"
publish
"
label
=
"
Publish
"
route
=
"
/uploads
"
icon
=
{
<
BackupIcon
/>
}
>
...
...
@@ -147,13 +127,13 @@ export default function MainMenu() {
tooltip
=
"
Upload and publish new data
"
icon
=
{
<
SearchIcon
/>
}
/
>
<
MenuBarItem
label
=
"
Your data
"
name
=
"
userdata
"
route
=
{
history
.
userdata
}
label
=
"
Your data
"
name
=
"
userdata
"
route
=
"
/
userdata
"
tooltip
=
"
Manage your uploaded data
"
icon
=
{
<
UserDataIcon
/>
}
/
>
<
/MenuBarMenu
>
<
MenuBarMenu
name
=
"
explore
"
route
=
{
history
.
search
}
icon
=
{
<
SearchIcon
/>
}
>
<
MenuBarMenu
name
=
"
explore
"
route
=
"
/
search
"
icon
=
{
<
SearchIcon
/>
}
>
<
MenuBarItem
name
=
"
search
"
route
=
{
history
.
search
}
name
=
"
search
"
route
=
"
/
search
"
tooltip
=
"
Find and download data
"
/>
{
encyclopediaEnabled
&&
<
MenuBarItem
...
...
gui/src/components/nav/MainMenu.spec.js
View file @
9c1d8fcf
...
...
@@ -22,7 +22,7 @@ import 'regenerator-runtime/runtime'
// import { render, screen, within } from '@testing-library/react'
// import { MemoryRouter } from 'react-router-dom'
// import MainMenu from './MainMenu'
// import {
allRoutes as
routes } from './Routes'
// import { routes } from './Routes'
// expect.extend({ toBeInTheDocument })
...
...
gui/src/components/nav/Routes.js
View file @
9c1d8fcf
...
...
@@ -17,7 +17,7 @@
*/
import
React
from
'
react
'
import
{
Route
}
from
'
react-router-dom
'
import
{
Route
,
useLocation
}
from
'
react-router-dom
'
import
About
from
'
../About
'
import
APIs
from
'
../APIs
'
import
AIToolkitPage
from
'
../aitoolkit/AIToolkitPage
'
...
...
@@ -33,63 +33,86 @@ import UploadPage, {help as uploadHelp} from '../uploads/UploadPage'
import
UserdataPage
,
{
help
as
userdataHelp
}
from
'
../UserdataPage
'
import
{
ErrorBoundary
}
from
'
../errors
'
export
const
routes
=
{
'
faq
'
:
{
function
createEntryRoute
(
props
)
{
return
({
path
:
'
/entry
'
,
title
:
'
Entry
'
,
help
:
{
title
:
'
The entry page
'
,
content
:
entryHelp
},
...
props
,
routes
:
[
{
path
:
'
/id
'
,
component
:
EntryPage
},
{
path
:
'
/query
'
,
exact
:
true
,
component
:
EntryQuery
},
{
path
:
'
/pid
'
,
component
:
ResolvePID
}
]
})
}
const
routeSpecs
=
[
{
path
:
'
/faq
'
,
exact
:
true
,
appBarT
itle
:
'
Frequently Asked Questions
'
,
t
itle
:
'
Frequently Asked Questions
'
,
component
:
FAQ
},
'
search
'
:
{
{
path
:
'
/search
'
,
exact
:
true
,
appBarT
itle
:
'
Find
and Download Data
'
,
appBarH
elp
:
{
t
itle
:
'
Search
and Download Data
'
,
h
elp
:
{
title
:
'
How to find and download data
'
,
content
:
searchHelp
},
navPath
:
'
explore/search
'
,
component
:
SearchPage
},
'
userdata
'
:
{
{
path
:
'
/userdata
'
,
exact
:
true
,
appBarT
itle
:
'
Manage Your Data
'
,
appBarH
elp
:
{
t
itle
:
'
Manage Your Data
'
,
h
elp
:
{
title
:
'
How to manage your data
'
,
content
:
userdataHelp
},
navPath
:
'
publish/userdata
'
,
component
:
UserdataPage
},
'
entry
'
:
{
path
:
'
/entry
'
,
appBarTitle
:
'
Entry
'
,
appBarHelp
:
{
title
:
'
The entry page
'
,
content
:
entryHelp
},
defaultNavPath
:
'
explore/search
'
,
component
:
UserdataPage
,
routes
:
[
createEntryRoute
({
navPath
:
'
publish/userdata
'
,
breadCrumbs
:
[
{
title
:
'
Your Data
'
,
path
:
'
/userdata
'
}
]
})
]
},
createEntryRoute
({
navPath
:
'
explore/search
'
,
breadCrumbs
:
[
{
path
:
'
/id
'
,
component
:
EntryPage
},
{
path
:
'
/query
'
,
exact
:
true
,
component
:
EntryQuery
},
{
path
:
'
/pid
'
,
component
:
ResolvePID
title
:
'
Search
'
,
path
:
'
/search
'
}
]
},
'
dataset
'
:
{
}
)
,
{
path
:
'
/dataset
'
,
appBarT
itle
:
'
Dataset
'
,
defaultN
avPath
:
'
explore/search
'
,
t
itle
:
'
Dataset
'
,
n
avPath
:
'
explore/search
'
,
routes
:
[
{
path
:
'
/id
'
,
...
...
@@ -101,68 +124,93 @@ export const routes = {
}
]
},
'
uploads
'
:
{
{
path
:
'
/uploads
'
,
exact
:
true
,
appBarT
itle
:
'
Upload and Publish Data
'
,
appBarH
elp
:
{
t
itle
:
'
Upload and Publish Data
'
,
h
elp
:
{
title
:
'
How to upload data
'
,
content
:
uploadHelp
},
navPath
:
'
publish/uploads
'
,
component
:
UploadPage
component
:
UploadPage
,
routes
:
[
createEntryRoute
({
navPath
:
'
publish/uploads
'
,
breadCrumbs
:
[
{
title
:
'
Uploads
'
,
path
:
'
/uploads
'
}
]
})
]
},
'
metainfo
'
:
{
{
path
:
'
/metainfo
'
,
appBarT
itle
:
'
The NOMAD Meta Info
'
,
appBarH
elp
:
{
t
itle
:
'
The NOMAD Meta Info
'
,
h
elp
:
{
title
:
'
About the NOMAD meta-info
'
,
content
:
metainfoHelp
},
navPath
:
'
analyze/metainfo
'
,
component
:
MetainfoPage
},
'
aitoolkit
'
:
{
{
path
:
'
/aitoolkit
'
,
appBarT
itle
:
'
Artificial Intelligence Toolkit
'
,
t
itle
:
'
Artificial Intelligence Toolkit
'
,
navPath
:
'
analyze/aitoolkit
'
,
component
:
AIToolkitPage
},
'
apis
'
:
{
{
exact
:
true
,
path
:
'
/apis
'
,
appBarT
itle
:
'
APIs
'
,
t
itle
:
'
APIs
'
,
navPath
:
'
analyze/apis
'
,
component
:
APIs
},
'
about
'
:
{
{
exact
:
true
,
path
:
'
/
'
,
appBarT
itle
:
'
About, Documentation, Getting Help
'
,
t
itle
:
'
About, Documentation, Getting Help
'
,
navPath
:
'
about/info
'
,
component
:
About
}
}
]
export
const
allRoutes
=
Object
.
keys
(
routes
).
map
(
key
=>
{
const
route
=
routes
[
key
]
return
route
.
routes
?
route
.
routes
.
map
(
subRoute
=>
({
function
flattenRouteSpecs
(
routeSpecs
,
parent
,
results
)
{
results
=
results
||
[]
parent
=
parent
||
{}
routeSpecs
.
forEach
(
route
=>
{
const
flatRoute
=
{
...
parent
,
component
:
null
,
exact
:
false
,
...
route
,
...
subRoute
,
path
:
`
${
route
.
path
}
/
${
subRoute
.
path
.
replace
(
/^
\/
+/
,
''
)}
`
,
path
:
parent
.
path
?
`
${
parent
.
path
}
/
${
route
.
path
.
replace
(
/^
\/
+/
,
''
)}
`
:
route
.
path
,
routes
:
undefined
}))
:
route
}).
flat
()
}
if
(
flatRoute
.
component
)
{
results
.
push
(
flatRoute
)
}
if
(
route
.
routes
)
{
flattenRouteSpecs
(
route
.
routes
,
flatRoute
,
results
)
}
})
return
results
}
export
const
routes
=
flattenRouteSpecs
(
routeSpecs
)
routes
.
sort
((
a
,
b
)
=>
(
a
.
path
>
b
.
path
)
?
-
1
:
1
)
export
default
function
Routes
()
{
return
<
React
.
Fragment
>
{
Object
.
keys
(
allRoutes
).
map
(
routeKey
=>
{
const
route
=
allRoutes
[
routeKey
]
const
{
path
,
exact
}
=
route
{
routes
.
map
(
route
=>
{
const
{
path
,
exact
}
=
route
const
children
=
childProps
=>
childProps
.
match
&&
<
route
.
component
{...
childProps
}
/
>
return
<
ErrorBoundary
key
=
{
routeKey
}
>
return
<
ErrorBoundary
key
=
{
path
}
>
<
Route
exact
=
{
exact
}
path
=
{
path
}
// eslint-disable-next-line react/no-children-prop
children
=
{
children
}
...
...
@@ -171,3 +219,16 @@ export default function Routes() {
})}
<
/React.Fragment
>
}
export
function
useRoute
()
{
const
{
pathname
,
search
}
=
useLocation
()
routes
.
forEach
(
route
=>
{
(
route
.
breadCrumbs
||
[]).
forEach
(
breadCrumb
=>
{
if
(
breadCrumb
.
path
.
startsWith
(
pathname
))
{
breadCrumb
.
path
=
pathname
+
(
search
||
''
)
}
})
})
const
route
=
routes
.
find
(
route
=>
pathname
.
startsWith
(
route
.
path
))
return
route
}
gui/src/components/search/EntryList.js
View file @
9c1d8fcf
...
...
@@ -96,7 +96,8 @@ export class EntryListUnstyled extends React.Component {
selectedColumns
:
PropTypes
.
arrayOf
(
PropTypes
.
string
),
domain
:
PropTypes
.
object
,
user
:
PropTypes
.
object
,
showAccessColumn
:
PropTypes
.
bool
showAccessColumn
:
PropTypes
.
bool
,
entryPagePathPrefix
:
PropTypes
.
string
}
static
styles
=
theme
=>
({
...
...
@@ -227,7 +228,9 @@ export class EntryListUnstyled extends React.Component {
}
handleClickCalc
(
calc
)
{
this
.
props
.
history
.
push
(
`/entry/id/
${
calc
.
upload_id
}
/
${
calc
.
calc_id
}
`
)
const
prefix
=
this
.
props
.
entryPagePathPrefix
||
''
const
url
=
`
${
prefix
}
/entry/id/
${
calc
.
upload_id
}
/
${
calc
.
calc_id
}
`
this
.
props
.
history
.
push
(
url
)
}
handleChangePage
=
(
event
,
page
)
=>
{
...
...
@@ -317,7 +320,9 @@ export class EntryListUnstyled extends React.Component {
handleViewEntryPage
(
event
,
row
)
{
event
.
stopPropagation
()
this
.
props
.
history
.
push
(
`/entry/id/
${
row
.
upload_id
}
/
${
row
.
calc_id
}
`
)
const
prefix
=
this
.
props
.
entryPagePathPrefix
||
''
const
url
=
`
${
prefix
}
/entry/id/
${
row
.
upload_id
}
/
${
row
.
calc_id
}
`
this
.
props
.
history
.
push
(
url
)
}
renderEntryActions
(
row
,
selected
)
{
...
...
gui/src/components/uploads/Upload.js
View file @
9c1d8fcf
...
...
@@ -246,8 +246,7 @@ class Upload extends React.Component {
showPublishDialog
:
false
,
showPublishToCentralNomadDialog
:
false
,
showDeleteDialog
:
false
,
columns
:
{},
expanded
:
null
columns
:
{}
}
_unmounted
=
false
...
...
@@ -267,10 +266,6 @@ class Upload extends React.Component {
}
componentDidUpdate
(
prevProps
,
prevState
)
{
if
(
prevProps
.
open
!==
this
.
props
.
open
&&
this
.
props
.
open
)
{
this
.
setState
({
expanded
:
null
})
}
if
(
prevProps
.
domain
!==
this
.
props
.
domain
)
{
this
.
updateColumns
()
}
...
...
@@ -710,6 +705,7 @@ class Upload extends React.Component {
onEdit
=
{
this
.
handleChange
}
actions
=
{
actions
}
showEntryActions
=
{
entry
=>
entry
.
processed
||
!
running
}
entryPagePathPrefix
=
"
/uploads
"
{...
this
.
state
.
params
}
/
>
}
...
...
@@ -737,17 +733,18 @@ class Upload extends React.Component {
render
()
{
const
{
classes
,
open
}
=
this
.
props
const
{
upload
,
showPublishDialog
,
showDeleteDialog
,
showPublishToCentralNomadDialog
,
expanded
}
=
this
.
state
const
{
upload
,
showPublishDialog
,
showDeleteDialog
,
showPublishToCentralNomadDialog
}
=
this
.
state
const
{
errors
,
last_status_message
}
=
upload
if
(
this
.
state
.
upload
)
{
return
(
<
div
className
=
{
classes
.
root
}
>
<
Accordion
expanded
=
{
expanded
===
null
?
open
:
expanded
}
onChange
=
{(
event
,
expanded
)
=>
{
this
.
setState
({
expanded
:
expanded
})
expanded
=
{
open
}
onChange
=
{(
event
,
open
)
=>
{
if
(
open
)
{
this
.
props
.
history
.
push
(
`/uploads?open=
${
upload
.
upload_id
}
`
)
}
else
{
this
.
props
.
history
.
push
(
'
/uploads
'
)
}
}}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment