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

Simplified the help system. Minor cosmetic changes. Fixe #191

parent 5c61906a
Pipeline #53334 failed with stages
in 10 minutes and 2 seconds
......@@ -5,19 +5,17 @@ import PropTypes, { instanceOf } from 'prop-types'
import { compose } from 'recompose'
import classNames from 'classnames'
import { MuiThemeProvider, withStyles } from '@material-ui/core/styles'
import { IconButton, Checkbox, FormLabel, LinearProgress, ListItemIcon, ListItemText,
MenuList, MenuItem, Typography, Drawer, AppBar, Toolbar, Divider, Button, DialogContent, DialogTitle, DialogActions, Dialog } from '@material-ui/core'
import { IconButton, LinearProgress, ListItemIcon, ListItemText,
MenuList, MenuItem, Typography, Drawer, AppBar, Toolbar, Divider, Button, DialogContent, DialogTitle, DialogActions, Dialog, Tooltip } from '@material-ui/core'
import { BrowserRouter, Switch, Route, Link, withRouter } from 'react-router-dom'
import BackupIcon from '@material-ui/icons/Backup'
import SearchIcon from '@material-ui/icons/Search'
import AboutIcon from '@material-ui/icons/Home'
import AboutIcon from '@material-ui/icons/Help'
import MetainfoIcon from '@material-ui/icons/Info'
import ChevronLeftIcon from '@material-ui/icons/ChevronLeft'
import MenuIcon from '@material-ui/icons/Menu'
import Uploads from './uploads/Uploads'
import SearchPage from './search/SearchPage'
import { HelpProvider, HelpContext } from './help'
import {help as searchHelp, default as SearchPage} from './search/SearchPage'
import HelpDialog from './Help'
import { ApiProvider, withApi } from './api'
import { ErrorSnacks } from './errors'
import Calc from './entry/Calc'
......@@ -25,17 +23,18 @@ import About from './About'
import LoginLogout from './LoginLogout'
import { genTheme, repoTheme, archiveTheme, appBase } from '../config'
import { DomainProvider } from './domains'
import MetaInfoBrowser from './metaInfoBrowser/MetaInfoBrowser'
import {help as metainfoHelp, default as MetaInfoBrowser} from './metaInfoBrowser/MetaInfoBrowser'
import packageJson from '../../package.json'
import { Cookies, withCookies } from 'react-cookie'
import Markdown from './Markdown'
import {help as uploadHelp, default as Uploads} from './uploads/Uploads'
const drawerWidth = 200
const toolbarTitles = {
'/': 'About nomad@FAIRDI',
'/search': 'Search',
'/uploads': 'Upload Your Own Data',
'/': 'About, Documentation, Getting Help',
'/search': 'Find and Download Data',
'/uploads': 'Upload and Publish Data',
'/metainfo': 'The Nomad Meta Info'
}
......@@ -46,6 +45,13 @@ const toolbarThemes = {
'/metainfo': archiveTheme
}
const toolbarHelp = {
'/': null,
'/search': {title: 'How to find and download data', content: searchHelp},
'/uploads': {title: 'How to upload data', content: uploadHelp},
'/metainfo': {title: 'About the Nomad meta-info', content: metainfoHelp}
}
class NavigationUnstyled extends React.Component {
static propTypes = {
classes: PropTypes.object.isRequired,
......@@ -56,8 +62,12 @@ class NavigationUnstyled extends React.Component {
static styles = theme => ({
root: {},
flex: {
flexGrow: 1
title: {
marginLeft: theme.spacing.unit,
flexGrow: 1,
display: 'flex',
alignItems: 'center',
alignContent: 'flex-start'
},
appFrame: {
zIndex: 1,
......@@ -85,8 +95,10 @@ class NavigationUnstyled extends React.Component {
})
},
menuButton: {
marginLeft: 12,
marginRight: 36
marginLeft: theme.spacing.unit
},
helpButton: {
marginLeft: theme.spacing.unit
},
hide: {
display: 'none'
......@@ -140,22 +152,19 @@ class NavigationUnstyled extends React.Component {
},
barButton: {
borderColor: theme.palette.getContrastText(theme.palette.primary.main),
marginRight: theme.spacing.unit * 4
marginRight: 0
},
barButtonDisabled: {
marginRight: theme.spacing.unit * 4
marginRight: 0
}
})
state = {
open: false
}
constructor(props) {
super(props)
this.handleDrawerOpen = this.handleDrawerOpen.bind(this)
this.handleDrawerClose = this.handleDrawerClose.bind(this)
this.state = {
open: false
}
}
componentDidMount() {
......@@ -172,12 +181,8 @@ class NavigationUnstyled extends React.Component {
})
}
handleDrawerOpen() {
this.setState({ open: true })
}
handleDrawerClose() {
this.setState({ open: false })
handleDrawerEvent(isOpen) {
this.setState({ open: !isOpen, openIsSet: true })
}
render() {
......@@ -191,6 +196,7 @@ class NavigationUnstyled extends React.Component {
}
const theme = selected(toolbarThemes)
const help = selected(toolbarHelp)
return (
<div className={classes.root}>
......@@ -203,25 +209,19 @@ class NavigationUnstyled extends React.Component {
<Toolbar disableGutters={!this.state.open}>
<IconButton
color="inherit"
onClick={this.handleDrawerOpen}
onClick={() => this.handleDrawerEvent(this.state.open)}
className={classNames(classes.menuButton, this.state.open && classes.hide)}
>
<MenuIcon />
</IconButton>
<Typography variant="h6" color="inherit" noWrap className={classes.flex}>
{selected(toolbarTitles)}
</Typography>
<div className={classes.title}>
<Typography variant="h6" color="inherit" noWrap>
{selected(toolbarTitles)}
</Typography>
{help ? <HelpDialog color="inherit" maxWidth="md" classes={{root: classes.helpButton}} {...help}/> : ''}
</div>
<div className={classes.barActions}>
<LoginLogout variant="outlined" color="inherit" classes={{button: classes.barButton, buttonDisabled: classes.barButtonDisabled}} />
<FormLabel className={classes.barSelect} >Show help</FormLabel>
<HelpContext.Consumer>{
help => (
<Checkbox
checked={!help.someClosed()} indeterminate={!help.allClosed() && help.someClosed()}
onClick={() => help.switchHelp()}
classes={{root: classes.barSelect, checked: classes.barSelect}} />
)
}</HelpContext.Consumer>
</div>
</Toolbar>
{loading ? <LinearProgress color="primary" /> : ''}
......@@ -233,38 +233,46 @@ class NavigationUnstyled extends React.Component {
anchor="left"
>
<div className={classes.toolbar}>
<IconButton onClick={this.handleDrawerClose}>
<IconButton onClick={() => this.handleDrawerEvent(this.state.open)}>
<ChevronLeftIcon/>
</IconButton>
</div>
<MenuList>
<MenuItem className={classes.menuItem} component={Link} to="/" selected={ pathname === '/' }>
<ListItemIcon>
<AboutIcon />
</ListItemIcon>
<ListItemText inset primary="About"/>
</MenuItem>
<Tooltip title="Upload and publish data">
<MenuItem className={classes.menuItem} component={Link} to="/uploads" selected={ pathname === '/uploads' }>
<ListItemIcon>
<BackupIcon style={{fill: repoTheme.palette.primary.main}}/>
</ListItemIcon>
<ListItemText inset primary="Upload"/>
</MenuItem>
</Tooltip>
<Tooltip title="Find and download data">
<MenuItem className={classes.menuItem} component={Link} to="/search" selected={ pathname.startsWith('/repo') }>
<ListItemIcon>
<SearchIcon style={{fill: repoTheme.palette.primary.main}}/>
</ListItemIcon>
<ListItemText inset primary="Search"/>
</MenuItem>
</Tooltip>
<Divider />
<MenuItem className={classes.menuItem} component={Link} to="/search" selected={ pathname.startsWith('/repo') }>
<ListItemIcon>
<SearchIcon style={{fill: repoTheme.palette.primary.main}}/>
</ListItemIcon>
<ListItemText inset primary="Search"/>
</MenuItem>
<MenuItem className={classes.menuItem} component={Link} to="/uploads" selected={ pathname === '/uploads' }>
<ListItemIcon>
<BackupIcon style={{fill: repoTheme.palette.primary.main}}/>
</ListItemIcon>
<ListItemText inset primary="Upload"/>
</MenuItem>
<Tooltip title="Browse the archive schema">
<MenuItem className={classes.menuItem} component={Link} to="/metainfo" selected={ pathname === '/metainfo' }>
<ListItemIcon>
<MetainfoIcon style={{fill: archiveTheme.palette.primary.main}}/>
</ListItemIcon>
<ListItemText inset primary="Meta Info"/>
</MenuItem>
</Tooltip>
<Divider />
<MenuItem className={classes.menuItem} component={Link} to="/metainfo" selected={ pathname === '/metainfo' }>
<ListItemIcon>
<MetainfoIcon style={{fill: archiveTheme.palette.primary.main}}/>
</ListItemIcon>
<ListItemText inset primary="Meta Info"/>
</MenuItem>
<Tooltip title="About, Documentation, Getting Help">
<MenuItem className={classes.menuItem} component={Link} to="/" selected={ pathname === '/' }>
<ListItemIcon>
<AboutIcon />
</ListItemIcon>
<ListItemText inset primary="Help"/>
</MenuItem>
</Tooltip>
</MenuList>
</Drawer>
......@@ -345,7 +353,6 @@ class LicenseAgreementUnstyled extends React.Component {
</Button>
</DialogActions>
</Dialog>
{this.state.accepted ? this.props.children : ''}
</div>
)
}
......@@ -438,24 +445,22 @@ export default class App extends React.Component {
<MuiThemeProvider theme={genTheme}>
<ErrorSnacks>
<BrowserRouter basename={process.env.PUBLIC_URL}>
<HelpProvider>
<ApiProvider>
<DomainProvider>
<Navigation>
<Switch>
{Object.keys(this.routes).map(route => (
// eslint-disable-next-line react/jsx-key
<Route key={'nop'}
// eslint-disable-next-line react/no-children-prop
children={props => this.renderChildren(route, props)}
exact={this.routes[route].exact}
path={this.routes[route].path} />
))}
</Switch>
</Navigation>
</DomainProvider>
</ApiProvider>
</HelpProvider>
<ApiProvider>
<DomainProvider>
<Navigation>
<Switch>
{Object.keys(this.routes).map(route => (
// eslint-disable-next-line react/jsx-key
<Route key={'nop'}
// eslint-disable-next-line react/no-children-prop
children={props => this.renderChildren(route, props)}
exact={this.routes[route].exact}
path={this.routes[route].path} />
))}
</Switch>
</Navigation>
</DomainProvider>
</ApiProvider>
</BrowserRouter>
</ErrorSnacks>
<LicenseAgreement />
......
......@@ -481,7 +481,7 @@ class LoginRequiredUnstyled extends React.Component {
let loginMessage = ''
if (message) {
loginMessage = <Typography>
{this.props.message} Register for a Nomad Repository account <Link href='http://nomad-repository.eu:8080/NomadRepository-1.1/register/'>here</Link>.
{this.props.message} If you do not have a Nomad Repository account, register <Link href='http://nomad-repository.eu:8080/NomadRepository-1.1/register/'>here</Link>.
</Typography>
}
......
......@@ -10,7 +10,16 @@ import DownloadIcon from '@material-ui/icons/CloudDownload'
import Download from './Download'
import { ValueAttributes, MetaAttribute } from '../metaInfoBrowser/ValueCard'
import ApiDialogButton from '../ApiDialogButton'
import { Help } from '../help'
export const help = `
The nomad **archive** provides data and meta-data in a common hierarchical format based on
well-defined quantity definitions that we call *metainfo*. This representation
is independent from the raw data format and provides a homogenous data stock.
You can click the various quantity values to see the quantity definition. Similarly,
you can click section names to get more information. Browse the *metainfo* to
learn more about nomad's archive format [here](/metainfo).
`
class ArchiveEntryView extends React.Component {
static propTypes = {
......@@ -111,15 +120,6 @@ class ArchiveEntryView extends React.Component {
return (
<div className={classes.root}>
<Help cookie="archiveView">{`
The nomad **archive** provides data and meta-data in a common hierarchical format based on
well-defined quantity definitions that we call *metainfo*. This representation
is independent from the raw data format and provides a homogenous data stock.
You can click the various quantity values to see the quantity definition. Similarly,
you can click section names to get more information. Browse the *metainfo* to
learn more about nomad's archive format [here](/metainfo).
`}</Help>
<Card className={classes.metaInfo}>
<CardContent className={classes.metaInfoContent}>{
showMetaInfo && metaInfo
......
......@@ -5,7 +5,6 @@ import { compose } from 'recompose'
import { withApi } from '../api'
import Download from './Download'
import DownloadIcon from '@material-ui/icons/CloudDownload'
import { Help } from '../help'
class ArchiveLogView extends React.Component {
static propTypes = {
......@@ -63,10 +62,6 @@ class ArchiveLogView extends React.Component {
return (
<div className={classes.root}>
<Help cookie="archiveLogView">{`
This log was creating during parsing and normalizing. If you uploaded data
that did not process correctly, these logs might offer some clues.
`}</Help>
<pre>{data || 'empty log'}</pre>
<Download
......
......@@ -8,7 +8,6 @@ import DownloadIcon from '@material-ui/icons/CloudDownload'
import ApiDialogButton from '../ApiDialogButton'
import Quantity from '../Quantity'
import { withDomain } from '../domains'
import { Help } from '../help'
class RepoEntryView extends React.Component {
static styles = theme => ({
......@@ -80,10 +79,6 @@ class RepoEntryView extends React.Component {
return (
<div className={classes.root}>
<Help cookie="repoView">{`
This page gives you a brief overview about the selected entry. It shows basic
metadata and allows you to download the raw data.
`}</Help>
<div className={classes.content}>
<Grid container spacing={24}>
<Grid item xs={7}>
......
import React from 'react'
import { withStyles, Button, Collapse } from '@material-ui/core'
import { withStyles, Button, IconButton, Dialog, DialogTitle, DialogContent, DialogActions, Tooltip } from '@material-ui/core'
import Markdown from './Markdown'
import PropTypes, { instanceOf } from 'prop-types'
import { Cookies, withCookies } from 'react-cookie'
import classNames from 'classnames'
import PropTypes from 'prop-types'
import HelpIcon from '@material-ui/icons/Help'
export const HelpContext = React.createContext()
class HelpProviderComponent extends React.Component {
class HelpDialogUnstyled extends React.Component {
static propTypes = {
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node
]).isRequired,
cookies: instanceOf(Cookies).isRequired
}
state = {
helpCookies: [],
allHelpCookies: [],
allClosed: () => this.state.helpCookies.length === this.state.allHelpCookies.length,
someClosed: () => this.state.helpCookies.length !== 0,
isOpen: (cookie) => {
if (this.state.allHelpCookies.indexOf(cookie) === -1) {
this.state.allHelpCookies.push(cookie)
}
return this.state.helpCookies.indexOf(cookie) === -1
},
gotIt: (cookie) => {
const updatedHelpCookies = [...this.state.helpCookies, cookie]
this.props.cookies.set('help', updatedHelpCookies)
this.setState({helpCookies: updatedHelpCookies})
},
switchHelp: () => {
const updatedCookies = this.state.someClosed() ? [] : this.state.allHelpCookies
this.setState({helpCookies: updatedCookies})
this.props.cookies.set('help', updatedCookies)
}
}
componentDidMount() {
this.setState({helpCookies: this.props.cookies.get('help') || []})
classes: PropTypes.object.isRequired,
title: PropTypes.string,
content: PropTypes.string.isRequired,
icon: PropTypes.node,
maxWidth: PropTypes.string
}
render() {
return (
<HelpContext.Provider value={this.state}>
{this.props.children}
</HelpContext.Provider>
)
}
}
static styles = theme => ({
root: {}
})
export class Help extends React.Component {
static propTypes = {
children: PropTypes.any,
cookie: PropTypes.string.isRequired
state = {
isOpen: false
}
render() {
const { children, cookie } = this.props
return (
<HelpContext.Consumer>{
help => (
<Collapse in={help.isOpen(cookie)}>
<GotIt onGotIt={() => help.gotIt(cookie)}>
{children}
</GotIt>
</Collapse>
)
}</HelpContext.Consumer>
)
constructor(props) {
super(props)
this.handleOpen = this.handleOpen.bind(this)
this.handleClose = this.handleClose.bind(this)
}
}
class GotItUnstyled extends React.Component {
static propTypes = {
classes: PropTypes.object.isRequired,
onGotIt: PropTypes.func.isRequired,
children: PropTypes.node.isRequired,
color: PropTypes.oneOf(['primary', 'error']).isRequired
handleClose() {
this.setState({isOpen: false})
}
static defaultProps = {
color: 'primary'
handleOpen() {
this.setState({isOpen: true})
}
static styles = theme => ({
root: {
marginTop: theme.spacing.unit * 2,
marginBottom: theme.spacing.unit * 2,
borderRadius: theme.spacing.unit * 0.5,
display: 'flex',
flexDirection: 'row',
alignItems: 'center'
},
rootPrimary: {
border: `1px solid ${theme.palette.primary.main}`
},
rootError: {
border: `1px solid ${theme.palette.error.main}`
},
content: {
padding: theme.spacing.unit * 2,
flex: '1 1 auto'
},
actions: {
padding: theme.spacing.unit,
paddingLef: 0,
flex: '0 0 auto'
}
})
render() {
const { classes, children, onGotIt, color } = this.props
const rootClassName = classNames(classes.root, {
[classes.rootPrimary]: color === 'primary',
[classes.rootError]: color === 'error'
})
const {classes, title, content, icon, maxWidth, ...rest} = this.props
return (
<div className={rootClassName}>
<div className={classes.content}>
<Markdown>
{children}
</Markdown>
</div>
<div className={classes.actions}>
<Button color="primary" onClick={onGotIt}>Got it</Button>
</div>
<div className={classes.root}>
<Tooltip title={title}>
<IconButton {...rest} onClick={this.handleOpen}>
{icon || <HelpIcon/>}
</IconButton>
</Tooltip>
<Dialog
maxWidth={maxWidth}
onClose={this.handleClose}
open={this.state.isOpen}
>
<DialogTitle>{title || 'Help'}</DialogTitle>
<DialogContent>
<Markdown>{content}</Markdown>
</DialogContent>
<DialogActions>
<Button onClick={() => this.handleClose()} color="primary">
Close
</Button>
</DialogActions>
</Dialog>
</div>
)
}
}
const GotIt = withStyles(GotItUnstyled.styles)(GotItUnstyled)
export const HelpProvider = withCookies(HelpProviderComponent)
export default withStyles(HelpDialogUnstyled.styles)(HelpDialogUnstyled)
......@@ -4,12 +4,41 @@ import { withRouter } from 'react-router-dom'
import Viewer from './Viewer'
import PropTypes from 'prop-types'
import { withApi } from '../api'
import { Help } from '../help'
import MetainfoSearch from './MetainfoSearch'
import { FormControl, withStyles, Select, Input, MenuItem, ListItemText, InputLabel } from '@material-ui/core'
import { compose } from 'recompose'
import { schema } from '../MetaInfoRepository'
export const help = `
The nomad *metainfo* defines all quantities used to represent archive data in
nomad. You could say it is the archive *schema*. You can browse this schema and
all its definitions here.
The nomad metainfo knows three different *kinds* of definitions:
- **sections**: A section are nested groups of quantities that allow a hierarchical data structure
- **values**: Actual quantities that contain data
- **references**: References that allow to connect related sections.
All definitions have a name that you can search for. Furthermore, all definitions
are organized in packages. There is a *common* package with definitions that are
used by all codes and there are packages for each code with code specific definitions.
You can select the package to browse below.
Depending on the selected package, there are quiet a large number of definitions.
You can use the *definition* field to search based on definition names.
All definitions are represented as *cards* below. Click on the various card items
to expand sub-sections, open values or references, hide and show compartments, or
collapse cards again. The highlighted *main* card cannot be collapsed. The
shapes in the background represent section containment (grey) and