UNPKG

webgme-dss

Version:

Design Studio for Dynamic Systems with Modelica as backend

407 lines (351 loc) 16.2 kB
import React, {Component} from 'react'; import PropTypes from 'prop-types'; import {connect} from 'react-redux'; import update from 'immutability-helper'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import ErrorIcon from '@material-ui/icons/Error'; import CheckIcon from '@material-ui/icons/Check'; import ExpansionPanel from '@material-ui/core/ExpansionPanel'; import ExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails'; import ExpansionPanelSummary from '@material-ui/core/ExpansionPanelSummary'; import Typography from '@material-ui/core/Typography'; import CircularProgress from '@material-ui/core/CircularProgress'; import LinearProgress from '@material-ui/core/LinearProgress'; import IconButton from '@material-ui/core/IconButton'; import Tooltip from '@material-ui/core/Tooltip'; import Edit from '@material-ui/icons/Edit'; import CloudDownload from '@material-ui/icons/CloudDownload'; import Delete from '@material-ui/icons/Delete'; import Description from '@material-ui/icons/Description'; import ChromeReaderMode from '@material-ui/icons/ChromeReaderMode'; import Territory from 'webgme-react-components/src/components/Territory'; import ConfirmDialog from 'webgme-react-components/src/components/ConfirmDialog'; import {downloadBlobArtifact} from '../../gme/utils/saveUrlToDisk'; import PlotVariableSelector from './PlotVariableSelector'; import RenameInput from './RenameInput'; import ConsoleDialog from '../../Dialogs/ConsoleDialog'; import getMetaNodeByName from '../../gme/utils/getMetaNodeByName'; import {setPlotNode, setSimResData, setResultNode} from '../../actions'; const mapStateToProps = state => ({ plotNode: state.plotData.nodeId, resultNode: state.resultNode, simRes: state.plotData.simRes, }); const mapDispatchToProps = dispatch => ({ setPlotNode: (nodeId) => { dispatch(setPlotNode(nodeId)); }, setSimResData: (simRes) => { dispatch(setSimResData(simRes)); }, setResultNode: (resultNode) => { dispatch(setResultNode(resultNode)); }, }); class ResultList extends Component { static propTypes = { gmeClient: PropTypes.object.isRequired, minimized: PropTypes.bool.isRequired, setPlotNode: PropTypes.func.isRequired, setSimResData: PropTypes.func.isRequired, setResultNode: PropTypes.func.isRequired, resultNode: PropTypes.string, // plotNode: PropTypes.string, simRes: PropTypes.object, }; static defaultProps = { // plotNode: null, resultNode: null, simRes: null, }; constructor(props) { super(props); const {gmeClient} = this.props; const simResNode = getMetaNodeByName(gmeClient, 'SimulationResults'); if (simResNode) { this.state.containerId = simResNode.getId(); this.state.territory = { [this.state.containerId]: {children: 1}, }; } else { console.error(new Error('Could not find SimulationResults in meta...')); } } state = { containerId: null, expandedResId: null, territory: null, results: {}, editResultName: false, showConfirmDelete: false, showConsoleDialog: false, }; onDeleteConfirmed = (doDelete) => { const {gmeClient} = this.props; const {expandedResId} = this.state; if (doDelete && expandedResId) { gmeClient.deleteNode(expandedResId, `Result was removed ${expandedResId}`); } this.setState({showConfirmDelete: false}); }; handleEvents = (hash, loads, updates, unloads) => { const {gmeClient, resultNode} = this.props; const {containerId} = this.state; const updateDesc = {}; const attrNames = ['name', 'simRes', 'simPackage', 'csvFile', 'timeStamp']; loads.forEach((nodeId) => { if (nodeId !== containerId) { const nodeObj = gmeClient.getNode(nodeId); const modelId = nodeObj.getChildrenIds()[0]; // FIXME: This is assuming one and only one model const isRunning = !nodeObj.getAttribute('stdout'); updateDesc[nodeId] = { $set: { isRunning, modelId, }, }; attrNames.forEach((attrName) => { updateDesc[nodeId].$set[attrName] = nodeObj.getAttribute(attrName); }); if (nodeId === resultNode && isRunning === false) { // FIXME setTimeout(() => this.switchPlotNode(nodeId)); } } }); updates.forEach((nodeId) => { if (nodeId !== containerId) { const nodeObj = gmeClient.getNode(nodeId); const modelId = nodeObj.getChildrenIds()[0]; // FIXME: This is assuming one and only one model const isRunning = !nodeObj.getAttribute('stdout'); updateDesc[nodeId] = { isRunning: {$set: isRunning}, modelId: {$set: modelId}, }; attrNames.forEach((attrName) => { updateDesc[nodeId][attrName] = {$set: nodeObj.getAttribute(attrName)}; }); if (nodeId === resultNode && isRunning === false) { // FIXME setTimeout(() => this.switchPlotNode(nodeId)); } } }); updateDesc.$unset = unloads.filter(nodeId => nodeId !== containerId); if (unloads.includes(resultNode)) { this.props.setResultNode(null); this.switchPlotNode(null); } this.setState({ results: update(this.state.results, updateDesc), }); }; handleExpand = resId => (event, expanded) => { // extract attribute simRes and add it to the state if (expanded) { this.props.setResultNode(resId); this.switchPlotNode(resId); this.setState({expandedResId: resId}); } else { this.setState({expandedResId: null}); } }; updateResultTitle = (resId, canceled, newName) => { const {gmeClient} = this.props; const {results} = this.state; if (results[resId] && canceled === false && results[resId].name !== newName) { gmeClient.setAttribute(resId, 'name', newName); } this.setState({editResultName: false}); }; downloadArtifact = (entirePackage) => { const {results, expandedResId} = this.state; if (expandedResId && results[expandedResId]) { if (entirePackage && results[expandedResId].simPackage) { downloadBlobArtifact(results[expandedResId].simPackage); } if (!entirePackage && results[expandedResId].csvFile) { downloadBlobArtifact(results[expandedResId].csvFile); } } }; switchPlotNode = (resId) => { const {results} = this.state; if (resId === null) { this.props.setPlotNode(null); this.props.setSimResData(null); } else { this.props.setPlotNode(results[resId].modelId); this.props.setSimResData(results[resId].simRes ? JSON.parse(results[resId].simRes) : null); } }; render() { const { minimized, gmeClient, simRes, } = this.props; const { territory, results, expandedResId, editResultName, showConfirmDelete, showConsoleDialog, } = this.state; const resultIds = Object.keys(results).sort((a, b) => { const tA = results[a].timeStamp || 0; const tB = results[b].timeStamp || 0; if (tA > tB) { return -1; } else if (tB > tA) { return 1; } return 0; }); return ( <div style={{display: minimized ? 'none' : undefined}}> <Territory gmeClient={gmeClient} territory={territory} onUpdate={this.handleEvents} onlyActualEvents reuseTerritory={false} /> {resultIds.map((resId) => { const resInfo = results[resId]; const hasResults = simRes !== null; const isExpanded = resId === expandedResId; const failed = resInfo.isRunning === false && !resInfo.simRes; const actionButtons = [ { id: 'modeEdit', toolTip: 'Rename result', iconClass: <Edit style={{color: 'rgba(0, 0, 0, 0.54)', fontSize: 18}}/>, onClick: () => { this.setState({editResultName: true}); }, }, { id: 'delete', toolTip: 'Delete result', iconClass: <Delete style={{color: 'rgba(0, 0, 0, 0.54)', fontSize: 18}}/>, onClick: () => { this.setState({showConfirmDelete: true}); }, }, { id: 'viewConsole', toolTip: 'View console output', iconClass: <ChromeReaderMode style={{color: 'rgba(0, 0, 0, 0.54)', fontSize: 18}}/>, onClick: () => { this.setState({showConsoleDialog: true}); }, }, ]; let statusIcon; if (resInfo.isRunning) { statusIcon = <CircularProgress size={18}/>; } else if (failed) { statusIcon = <ErrorIcon style={{color: 'pink', width: 18, height: 18}}/>; } else { statusIcon = <CheckIcon style={{color: 'lightgreen', width: 18, height: 18}}/>; actionButtons.push({ id: 'downloadCSV', toolTip: 'Download CSV result file', iconClass: <Description style={{color: 'rgba(0, 0, 0, 0.54)', fontSize: 18}}/>, onClick: () => { this.downloadArtifact(false); }, }); actionButtons.push({ id: 'downloadPackage', toolTip: 'Download simulation package', iconClass: <CloudDownload style={{color: 'rgba(0, 0, 0, 0.54)', fontSize: 18}}/>, onClick: () => { this.downloadArtifact(true); }, }); } return ( <ExpansionPanel key={resId} expanded={isExpanded} onChange={this.handleExpand(resId)} > <ExpansionPanelSummary expandIcon={<ExpandMoreIcon/>}> <div style={{marginRight: 6, marginLeft: -18}}> {statusIcon} </div> {editResultName && expandedResId === resId && results[expandedResId] ? ( <RenameInput initialValue={results[expandedResId].name} onDone={(canceled, newValue) => { this.updateResultTitle(resId, canceled, newValue); }} />) : ( <Typography type="subheading" style={{ textOverflow: 'ellipsis', maxWidth: 160, overflow: 'hidden', }} >{resInfo.name} </Typography>) } </ExpansionPanelSummary> <ExpansionPanelDetails style={{display: 'block', padding: 0, paddingBottom: 10}}> <span> {actionButtons.map(desc => ( <Tooltip key={desc.id} id={desc.id} title={desc.toolTip} placement="top" > <IconButton key={desc.id} onClick={desc.onClick} > {desc.iconClass} </IconButton> </Tooltip> ))} </span> {(() => { if (hasResults) { return <PlotVariableSelector gmeClient={gmeClient} nodeId={resId}/>; } else if (failed) { return null; } return ( <div> <LinearProgress/> <br/> <LinearProgress color="secondary"/> </div>); })()} </ExpansionPanelDetails> </ExpansionPanel>); })} {showConfirmDelete ? <ConfirmDialog title="Delete Result" message="Do you want to delete the result? If the simulation is running it will be aborted.." onClose={this.onDeleteConfirmed} /> : null } {showConsoleDialog && expandedResId ? <ConsoleDialog gmeClient={gmeClient} attributeName="stdout" title="Simulation ouput" nodeId={expandedResId} onClose={() => this.setState({showConsoleDialog: false})} /> : null } </div>); } } export default connect(mapStateToProps, mapDispatchToProps)(ResultList);