Commit b866ceca authored by Markus Scheidgen's avatar Markus Scheidgen
Browse files

Many minor bugfixes. #342

parent cbce84c9
Pipeline #75140 canceled with stages
in 8 minutes and 17 seconds
// trigger rebuild // trigger rebuild
import React, { useEffect, useState, useContext, useCallback } from 'react' import React, { useEffect, useState, useContext, useCallback, useRef } from 'react'
import PropTypes, { instanceOf } from 'prop-types' import PropTypes, { instanceOf } from 'prop-types'
import { compose } from 'recompose' import { compose } from 'recompose'
import classNames from 'classnames' import classNames from 'classnames'
...@@ -37,6 +37,7 @@ import {help as userdataHelp, default as UserdataPage} from './UserdataPage' ...@@ -37,6 +37,7 @@ import {help as userdataHelp, default as UserdataPage} from './UserdataPage'
import ResolveDOI from './dataset/ResolveDOI' import ResolveDOI from './dataset/ResolveDOI'
import FAQ from './FAQ' import FAQ from './FAQ'
import EntryQuery from './entry/EntryQuery' import EntryQuery from './entry/EntryQuery'
import KeepState from './KeepState'
export const ScrollContext = React.createContext({scrollParentRef: null}) export const ScrollContext = React.createContext({scrollParentRef: null})
...@@ -97,7 +98,6 @@ function MainMenuItem({tooltip, title, path, href, icon}) { ...@@ -97,7 +98,6 @@ function MainMenuItem({tooltip, title, path, href, icon}) {
</Button> </Button>
</Tooltip> </Tooltip>
} }
MainMenuItem.propTypes = { MainMenuItem.propTypes = {
'tooltip': PropTypes.string.isRequired, 'tooltip': PropTypes.string.isRequired,
'title': PropTypes.string.isRequired, 'title': PropTypes.string.isRequired,
...@@ -106,6 +106,90 @@ MainMenuItem.propTypes = { ...@@ -106,6 +106,90 @@ MainMenuItem.propTypes = {
'icon': PropTypes.element.isRequired 'icon': PropTypes.element.isRequired
} }
const useMainMenuStyles = makeStyles(theme => ({
root: {
display: 'inline-flex',
padding: 0,
width: '100%',
backgroundColor: 'white'
},
divider: {
width: theme.spacing(3)
}
}))
function MainMenu() {
const classes = useMainMenuStyles()
// 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
}
})
return <MenuList classes={{root: classes.root}}>
<MainMenuItem
title="Search"
path={history.search}
tooltip="Find and download data"
icon={<SearchIcon/>}
/>
<MainMenuItem
title="Upload"
path="/uploads"
tooltip="Upload and publish data"
icon={<BackupIcon/>}
/>
<MainMenuItem
title="Your data"
path={history.userdata}
tooltip="Manage your data"
icon={<UserDataIcon/>}
/>
<MainMenuItem
title="Meta Info"
path="/metainfo"
tooltip="Browse the archive schema"
icon={<MetainfoIcon/>}
/>
<div className={classes.divider} />
<MainMenuItem
title="About"
path="/"
tooltip="NOMAD Repository and Archive"
icon={<AboutIcon/>}
/>
<MainMenuItem
title="FAQ"
path="/faq"
tooltip="Frequently Asked Questions (FAQ)"
icon={<FAQIcon/>}
/>
<MainMenuItem
title="Docs"
href={`${appBase}/docs/index.html`}
tooltip="The NOMAD documentation"
icon={<DocIcon/>}
/>
<MainMenuItem
title="Sources"
href="https://gitlab.mpcdf.mpg.de/nomad-lab/nomad-FAIR"
tooltip="NOMAD's Gitlab project"
icon={<CodeIcon/>}
/>
</MenuList>
}
class NavigationUnstyled extends React.Component { class NavigationUnstyled extends React.Component {
static propTypes = { static propTypes = {
classes: PropTypes.object.isRequired, classes: PropTypes.object.isRequired,
...@@ -154,12 +238,6 @@ class NavigationUnstyled extends React.Component { ...@@ -154,12 +238,6 @@ class NavigationUnstyled extends React.Component {
height: theme.spacing(7), height: theme.spacing(7),
marginRight: theme.spacing(2) marginRight: theme.spacing(2)
}, },
menu: {
display: 'inline-flex',
padding: 0,
width: '100%',
backgroundColor: 'white'
},
content: { content: {
marginTop: theme.spacing(13), marginTop: theme.spacing(13),
flexGrow: 1, flexGrow: 1,
...@@ -184,9 +262,6 @@ class NavigationUnstyled extends React.Component { ...@@ -184,9 +262,6 @@ class NavigationUnstyled extends React.Component {
barButton: { barButton: {
borderColor: theme.palette.getContrastText(theme.palette.primary.main), borderColor: theme.palette.getContrastText(theme.palette.primary.main),
marginRight: 0 marginRight: 0
},
divider: {
width: theme.spacing(3)
} }
}) })
...@@ -285,57 +360,7 @@ class NavigationUnstyled extends React.Component { ...@@ -285,57 +360,7 @@ class NavigationUnstyled extends React.Component {
<LoginLogout color="primary" classes={{button: classes.barButton}} /> <LoginLogout color="primary" classes={{button: classes.barButton}} />
</div> </div>
</Toolbar> </Toolbar>
<MenuList classes={{root: classes.menu}}> <MainMenu />
<MainMenuItem
title="Search"
path="/search"
tooltip="Find and download data"
icon={<SearchIcon/>}
/>
<MainMenuItem
title="Upload"
path="/uploads"
tooltip="Upload and publish data"
icon={<BackupIcon/>}
/>
<MainMenuItem
title="Your data"
path="/userdata"
tooltip="Manage your data"
icon={<UserDataIcon/>}
/>
<MainMenuItem
title="Meta Info"
path="/metainfo"
tooltip="Browse the archive schema"
icon={<MetainfoIcon/>}
/>
<div className={classes.divider} />
<MainMenuItem
title="About"
path="/"
tooltip="NOMAD Repository and Archive"
icon={<AboutIcon/>}
/>
<MainMenuItem
title="FAQ"
path="/faq"
tooltip="Frequently Asked Questions (FAQ)"
icon={<FAQIcon/>}
/>
<MainMenuItem
title="Docs"
href={`${appBase}/docs/index.html`}
tooltip="The NOMAD documentation"
icon={<DocIcon/>}
/>
<MainMenuItem
title="Sources"
href="https://gitlab.mpcdf.mpg.de/nomad-lab/nomad-FAIR"
tooltip="NOMAD's Gitlab project"
icon={<CodeIcon/>}
/>
</MenuList>
<LoadingIndicator /> <LoadingIndicator />
</AppBar> </AppBar>
...@@ -410,74 +435,82 @@ class LicenseAgreementUnstyled extends React.Component { ...@@ -410,74 +435,82 @@ class LicenseAgreementUnstyled extends React.Component {
const LicenseAgreement = compose(withCookies, withStyles(LicenseAgreementUnstyled.styles))(LicenseAgreementUnstyled) const LicenseAgreement = compose(withCookies, withStyles(LicenseAgreementUnstyled.styles))(LicenseAgreementUnstyled)
class App extends React.PureComponent { const routes = {
routes = { 'about': {
'about': { exact: true,
exact: true, path: '/',
path: '/', component: About
component: About },
}, 'faq': {
'faq': { exact: true,
exact: true, path: '/faq',
path: '/faq', component: FAQ
component: FAQ },
}, 'search': {
'search': { exact: true,
exact: true, path: '/search',
path: '/search', component: SearchPage
component: SearchPage },
}, 'userdata': {
'userdata': { exact: true,
exact: true, path: '/userdata',
path: '/userdata', component: UserdataPage
component: UserdataPage },
}, 'entry': {
'entry': { path: '/entry/id',
path: '/entry/id', component: EntryPage
component: EntryPage },
}, 'entry_query': {
'entry_query': { exact: true,
exact: true, path: '/entry/query',
path: '/entry/query', component: EntryQuery
component: EntryQuery },
}, 'entry_pid': {
'entry_pid': { path: '/entry/pid',
path: '/entry/pid', component: ResolvePID
component: ResolvePID },
}, 'dataset': {
'dataset': { path: '/dataset/id',
path: '/dataset/id', component: DatasetPage
component: DatasetPage },
}, 'dataset_doi': {
'dataset_doi': { path: '/dataset/doi',
path: '/dataset/doi', component: ResolveDOI
component: ResolveDOI },
}, 'uploads': {
'uploads': { exact: true,
exact: true, path: '/uploads',
path: '/uploads', component: UploadPage
component: UploadPage },
}, 'metainfo': {
'metainfo': { path: '/metainfo',
path: '/metainfo', keepState: true,
component: MetaInfoBrowser exists: false,
} component: MetaInfoBrowser
} }
}
class App extends React.PureComponent {
render() { render() {
return ( return (
<MuiThemeProvider theme={nomadTheme}> <MuiThemeProvider theme={nomadTheme}>
<ErrorSnacks> <ErrorSnacks>
<ApiProvider> <ApiProvider>
<Navigation> <Navigation>
{Object.keys(this.routes).map(routeKey => { {Object.keys(routes).map(routeKey => {
const route = this.routes[routeKey] const route = routes[routeKey]
const { path, exact } = route const { path, exact } = route
return <Route key={routeKey} exact={exact} path={path} return <Route key={routeKey} exact={exact} path={path}
// eslint-disable-next-line react/no-children-prop // eslint-disable-next-line react/no-children-prop
children={props => { children={props => {
// return <KeepState visible={props.match && true} render={(props) => <route.component {...props} />} {...props} /> if (route.keepState) {
return props.match && <route.component {...props} /> if (props.match || route.exists) {
route.exists = true
return <route.component visible={props.match && true} {...props} />
}
} else {
return props.match && <route.component {...props} />
}
}} }}
/> />
})} })}
......
...@@ -18,6 +18,8 @@ import { Popover, List, ListItemText, ListItem, Collapse } from '@material-ui/co ...@@ -18,6 +18,8 @@ import { Popover, List, ListItemText, ListItem, Collapse } from '@material-ui/co
import { compose } from 'recompose' import { compose } from 'recompose'
import _ from 'lodash' import _ from 'lodash'
const globalSelectedColumns = {}
class DataTableToolbarUnStyled extends React.Component { class DataTableToolbarUnStyled extends React.Component {
static propTypes = { static propTypes = {
classes: PropTypes.object.isRequired, classes: PropTypes.object.isRequired,
...@@ -169,6 +171,11 @@ class DataTableUnStyled extends React.Component { ...@@ -169,6 +171,11 @@ class DataTableUnStyled extends React.Component {
* The set of columns initially shown as an array of column keys. * The set of columns initially shown as an array of column keys.
*/ */
selectedColumns: PropTypes.arrayOf(PropTypes.string), selectedColumns: PropTypes.arrayOf(PropTypes.string),
/**
* If this key is given, it is used to globaly store user modifications to the selected
* columns under this key.
*/
selectedColumnsKey: PropTypes.string,
/** /**
* Single element that is rendered to display actions for the selection. With no * Single element that is rendered to display actions for the selection. With no
* select actions, no selection will be shown. * select actions, no selection will be shown.
...@@ -285,9 +292,14 @@ class DataTableUnStyled extends React.Component { ...@@ -285,9 +292,14 @@ class DataTableUnStyled extends React.Component {
constructor(props) { constructor(props) {
super(props) super(props)
this.handleSelectAllClick = this.handleSelectAllClick.bind(this) this.handleSelectAllClick = this.handleSelectAllClick.bind(this)
this.handleSelectedColumnsChanged = this.handleSelectedColumnsChanged.bind(this)
let selectedColumns = this.props.selectedColumns || Object.keys(this.props.columns)
if (this.props.selectedColumnsKey) {
selectedColumns = globalSelectedColumns[this.props.selectedColumnsKey] || selectedColumns
}
this.state = { this.state = {
...this.state, ...this.state,
selectedColumns: this.props.selectedColumns || Object.keys(this.props.columns) selectedColumns: selectedColumns
} }
} }
...@@ -296,12 +308,6 @@ class DataTableUnStyled extends React.Component { ...@@ -296,12 +308,6 @@ class DataTableUnStyled extends React.Component {
selectedColumns: null selectedColumns: null
} }
componentDidUpdate(prevProps) {
if (prevProps.columns !== this.props.columns) {
this.setState({selectedColumns: this.props.selectedColumns})
}
}
handleRequestSort(event, property) { handleRequestSort(event, property) {
const { orderBy, order, onOrderChanged } = this.props const { orderBy, order, onOrderChanged } = this.props
const isDesc = orderBy === property && order === 'desc' const isDesc = orderBy === property && order === 'desc'
...@@ -360,6 +366,13 @@ class DataTableUnStyled extends React.Component { ...@@ -360,6 +366,13 @@ class DataTableUnStyled extends React.Component {
} }
} }
handleSelectedColumnsChanged(columns) {
if (this.props.selectedColumnsKey) {
globalSelectedColumns[this.props.selectedColumnsKey] = columns
}
this.setState({selectedColumns: columns})
}
renderDetails(row) { renderDetails(row) {
const { classes, entryDetails, id, entryActions } = this.props const { classes, entryDetails, id, entryActions } = this.props
const { selectedColumns, selectedEntry } = this.state const { selectedColumns, selectedEntry } = this.state
...@@ -415,7 +428,7 @@ class DataTableUnStyled extends React.Component { ...@@ -415,7 +428,7 @@ class DataTableUnStyled extends React.Component {
selectedColumns={selectedColumns} selectedColumns={selectedColumns}
selectActions={selectActions} selectActions={selectActions}
actions={actions} actions={actions}
onColumnsChanged={columns => this.setState({selectedColumns: columns})} onColumnsChanged={this.handleSelectedColumnsChanged}
/> />
<div className={classes.tableWrapper}> <div className={classes.tableWrapper}>
<Table className={classes.table} size="small"> <Table className={classes.table} size="small">
......
...@@ -59,7 +59,7 @@ function UserdataPage() { ...@@ -59,7 +59,7 @@ function UserdataPage() {
initialRequest={{order_by: 'upload_time', uploads_grouped: true}} initialRequest={{order_by: 'upload_time', uploads_grouped: true}}
initialResultTab="uploads" initialResultTab="uploads"
availableResultTabs={['uploads', 'datasets', 'entries']} availableResultTabs={['uploads', 'datasets', 'entries']}
resultListProps={{selectedColumns: ['formula', 'upload_time', 'mainfile', 'published', 'co_authors', 'references', 'datasets']}} resultListProps={{selectedColumnsKey: 'userEntries', selectedColumns: ['formula', 'upload_time', 'mainfile', 'published', 'co_authors', 'references', 'datasets']}}
/> />
} }
......
...@@ -65,15 +65,15 @@ export function DFTSystemVisualizations(props) { ...@@ -65,15 +65,15 @@ export function DFTSystemVisualizations(props) {
return ( return (
<Grid container spacing={2}> <Grid container spacing={2}>
<Grid item xs={4}>
<QuantityHistogram quantity="dft.compound_type" title="Compound type" initialScale={0.25} />
</Grid>
<Grid item xs={4}> <Grid item xs={4}>
<QuantityHistogram quantity="dft.system" title="System type" initialScale={0.25} /> <QuantityHistogram quantity="dft.system" title="System type" initialScale={0.25} />
<QuantityHistogram quantity="dft.crystal_system" title="Crystal system" /> <QuantityHistogram quantity="dft.crystal_system" title="Crystal system" />
</Grid> </Grid>
<Grid item xs={4}> <Grid item xs={4}>
<QuantityHistogram quantity="dft.labels_springer_compound_class" title="Springer compound" /> <QuantityHistogram quantity="dft.compound_type" title="Compound type" initialScale={0.25} />
</Grid>
<Grid item xs={4}>
<QuantityHistogram quantity="dft.labels_springer_compound_class" title="Compound classification" />
</Grid> </Grid>
</Grid> </Grid>
) )
...@@ -97,13 +97,13 @@ const electronic_quantities = [ ...@@ -97,13 +97,13 @@ const electronic_quantities = [
'eigenvalues_values', 'eigenvalues_values',
'volumetric_data_values', 'volumetric_data_values',
'electronic_kinetic_energy', 'electronic_kinetic_energy',
'total_charge', 'total_charge'
'atomic_multipole_values' // 'atomic_multipole_values'
] ]
const forces_quantities = [ const forces_quantities = [
'atom_forces_free', 'atom_forces_free',
'atom_forces_raw', 'atom_forces_raw',
'atom_forces_T0', // 'atom_forces_T0',
'atom_forces', 'atom_forces',
'stress_tensor' 'stress_tensor'
] ]
...@@ -177,6 +177,7 @@ export function DFTPropertyVisualizations(props) { ...@@ -177,6 +177,7 @@ export function DFTPropertyVisualizations(props) {
<Grid item xs={4}> <Grid item xs={4}>
<QuantityHistogram quantity="dft.searchable_quantities" values={energy_quantities} valueLabels={labels} title="Energy" initialScale={0.5} tooltips /> <QuantityHistogram quantity="dft.searchable_quantities" values={energy_quantities} valueLabels={labels} title="Energy" initialScale={0.5} tooltips />
<QuantityHistogram quantity="dft.searchable_quantities" values={electronic_quantities} valueLabels={labels} title="Electronic" initialScale={0.5} tooltips /> <QuantityHistogram quantity="dft.searchable_quantities" values={electronic_quantities} valueLabels={labels} title="Electronic" initialScale={0.5} tooltips />
<QuantityHistogram quantity="dft.searchable_quantities" values={magnetic_quantities} valueLabels={labels} title="Magnetic" initialScale={1} tooltips />
</Grid> </Grid>
<Grid item xs={4}> <Grid item xs={4}>
<QuantityHistogram quantity="dft.searchable_quantities" values={forces_quantities} valueLabels={labels} title="Forces" initialScale={0.5} tooltips /> <QuantityHistogram quantity="dft.searchable_quantities" values={forces_quantities} valueLabels={labels} title="Forces" initialScale={0.5} tooltips />
...@@ -184,8 +185,7 @@ export function DFTPropertyVisualizations(props) { ...@@ -184,8 +185,7 @@ export function DFTPropertyVisualizations(props) {
<QuantityHistogram quantity="dft.searchable_quantities" values={optical_quantities} valueLabels={labels} title="Optical" initialScale={1} tooltips /> <QuantityHistogram quantity="dft.searchable_quantities" values={optical_quantities} valueLabels={labels} title="Optical" initialScale={1} tooltips />
</Grid> </Grid>
<Grid item xs={4}> <Grid item xs={4}>
<QuantityHistogram quantity="dft.labels_springer_classification" title="Springer classification" initialScale={1} tooltips /> <QuantityHistogram quantity="dft.labels_springer_classification" title="Property classification" initialScale={1} tooltips />
<QuantityHistogram quantity="dft.searchable_quantities" values={magnetic_quantities} valueLabels={labels} title="Magnetic" initialScale={1} tooltips />
</Grid> </Grid>
</Grid> </Grid>
) )
......
import React, { useContext, useState, useEffect } from 'react' import React, { useContext, useState, useEffect, useRef } from 'react'
import PropTypes from 'prop-types'
import { matchPath, useLocation, useHistory, useRouteMatch } from 'react-router-dom' import { matchPath, useLocation, useHistory, useRouteMatch } from 'react-router-dom'
import Viewer from './Viewer' import Viewer from './Viewer'
import { apiContext } from '../api' import { apiContext } from '../api'
...@@ -60,14 +61,22 @@ const useStyles = makeStyles(theme => ({ ...@@ -60,14 +61,22 @@ const useStyles = makeStyles(theme => ({
} }
})) }))