Commit 390cb717 authored by Markus Scheidgen's avatar Markus Scheidgen
Browse files

Merge branch 'gui-improvements' into 'v1.0.0'

Many minor GUI tweaks.

See merge request !178
parents 7f2a9151 d3726f08
Pipeline #82423 passed with stages
in 23 minutes and 39 seconds
......@@ -2,7 +2,7 @@ window.nomadEnv = {
'keycloakBase': 'https://nomad-lab.eu/fairdi/keycloak/auth/',
'keycloakRealm': 'fairdi_nomad_test',
'keycloakClientId': 'nomad_gui_dev',
//'appBase': 'http://nomad-lab.eu/prod/rae/beta',
'appBase': 'http://nomad-lab.eu/prod/rae/beta',
'appBase': 'http://localhost:8000/fairdi/nomad/latest',
'debug': false,
'matomoEnabled': false,
......
......@@ -198,11 +198,11 @@ export default function About() {
let stringValue = null
if (nominal) {
if (nominal >= 1.0e+9) {
stringValue = Math.floor(nominal / 1.0e+9) + ' bln.'
stringValue = (nominal / 1.0e+9).toFixed(1) + ' billion'
} else if (nominal >= 1.0e+6) {
stringValue = Math.floor(nominal / 1.0e+6) + ' mln.'
stringValue = (nominal / 1.0e+6).toFixed(1) + ' million'
} else if (nominal >= 1.0e+3) {
stringValue = Math.floor(nominal / 1.0e+3) + ' tsd.'
stringValue = (nominal / 1.0e+3).toFixed(1) + ' thousand'
} else {
stringValue = nominal.toString()
}
......
......@@ -5,7 +5,7 @@ import classNames from 'classnames'
import { MuiThemeProvider, withStyles, makeStyles } from '@material-ui/core/styles'
import { LinearProgress, MenuList, Typography,
AppBar, Toolbar, Button, DialogContent, DialogTitle, DialogActions, Dialog, Tooltip,
Snackbar, SnackbarContent, FormGroup, FormControlLabel, Switch, IconButton } from '@material-ui/core'
Snackbar, SnackbarContent, FormGroup, FormControlLabel, Switch, IconButton, Link as MuiLink } from '@material-ui/core'
import { Route, Link, withRouter, useLocation } from 'react-router-dom'
import BackupIcon from '@material-ui/icons/Backup'
import SearchIcon from '@material-ui/icons/Search'
......@@ -143,7 +143,7 @@ function BetaSnack() {
message={<span style={{color: 'white'}}>
You are using a {version.isBeta ? 'beta' : 'test'} version of NOMAD ({version.label}). {
version.usesBetaData ? 'This version is not using the official data. Everything you upload here, might get lost.' : ''
} Click <a style={{color: 'white'}} href={version.officialUrl}>here for the official NOMAD version</a>.
} Click <MuiLink style={{color: 'white'}} href={version.officialUrl}>here for the official NOMAD version</MuiLink>.
</span>}
action={[
<IconButton key={0} color="inherit" onClick={() => setUnderstood(true)}>
......@@ -419,7 +419,8 @@ class NavigationUnstyled extends React.Component {
'/userdata': 'Manage Your Data',
'/metainfo': 'The NOMAD Meta Info',
'/entry': 'Entry',
'/dataset': 'Dataset'
'/dataset': 'Dataset',
'/aitoolkit': 'Artificial Intelligence Toolkit'
}
toolbarHelp = {
......@@ -484,9 +485,9 @@ class NavigationUnstyled extends React.Component {
disableGutters
>
<div className={classes.title}>
<a href="https://nomad-lab.eu">
<MuiLink href="https://nomad-lab.eu">
<img alt="The NOMAD logo" className={classes.logo} src={`${guiBase}/nomad.png`}></img>
</a>
</MuiLink>
<Typography variant="h6" color="inherit" noWrap>
{selected(toolbarTitles)}
</Typography>
......
......@@ -14,10 +14,12 @@ import Checkbox from '@material-ui/core/Checkbox'
import IconButton from '@material-ui/core/IconButton'
import Tooltip from '@material-ui/core/Tooltip'
import ViewColumnIcon from '@material-ui/icons/ViewColumn'
import { Popover, List, ListItemText, ListItem, Collapse } from '@material-ui/core'
import { Popover, List, ListItemText, ListItem, Collapse, Icon, Box } from '@material-ui/core'
import { compose } from 'recompose'
import _ from 'lodash'
import { normalizeDisplayValue } from '../config'
import SortIcon from '@material-ui/icons/Sort'
import searchQuantities from '../searchQuantities'
const globalSelectedColumns = {}
......@@ -448,6 +450,7 @@ class DataTableUnStyled extends React.Component {
</TableCell> : <React.Fragment/>}
{Object.keys(columns).filter(key => selectedColumns.indexOf(key) !== -1).map(key => {
const column = columns[key]
const description = column.description || (searchQuantities[key] && searchQuantities[key].description)
return (
<TableCell
key={key}
......@@ -455,7 +458,7 @@ class DataTableUnStyled extends React.Component {
align={column.align || 'left'}
sortDirection={orderBy === key ? order : false}
>
<Tooltip title={column.description || ''}>
<Tooltip title={description || ''}>
{column.supportsSort ? <TableSortLabel
active={orderBy === key}
hideSortIcon
......@@ -463,6 +466,9 @@ class DataTableUnStyled extends React.Component {
onClick={event => this.handleRequestSort(event, key)}
>
{column.label}
<Box paddingLeft={1} fontSize="1rem">
{orderBy !== key && <Icon fontSize="small"><SortIcon style={{fontSize: '1rem'}}/></Icon>}
</Box>
{orderBy === key ? (
<span className={classes.visuallyHidden}>
{order === 'desc' ? 'sorted descending' : 'sorted ascending'}
......
......@@ -3,7 +3,7 @@ import PropTypes from 'prop-types'
import { Select, MenuItem, Card, CardContent, CardHeader, makeStyles, Tooltip } from '@material-ui/core'
import * as d3 from 'd3'
import { scaleBand, scalePow } from 'd3-scale'
import { formatQuantity, nomadPrimaryColor, nomadSecondaryColor, nomadFontFamily } from '../config.js'
import { formatQuantity, nomadPrimaryColor, nomadSecondaryColor, nomadFontFamily, nomadTheme } from '../config.js'
function split(array, cols) {
if (cols === 1) {
......@@ -47,6 +47,8 @@ const useStyles = makeStyles(theme => ({
},
tooltipContent: {
position: 'absolute',
fontSize: nomadTheme.overrides.MuiTooltip.tooltip.fontSize,
fontWeight: nomadTheme.overrides.MuiTooltip.tooltip.fontWeight,
// backgroundColor: '#ffbb00' // Uncomment for debugging tooltips
display: 'none',
zIndex: 1,
......@@ -262,17 +264,19 @@ export default function Histogram({
title={title}
titleTypographyProps={{variant: 'body1'}}
action={(
<Select
value={scale}
onChange={(event) => setScale(event.target.value)}
displayEmpty
name="scale power"
>
<MenuItem value={1}>linear</MenuItem>
<MenuItem value={0.5}>1/2</MenuItem>
<MenuItem value={0.25}>1/4</MenuItem>
<MenuItem value={0.125}>1/8</MenuItem>
</Select>
<Tooltip title="Select the power of the scale">
<Select
value={scale}
onChange={(event) => setScale(event.target.value)}
displayEmpty
name="scale power"
>
<MenuItem value={1}>linear</MenuItem>
<MenuItem value={0.5}>1/2</MenuItem>
<MenuItem value={0.25}>1/4</MenuItem>
<MenuItem value={0.125}>1/8</MenuItem>
</Select>
</Tooltip>
)}
/>
<CardContent classes={{root: classes.content}}>
......
......@@ -4,6 +4,7 @@ import { withStyles, Typography, Tooltip, IconButton } from '@material-ui/core'
import ClipboardIcon from '@material-ui/icons/Assignment'
import { CopyToClipboard } from 'react-copy-to-clipboard'
import _ from 'lodash'
import searchQuanitites from '../searchQuantities'
class Quantity extends React.Component {
static propTypes = {
......@@ -22,7 +23,8 @@ class Quantity extends React.Component {
PropTypes.func
]),
withClipboard: PropTypes.bool,
ellipsisFront: PropTypes.bool
ellipsisFront: PropTypes.bool,
hideIfUnavailable: PropTypes.bool
}
static styles = theme => ({
......@@ -73,7 +75,10 @@ class Quantity extends React.Component {
})
render() {
const {classes, children, label, typography, loading, placeholder, noWrap, row, column, quantity, data, withClipboard, ellipsisFront} = this.props
const {
classes, children, label, typography, loading, placeholder, noWrap, row, column,
quantity, data, withClipboard, ellipsisFront, hideIfUnavailable
} = this.props
let content = null
let clipboardContent = null
......@@ -98,6 +103,10 @@ class Quantity extends React.Component {
value = 'unavailable'
}
if ((!value || value === 'unavailable') && hideIfUnavailable) {
return ''
}
if (children && children.length !== 0) {
content = children
} else if (value) {
......@@ -118,25 +127,27 @@ class Quantity extends React.Component {
return <div className={row ? classes.row : classes.column}>{children}</div>
} else {
return (
<div className={classes.root}>
<Typography noWrap classes={{root: classes.label}} variant="caption">{useLabel}</Typography>
<div className={classes.valueContainer}>
{loading
? <Typography noWrap={noWrap} variant={typography} className={valueClassName}>
<i>loading ...</i>
</Typography> : content}
{withClipboard
? <CopyToClipboard className={classes.valueAction} text={clipboardContent} onCopy={() => null}>
<Tooltip title={`Copy ${useLabel} to clipboard`}>
<div>
<IconButton disabled={!clipboardContent} classes={{root: classes.valueActionButton}} >
<ClipboardIcon classes={{root: classes.valueActionIcon}}/>
</IconButton>
</div>
</Tooltip>
</CopyToClipboard> : ''}
<Tooltip title={(searchQuanitites[quantity] && searchQuanitites[quantity].description) || ''}>
<div className={classes.root}>
<Typography noWrap classes={{root: classes.label}} variant="caption">{useLabel}</Typography>
<div className={classes.valueContainer}>
{loading
? <Typography noWrap={noWrap} variant={typography} className={valueClassName}>
<i>loading ...</i>
</Typography> : content}
{withClipboard
? <CopyToClipboard className={classes.valueAction} text={clipboardContent} onCopy={() => null}>
<Tooltip title={`Copy ${useLabel} to clipboard`}>
<div>
<IconButton disabled={!clipboardContent} classes={{root: classes.valueActionButton}} >
<ClipboardIcon classes={{root: classes.valueActionIcon}}/>
</IconButton>
</div>
</Tooltip>
</CopyToClipboard> : ''}
</div>
</div>
</div>
</Tooltip>
)
}
}
......
......@@ -2,7 +2,7 @@
import React, { useMemo } from 'react'
import PropTypes from 'prop-types'
import { atom, useRecoilState, useRecoilValue } from 'recoil'
import { Box, FormGroup, FormControlLabel, Checkbox, TextField, Typography, makeStyles } from '@material-ui/core'
import { Box, FormGroup, FormControlLabel, Checkbox, TextField, Typography, makeStyles, Tooltip } from '@material-ui/core'
import { useRouteMatch, useHistory } from 'react-router-dom'
import Autocomplete from '@material-ui/lab/Autocomplete'
import Browser, { Item, Content, Compartment, List, Adaptor } from './Browser'
......@@ -75,35 +75,41 @@ function ArchiveConfigForm({searchOptions}) {
renderInput={(params) => <TextField {...params} label="search" margin="normal" />}
/>
<Box flexGrow={1} />
<FormControlLabel
control={
<Checkbox
checked={config.showCodeSpecific}
onChange={handleConfigChange}
name="showCodeSpecific"
/>
}
label="code specific"
/>
<FormControlLabel
control={
<Checkbox
checked={config.showAllDefined}
onChange={handleConfigChange}
name="showAllDefined"
/>
}
label="all defined"
/>
<FormControlLabel
control={
<Checkbox
checked={config.showMeta}
onChange={handleConfigChange}
name="showMeta" />
}
label="definitions"
/>
<Tooltip title="Enable to also show all code specfic data">
<FormControlLabel
control={
<Checkbox
checked={config.showCodeSpecific}
onChange={handleConfigChange}
name="showCodeSpecific"
/>
}
label="code specific"
/>
</Tooltip>
<Tooltip title="Enable to also show metadata that is in principle available, but not within this entry">
<FormControlLabel
control={
<Checkbox
checked={config.showAllDefined}
onChange={handleConfigChange}
name="showAllDefined"
/>
}
label="all defined"
/>
</Tooltip>
<Tooltip title="Show the Metainfo definition on the bottom of each lane">
<FormControlLabel
control={
<Checkbox
checked={config.showMeta}
onChange={handleConfigChange}
name="showMeta" />
}
label="definitions"
/>
</Tooltip>
</FormGroup>
</Box>
)
......
import React, { useContext, useEffect } from 'react'
import PropTypes from 'prop-types'
import { Grid } from '@material-ui/core'
import { useTheme } from '@material-ui/core/styles'
import { makeStyles } from '@material-ui/core/styles'
import QuantityHistogram from '../search/QuantityHistogram'
import { searchContext } from '../search/SearchContext'
import { path, defsByName } from '../archive/metainfo'
import { nomadTheme } from '../../config'
import Markdown from '../Markdown'
export function DFTMethodVisualizations(props) {
const {info} = props
......@@ -110,25 +112,25 @@ const optical_quantities = [
]
const labels = {
'eigenvalues_values': 'Eigenvalues',
'stress_tensor': 'Stress tensor',
'electronic_band_structure': 'Electronic band structure',
'electronic_dos': 'Electronic density of states',
'phonon_band_structure': 'Phonon dispersion',
'phonon_dos': 'Phonon density of states',
'thermodynamical_property_heat_capacity_C_v': 'Heat capacity',
'vibrational_free_energy_at_constant_volume': 'Helmholtz free energy',
'spin_S2': 'Angular spin momentum squared',
'oscillator_strengths': 'Oscillator strengths',
'transition_dipole_moments': 'Transition dipole moments'
'eigenvalues_values': 'eigenvalues',
'stress_tensor': 'stress tensor',
'electronic_band_structure': 'electronic band structure',
'electronic_dos': 'electronic density of states',
'phonon_band_structure': 'phonon dispersion',
'phonon_dos': 'phonon density of states',
'thermodynamical_property_heat_capacity_C_v': 'heat capacity',
'vibrational_free_energy_at_constant_volume': 'helmholtz free energy',
'spin_S2': 'angular spin momentum squared',
'oscillator_strengths': 'oscillator strengths',
'transition_dipole_moments': 'transition dipole moments'
}
const metainfoPaths = {
'eigenvalues_values': path('eigenvalues_values'),
'stress_tensor': path('stress_tensor'),
'electronic_band_structure': 'section_run/section_single_configuration_calculation/section_k_band',
'electronic_dos': 'section_run/section_single_configuration_calculation/section_dos',
'phonon_band_structure': 'section_run/section_single_configuration_calculation/section_k_band',
'phonon_dos': 'section_run/section_single_configuration_calculation/section_dos',
'electronic_band_structure': 'EntryArchive/section_run/section_single_configuration_calculation/section_k_band',
'electronic_dos': 'EntryArchive/section_run/section_single_configuration_calculation/section_dos',
'phonon_band_structure': 'EntryArchive/section_run/section_single_configuration_calculation/section_k_band',
'phonon_dos': 'EntryArchive/section_run/section_single_configuration_calculation/section_dos',
'thermodynamical_property_heat_capacity_C_v': path('thermodynamical_property_heat_capacity_C_v'),
'vibrational_free_energy_at_constant_volume': path('vibrational_free_energy_at_constant_volume'),
'spin_S2': path('spin_S2'),
......@@ -136,11 +138,27 @@ const metainfoPaths = {
'transition_dipole_moments': path('transition_dipole_moments')
}
const useMetainfoTooltipStyles = makeStyles(theme => ({
root: {
display: 'flex',
flexDirection: 'column',
padding: 2
},
tooltipMarkdown: {
fontSize: nomadTheme.overrides.MuiTooltip.tooltip.fontSize,
color: 'white',
'& a': {
color: theme.palette.primary.light
}
}
}))
function MetaInfoTooltip({def, path}) {
const theme = useTheme()
return <div style={{display: 'flex', flexDirection: 'column', padding: 2}}>
<p style={{fontSize: 14, margin: 0, padding: 0}}>Metainfo definition:</p>
<a style={{fontSize: 14, color: theme.palette.secondary.light}} href={`/fairdi/nomad/latest/gui/metainfo/${path}`}>{def.name}</a>
const classes = useMetainfoTooltipStyles()
return <div className={classes.root} >
<Markdown
classes={{root: classes.tooltipMarkdown}}
>{`${def.description.slice(0, def.description.indexOf('.') || undefined)}. Click [here](/metainfo/${path}) for full the definition.`}</Markdown>
</div>
}
......@@ -158,10 +176,10 @@ for (const label in labels) {
}
const workflowTypeLabels = {
'geometry_optimization': 'Geometry optimization',
'phonon': 'Phonons',
'elastic': 'Elastic constants',
'molecular_dynamics': 'Molecular dynamics'
'geometry_optimization': 'geometry optimization',
'phonon': 'phonons',
'elastic': 'elastic constants',
'molecular_dynamics': 'molecular dynamics'
}
export function DFTPropertyVisualizations(props) {
......
......@@ -9,7 +9,8 @@ import {
import EMSVisualizations from './ems/EMSVisualizations'
import QCMSEntryOverview from './qcms/QCMSEntryOverview'
import QCMSEntryCards from './qcms/QCMSEntryCards'
import { Link } from '@material-ui/core'
import { Link, Typography } from '@material-ui/core'
import { amber } from '@material-ui/core/colors'
/* eslint-disable react/display-name */
......@@ -19,9 +20,15 @@ export const domains = ({
label: 'Computational material science data',
key: 'dft',
about: 'This include data from many computational material science codes',
disclaimer: <Typography>
First time users can find an introduction to NOMAD, tutorials, and videos <Link href="https://nomad-lab.eu/services/repo-arch" target="nomad-lab">here</Link>.
</Typography>,
entryLabel: 'entry',
entryLabelPlural: 'entries',
entryTitle: data => data.dft && data.dft.code_name ? data.dft.code_name + ' run' : 'Code run',
entryTitle: data =>
data.dft && data.dft.code_name
? data.dft.code_name.charAt(0).toUpperCase() + data.dft.code_name.slice(1) + ' run'
: 'Code run',
searchPlaceholder: 'enter atoms, codes, functionals, or other quantity values',
/**
* A set of components and metadata that is used to present tabs of search visualizations
......@@ -165,8 +172,13 @@ export const domains = ({
ems: {
name: 'Experimental data',
key: 'ems',
label: 'Experimental material science data',
about: 'This is metadata from material science experiments',
label: 'Experimental data (beta)',
about: 'This includes first metadata from material science experiments. This aspect of NOMAD is still in development and mnight change frequently.',
disclaimer: <Typography style={{color: amber[700]}}>
This aspect of NOMAD is still under development. The offered functionality and displayed data
might change frequently, is not necessarely reviewed by NOMAD, and might contain
errors. Some of the information is taken verbatim from external sources.
</Typography>,
entryLabel: 'entry',
entryLabelPlural: 'entries',
entryTitle: () => 'Experiment',
......@@ -247,11 +259,16 @@ export const domains = ({
qcms: {
name: 'Quantum-computer data',
key: 'qcms',
label: 'Quantum-computer material science data',
about: 'This is computational material science data calculated by quantum computers',
label: 'Quantum-computer material science data (beta)',
about: 'This includes first data material science data calculated by quantum-computers. This aspect of NOMAD is still in development and mnight change frequently.',
disclaimer: <Typography style={{color: amber[700]}}>
This aspect of NOMAD is still under development. The offered functionality and displayed data
might change frequently, is not necessarely reviewed by NOMAD, and might contain
errors. Some of the information is taken verbatim from external sources.
</Typography>,
entryLabel: 'calculation',
entryLabelPlural: 'calculations',
entryTitle: () => 'Quantum computer calculation',
entryTitle: () => 'Quantum-computer calculation',
searchPlaceholder: 'enter atoms',
searchVisualizations: {
},
......
......@@ -49,7 +49,7 @@ export default class EMSEntryOverview extends React.Component {
{this.state.previewBroken
? data.ems.entry_repository_url && <Quantity label="preview" {...this.props}>
<Typography noWrap>
<a target="external" href={ems.entry_repository_url}>visit this entry on the external database</a>
<Link target="external" href={ems.entry_repository_url}>visit this entry on the external database</Link>
</Typography>
</Quantity>
: <Quantity label="preview" {...this.props}>
......
......@@ -33,6 +33,9 @@ class ArchiveEntryView extends React.Component {
paddingTop: theme.spacing(2),
paddingBottom: theme.spacing(2)
},
archiveBrowser: {
marginTop: theme.spacing(2)
},
error: {
marginTop: theme.spacing(2)
},
......@@ -113,8 +116,9 @@ class ArchiveEntryView extends React.Component {
<EntryPageContent className={classes.root}>
{
data && typeof data !== 'string'
? <ArchiveBrowser data={data} />
: <div>{
? <div className={classes.archiveBrowser}>
<ArchiveBrowser data={data} />
</div> : <div>{
data
? <div>
<Typography>Archive data is not valid JSON. Displaying plain text instead.</Typography>
......
......@@ -10,7 +10,7 @@ import { DOI } from '../search/DatasetList'
import { domains } from '../domains'
import { EntryPageContent } from './EntryPage'
import { errorContext } from '../errors'
import { titleCase } from '../../utils'
import { authorList } from '../../utils'
const useStyles = makeStyles(theme => ({
root: {
......@@ -94,13 +94,13 @@ export default function RepoEntryView({uploadId, calcId}) {
{calcData.references &&
<div style={{display: 'inline-grid'}}>
{calcData.references.map(ref => <Typography key={ref} noWrap>
<a href={ref}>{ref}</a>
<Link href={ref}>{ref}</Link>
</Typography>)}
</div>}
</Quantity>
<Quantity quantity='authors' {...quantityProps}>
<Typography>
{(authors || []).map(author => titleCase(author.name)).join('; ')}
{authorList(authors || [])}
</Typography>
</Quantity>
<Quantity quantity='datasets' placeholder='no datasets' {...quantityProps}>
......@@ -124,9 +124,7 @@ export default function RepoEntryView({uploadId, calcId}) {
<CardContent classes={{root: classes.cardContent}}>
<Quantity column style={{maxWidth: 350}}>
<Quantity quantity="calc_id" label={`${domain ? domain.entryLabel : 'entry'} id`} noWrap withClipboard {...quantityProps} />
<Quantity quantity={entry => entry.encyclopedia.material.material_id} label='material id' loading={loading} noWrap {...quantityProps} withClipboard />
<Quantity quantity="raw_id" label='raw id' loading={loading} noWrap {...quantityProps} withClipboard />
<Quantity quantity="external_id" label='external id' loading={loading} noWrap {...quantityProps} withClipboard />
<Quantity quantity="encyclopedia.material.material_id" label='material id' loading={loading} noWrap {...quantityProps} withClipboard />
<Quantity quantity="mainfile" loading={loading} noWrap ellipsisFront {...quantityProps} withClipboard />
<Quantity quantity="upload_id" label='upload id' {...quantityProps} noWrap withClipboard />
<Quantity quantity="upload_time" label='upload time' noWrap {...quantityProps} >
......@@ -134,6 +132,8 @@ export default function RepoEntryView({uploadId, calcId}) {
{new Date(calcData.upload_time).toLocaleString()}
</Typography>
</Quantity>
<Quantity quantity="raw_id" label='raw id' loading={loading} noWrap hideIfUnavailable {...quantityProps} withClipboard />
<Quantity quantity="external_id" label='external id' loading={loading} hideIfUnavailable noWrap {...quantityProps} withClipboard />
<Quantity quantity="last_processing" label='last processing' loading={loading} placeholder="not processed" noWrap {...quantityProps}>
<Typography noWrap>
{new Date(calcData.last_processing).toLocaleString()}
......
......@@ -16,6 +16,7 @@ import ClipboardIcon from '@material-ui/icons/Assignment'
import { CopyToClipboard } from 'react-copy-to-clipboard'
import ConfirmDialog from '../uploads/ConfirmDialog'
import { oasis } from '../../config'
import { authorList } from '../../utils'
class DOIUnstyled extends React.Component {
static propTypes = {
......@@ -217,28 +218,33 @@ class DatasetListUnstyled extends React.Component {