Commit 2c490025 authored by Markus Scheidgen's avatar Markus Scheidgen
Browse files

Refactored some of the gui views. Consolidated repo, archive, log into on...

Refactored some of the gui views. Consolidated repo, archive, log into on dialog. Removed individual calc pages for repo, archive.
parent c9c9348c
Pipeline #42536 passed with stages
in 20 minutes
......@@ -19,8 +19,10 @@
"react-router-dom": "^4.3.1",
"react-router-hash-link": "^1.2.0",
"react-scripts": "1.1.4",
"react-swipeable-views": "^0.13.0",
"recompose": "^0.28.2",
"swagger-client": "^3.8.22",
"three.js": "^0.77.1",
"url-parse": "^1.4.3"
},
"scripts": {
......
......@@ -53,7 +53,6 @@ class Upload {
// never seen in the GUI, needs a GUI id
this.gui_upload_id = gui_upload_id_counter++
upload_to_gui_ids[json.upload_id] = this.gui_upload_id
console.log('new gui ui')
}
} else {
// new instance, not from the API
......@@ -209,7 +208,6 @@ async function getMetaInfo() {
} else {
const loadMetaInfo = async(path) => {
const client = await swaggerPromise
console.log(path)
return client.apis.archive.get_metainfo({metainfo_path: path})
.catch(handleApiError)
.then(response => response.body)
......
......@@ -4,8 +4,6 @@ import { genTheme, appBase } from '../config'
import Navigation from './Navigation'
import { BrowserRouter, Switch, Route } from 'react-router-dom'
import Uploads from './Uploads'
import ArchiveCalc from './ArchiveCalc'
import RepoCalc from './RepoCalc'
import Repo from './Repo'
import Documentation from './Documentation'
import Development from './Development'
......@@ -19,10 +17,10 @@ function App() {
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/repo" component={Repo} />
<Route path="/repo/:uploadId/:calcId" component={RepoCalc} />
{/* <Route path="/repo/:uploadId/:calcId" component={RepoCalc} /> */}
<Route path="/upload" component={Uploads} />
<Route exact path="/archive" render={() => <div>Archive</div>} />
<Route path="/archive/:uploadId/:calcId" component={ArchiveCalc} />
{/* <Route path="/archive/:uploadId/:calcId" component={ArchiveCalc} /> */}
<Route path="/enc" render={() => <div>{'In the future, you\'ll see charts\'n\'stuff for your calculations and materials.'}</div>} />
<Route path="/analytics" render={() => <div>{'In the future, you\'ll see analytics notebooks here.'}</div>} />
<Route path="/profile" render={() => <div>Profile</div>} />
......
import React from 'react'
import PropTypes from 'prop-types'
import { withStyles, Paper, LinearProgress, Typography, Popover } from '@material-ui/core'
import ReactJson from 'react-json-view'
import api from '../api'
import Markdown from './Markdown'
import { compose } from 'recompose'
import { withErrors } from './errors'
import CalcProcLogPopper from './CalcProcLogPopper'
class ArchiveCalc extends React.Component {
static propTypes = {
classes: PropTypes.object.isRequired,
raiseError: PropTypes.func.isRequired,
match: PropTypes.object.isRequired
}
static styles = theme => ({
root: {},
calcData: {
padding: theme.spacing.unit
},
logs: {
marginTop: theme.spacing.unit * 2,
padding: theme.spacing.unit
},
metaInfo: {
height: 120,
padding: theme.spacing.unit * 2,
overflowY: 'scroll'
},
metaInfoInstructions: {
height: 100,
paddingTop: 30,
textAlign: 'center',
color: 'grey'
},
logLink: {
fontSize: '1rem',
lineHeight: '2',
marginBlockStart: '1rem',
marginBlockEnd: '1rem',
'& a': {
color: theme.palette.secondary.main,
textDecoration: 'none',
'&:hover': {
textDecoration: 'underline'
}
}
}
});
static metainfo = null
state = {
data: null,
logs: null,
metaInfo: null,
showMetaInfo: false,
showLogs: false
}
constructor(props) {
super(props)
this.logPopperAnchor = React.createRef()
}
componentDidMount() {
const {uploadId, calcId} = this.props.match.params
api.archive(uploadId, calcId).then(data => {
this.setState({data: data})
}).catch(error => {
this.setState({data: null})
this.props.raiseError(error)
})
api.getMetaInfo().then(metaInfo => {
this.setState({metaInfo: metaInfo})
}).catch(error => {
this.props.raiseError(error)
})
}
handleShowMetaInfo(selection, more) {
if (selection.name === '_name') {
this.setState({showMetaInfo: selection.value})
} else {
this.setState({showMetaInfo: selection.name})
}
}
render() {
const { classes } = this.props
const { data, showMetaInfo, metaInfo } = this.state
const metaInfoData = metaInfo ? metaInfo[showMetaInfo] : null
const { uploadId, calcId } = this.props.match.params
return (
<div className={classes.root} ref={this.logPopperAnchor}>
<Markdown>{`
## The Archive – Code Independent Data
All values in the archive data have a specific type and documentation
associated with this type. This information is called the *meta-info*.
You can learn more about the different *sections* and
*quantities* by visiting the [meta-info](/metainfo) browser.
The tree below shows all calculation data in nomad's *hierachical* and
*code independent* archive format. Click on values to
see a *meta-info* description.
`}</Markdown>
<Typography className={classes.logLink}>
The processing logs are available <a href="#logs" onClick={() => this.setState({showLogs: true})}>here</a>.
</Typography>
<Paper className={classes.calcData}>
{
data
? <ReactJson
src={this.state.data}
enableClipboard={false}
collapsed={4}
displayObjectSize={false}
onSelect={this.handleShowMetaInfo.bind(this)}/>
: <LinearProgress variant="query" />
}
</Paper>
<CalcProcLogPopper
open={this.state.showLogs}
uploadId={uploadId}
calcId={calcId}
onClose={() => this.setState({showLogs: false})}
anchorEl={this.logPopperAnchor.current}
raiseError={this.props.raiseError}
/>
<Popover
open={(showMetaInfo && metaInfo && metaInfoData) ? true : false}
anchorEl={this.logPopperAnchor.current}
onClose={() => this.setState({showMetaInfo: null})}
anchorOrigin={{
vertical: 'center',
horizontal: 'center',
}}
transformOrigin={{
vertical: 'center',
horizontal: 'center',
}}
>
<Paper className={classes.metaInfo}>
{showMetaInfo && metaInfo
? metaInfoData
? <div>
<Typography variant="title">{metaInfoData.name}</Typography>
<Markdown>{metaInfoData.description}</Markdown>
</div>
: <div className={classes.metaInfoInstructions}>
this value has no meta-info attached to it
</div>
: <div className={classes.metaInfoInstructions}>
click a value to show its meta-info
</div>
}
</Paper>
</Popover>
</div>
)
}
}
export default compose(withErrors, withStyles(ArchiveCalc.styles))(ArchiveCalc)
import React from 'react'
import PropTypes from 'prop-types'
import { withStyles, LinearProgress, Typography } from '@material-ui/core'
import { withStyles, LinearProgress } from '@material-ui/core'
import ReactJson from 'react-json-view'
import api from '../api'
import { compose } from 'recompose'
......@@ -16,9 +16,18 @@ class ArchiveCalcView extends React.Component {
}
static styles = theme => ({
root: {},
root: {
display: 'flex',
flexDirection: 'column',
height: '100%'
},
metaInfo: {
flex: '0 0 auto',
overflowY: 'auto'
},
data: {
overflow: 'scroll'
flex: '1 1',
overflowY: 'auto'
}
});
......@@ -62,14 +71,6 @@ class ArchiveCalcView extends React.Component {
return (
<div className={classes.root}>
<div>{
showMetaInfo && metaInfo
? metaInfoData
? <Markdown>{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 className={classes.data}>{
data
? <ReactJson
......@@ -80,6 +81,14 @@ class ArchiveCalcView extends React.Component {
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 } from '@material-ui/core'
import ReactJson from 'react-json-view'
import api from '../api'
import { compose } from 'recompose'
import { withErrors } from './errors'
......
import React from 'react'
import PropTypes from 'prop-types'
import { withStyles, Dialog, DialogContent, DialogTitle, DialogActions, Button, IconButton, Typography } from '@material-ui/core'
import { withStyles, Dialog, DialogContent, DialogActions, Button, DialogTitle, Tab, Tabs,
Typography, FormGroup, FormControlLabel, Checkbox, Divider, FormLabel, IconButton,
LinearProgress } from '@material-ui/core'
import api from '../api'
import SwipeableViews from 'react-swipeable-views'
import ArchiveCalcView from './ArchiveCalcView'
import DownloadIcon from '@material-ui/icons/CloudDownload'
import ArchiveLogView from './ArchiveLogView'
function CalcQuantity(props) {
const {children, label, typography} = props
return (
<div style={{margin: '0px 24px 8px 0'}}>
<Typography variant="caption">{label}</Typography>
<Typography variant={typography || 'body1'}>{children || 'loading...'}</Typography>
</div>
)
}
CalcQuantity.propTypes = {
classes: PropTypes.object,
children: PropTypes.node,
label: PropTypes.string,
typography: PropTypes.string
}
class CalcDialog extends React.Component {
static styles = theme => ({
dialog: {
},
dialogTitle: {
padding: 0
},
dialogContent: {
paddingBottom: 0
},
tabContent: {
paddingTop: theme.spacing.unit * 3,
overflowY: 'auto',
height: '70vh',
zIndex: 1
},
formLabel: {
padding: theme.spacing.unit * 2
},
quantityRow: {
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap',
marginBottom: theme.spacing.unit
}
})
static propTypes = {
classes: PropTypes.object.isRequired,
raiseError: PropTypes.func.isRequired,
uploadId: PropTypes.string.isRequired,
calcId: PropTypes.string.isRequired,
disabled: PropTypes.bool,
icon: PropTypes.element,
component: PropTypes.func.isRequired,
title: PropTypes.string
onClose: PropTypes.func.isRequired
}
state = {
open: false
open: false,
calcData: null,
viewIndex: 0
}
componentDidMount() {
const {uploadId, calcId} = this.props
api.repo(uploadId, calcId).then(data => {
this.setState({calcData: data})
}).catch(error => {
this.setState({calcData: null})
this.props.raiseError(error)
})
}
data(quantity) {
const path = quantity.split('.')
let data = this.state.calcData
for (let i = 0; i < path.length; i++) {
if (data) {
data = data[path[i]]
}
}
return data
}
renderQuantity(quantity, label, defaultValue) {
const value = this.data(quantity) || defaultValue || ''
return (
<div key={quantity}>
<Typography variant="caption">{label}</Typography>
<Typography variant="body1">{value}</Typography>
</div>
)
}
render() {
const {title, icon, component, disabled, uploadId, calcId, raiseError} = this.props
const { classes, onClose, ...calcProps } = this.props
const { viewIndex } = this.state
const filePaths = this.data('section_repository_info.repository_filepaths') || []
const files = filePaths.map(filePath => filePath.substring(filePath.lastIndexOf('/') + 1))
return (
<span>
<IconButton color="primary" onClick={() => this.setState({open: true})} disabled={disabled}>{icon}</IconButton>
<Dialog open={this.state.open} onClose={() => this.setState({open: false})} fullWidth={true} maxWidth={'md'}>
<DialogTitle>
<Typography variant="h6" gutterBottom>{title || 'Calculation data'}</Typography>
<Typography variant="caption" gutterBottom>{`${uploadId}/${calcId}`}</Typography>
</DialogTitle>
<DialogContent>
{component({uploadId: uploadId, calcId: calcId, raiseError: raiseError})}
</DialogContent>
<DialogActions>
<Button onClick={() => this.setState({open: false})} color="primary" autoFocus>
Close
</Button>
</DialogActions>
</Dialog>
</span>
<Dialog className={classes.dialog} open={true} onClose={onClose} fullWidth={true} maxWidth={'md'} >
<DialogTitle disableTypography classes={{root: classes.dialogTitle}}>
{(!this.state.calcData) ? <LinearProgress /> : ''}
<Tabs
className={classes.tabs}
value={viewIndex}
onChange={(event, state) => this.setState({viewIndex: state})}
indicatorColor="primary"
textColor="primary"
variant="fullWidth"
>
<Tab label="Raw data" />
<Tab label="Archive" />
<Tab label="Logs" />
</Tabs>
</DialogTitle>
<DialogContent classes={{root: classes.dialogContent}}>
<SwipeableViews
// axis={theme.direction === 'rtl' ? 'x-reverse' : 'x'}
index={viewIndex}
onChangeIndex={() => null}
>
<div className={classes.tabContent}>
<div className={classes.quantityRow}>
<CalcQuantity label="chemical formula" typography="h4">
{this.data('section_repository_info.section_repository_parserdata.repository_chemical_formula')}
</CalcQuantity>
</div>
<div className={classes.quantityRow}>
<CalcQuantity label='dft code'>
{this.data('section_repository_info.section_repository_parserdata.repository_program_name')}
</CalcQuantity>
<CalcQuantity label='dft code version'>
{this.data('section_repository_info.section_repository_parserdata.repository_code_version')}
</CalcQuantity>
</div>
<div className={classes.quantityRow}>
<CalcQuantity label='basis set'>
{this.data('section_repository_info.section_repository_parserdata.repository_basis_set_type')}
</CalcQuantity>
<CalcQuantity label='xc functional'>
{this.data('section_repository_info.section_repository_parserdata.repository_xc_treatment')}
</CalcQuantity>
</div>
<div className={classes.quantityRow}>
<CalcQuantity label='system type'>
{this.data('section_repository_info.section_repository_parserdata.repository_system_type')}
</CalcQuantity>
<CalcQuantity label='crystal system'>
{this.data('section_repository_info.section_repository_parserdata.repository_crystal_system')}
</CalcQuantity>
<CalcQuantity label='spacegroup'>
{this.data('section_repository_info.section_repository_parserdata.repository_spacegroup_nr')}
</CalcQuantity>
</div>
<div className={classes.quantityRow}>
<CalcQuantity label='upload id'>
{this.data('section_calculation_info.upload_id')}
</CalcQuantity>
<CalcQuantity label='calculation id'>
{this.data('section_calculation_info.calc_id')}
</CalcQuantity>
<CalcQuantity label='mainfile'>
{this.data('section_calculation_info.main_file')}
</CalcQuantity>
<CalcQuantity label='calculation hash'>
{this.data('section_calculation_info.calc_hash')}
</CalcQuantity>
</div>
<Divider />
<FormGroup row>
<FormControlLabel label="select all" control={<Checkbox checked={false} value="select_all" />} style={{flexGrow: 1}}/>
<FormLabel className={classes.formLabel}>0/10 files selected</FormLabel>
<IconButton><DownloadIcon /></IconButton>
</FormGroup>
<Divider />
<FormGroup row>
{files.map((file, index) => (
<FormControlLabel key={index} label={file}
control={<Checkbox checked={false} onChange={() => true} value={file} />}
/>
))}
</FormGroup>
</div>
<div className={classes.tabContent}>
<ArchiveCalcView {...calcProps} />
</div>
<div className={classes.tabContent}>
<ArchiveLogView {...calcProps} />
</div>
</SwipeableViews>
</DialogContent>
<DialogActions>
<Button onClick={onClose} color="primary" autoFocus>
Close
</Button>
</DialogActions>
</Dialog>
)
}
}
......
import React from 'react'
import PropTypes from 'prop-types'
import { MuiThemeProvider, withStyles } from '@material-ui/core'
import RepoIcon from '@material-ui/icons/Cloud'
import ArchiveIcon from '@material-ui/icons/Storage'
import ArchiveLogIcon from '@material-ui/icons/BugReport'
import { repoTheme, archiveTheme } from '../config'
import CalcDialog from './CalcDialog'
import RepoCalcView from './RepoCalcView'
import ArchiveCalcView from './ArchiveCalcView'
import ArchiveLogView from './ArchiveLogView'
class CalcDialogButtons extends React.Component {
static propTypes = {
classes: PropTypes.object.isRequired,
uploadId: PropTypes.string,
calcId: PropTypes.string,
disabled: PropTypes.bool,
raiseError: PropTypes.func.isRequired
}
static styles = theme => ({
root: {
overflow: 'hidden',
whiteSpace: 'nowrap'
}
});
onClickOpen
render() {
const { classes, ...others } = this.props
return (
<div className={classes.root}>
<MuiThemeProvider theme={repoTheme}>
<CalcDialog icon={<RepoIcon/>} component={RepoCalcView} title={'Repository calculation data'} {...others} />
</MuiThemeProvider>
<MuiThemeProvider theme={archiveTheme}>
<CalcDialog icon={<ArchiveIcon/>} component={ArchiveCalcView} title={'Archive calculation data'} {...others} />
<CalcDialog icon={<ArchiveLogIcon/>} component={ArchiveLogView} title={'Calculation processing logs'} {...others} />
</MuiThemeProvider>
</div>
)
}
}
export default withStyles(CalcDialogButtons.styles)(CalcDialogButtons)
import React from 'react'
import PropTypes from 'prop-types'
import { MuiThemeProvider, IconButton, withStyles } from '@material-ui/core'
import RepoIcon from '@material-ui/icons/Cloud'
import ArchiveIcon from '@material-ui/icons/Storage'
import EncIcon from '@material-ui/icons/Assessment'
import { repoTheme, archiveTheme, encTheme } from '../config'
import Link from 'react-router-dom/Link'
class CalcLink extends React.Component {
static propTypes = {
classes: PropTypes.object.isRequired,
uploadId: PropTypes.string,