Commit 0bd1badb authored by Markus Scheidgen's avatar Markus Scheidgen
Browse files

Merge branch 'v0.8.0-bugfixes' into 'v0.8.0'

V0.8.0 bugfixes

See merge request !110
parents e0ca6a9d b8f536e5
Pipeline #75217 passed with stages
in 18 minutes and 26 seconds
// 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 { compose } from 'recompose'
import classNames from 'classnames'
......@@ -97,7 +97,6 @@ function MainMenuItem({tooltip, title, path, href, icon}) {
</Button>
</Tooltip>
}
MainMenuItem.propTypes = {
'tooltip': PropTypes.string.isRequired,
'title': PropTypes.string.isRequired,
......@@ -106,6 +105,90 @@ MainMenuItem.propTypes = {
'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 {
static propTypes = {
classes: PropTypes.object.isRequired,
......@@ -154,12 +237,6 @@ class NavigationUnstyled extends React.Component {
height: theme.spacing(7),
marginRight: theme.spacing(2)
},
menu: {
display: 'inline-flex',
padding: 0,
width: '100%',
backgroundColor: 'white'
},
content: {
marginTop: theme.spacing(13),
flexGrow: 1,
......@@ -184,9 +261,6 @@ class NavigationUnstyled extends React.Component {
barButton: {
borderColor: theme.palette.getContrastText(theme.palette.primary.main),
marginRight: 0
},
divider: {
width: theme.spacing(3)
}
})
......@@ -285,57 +359,7 @@ class NavigationUnstyled extends React.Component {
<LoginLogout color="primary" classes={{button: classes.barButton}} />
</div>
</Toolbar>
<MenuList classes={{root: classes.menu}}>
<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>
<MainMenu />
<LoadingIndicator />
</AppBar>
......@@ -410,74 +434,82 @@ class LicenseAgreementUnstyled extends React.Component {
const LicenseAgreement = compose(withCookies, withStyles(LicenseAgreementUnstyled.styles))(LicenseAgreementUnstyled)
class App extends React.PureComponent {
routes = {
'about': {
exact: true,
path: '/',
component: About
},
'faq': {
exact: true,
path: '/faq',
component: FAQ
},
'search': {
exact: true,
path: '/search',
component: SearchPage
},
'userdata': {
exact: true,
path: '/userdata',
component: UserdataPage
},
'entry': {
path: '/entry/id',
component: EntryPage
},
'entry_query': {
exact: true,
path: '/entry/query',
component: EntryQuery
},
'entry_pid': {
path: '/entry/pid',
component: ResolvePID
},
'dataset': {
path: '/dataset/id',
component: DatasetPage
},
'dataset_doi': {
path: '/dataset/doi',
component: ResolveDOI
},
'uploads': {
exact: true,
path: '/uploads',
component: UploadPage
},
'metainfo': {
path: '/metainfo',
component: MetaInfoBrowser
}
const routes = {
'about': {
exact: true,
path: '/',
component: About
},
'faq': {
exact: true,
path: '/faq',
component: FAQ
},
'search': {
exact: true,
path: '/search',
component: SearchPage
},
'userdata': {
exact: true,
path: '/userdata',
component: UserdataPage
},
'entry': {
path: '/entry/id',
component: EntryPage
},
'entry_query': {
exact: true,
path: '/entry/query',
component: EntryQuery
},
'entry_pid': {
path: '/entry/pid',
component: ResolvePID
},
'dataset': {
path: '/dataset/id',
component: DatasetPage
},
'dataset_doi': {
path: '/dataset/doi',
component: ResolveDOI
},
'uploads': {
exact: true,
path: '/uploads',
component: UploadPage
},
'metainfo': {
path: '/metainfo',
keepState: true,
exists: false,
component: MetaInfoBrowser
}
}
class App extends React.PureComponent {
render() {
return (
<MuiThemeProvider theme={nomadTheme}>
<ErrorSnacks>
<ApiProvider>
<Navigation>
{Object.keys(this.routes).map(routeKey => {
const route = this.routes[routeKey]
{Object.keys(routes).map(routeKey => {
const route = routes[routeKey]
const { path, exact } = route
return <Route key={routeKey} exact={exact} path={path}
// eslint-disable-next-line react/no-children-prop
children={props => {
// return <KeepState visible={props.match && true} render={(props) => <route.component {...props} />} {...props} />
return props.match && <route.component {...props} />
if (route.keepState) {
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
import { compose } from 'recompose'
import _ from 'lodash'
const globalSelectedColumns = {}
class DataTableToolbarUnStyled extends React.Component {
static propTypes = {
classes: PropTypes.object.isRequired,
......@@ -169,6 +171,11 @@ class DataTableUnStyled extends React.Component {
* The set of columns initially shown as an array of column keys.
*/
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
* select actions, no selection will be shown.
......@@ -285,9 +292,14 @@ class DataTableUnStyled extends React.Component {
constructor(props) {
super(props)
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,
selectedColumns: this.props.selectedColumns || Object.keys(this.props.columns)
selectedColumns: selectedColumns
}
}
......@@ -296,12 +308,6 @@ class DataTableUnStyled extends React.Component {
selectedColumns: null
}
componentDidUpdate(prevProps) {
if (prevProps.columns !== this.props.columns) {
this.setState({selectedColumns: this.props.selectedColumns})
}
}
handleRequestSort(event, property) {
const { orderBy, order, onOrderChanged } = this.props
const isDesc = orderBy === property && order === 'desc'
......@@ -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) {
const { classes, entryDetails, id, entryActions } = this.props
const { selectedColumns, selectedEntry } = this.state
......@@ -415,7 +428,7 @@ class DataTableUnStyled extends React.Component {
selectedColumns={selectedColumns}
selectActions={selectActions}
actions={actions}
onColumnsChanged={columns => this.setState({selectedColumns: columns})}
onColumnsChanged={this.handleSelectedColumnsChanged}
/>
<div className={classes.tableWrapper}>
<Table className={classes.table} size="small">
......
......@@ -59,7 +59,7 @@ function UserdataPage() {
initialRequest={{order_by: 'upload_time', uploads_grouped: true}}
initialResultTab="uploads"
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) {
return (
<Grid container spacing={2}>
<Grid item xs={4}>
<QuantityHistogram quantity="dft.compound_type" title="Compound type" initialScale={0.25} />
</Grid>
<Grid item xs={4}>
<QuantityHistogram quantity="dft.system" title="System type" initialScale={0.25} />
<QuantityHistogram quantity="dft.crystal_system" title="Crystal system" />
</Grid>
<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>
)
......@@ -97,13 +97,13 @@ const electronic_quantities = [
'eigenvalues_values',
'volumetric_data_values',
'electronic_kinetic_energy',
'total_charge',
'atomic_multipole_values'
'total_charge'
// 'atomic_multipole_values'
]
const forces_quantities = [
'atom_forces_free',
'atom_forces_raw',
'atom_forces_T0',
// 'atom_forces_T0',
'atom_forces',
'stress_tensor'
]
......@@ -177,6 +177,7 @@ export function DFTPropertyVisualizations(props) {
<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={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 item xs={4}>
<QuantityHistogram quantity="dft.searchable_quantities" values={forces_quantities} valueLabels={labels} title="Forces" initialScale={0.5} tooltips />
......@@ -184,8 +185,7 @@ export function DFTPropertyVisualizations(props) {
<QuantityHistogram quantity="dft.searchable_quantities" values={optical_quantities} valueLabels={labels} title="Optical" initialScale={1} tooltips />
</Grid>
<Grid item xs={4}>
<QuantityHistogram quantity="dft.labels_springer_classification" title="Springer classification" initialScale={1} tooltips />
<QuantityHistogram quantity="dft.searchable_quantities" values={magnetic_quantities} valueLabels={labels} title="Magnetic" initialScale={1} tooltips />
<QuantityHistogram quantity="dft.labels_springer_classification" title="Property classification" initialScale={1} tooltips />
</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 Viewer from './Viewer'
import { apiContext } from '../api'
......@@ -60,14 +61,22 @@ const useStyles = makeStyles(theme => ({
}
}))
export default function MetaInfoBrowser(props) {
export default function MetaInfoBrowser({visible}) {
const classes = useStyles()
const location = useLocation()
const history = useHistory()
const routingRef = useRef({})
const routing = {
location: useLocation(),
history: useHistory(),
routeMatch: useRouteMatch()
}
if (visible) {
routingRef.current = routing
}
const {location, history, routeMatch} = routingRef.current
const match = matchPath(location.pathname, {
path: `${useRouteMatch().path}/:pkg?/:metainfo?`
path: `${routeMatch.path}/:pkg?/:metainfo?`
})
const pkg = match.params.pkg || 'general'
const metainfoName = match.params.metainfo || 'section_run'
......@@ -112,7 +121,7 @@ export default function MetaInfoBrowser(props) {
const metainfo = metainfos.resolve(metainfos.createProxy(metainfoName))
console.log(metainfoName)
return <div>
return <div style={{display: visible ? 'block' : 'none'}}>
<div className={classes.forms}>
<form style={{ display: 'flex' }}>
<MetainfoSearch
......@@ -143,3 +152,6 @@ export default function MetaInfoBrowser(props) {
<Viewer key={`${metainfo.package.name}/${metainfo.name}`} rootElement={metainfo} packages={metainfos.contents} />
</div>
}
MetaInfoBrowser.propTypes = {
visible: PropTypes.bool
}
......@@ -289,6 +289,7 @@ class DatasetListUnstyled extends React.Component {
total={total}
columns={this.columns}
selectedColumns={['name', 'DOI', 'entries', 'authors']}
selectedColumnsKey="datasets"
entryActions={this.renderEntryActions}
data={results}
rows={perPage}
......
......@@ -342,6 +342,7 @@ export class EntryListUnstyled extends React.Component {
total={total}
columns={columns}
selectedColumns={defaultSelectedColumns}
selectedColumnsKey="entries"
entryDetails={this.renderEntryDetails.bind(this)}
entryActions={this.renderEntryActions.bind(this)}
data={results}
......
......@@ -210,6 +210,7 @@ class GroupListUnstyled extends React.Component {
total={total}
columns={this.columns}
selectedColumns={defaultSelectedColumns}
selectedColumnsKey="groups"
entryDetails={this.renderEntryDetails.bind(this)}
entryActions={this.renderEntryActions}
data={results}
......
......@@ -43,19 +43,6 @@ const resultTabs = {
}
}
const defaultVisalizations = {
'elements': {
component: ElementsVisualization,
label: 'Elements',
description: 'Shows data as a heatmap over the periodic table'
},
'users': {
component: UsersVisualization,
label: 'Users',
description: 'Show statistics on user metadata'
}
}
const useSearchStyles = makeStyles(theme => ({
root: {
padding: theme.spacing(3)
......@@ -162,8 +149,18 @@ function SearchEntry({initialTab, initialOwner, ownerTypes, initialDomain, initi
const {domain} = useContext(searchContext)
const visualizations = {}
Object.assign(visualizations, defaultVisalizations)
visualizations.elements = {
component: ElementsVisualization,
label: 'Elements',
description: 'Shows data as a heatmap over the periodic table'
}
Object.assign(visualizations, domain.searchVisualizations)
visualizations.users = {
component: UsersVisualization,
label: 'Uploads',
description: 'Show statistics about when and by whom data was uploaded'
}
const openVisualizationKey = openVisualizationParam || initialTab
const openVisualizationTab = visualizations[openVisualizationKey]
......@@ -216,6 +213,13 @@ SearchEntry.propTypes = {
ownerTypes: PropTypes.arrayOf(PropTypes.string)
}
const originLabels = {
'Stefano Curtarolo': 'AFLOW',
'Chris Wolverton': 'OQMD',
'Patrick Huck': 'Materials Project',
'Markus Scheidgen': 'NOMAD Laboratory'