Commit 4baa45cc authored by Markus Scheidgen's avatar Markus Scheidgen
Browse files

Refactored loading, downloading, archive view.

parent 804614ba
......@@ -56,6 +56,7 @@ your browser.
## Change log
### v0.4.4
- improved GUI navigation
- support for multiple domains
- info API endpoint
......
import React from 'react'
import PropTypes from 'prop-types'
import { withStyles, LinearProgress, Fab } from '@material-ui/core'
import { withStyles, Fab, Card, CardContent } from '@material-ui/core'
import ReactJson from 'react-json-view'
import { compose } from 'recompose'
import { withErrors } from './errors'
......@@ -19,23 +19,22 @@ class ArchiveCalcView extends React.Component {
}
static styles = theme => ({
root: {
display: 'flex',
flexDirection: 'column',
height: '100%'
},
root: {},
metaInfo: {
flex: '0 0 auto',
overflowY: 'auto'
height: '20vh',
overflowY: 'auto',
marginTop: theme.spacing.unit * 3,
marginBottom: theme.spacing.unit * 3
},
data: {
flex: '1 1'
height: '60vh',
overflowY: 'auto'
},
downloadFab: {
position: 'absolute',
zIndex: 1,
top: theme.spacing.unit,
right: theme.spacing.unit * 3
right: 32,
bottom: 32,
position: 'fixed !important'
}
});
......@@ -79,6 +78,30 @@ class ArchiveCalcView extends React.Component {
return (
<div className={classes.root}>
<Card className={classes.metaInfo}>
<CardContent>{
showMetaInfo && metaInfo
? metaInfoData
? <Markdown>{`**${metaInfoData.name}**: ${metaInfoData.description}`}</Markdown>
: <Markdown>This value has **no** *meta-info* attached to it.</Markdown>
: <Markdown>Click a value to show its *meta-info*!</Markdown>
}</CardContent>
</Card>
<Card className={classes.data}>
<CardContent>
{
data
? <ReactJson
src={this.state.data}
enableClipboard={false}
collapsed={2}
displayObjectSize={false}
onSelect={this.handleShowMetaInfo.bind(this)} />
: ''
}
</CardContent>
</Card>
<Download
classes={{root: classes.downloadFab}} tooltip="download calculation archive"
component={Fab} className={classes.downloadFab} color="primary" size="medium"
......@@ -86,24 +109,6 @@ class ArchiveCalcView extends React.Component {
>
<DownloadIcon />
</Download>
<div className={classes.data}>{
data
? <ReactJson
src={this.state.data}
enableClipboard={false}
collapsed={4}
displayObjectSize={false}
onSelect={this.handleShowMetaInfo.bind(this)} />
: <LinearProgress variant="query" />
}</div>
<div className={classes.metaInfo}>{
showMetaInfo && metaInfo
? metaInfoData
? <Markdown>{`**${metaInfoData.name}**: ${metaInfoData.description}`}</Markdown>
: <Markdown>This value has **no** *meta-info* attached to it.</Markdown>
: <Markdown>Click a value to show its *meta-info*!</Markdown>
}
</div>
</div>
)
}
......
import React from 'react'
import PropTypes from 'prop-types'
import { withStyles, LinearProgress, Fab } from '@material-ui/core'
import { withStyles, Fab } from '@material-ui/core'
import { compose } from 'recompose'
import { withErrors } from './errors'
import { withApi } from './api'
......@@ -23,10 +23,10 @@ class ArchiveLogView extends React.Component {
}
},
downloadFab: {
position: 'absolute',
zIndex: 1,
top: theme.spacing.unit,
right: theme.spacing.unit * 3
right: 32,
bottom: 32,
position: 'fixed !important'
}
});
......@@ -53,6 +53,8 @@ class ArchiveLogView extends React.Component {
return (
<div className={classes.root}>
<pre>{data || 'empty log'}</pre>
<Download
classes={{root: classes.downloadFab}} tooltip="download logfile"
component={Fab} className={classes.downloadFab} color="primary" size="medium"
......@@ -60,11 +62,6 @@ class ArchiveLogView extends React.Component {
>
<DownloadIcon />
</Download>
{
data !== null
? <pre>{data || 'empty log'}</pre>
: <LinearProgress variant="query" />
}
</div>
)
}
......
import React from 'react'
import PropTypes from 'prop-types'
import { withStyles, Tab, Tabs } from '@material-ui/core'
import SwipeableViews from 'react-swipeable-views'
import ArchiveCalcView from './ArchiveCalcView'
import ArchiveLogView from './ArchiveLogView'
import RepoCalcView from './RepoCalcView'
......@@ -47,9 +46,15 @@ class Calc extends React.Component {
</Tabs>
<div className={classes.content}>
{viewIndex === 0 && <RepoCalcView {...calcProps} />}
{viewIndex === 1 && <ArchiveCalcView {...calcProps} />}
{viewIndex === 2 && <ArchiveLogView {...calcProps} />}
<div style={viewIndex !== 0 ? {display: 'none'} : {}} >
<RepoCalcView {...calcProps} />
</div>
<div style={viewIndex !== 1 ? {display: 'none'} : {}} >
<ArchiveCalcView {...calcProps} />
</div>
<div style={viewIndex !== 2 ? {display: 'none'} : {}} >
<ArchiveLogView {...calcProps} />
</div>
</div>
</div>
)
......
......@@ -42,6 +42,9 @@ var styles = theme => ({
fontSize: 14,
lineHeight: 1.6
},
'& p:first-child': {
marginTop: 0
},
'& h1': (0, extend)({}, theme.typography.h3, {
color: theme.palette.text.primary,
margin: '32px 0 16px'
......
......@@ -23,11 +23,12 @@ import ChevronLeftIcon from '@material-ui/icons/ChevronLeft'
import MenuIcon from '@material-ui/icons/Menu'
import { Link, withRouter } from 'react-router-dom'
import { compose } from 'recompose'
import { MuiThemeProvider, IconButton, Checkbox, FormLabel } from '@material-ui/core'
import { MuiThemeProvider, IconButton, Checkbox, FormLabel, LinearProgress } from '@material-ui/core'
import { genTheme, repoTheme, archiveTheme, encTheme, analyticsTheme } from '../config'
import classNames from 'classnames'
import { HelpContext } from './help'
import LoginLogout from './LoginLogout'
import { withApi } from './api'
const drawerWidth = 200
......@@ -59,7 +60,8 @@ class Navigation extends React.Component {
static propTypes = {
classes: PropTypes.object.isRequired,
children: PropTypes.any,
location: PropTypes.object.isRequired
location: PropTypes.object.isRequired,
loading: PropTypes.number.isRequired
}
static styles = theme => ({
......@@ -262,7 +264,7 @@ class Navigation extends React.Component {
}
render() {
const { classes, children, location: { pathname } } = this.props
const { classes, children, location: { pathname }, loading } = this.props
const drawer = (
<Drawer variant="permanent"
......@@ -324,6 +326,7 @@ class Navigation extends React.Component {
}</HelpContext.Consumer>
</div>
</Toolbar>
{loading ? <LinearProgress color="secondary" /> : ''}
</AppBar>
</MuiThemeProvider>
{drawer}
......@@ -339,4 +342,4 @@ class Navigation extends React.Component {
}
}
export default compose(withRouter, withStyles(Navigation.styles))(Navigation)
export default compose(withRouter, withApi(false), withStyles(Navigation.styles))(Navigation)
......@@ -7,14 +7,14 @@ import TableCell from '@material-ui/core/TableCell'
import TablePagination from '@material-ui/core/TablePagination'
import TableRow from '@material-ui/core/TableRow'
import Paper from '@material-ui/core/Paper'
import { TableHead, LinearProgress, FormControl, FormControlLabel, Checkbox, FormGroup,
import { TableHead, FormControl, FormControlLabel, Checkbox, FormGroup,
FormLabel, IconButton, MuiThemeProvider, Typography, Tooltip, TableSortLabel, ExpansionPanelDetails, ExpansionPanelSummary, ExpansionPanel, Grid } from '@material-ui/core'
import { compose } from 'recompose'
import { withErrors } from './errors'
import AnalyticsIcon from '@material-ui/icons/Settings'
import { analyticsTheme } from '../config'
import Link from 'react-router-dom/Link'
import { withApi } from './api'
import { withApi, DisableOnLoading } from './api'
import PeriodicTable from './PeriodicTable'
import ExpandMoreIcon from '@material-ui/icons/ExpandMore'
import QuantityHistogram from './QuantityHistogram'
......@@ -150,21 +150,20 @@ class Repo extends React.Component {
page: 1,
rowsPerPage: 10,
total: 0,
loading: true,
owner: 'all',
sortedBy: 'formula',
sortOrder: 'asc',
searchValues: {},
aggregations: {},
metrics: {},
metric: 'code_runs'
metric: 'code_runs',
useMetric: 'code_runs'
}
update(changes) {
changes = changes || {}
const { page, rowsPerPage, owner, sortedBy, sortOrder, searchValues, metric } = {...this.state, ...changes}
delete changes.metric
this.setState({loading: true, ...changes})
this.setState({...changes})
// code_runs is returned anyways
const metrics_to_retrieve = metric === 'code_runs' ? [] : [metric]
......@@ -188,12 +187,12 @@ class Repo extends React.Component {
rowsPerPage:
per_page,
total: total,
loading: false,
owner: owner,
metric: metric
metric: metric,
useMetric: metric
})
}).catch(errors => {
this.setState({data: [], total: 0, loading: false, owner: owner})
this.setState({data: [], total: 0, owner: owner})
this.props.raiseError(errors)
})
}
......@@ -269,174 +268,176 @@ class Repo extends React.Component {
render() {
const { classes, user } = this.props
const { data, rowsPerPage, page, total, loading, sortedBy, sortOrder, searchValues, aggregations, metrics, metric } = this.state
const { data, rowsPerPage, page, total, sortedBy, sortOrder, searchValues, aggregations, metrics, useMetric } = this.state
const emptyRows = rowsPerPage - Math.min(rowsPerPage, total - (page - 1) * rowsPerPage)
const quantity = (key, title) => (<QuantityHistogram
classes={{root: classes.quantity}} title={title || key} width={300}
data={aggregations[key]} metric={metric}
data={aggregations[key]} metric={useMetric}
value={searchValues[key]}
onChanged={(selection) => this.handleQuantityChanged(key, selection)}/>)
const ownerLabel = {
all: 'All calculations',
user: 'Your calculations',
staging: 'Only calculations from your staging area'
all: 'All entries',
public: 'Only public entries',
user: 'Only your entries',
staging: 'Only entries from your staging area'
}
const metricsLabel = {
code_runs: 'Code runs',
code_runs: 'Entries',
total_energies: 'Total energy calculations',
geometries: 'Unique geometries',
datasets: 'Datasets',
unique_code_runs: 'Unique code runs'
unique_code_runs: 'Unique entries'
}
return (
<div className={classes.root}>
{ user
? <FormControl>
<FormLabel>Filter calculations and only show: </FormLabel>
<FormGroup row>
{['all', 'user', 'staging'].map(owner => (
<FormControlLabel key={owner}
control={
<Checkbox checked={this.state.owner === owner} onChange={() => this.handleOwnerChange(owner)} value="owner" />
}
label={ownerLabel[owner]}
/>
))}
</FormGroup>
</FormControl> : ''
}
<DisableOnLoading>
{ user
? <FormControl>
<FormLabel>Filter entries and show: </FormLabel>
<FormGroup row>
{['all', 'public', 'user', 'staging'].map(owner => (
<FormControlLabel key={owner}
control={
<Checkbox checked={this.state.owner === owner} onChange={() => this.handleOwnerChange(owner)} value="owner" />
}
label={ownerLabel[owner]}
/>
))}
</FormGroup>
</FormControl> : ''
}
<div className={classes.searchBarContainer}>
<SearchBar
fullWidth fullWidthInput={false} label="search" placeholder="enter atoms or other quantities"
aggregations={aggregations} values={searchValues} metric={metric}
onChanged={values => this.handleSearchChanged(values)}
/>
</div>
<div className={classes.searchBarContainer}>
<SearchBar
fullWidth fullWidthInput={false} label="search" placeholder="enter atoms or other quantities"
aggregations={aggregations} values={searchValues} metric={useMetric}
onChanged={values => this.handleSearchChanged(values)}
/>
</div>
<ExpansionPanel>
<ExpansionPanelSummary expandIcon={<ExpandMoreIcon/>} className={classes.searchSummary}>
<Typography variant="h6" style={{textAlign: 'center', width: '100%', fontWeight: 'normal'}}>
Found <b>{metrics.code_runs}</b>{metric === 'unique_code_runs' ? (<span>(<b>{metrics.unique_code_runs}</b> unique)</span>) : ''} code runs
{metric === 'geometries' ? (<span> that simulate <b>{metrics.geometries}</b> unique geometries</span>) : ''}
{metric === 'total_energies' ? (<span> with <b>{metrics.total_energies}</b> total energy calculations</span>) : ''}
{metric === 'datasets' ? (<span> curated in <b>{metrics.datasets}</b> datasets</span>) : ''}.
</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails className={classes.searchDetails}>
<div className={classes.statistics}>
<FormControl>
<FormLabel>Metric used in statistics: </FormLabel>
<FormGroup row>
{['code_runs', 'unique_code_runs', 'total_energies', 'geometries', 'datasets'].map(metric => (
<FormControlLabel key={metric}
control={
<Checkbox checked={this.state.metric === metric} onChange={() => this.handleMetricChange(metric)} value={metric} />
}
label={metricsLabel[metric]}
/>
))}
</FormGroup>
</FormControl>
</div>
<ExpansionPanel>
<ExpansionPanelSummary expandIcon={<ExpandMoreIcon/>} className={classes.searchSummary}>
<Typography variant="h6" style={{textAlign: 'center', width: '100%', fontWeight: 'normal'}}>
Found <b>{metrics.code_runs}</b>{useMetric === 'unique_code_runs' ? (<span>(<b>{metrics.unique_code_runs}</b> unique)</span>) : ''} code runs
{useMetric === 'geometries' ? (<span> that simulate <b>{metrics.geometries}</b> unique geometries</span>) : ''}
{useMetric === 'total_energies' ? (<span> with <b>{metrics.total_energies}</b> total energy calculations</span>) : ''}
{useMetric === 'datasets' ? (<span> curated in <b>{metrics.datasets}</b> datasets</span>) : ''}.
</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails className={classes.searchDetails}>
<div className={classes.statistics}>
<FormControl>
<FormLabel>Metric used in statistics: </FormLabel>
<FormGroup row>
{['code_runs', 'unique_code_runs', 'total_energies', 'geometries', 'datasets'].map(metric => (
<FormControlLabel key={metric}
control={
<Checkbox checked={this.state.metric === metric} onChange={() => this.handleMetricChange(metric)} value={metric} />
}
label={metricsLabel[metric]}
/>
))}
</FormGroup>
</FormControl>
</div>
<PeriodicTable
aggregations={aggregations.atoms} metric={metric}
values={searchValues.atoms || []}
onChanged={(selection) => this.handleAtomsChanged(selection)}
/>
<PeriodicTable
aggregations={aggregations.atoms} metric={useMetric}
values={searchValues.atoms || []}
onChanged={(selection) => this.handleAtomsChanged(selection)}
/>
<Grid container spacing={24} className={classes.quantityGrid}>
<Grid container spacing={24} className={classes.quantityGrid}>
<Grid item xs={4}>
{quantity('system', 'System')}
{quantity('crystal_system', 'Crystal system')}
</Grid>
<Grid item xs={4}>
{quantity('basis_set', 'Basis set')}
{quantity('xc_functional', 'XC functionals')}
</Grid>
<Grid item xs={4}>
{quantity('code_name', 'Code')}
<Grid item xs={4}>
{quantity('system', 'System')}
{quantity('crystal_system', 'Crystal system')}
</Grid>
<Grid item xs={4}>
{quantity('basis_set', 'Basis set')}
{quantity('xc_functional', 'XC functionals')}
</Grid>
<Grid item xs={4}>
{quantity('code_name', 'Code')}
</Grid>
</Grid>
</Grid>
</ExpansionPanelDetails>
</ExpansionPanel>
</ExpansionPanelDetails>
</ExpansionPanel>
<FormGroup className={classes.selectFormGroup} row>
<FormLabel classes={{root: classes.selectLabel}} style={{flexGrow: 1}}>
<FormGroup className={classes.selectFormGroup} row>
<FormLabel classes={{root: classes.selectLabel}} style={{flexGrow: 1}}>
</FormLabel>
<FormLabel classes={{root: classes.selectLabel}}>
</FormLabel>
<FormLabel classes={{root: classes.selectLabel}}>
Analyse {total} code runs in an analytics notebook
</FormLabel>
<MuiThemeProvider theme={analyticsTheme}>
<IconButton color="primary" component={Link} to={`/analytics`}>
<AnalyticsIcon />
</IconButton>
</MuiThemeProvider>
</FormGroup>
</FormLabel>
<MuiThemeProvider theme={analyticsTheme}>
<IconButton color="primary" component={Link} to={`/analytics`}>
<AnalyticsIcon />
</IconButton>
</MuiThemeProvider>
</FormGroup>
<Paper className={classes.data}>
{loading ? <LinearProgress variant="query" /> : <div className={classes.progressPlaceholder} />}
<Table>
<TableHead>
<TableRow>
{Object.keys(this.rowConfig).map(key => (
<TableCell padding="dense" key={key}>
<Tooltip
title="Sort"
placement={'bottom-start'}
enterDelay={300}
>
<TableSortLabel
active={sortedBy === key}
direction={sortOrder}
onClick={() => this.handleSort(key)}
<Paper className={classes.data}>
<Table>
<TableHead>
<TableRow>
{Object.keys(this.rowConfig).map(key => (
<TableCell padding="dense" key={key}>
<Tooltip
title="Sort"
placement={'bottom-start'}
enterDelay={300}
>
{this.rowConfig[key].label}
</TableSortLabel>
</Tooltip>
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{data.map((calc, index) => (
<TableRow hover tabIndex={-1} key={index} className={classes.clickableRow}>
{Object.keys(this.rowConfig).map((key, rowIndex) => (
<TableCell padding="dense" key={rowIndex} onClick={() => this.handleClickCalc(calc)} >
{this.renderCell(key, this.rowConfig[key], calc)}
<TableSortLabel
active={sortedBy === key}
direction={sortOrder}
onClick={() => this.handleSort(key)}
>
{this.rowConfig[key].label}
</TableSortLabel>
</Tooltip>
</TableCell>
))}
</TableRow>
))}
{emptyRows > 0 && (
<TableRow style={{ height: 57 * emptyRows }}>
<TableCell colSpan={6} />
</TableHead>
<TableBody>
{data.map((calc, index) => (
<TableRow hover tabIndex={-1} key={index} className={classes.clickableRow}>
{Object.keys(this.rowConfig).map((key, rowIndex) => (
<TableCell padding="dense" key={rowIndex} onClick={() => this.handleClickCalc(calc)} >
{this.renderCell(key, this.rowConfig[key], calc)}
</TableCell>
))}
</TableRow>
))}
{emptyRows > 0 && (
<TableRow style={{ height: 57 * emptyRows }}>
<TableCell colSpan={6} />
</TableRow>
)}
<TableRow>
<TablePagination
count={total}
rowsPerPage={rowsPerPage}
page={page - 1}
backIconButtonProps={{
'aria-label': 'Previous Page'
}}
nextIconButtonProps={{
'aria-label': 'Next Page'
}}
onChangePage={this.handleChangePage}
onChangeRowsPerPage={this.handleChangeRowsPerPage}
/>
</TableRow>
)}
<TableRow>
<TablePagination
count={total}
rowsPerPage={rowsPerPage}
page={page - 1}
backIconButtonProps={{
'aria-label': 'Previous Page'
}}
nextIconButtonProps={{
'aria-label': 'Next Page'