mirror of
https://git.rwth-aachen.de/acs/public/villas/web/
synced 2025-03-09 00:00:01 +01:00
Merge branch '168-add-simulation-model-edit-view' into 'develop'
Resolve "Add simulation model edit view" Closes #168 See merge request acs/public/villas/VILLASweb!34
This commit is contained in:
commit
b6fa751869
16 changed files with 799 additions and 624 deletions
31
package-lock.json
generated
31
package-lock.json
generated
|
@ -8322,11 +8322,29 @@
|
|||
}
|
||||
},
|
||||
"prop-types": {
|
||||
"version": "https://registry.npmjs.org/prop-types/-/prop-types-15.5.10.tgz",
|
||||
"integrity": "sha512-vCFzoUFaZkVNeFkhK1KbSq4cn97GDrpfBt9K2qLkGnPAEFhEv3M61Lk5t+B7c0QfMLWo0fPkowk/4SuXerh26Q==",
|
||||
"version": "15.6.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.1.tgz",
|
||||
"integrity": "sha512-4ec7bY1Y66LymSUOH/zARVYObB23AT2h8cf6e/O6ZALB/N0sqZFEx7rq6EYPX2MkOdKORuooI/H5k9TlR4q7kQ==",
|
||||
"requires": {
|
||||
"fbjs": "^0.8.9",
|
||||
"loose-envify": "^1.3.1"
|
||||
"fbjs": "^0.8.16",
|
||||
"loose-envify": "^1.3.1",
|
||||
"object-assign": "^4.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"fbjs": {
|
||||
"version": "0.8.16",
|
||||
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz",
|
||||
"integrity": "sha1-XmdDL1UNxBtXK/VYR7ispk5TN9s=",
|
||||
"requires": {
|
||||
"core-js": "^1.0.0",
|
||||
"isomorphic-fetch": "^2.1.1",
|
||||
"loose-envify": "^1.0.0",
|
||||
"object-assign": "^4.1.0",
|
||||
"promise": "^7.1.1",
|
||||
"setimmediate": "^1.0.5",
|
||||
"ua-parser-js": "^0.7.9"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"proxy-addr": {
|
||||
|
@ -12309,6 +12327,11 @@
|
|||
"spdx-expression-parse": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"validator": {
|
||||
"version": "10.2.0",
|
||||
"resolved": "https://registry.npmjs.org/validator/-/validator-10.2.0.tgz",
|
||||
"integrity": "sha512-gz/uknWtNfZTj1BLUzYHDxOoiQ7A4wZ6xPuuE6RpxswR4cNyT4I5kN9jmU0AQr7IBEap9vfYChI2TpssTN6Itg=="
|
||||
},
|
||||
"vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
"gaugeJS": "^1.3.2",
|
||||
"immutable": "^3.8.1",
|
||||
"lodash": "^4.17.5",
|
||||
"prop-types": "^15.6.1",
|
||||
"rc-slider": "^8.3.0",
|
||||
"react": "^15.4.2",
|
||||
"react-bootstrap": "^0.31.1",
|
||||
|
@ -34,7 +35,8 @@
|
|||
"react-scripts": "1.0.10",
|
||||
"react-sortable-tree": "^0.1.19",
|
||||
"react-svg-pan-zoom": "^2.14.1",
|
||||
"superagent": "^3.5.0"
|
||||
"superagent": "^3.5.0",
|
||||
"validator": "^10.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "^4.1.0"
|
||||
|
|
|
@ -31,7 +31,7 @@ describe('edit widget control creator', () => {
|
|||
{ args: { widgetType: 'Image' }, result: { controlNumber: 2, controlTypes: [EditImageWidgetControl, EditWidgetAspectControl] } },
|
||||
{ args: { widgetType: 'Gauge' }, result: { controlNumber: 6, controlTypes: [EditWidgetTextControl, EditWidgetSimulatorControl, EditWidgetSignalControl, EditWidgetCheckboxControl, EditWidgetColorZonesControl, EditWidgetMinMaxControl] } },
|
||||
{ args: { widgetType: 'PlotTable' }, result: { controlNumber: 5, controlTypes: [EditWidgetSimulatorControl, EditWidgetSignalsControl, EditWidgetTextControl, EditWidgetTimeControl, EditWidgetMinMaxControl] } },
|
||||
{ args: { widgetType: 'Slider' }, result: { controlNumber: 3, controlTypes: [EditWidgetOrientation, EditWidgetSimulatorControl, EditWidgetSignalControl] } },
|
||||
{ args: { widgetType: 'Slider' }, result: { controlNumber: 4, controlTypes: [EditWidgetTextControl, EditWidgetOrientation, EditWidgetSimulatorControl, EditWidgetSignalControl] } },
|
||||
{ args: { widgetType: 'Button' }, result: { controlNumber: 4, controlTypes: [EditWidgetColorControl, EditWidgetSimulatorControl, EditWidgetSignalControl] } },
|
||||
{ args: { widgetType: 'Box' }, result: { controlNumber: 1, controlTypes: [EditWidgetColorControl] } },
|
||||
{ args: { widgetType: 'Label' }, result: { controlNumber: 3, controlTypes: [EditWidgetTextControl, EditWidgetTextSizeControl, EditWidgetColorControl] } },
|
||||
|
|
|
@ -1,186 +0,0 @@
|
|||
/**
|
||||
* File: edit-simulation-model.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 04.03.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { FormGroup, FormControl, ControlLabel, HelpBlock } from 'react-bootstrap';
|
||||
import _ from 'lodash';
|
||||
|
||||
import Table from '../table';
|
||||
import TableColumn from '../table-column';
|
||||
import Dialog from './dialog';
|
||||
|
||||
class EditSimulationModelDialog extends React.Component {
|
||||
valid = false;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
_id: '',
|
||||
name: '',
|
||||
simulator: '',
|
||||
simulation: '',
|
||||
outputLength: 1,
|
||||
inputLength: 1,
|
||||
outputMapping: [{ name: 'Signal', type: 'Type' }],
|
||||
inputMapping: [{ name: 'Signal', type: 'Type' }]
|
||||
}
|
||||
}
|
||||
|
||||
onClose(canceled) {
|
||||
if (canceled === false) {
|
||||
if (this.valid) {
|
||||
this.props.onClose(this.state);
|
||||
}
|
||||
} else {
|
||||
this.props.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
handleChange(e) {
|
||||
let mapping = null;
|
||||
|
||||
if (e.target.id === 'outputLength') {
|
||||
mapping = this.state.outputMapping;
|
||||
} else if (e.target.id === 'inputLength') {
|
||||
mapping = this.state.inputMapping;
|
||||
}
|
||||
|
||||
if (mapping != null) {
|
||||
// change mapping size
|
||||
if (e.target.value > mapping.length) {
|
||||
// add missing signals
|
||||
while (mapping.length < e.target.value) {
|
||||
mapping.push({ name: 'Signal', type: 'Type' });
|
||||
}
|
||||
} else {
|
||||
// remove signals
|
||||
mapping.splice(e.target.value, mapping.length - e.target.value);
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({ [e.target.id]: e.target.value });
|
||||
}
|
||||
|
||||
handleMappingChange(key, event, row, column) {
|
||||
const mapping = this.state[key];
|
||||
|
||||
if (column === 1) {
|
||||
mapping[row].name = event.target.value;
|
||||
} else if (column === 2) {
|
||||
mapping[row].type = event.target.value;
|
||||
}
|
||||
|
||||
this.setState({ [key]: mapping });
|
||||
}
|
||||
|
||||
resetState() {
|
||||
this.setState({
|
||||
_id: this.props.data._id,
|
||||
simulation: this.props.data.simulation,
|
||||
name: this.props.data.name,
|
||||
simulator: this.props.data.simulator,
|
||||
outputLength: this.props.data.outputLength,
|
||||
inputLength: this.props.data.inputLength,
|
||||
outputMapping: this.props.data.outputMapping,
|
||||
inputMapping: this.props.data.inputMapping
|
||||
});
|
||||
}
|
||||
|
||||
validateForm(target) {
|
||||
// check all controls
|
||||
var name = true;
|
||||
let inputLength = true;
|
||||
let outputLength = true;
|
||||
|
||||
if (this.state.name === '') {
|
||||
name = false;
|
||||
}
|
||||
|
||||
// test if simulatorid is a number (in a string, not type of number)
|
||||
if (!/^\d+$/.test(this.state.outputLength)) {
|
||||
outputLength = false;
|
||||
}
|
||||
|
||||
if (!/^\d+$/.test(this.state.inputLength)) {
|
||||
inputLength = false;
|
||||
}
|
||||
|
||||
this.valid = name && inputLength && outputLength;
|
||||
|
||||
// return state to control
|
||||
if (target === 'name') return name ? "success" : "error";
|
||||
else if (target === 'outputLength') return outputLength ? "success" : "error";
|
||||
else if (target === 'inputLength') return inputLength ? "success" : "error";
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Dialog show={this.props.show} title="New Simulation Model" buttonTitle="Save" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
|
||||
<form>
|
||||
<FormGroup controlId="name" validationState={this.validateForm('name')}>
|
||||
<ControlLabel>Name</ControlLabel>
|
||||
<FormControl type="text" placeholder="Enter name" value={this.state.name} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
<FormGroup controlId="simulator" validationState={this.validateForm('simulator')}>
|
||||
<ControlLabel>Simulator</ControlLabel>
|
||||
<FormControl componentClass="select" placeholder="Select simulator" value={this.state.simulator} onChange={(e) => this.handleChange(e)}>
|
||||
{this.props.simulators.map(simulator => (
|
||||
<option key={simulator._id} value={simulator._id}>{_.get(simulator, 'properties.name') || _.get(simulator, 'rawProperties.name')}</option>
|
||||
))}
|
||||
</FormControl>
|
||||
</FormGroup>
|
||||
<FormGroup controlId="outputLength" validationState={this.validateForm('outputLength')}>
|
||||
<ControlLabel>Output Length</ControlLabel>
|
||||
<FormControl type="number" placeholder="Enter length" min="1" value={this.state.outputLength} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
<FormGroup controlId="outputMapping">
|
||||
<ControlLabel>Output Mapping</ControlLabel>
|
||||
<HelpBlock>Click Name or Type cell to edit</HelpBlock>
|
||||
<Table data={this.state.outputMapping}>
|
||||
<TableColumn title='ID' width='60' dataIndex />
|
||||
<TableColumn title='Name' dataKey='name' inlineEditable onInlineChange={(event, row, column) => this.handleMappingChange('outputMapping', event, row, column)} />
|
||||
<TableColumn title='Type' dataKey='type' inlineEditable onInlineChange={(event, row, column) => this.handleMappingChange('outputMapping', event, row, column)} />
|
||||
</Table>
|
||||
</FormGroup>
|
||||
<FormGroup controlId="inputLength" validationState={this.validateForm('inputLength')}>
|
||||
<ControlLabel>Input Length</ControlLabel>
|
||||
<FormControl type="number" placeholder="Enter length" min="1" value={this.state.inputLength} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
<FormGroup controlId="inputMapping">
|
||||
<ControlLabel>Input Mapping</ControlLabel>
|
||||
<HelpBlock>Click Name or Type cell to edit</HelpBlock>
|
||||
<Table data={this.state.inputMapping}>
|
||||
<TableColumn title='ID' width='60' dataIndex />
|
||||
<TableColumn title='Name' dataKey='name' inlineEditable onInlineChange={(event, row, column) => this.handleMappingChange('inputMapping', event, row, column)} />
|
||||
<TableColumn title='Type' dataKey='type' inlineEditable onInlineChange={(event, row, column) => this.handleMappingChange('inputMapping', event, row, column)} />
|
||||
</Table>
|
||||
</FormGroup>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default EditSimulationModelDialog;
|
|
@ -20,194 +20,89 @@
|
|||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { FormGroup, FormControl, ControlLabel, HelpBlock } from 'react-bootstrap';
|
||||
import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
|
||||
import _ from 'lodash';
|
||||
|
||||
import Table from '../table';
|
||||
import TableColumn from '../table-column';
|
||||
import Dialog from './dialog';
|
||||
|
||||
class ImportSimulationModelDialog extends React.Component {
|
||||
valid = false;
|
||||
imported = false;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
name: '',
|
||||
simulator: '',
|
||||
outputLength: '1',
|
||||
inputLength: '1',
|
||||
outputMapping: [ { name: 'Signal', type: 'Type' } ],
|
||||
inputMapping: [{ name: 'Signal', type: 'Type' }]
|
||||
model: {}
|
||||
};
|
||||
}
|
||||
|
||||
onClose(canceled) {
|
||||
if (canceled === false) {
|
||||
this.props.onClose(this.state);
|
||||
} else {
|
||||
onClose = canceled => {
|
||||
if (canceled) {
|
||||
this.props.onClose();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.onClose(this.state.model);
|
||||
}
|
||||
|
||||
resetState() {
|
||||
resetState = () => {
|
||||
this.setState({
|
||||
name: '',
|
||||
simulator: '',
|
||||
outputLength: '1',
|
||||
inputLength: '1',
|
||||
outputMapping: [{ name: 'Signal', type: 'Type' }],
|
||||
inputMapping: [{ name: 'Signal', type: 'Type' }]
|
||||
model: {}
|
||||
});
|
||||
|
||||
this.imported = false;
|
||||
}
|
||||
|
||||
handleChange(e) {
|
||||
let mapping = null;
|
||||
|
||||
if (e.target.id === 'outputLength') {
|
||||
mapping = this.state.outputMapping;
|
||||
} else if (e.target.id === 'inputLength') {
|
||||
mapping = this.state.inputMapping;
|
||||
}
|
||||
|
||||
if (mapping != null) {
|
||||
// change mapping size
|
||||
if (e.target.value > mapping.length) {
|
||||
// add missing signals
|
||||
while (mapping.length < e.target.value) {
|
||||
mapping.push({ name: 'Signal', type: 'Type' });
|
||||
}
|
||||
} else {
|
||||
// remove signals
|
||||
mapping.splice(e.target.value, mapping.length - e.target.value);
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({ [e.target.id]: e.target.value });
|
||||
}
|
||||
|
||||
handleMappingChange(key, event, row, column) {
|
||||
const mapping = this.state[key];
|
||||
|
||||
if (column === 1) {
|
||||
mapping[row].name = event.target.value;
|
||||
} else if (column === 2) {
|
||||
mapping[row].type = event.target.value;
|
||||
}
|
||||
|
||||
this.setState({ [key]: mapping });
|
||||
}
|
||||
|
||||
loadFile(fileList) {
|
||||
loadFile = event => {
|
||||
// get file
|
||||
const file = fileList[0];
|
||||
if (!file.type.match('application/json')) {
|
||||
const file = event.target.files[0];
|
||||
if (file.type.match('application/json') === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// create file reader
|
||||
var reader = new FileReader();
|
||||
var self = this;
|
||||
const reader = new FileReader();
|
||||
const self = this;
|
||||
|
||||
reader.onload = function(event) {
|
||||
// read simulator
|
||||
reader.onload = event => {
|
||||
const model = JSON.parse(event.target.result);
|
||||
|
||||
model.simulator = this.props.simulators.length > 0 ? this.props.simulators[0]._id : null;
|
||||
|
||||
self.imported = true;
|
||||
self.valid = true;
|
||||
self.setState({ name: model.name, mapping: model.mapping, length: model.length, simulator: { node: self.props.nodes[0]._id, simulator: 0 } });
|
||||
|
||||
this.setState({ model });
|
||||
};
|
||||
|
||||
reader.readAsText(file);
|
||||
}
|
||||
|
||||
validateForm(target) {
|
||||
// check all controls
|
||||
var name = true;
|
||||
let inputLength = true;
|
||||
let outputLength = true;
|
||||
var simulator = true;
|
||||
handleSimulatorChange = event => {
|
||||
const model = this.state.model;
|
||||
|
||||
if (this.state.name === '') {
|
||||
name = false;
|
||||
}
|
||||
model.simulator = event.target.value;
|
||||
|
||||
if (this.state.simulator === '') {
|
||||
simulator = false;
|
||||
}
|
||||
|
||||
// test if simulatorid is a number (in a string, not type of number)
|
||||
if (!/^\d+$/.test(this.state.outputLength)) {
|
||||
outputLength = false;
|
||||
}
|
||||
|
||||
if (!/^\d+$/.test(this.state.inputLength)) {
|
||||
inputLength = false;
|
||||
}
|
||||
|
||||
this.valid = name && inputLength && outputLength && simulator;
|
||||
|
||||
// return state to control
|
||||
if (target === 'name') return name ? "success" : "error";
|
||||
else if (target === 'outputLength') return outputLength ? "success" : "error";
|
||||
else if (target === 'inputLength') return inputLength ? "success" : "error";
|
||||
else if (target === 'simulator') return simulator ? "success" : "error";
|
||||
this.setState({ model });
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Dialog show={this.props.show} title="Import Simulation Model" buttonTitle="Import" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
|
||||
<Dialog show={this.props.show} title="Import Simulation Model" buttonTitle="Import" onClose={this.onClose} onReset={this.resetState} valid={this.imported}>
|
||||
<form>
|
||||
<FormGroup controlId="file">
|
||||
<FormGroup controlId='file'>
|
||||
<ControlLabel>Simulation Model File</ControlLabel>
|
||||
<FormControl type="file" onChange={(e) => this.loadFile(e.target.files)} />
|
||||
<FormControl type='file' onChange={this.loadFile} />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup controlId="name" validationState={this.validateForm('name')}>
|
||||
<ControlLabel>Name</ControlLabel>
|
||||
<FormControl readOnly={!this.imported} type="text" placeholder="Enter name" value={this.state.name} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
<FormGroup controlId="simulator">
|
||||
<FormGroup controlId='simulator'>
|
||||
<ControlLabel>Simulator</ControlLabel>
|
||||
<FormControl readOnly={!this.imported} componentClass="select" placeholder="Select simulator" value={this.state.simulator} onChange={(e) => this.handleChange(e)}>
|
||||
<FormControl disabled={this.imported === false} componentClass='select' placeholder='Select simulator' value={this.state.model.simulator} onChange={this.handleSimulatorChange}>
|
||||
{this.props.simulators.map(simulator => (
|
||||
<option key={simulator._id} value={simulator}>{_.get(simulator, 'properties.name') || _.get(simulator, 'rawProperties.name')}</option>
|
||||
<option key={simulator._id} value={simulator._id}>{_.get(simulator, 'properties.name') || _.get(simulator, 'rawProperties.name')}</option>
|
||||
))}
|
||||
</FormControl>
|
||||
</FormGroup>
|
||||
<FormGroup controlId="outputLength" validationState={this.validateForm('outputLength')}>
|
||||
<ControlLabel>Output Length</ControlLabel>
|
||||
<FormControl type="number" placeholder="Enter length" min="1" value={this.state.outputLength} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
<FormGroup controlId="outputMapping">
|
||||
<ControlLabel>Output Mapping</ControlLabel>
|
||||
<HelpBlock>Click Name or Type cell to edit</HelpBlock>
|
||||
<Table data={this.state.outputMapping}>
|
||||
<TableColumn title='ID' width='60' dataIndex />
|
||||
<TableColumn title='Name' dataKey='name' inlineEditable onInlineChange={(event, row, column) => this.handleMappingChange('outputMapping', event, row, column)} />
|
||||
<TableColumn title='Type' dataKey='type' inlineEditable onInlineChange={(event, row, column) => this.handleMappingChange('outputMapping', event, row, column)} />
|
||||
</Table>
|
||||
</FormGroup>
|
||||
<FormGroup controlId="inputLength" validationState={this.validateForm('inputLength')}>
|
||||
<ControlLabel>Input Length</ControlLabel>
|
||||
<FormControl type="number" placeholder="Enter length" min="1" value={this.state.inputLength} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
<FormGroup controlId="inputMapping">
|
||||
<ControlLabel>Input Mapping</ControlLabel>
|
||||
<HelpBlock>Click Name or Type cell to edit</HelpBlock>
|
||||
<Table data={this.state.inputMapping}>
|
||||
<TableColumn title='ID' width='60' dataIndex />
|
||||
<TableColumn title='Name' dataKey='name' inlineEditable onInlineChange={(event, row, column) => this.handleMappingChange('inputMapping', event, row, column)} />
|
||||
<TableColumn title='Type' dataKey='type' inlineEditable onInlineChange={(event, row, column) => this.handleMappingChange('inputMapping', event, row, column)} />
|
||||
</Table>
|
||||
</FormGroup>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
|
|
|
@ -1,182 +0,0 @@
|
|||
/**
|
||||
* File: new-simulation-model.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 04.03.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { FormGroup, FormControl, ControlLabel, HelpBlock } from 'react-bootstrap';
|
||||
import _ from 'lodash';
|
||||
|
||||
import Table from '../table';
|
||||
import TableColumn from '../table-column';
|
||||
import Dialog from './dialog';
|
||||
|
||||
class NewSimulationModelDialog extends React.Component {
|
||||
valid = false;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
name: '',
|
||||
simulator: '',
|
||||
outputLength: '1',
|
||||
inputLength: '1',
|
||||
outputMapping: [ { name: 'Signal', type: 'Type' } ],
|
||||
inputMapping: [ { name: 'Signal', type: 'Type' } ]
|
||||
};
|
||||
}
|
||||
|
||||
onClose(canceled) {
|
||||
if (canceled === false) {
|
||||
if (this.valid) {
|
||||
this.props.onClose(this.state);
|
||||
}
|
||||
} else {
|
||||
this.props.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
handleChange(e) {
|
||||
let mapping = null;
|
||||
|
||||
if (e.target.id === 'outputLength') {
|
||||
mapping = this.state.outputMapping;
|
||||
} else if (e.target.id === 'inputLength') {
|
||||
mapping = this.state.inputMapping;
|
||||
}
|
||||
|
||||
if (mapping != null) {
|
||||
// change mapping size
|
||||
if (e.target.value > mapping.length) {
|
||||
// add missing signals
|
||||
while (mapping.length < e.target.value) {
|
||||
mapping.push({ name: 'Signal', type: 'Type' });
|
||||
}
|
||||
} else {
|
||||
// remove signals
|
||||
mapping.splice(e.target.value, mapping.length - e.target.value);
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({ [e.target.id]: e.target.value });
|
||||
}
|
||||
|
||||
handleMappingChange(key, event, row, column) {
|
||||
const mapping = this.state[key];
|
||||
|
||||
if (column === 1) {
|
||||
mapping[row].name = event.target.value;
|
||||
} else if (column === 2) {
|
||||
mapping[row].type = event.target.value;
|
||||
}
|
||||
|
||||
this.setState({ [key]: mapping });
|
||||
}
|
||||
|
||||
resetState() {
|
||||
this.setState({
|
||||
name: '',
|
||||
simulator: this.props.simulators[0]._id || '',
|
||||
outputLength: '1',
|
||||
inputLength: '1',
|
||||
outputMapping: [{ name: 'Signal', type: 'Type' }],
|
||||
inputMapping: [{ name: 'Signal', type: 'Type' }]
|
||||
});
|
||||
}
|
||||
|
||||
validateForm(target) {
|
||||
// check all controls
|
||||
let name = true;
|
||||
let inputLength = true;
|
||||
let outputLength = true;
|
||||
|
||||
if (this.state.name === '') {
|
||||
name = false;
|
||||
}
|
||||
|
||||
// test if simulatorid is a number (in a string, not type of number)
|
||||
if (!/^\d+$/.test(this.state.outputLength)) {
|
||||
outputLength = false;
|
||||
}
|
||||
|
||||
if (!/^\d+$/.test(this.state.inputLength)) {
|
||||
inputLength = false;
|
||||
}
|
||||
|
||||
this.valid = name && inputLength && outputLength;
|
||||
|
||||
// return state to control
|
||||
if (target === 'name') return name ? "success" : "error";
|
||||
else if (target === 'outputLength') return outputLength ? "success" : "error";
|
||||
else if (target === 'inputLength') return inputLength ? "success" : "error";
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Dialog show={this.props.show} title="New Simulation Model" buttonTitle="Add" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
|
||||
<form>
|
||||
<FormGroup controlId="name" validationState={this.validateForm('name')}>
|
||||
<ControlLabel>Name</ControlLabel>
|
||||
<FormControl type="text" placeholder="Enter name" value={this.state.name} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
<FormGroup controlId="simulator">
|
||||
<ControlLabel>Simulator</ControlLabel>
|
||||
<FormControl componentClass="select" placeholder="Select simulator" value={this.state.simulator} onChange={(e) => this.handleChange(e)}>
|
||||
{this.props.simulators.map(simulator => (
|
||||
<option key={simulator._id} value={simulator._id}>{_.get(simulator, 'properties.name') || _.get(simulator, 'rawProperties.name')}</option>
|
||||
))}
|
||||
</FormControl>
|
||||
</FormGroup>
|
||||
<FormGroup controlId="outputLength" validationState={this.validateForm('outputLength')}>
|
||||
<ControlLabel>Output Length</ControlLabel>
|
||||
<FormControl type="number" placeholder="Enter length" min="1" value={this.state.outputLength} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
<FormGroup controlId="outputMapping">
|
||||
<ControlLabel>Output Mapping</ControlLabel>
|
||||
<HelpBlock>Click Name or Type cell to edit</HelpBlock>
|
||||
<Table data={this.state.outputMapping}>
|
||||
<TableColumn title='ID' width='60' dataIndex />
|
||||
<TableColumn title='Name' dataKey='name' inlineEditable onInlineChange={(event, row, column) => this.handleMappingChange('outputMapping', event, row, column)} />
|
||||
<TableColumn title='Type' dataKey='type' inlineEditable onInlineChange={(event, row, column) => this.handleMappingChange('outputMapping', event, row, column)} />
|
||||
</Table>
|
||||
</FormGroup>
|
||||
<FormGroup controlId="inputLength" validationState={this.validateForm('inputLength')}>
|
||||
<ControlLabel>Input Length</ControlLabel>
|
||||
<FormControl type="number" placeholder="Enter length" min="1" value={this.state.inputLength} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
<FormGroup controlId="inputMapping">
|
||||
<ControlLabel>Input Mapping</ControlLabel>
|
||||
<HelpBlock>Click Name or Type cell to edit</HelpBlock>
|
||||
<Table data={this.state.inputMapping}>
|
||||
<TableColumn title='ID' width='60' dataIndex />
|
||||
<TableColumn title='Name' dataKey='name' inlineEditable onInlineChange={(event, row, column) => this.handleMappingChange('inputMapping', event, row, column)} />
|
||||
<TableColumn title='Type' dataKey='type' inlineEditable onInlineChange={(event, row, column) => this.handleMappingChange('inputMapping', event, row, column)} />
|
||||
</Table>
|
||||
</FormGroup>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default NewSimulationModelDialog;
|
107
src/components/editable-header.js
Normal file
107
src/components/editable-header.js
Normal file
|
@ -0,0 +1,107 @@
|
|||
/**
|
||||
* File: header.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 25.05.2018
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Glyphicon, FormControl } from 'react-bootstrap';
|
||||
|
||||
class EditableHeader extends React.Component {
|
||||
titleInput = null;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
editing: false,
|
||||
title: props.title
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.setState({ title: nextProps.title });
|
||||
}
|
||||
|
||||
edit = () => {
|
||||
this.setState({ editing: true });
|
||||
}
|
||||
|
||||
save = () => {
|
||||
this.setState({ editing: false });
|
||||
|
||||
if (this.props.onChange != null) {
|
||||
this.props.onChange(this.state.title);
|
||||
}
|
||||
}
|
||||
|
||||
cancel = () => {
|
||||
this.setState({ editing: false, title: this.props.title });
|
||||
}
|
||||
|
||||
onChange = event => {
|
||||
this.setState({ title: event.target.value });
|
||||
}
|
||||
|
||||
render() {
|
||||
const wrapperStyle= {
|
||||
float: 'left'
|
||||
};
|
||||
|
||||
const glyphStyle = {
|
||||
float: 'left',
|
||||
|
||||
marginLeft: '10px',
|
||||
marginTop: '25px',
|
||||
marginBottom: '20px'
|
||||
};
|
||||
|
||||
if (this.state.editing) {
|
||||
const editStyle = {
|
||||
maxWidth: '500px',
|
||||
|
||||
marginTop: '10px'
|
||||
};
|
||||
|
||||
return <div>
|
||||
<form style={wrapperStyle}>
|
||||
<FormControl type='text' bsSize='large' value={this.state.title} onChange={this.onChange} style={editStyle} autoFocus />
|
||||
</form>
|
||||
|
||||
<a onClick={this.save}><Glyphicon glyph='ok' style={glyphStyle} /></a>
|
||||
<a onClick={this.cancel}><Glyphicon glyph='remove' style={glyphStyle} /></a>
|
||||
</div>;
|
||||
}
|
||||
|
||||
return <div>
|
||||
<h1 style={wrapperStyle}>
|
||||
{this.state.title}
|
||||
</h1>
|
||||
|
||||
<a onClick={this.edit}><Glyphicon glyph='pencil' style={glyphStyle} /></a>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
EditableHeader.PropTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func
|
||||
};
|
||||
|
||||
export default EditableHeader;
|
125
src/components/signal-mapping.js
Normal file
125
src/components/signal-mapping.js
Normal file
|
@ -0,0 +1,125 @@
|
|||
/**
|
||||
* File: signalMapping.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 10.08.2018
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormGroup, FormControl, ControlLabel, HelpBlock } from 'react-bootstrap';
|
||||
import validator from 'validator';
|
||||
|
||||
import Table from './table';
|
||||
import TableColumn from './table-column';
|
||||
|
||||
class SignalMapping extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
length: props.length,
|
||||
signals: props.signals
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.length === this.state.length && nextProps.signals === this.state.signals) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ length: nextProps.length, signals: nextProps.signals });
|
||||
}
|
||||
|
||||
validateLength(){
|
||||
const valid = validator.isInt(this.state.length + '', { min: 1, max: 100 });
|
||||
|
||||
return valid ? 'success' : 'error';
|
||||
}
|
||||
|
||||
handleLengthChange = event => {
|
||||
const length = event.target.value;
|
||||
|
||||
// update signals to represent length
|
||||
const signals = this.state.signals;
|
||||
|
||||
if (this.state.length < length) {
|
||||
while (signals.length < length) {
|
||||
signals.push({ name: 'Signal', type: 'Type' });
|
||||
}
|
||||
} else {
|
||||
signals.splice(length, signals.length - length);
|
||||
}
|
||||
|
||||
// save updated state
|
||||
this.setState({ length, signals });
|
||||
|
||||
if (this.props.onChange != null) {
|
||||
this.props.onChange(length, signals);
|
||||
}
|
||||
}
|
||||
|
||||
handleMappingChange = (event, row, column) => {
|
||||
const signals = this.state.signals;
|
||||
|
||||
if (column === 1) {
|
||||
signals[row].name = event.target.value;
|
||||
} else if (column === 2) {
|
||||
signals[row].type = event.target.value;
|
||||
}
|
||||
|
||||
this.setState({ signals });
|
||||
|
||||
if (this.props.onChange != null) {
|
||||
this.props.onChange(this.state.length, signals);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div>
|
||||
<FormGroup validationState={this.validateLength()}>
|
||||
<ControlLabel>{this.props.name} Length</ControlLabel>
|
||||
<FormControl type='number' placeholder='Enter length' min='1' value={this.state.length} onChange={this.handleLengthChange} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<ControlLabel>{this.props.name} Mapping</ControlLabel>
|
||||
<HelpBlock>Click <i>name</i> or <i>type</i> cell to edit</HelpBlock>
|
||||
<Table data={this.props.signals}>
|
||||
<TableColumn title='ID' width='60' dataIndex />
|
||||
<TableColumn title='Name' dataKey='name' inlineEditable onInlineChange={this.handleMappingChange} />
|
||||
<TableColumn title='Type' dataKey='type' inlineEditable onInlineChange={this.handleMappingChange} />
|
||||
</Table>
|
||||
</FormGroup>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
SignalMapping.propTypes = {
|
||||
name: PropTypes.string,
|
||||
length: PropTypes.number,
|
||||
signals: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
type: PropTypes.string.isRequired
|
||||
})
|
||||
),
|
||||
onChange: PropTypes.func
|
||||
};
|
||||
|
||||
export default SignalMapping;
|
|
@ -24,15 +24,14 @@ import _ from 'lodash';
|
|||
import { Table, Button, Glyphicon, FormControl, Label, Checkbox } from 'react-bootstrap';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
//import TableColumn from './table-column';
|
||||
|
||||
class CustomTable extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.activeInput = null;
|
||||
|
||||
this.state = {
|
||||
rows: [],
|
||||
rows: this.getRows(props),
|
||||
editCell: [ -1, -1 ]
|
||||
};
|
||||
}
|
||||
|
@ -119,30 +118,9 @@ class CustomTable extends Component {
|
|||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
// check if data exists
|
||||
if (nextProps.data == null) {
|
||||
this.setState({ rows: [] });
|
||||
return;
|
||||
}
|
||||
const rows = this.getRows(nextProps);
|
||||
|
||||
// create row data
|
||||
var rows = nextProps.data.map((data, index) => {
|
||||
// check if multiple columns
|
||||
if (Array.isArray(nextProps.children)) {
|
||||
var row = [];
|
||||
|
||||
nextProps.children.forEach(child => {
|
||||
row.push(this.addCell(data, index, child));
|
||||
});
|
||||
|
||||
return row;
|
||||
} else {
|
||||
// table only has a single column
|
||||
return [ this.addCell(data, index, nextProps.children) ];
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({ rows: rows });
|
||||
this.setState({ rows });
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
|
@ -162,6 +140,28 @@ class CustomTable extends Component {
|
|||
this.setState({ editCell: [ -1, -1 ] });
|
||||
}
|
||||
|
||||
getRows(props) {
|
||||
if (props.data == null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return props.data.map((data, index) => {
|
||||
// check if multiple columns
|
||||
if (Array.isArray(props.children) === false) {
|
||||
// table only has a single column
|
||||
return [ this.addCell(data, index, props.children) ];
|
||||
}
|
||||
|
||||
const row = [];
|
||||
|
||||
for (let child of props.children) {
|
||||
row.push(this.addCell(data, index, child));
|
||||
}
|
||||
|
||||
return row;
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
// get children
|
||||
let children = this.props.children;
|
||||
|
|
|
@ -45,6 +45,7 @@ import Simulators from './simulators';
|
|||
import Visualization from './visualization';
|
||||
import Simulations from './simulations';
|
||||
import Simulation from './simulation';
|
||||
import SimulationModel from './simulation-model';
|
||||
import Users from './users';
|
||||
|
||||
import '../styles/app.css';
|
||||
|
@ -134,6 +135,7 @@ class App extends React.Component {
|
|||
<Route path="/visualizations/:visualization" component={Visualization} />
|
||||
<Route exact path="/simulations" component={Simulations} />
|
||||
<Route path="/simulations/:simulation" component={Simulation} />
|
||||
<Route path="/simulationModel/:simulationModel" component={SimulationModel} />
|
||||
<Route path="/simulators" component={Simulators} />
|
||||
<Route path="/users" component={Users} />
|
||||
</div>
|
||||
|
|
151
src/containers/select-file.js
Normal file
151
src/containers/select-file.js
Normal file
|
@ -0,0 +1,151 @@
|
|||
/**
|
||||
* File: selectFile.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 10.05.2018
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { Container } from 'flux/utils';
|
||||
import { FormGroup, FormControl, ControlLabel, Button, ProgressBar, Col } from 'react-bootstrap';
|
||||
|
||||
import FileStore from '../stores/file-store';
|
||||
import UserStore from '../stores/user-store';
|
||||
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
|
||||
class SelectFile extends React.Component {
|
||||
static getStores() {
|
||||
return [ FileStore, UserStore ];
|
||||
}
|
||||
|
||||
static calculateState() {
|
||||
return {
|
||||
files: FileStore.getState(),
|
||||
sessionToken: UserStore.getState().token,
|
||||
selectedFile: '',
|
||||
uploadFile: null,
|
||||
uploadProgress: 0
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'files/start-load',
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.value === this.state.selectedSimulator) {
|
||||
return;
|
||||
}
|
||||
|
||||
let selectedSimulator = nextProps.value;
|
||||
if (selectedSimulator == null) {
|
||||
if (this.state.simulators.length > 0) {
|
||||
selectedSimulator = this.state.simulators[0]._id;
|
||||
} else {
|
||||
selectedSimulator = '';
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({ selectedSimulator });
|
||||
}
|
||||
|
||||
handleChange = event => {
|
||||
this.setState({ selectedFile: event.target.value });
|
||||
|
||||
// send file to callback
|
||||
if (this.props.onChange != null) {
|
||||
const file = this.state.files.find(f => f._id === event.target.value);
|
||||
|
||||
this.props.onChange(file);
|
||||
}
|
||||
}
|
||||
|
||||
selectUploadFile = event => {
|
||||
this.setState({ uploadFile: event.target.files[0] });
|
||||
}
|
||||
|
||||
startFileUpload = () => {
|
||||
// upload file
|
||||
const formData = new FormData();
|
||||
formData.append(0, this.state.uploadFile);
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'files/start-upload',
|
||||
data: formData,
|
||||
token: this.state.sessionToken,
|
||||
progressCallback: this.updateUploadProgress,
|
||||
finishedCallback: this.clearProgress
|
||||
});
|
||||
}
|
||||
|
||||
updateUploadProgress = event => {
|
||||
this.setState({ uploadProgress: parseInt(event.percent.toFixed(), 10) });
|
||||
}
|
||||
|
||||
clearProgress = () => {
|
||||
// select uploaded file
|
||||
const selectedFile = this.state.files[this.state.files.length - 1]._id;
|
||||
this.setState({ selectedFile, uploadProgress: 0 });
|
||||
}
|
||||
|
||||
render() {
|
||||
const fileOptions = this.state.files.map(f =>
|
||||
<option key={f._id} value={f._id}>{f.name}</option>
|
||||
);
|
||||
|
||||
const progressBarStyle = {
|
||||
marginLeft: '100px',
|
||||
marginTop: '-25px'
|
||||
};
|
||||
|
||||
return <div>
|
||||
<FormGroup>
|
||||
<Col componentClass={ControlLabel} sm={3} md={2}>
|
||||
{this.props.name}
|
||||
</Col>
|
||||
|
||||
<Col sm={9} md={10}>
|
||||
<FormControl disabled={this.props.disabled} componentClass='select' placeholder='Select file' onChange={this.handleChange}>
|
||||
{fileOptions}
|
||||
</FormControl>
|
||||
</Col>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<Col sm={9} md={10} smOffset={3} mdOffset={2}>
|
||||
<FormControl disabled={this.props.disabled} type='file' onChange={this.selectUploadFile} />
|
||||
</Col>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<Col sm={9} md={10} smOffset={3} mdOffset={2}>
|
||||
<Button disabled={this.props.disabled} bsSize='small' onClick={this.startFileUpload}>
|
||||
Upload file
|
||||
</Button>
|
||||
|
||||
<ProgressBar striped active now={this.state.uploadProgress} label={this.state.uploadProgress + '%'} style={progressBarStyle} />
|
||||
</Col>
|
||||
</FormGroup>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export default Container.create(SelectFile);
|
88
src/containers/select-simulator.js
Normal file
88
src/containers/select-simulator.js
Normal file
|
@ -0,0 +1,88 @@
|
|||
/**
|
||||
* File: selectSimulator.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 10.05.2018
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { Container } from 'flux/utils';
|
||||
import { FormGroup, FormControl, ControlLabel, Col } from 'react-bootstrap';
|
||||
import _ from 'lodash';
|
||||
|
||||
import SimulatorStore from '../stores/simulator-store';
|
||||
|
||||
class SelectSimulator extends React.Component {
|
||||
static getStores() {
|
||||
return [ SimulatorStore ];
|
||||
}
|
||||
|
||||
static calculateState() {
|
||||
return {
|
||||
simulators: SimulatorStore.getState(),
|
||||
selectedSimulator: ''
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.value === this.state.selectedSimulator) {
|
||||
return;
|
||||
}
|
||||
|
||||
let selectedSimulator = nextProps.value;
|
||||
if (selectedSimulator == null) {
|
||||
if (this.state.simulators.length > 0) {
|
||||
selectedSimulator = this.state.simulators[0]._id;
|
||||
} else {
|
||||
selectedSimulator = '';
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({ selectedSimulator });
|
||||
}
|
||||
|
||||
handleChange = event => {
|
||||
this.setState({ selectedSimulator: event.target.value });
|
||||
|
||||
// send complete simulator to callback
|
||||
if (this.props.onChange != null) {
|
||||
const simulator = this.state.simulators.find(s => s._id === event.target.value);
|
||||
|
||||
this.props.onChange(simulator);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const simulatorOptions = this.state.simulators.map(s =>
|
||||
<option key={s._id} value={s._id}>{_.get(s, 'properties.name') || _.get(s, 'rawProperties.name') || s.uuid}</option>
|
||||
);
|
||||
|
||||
return <FormGroup>
|
||||
<Col componentClass={ControlLabel} sm={3} md={2}>
|
||||
Simulator
|
||||
</Col>
|
||||
|
||||
<Col sm={9} md={10}>
|
||||
<FormControl componentClass='select' placeholder='Select simulator' value={this.state.selectedSimulator} onChange={this.handleChange}>
|
||||
{simulatorOptions}
|
||||
</FormControl>
|
||||
</Col>
|
||||
</FormGroup>;
|
||||
}
|
||||
}
|
||||
|
||||
export default Container.create(SelectSimulator);
|
151
src/containers/simulation-model.js
Normal file
151
src/containers/simulation-model.js
Normal file
|
@ -0,0 +1,151 @@
|
|||
/**
|
||||
* File: simulationModel.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 10.05.2018
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { Container } from 'flux/utils';
|
||||
import { Button, Col, Form } from 'react-bootstrap';
|
||||
|
||||
import SimulationModelStore from '../stores/simulation-model-store';
|
||||
import UserStore from '../stores/user-store';
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
|
||||
import SelectSimulator from './select-simulator';
|
||||
import SelectFile from './select-file';
|
||||
import SignalMapping from '../components/signal-mapping';
|
||||
import EditableHeader from '../components/editable-header';
|
||||
|
||||
class SimulationModel extends React.Component {
|
||||
static getStores() {
|
||||
return [ SimulationModelStore, UserStore ];
|
||||
}
|
||||
|
||||
static calculateState(prevState, props) {
|
||||
const simulationModel = SimulationModelStore.getState().find(m => m._id === props.match.params.simulationModel);
|
||||
|
||||
return {
|
||||
simulationModel: simulationModel || {},
|
||||
sessionToken: UserStore.getState().token
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulationModels/start-load',
|
||||
data: this.props.match.params.simulationModel,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
|
||||
submitForm = event => {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
saveChanges = () => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulationModels/start-edit',
|
||||
data: this.state.simulationModel,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
|
||||
this.props.history.push('/simulations/' + this.state.simulationModel.simulation);
|
||||
}
|
||||
|
||||
discardChanges = () => {
|
||||
this.props.history.push('/simulations/' + this.state.simulationModel.simulation);
|
||||
}
|
||||
|
||||
handleSimulatorChange = simulator => {
|
||||
const simulationModel = this.state.simulationModel;
|
||||
|
||||
simulationModel.simulator = simulator;
|
||||
|
||||
this.setState({ simulationModel });
|
||||
}
|
||||
|
||||
handleModelChange = file => {
|
||||
console.log(file);
|
||||
}
|
||||
|
||||
handleConfigurationChange = file => {
|
||||
console.log(file);
|
||||
}
|
||||
|
||||
handleOutputMappingChange = (length, signals) => {
|
||||
const simulationModel = this.state.simulationModel;
|
||||
|
||||
simulationModel.outputMapping = signals;
|
||||
simulationModel.outputLength = length;
|
||||
|
||||
this.setState({ simulationModel });
|
||||
}
|
||||
|
||||
handleInputMappingChange = (length, signals) => {
|
||||
const simulationModel = this.state.simulationModel;
|
||||
|
||||
simulationModel.inputMapping = signals;
|
||||
simulationModel.inputLength = length;
|
||||
|
||||
this.setState({ simulationModel });
|
||||
}
|
||||
|
||||
handleTitleChange = title => {
|
||||
const simulationModel = this.state.simulationModel;
|
||||
|
||||
simulationModel.name = title;
|
||||
|
||||
this.setState({ simulationModel });
|
||||
}
|
||||
|
||||
render() {
|
||||
const buttonStyle = {
|
||||
marginRight: '10px'
|
||||
};
|
||||
|
||||
return <div className='section'>
|
||||
<EditableHeader title={this.state.simulationModel.name} onChange={this.handleTitleChange} />
|
||||
|
||||
<Form horizontal onSubmit={this.submitForm}>
|
||||
<Col xs={12} sm={12}>
|
||||
<SelectSimulator onChange={this.handleSimulatorChange} value={this.state.simulationModel.simulator} />
|
||||
|
||||
<SelectFile disabled type='model' name='Model' onChange={this.handleModelChange} value={this.state.simulationModel.model} />
|
||||
|
||||
<SelectFile disabled type='configuration' name='Configuration' onChange={this.handleConfigurationChange} value={this.state.simulationModel.configuration} />
|
||||
</Col>
|
||||
|
||||
<Col xs={12} sm={6}>
|
||||
<SignalMapping name='Output' length={this.state.simulationModel.outputLength} signals={this.state.simulationModel.outputMapping} onChange={this.handleOutputMappingChange} />
|
||||
</Col>
|
||||
|
||||
<Col xs={12} sm={6}>
|
||||
<SignalMapping name='Input' length={this.state.simulationModel.inputLength} signals={this.state.simulationModel.inputMapping} onChange={this.handleInputMappingChange} />
|
||||
</Col>
|
||||
|
||||
<div style={{ clear: 'both' }}></div>
|
||||
|
||||
<Button onClick={this.discardChanges} style={buttonStyle}>Cancel</Button>
|
||||
<Button bsStyle='primary' onClick={this.saveChanges} style={buttonStyle}>Save</Button>
|
||||
</Form>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export default Container.create(SimulationModel, { withProps: true });
|
|
@ -33,8 +33,6 @@ import AppDispatcher from '../app-dispatcher';
|
|||
|
||||
import Table from '../components/table';
|
||||
import TableColumn from '../components/table-column';
|
||||
import NewSimulationModelDialog from '../components/dialog/new-simulation-model';
|
||||
import EditSimulationModelDialog from '../components/dialog/edit-simulation-model';
|
||||
import ImportSimulationModelDialog from '../components/dialog/import-simulation-model';
|
||||
|
||||
import SimulatorAction from '../components/simulator-action';
|
||||
|
@ -73,12 +71,9 @@ class Simulation extends React.Component {
|
|||
simulators: SimulatorStore.getState(),
|
||||
sessionToken,
|
||||
|
||||
newModal: false,
|
||||
deleteModal: false,
|
||||
editModal: false,
|
||||
importModal: false,
|
||||
modalData: {},
|
||||
modalIndex: null,
|
||||
|
||||
selectedSimulationModels: []
|
||||
}
|
||||
|
@ -101,26 +96,30 @@ class Simulation extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
closeNewModal(data) {
|
||||
this.setState({ newModal : false });
|
||||
addSimulationModel = () => {
|
||||
const simulationModel = {
|
||||
simulation: this.state.simulation._id,
|
||||
name: 'New Simulation Model',
|
||||
simulator: this.state.simulators.length > 0 ? this.state.simulators[0]._id : null,
|
||||
outputLength: 1,
|
||||
outputMapping: [{ name: 'Signal', type: 'Type' }],
|
||||
inputLength: 1,
|
||||
inputMapping: [{ name: 'Signal', type: 'Type' }]
|
||||
};
|
||||
|
||||
if (data) {
|
||||
data.simulation = this.state.simulation._id;
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulationModels/start-add',
|
||||
data: simulationModel,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
|
||||
this.setState({ simulation: {} }, () => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulationModels/start-add',
|
||||
data,
|
||||
type: 'simulations/start-load',
|
||||
data: this.props.match.params.simulation,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
|
||||
this.setState({ simulation: {} }, () => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulations/start-load',
|
||||
data: this.props.match.params.simulation,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
closeDeleteModal = confirmDelete => {
|
||||
|
@ -137,38 +136,30 @@ class Simulation extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
closeEditModal(data) {
|
||||
this.setState({ editModal : false });
|
||||
|
||||
if (data) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulationModels/start-edit',
|
||||
data,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
closeImportModal(data) {
|
||||
importSimulationModel = simulationModel => {
|
||||
this.setState({ importModal: false });
|
||||
|
||||
if (data) {
|
||||
data.simulation = this.state.simulation._id;
|
||||
if (simulationModel == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
simulationModel.simulation = this.state.simulation._id;
|
||||
|
||||
console.log(simulationModel);
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulationModels/start-add',
|
||||
data: simulationModel,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
|
||||
this.setState({ simulation: {} }, () => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulationModels/start-add',
|
||||
data,
|
||||
type: 'simulations/start-load',
|
||||
data: this.props.match.params.simulation,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
|
||||
this.setState({ simulation: {} }, () => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulations/start-load',
|
||||
data: this.props.match.params.simulation,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getSimulatorName(simulatorId) {
|
||||
|
@ -182,6 +173,7 @@ class Simulation extends React.Component {
|
|||
exportModel(index) {
|
||||
// filter properties
|
||||
const model = Object.assign({}, this.state.simulationModels[index]);
|
||||
|
||||
delete model.simulator;
|
||||
delete model.simulation;
|
||||
|
||||
|
@ -239,52 +231,52 @@ class Simulation extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className='section'>
|
||||
<h1>{this.state.simulation.name}</h1>
|
||||
const buttonStyle = {
|
||||
marginLeft: '10px'
|
||||
};
|
||||
|
||||
<Table data={this.state.simulationModels}>
|
||||
<TableColumn checkbox onChecked={(index, event) => this.onSimulationModelChecked(index, event)} width='30' />
|
||||
<TableColumn title='Name' dataKey='name' />
|
||||
<TableColumn title='Simulator' dataKey='simulator' width='180' modifier={(simulator) => this.getSimulatorName(simulator)} />
|
||||
<TableColumn title='Output' dataKey='outputLength' width='100' />
|
||||
<TableColumn title='Input' dataKey='inputLength' width='100' />
|
||||
<TableColumn
|
||||
title=''
|
||||
width='100'
|
||||
editButton
|
||||
deleteButton
|
||||
exportButton
|
||||
onEdit={(index) => this.setState({ editModal: true, modalData: this.state.simulationModels[index], modalIndex: index })}
|
||||
onDelete={(index) => this.setState({ deleteModal: true, modalData: this.state.simulationModels[index], modalIndex: index })}
|
||||
onExport={index => this.exportModel(index)}
|
||||
/>
|
||||
</Table>
|
||||
return <div className='section'>
|
||||
<h1>{this.state.simulation.name}</h1>
|
||||
|
||||
<div style={{ float: 'left' }}>
|
||||
<SimulatorAction
|
||||
runDisabled={this.state.selectedSimulationModels.length === 0}
|
||||
runAction={this.runAction}
|
||||
actions={[
|
||||
{ id: '0', title: 'Start', data: { action: 'start' } },
|
||||
{ id: '1', title: 'Stop', data: { action: 'stop' } },
|
||||
{ id: '2', title: 'Pause', data: { action: 'pause' } },
|
||||
{ id: '3', title: 'Resume', data: { action: 'resume' } }
|
||||
]}/>
|
||||
</div>
|
||||
<Table data={this.state.simulationModels}>
|
||||
<TableColumn checkbox onChecked={(index, event) => this.onSimulationModelChecked(index, event)} width='30' />
|
||||
<TableColumn title='Name' dataKey='name' link='/simulationModel/' linkKey='_id' />
|
||||
<TableColumn title='Simulator' dataKey='simulator' modifier={(simulator) => this.getSimulatorName(simulator)} />
|
||||
<TableColumn title='Output' dataKey='outputLength' width='100' />
|
||||
<TableColumn title='Input' dataKey='inputLength' width='100' />
|
||||
<TableColumn
|
||||
title=''
|
||||
width='70'
|
||||
deleteButton
|
||||
exportButton
|
||||
onDelete={(index) => this.setState({ deleteModal: true, modalData: this.state.simulationModels[index], modalIndex: index })}
|
||||
onExport={index => this.exportModel(index)}
|
||||
/>
|
||||
</Table>
|
||||
|
||||
<div style={{ float: 'right' }}>
|
||||
<Button onClick={() => this.setState({ newModal: true })}><Glyphicon glyph="plus" /> Simulation Model</Button>
|
||||
<Button onClick={() => this.setState({ importModal: true })}><Glyphicon glyph="import" /> Import</Button>
|
||||
</div>
|
||||
|
||||
<NewSimulationModelDialog show={this.state.newModal} onClose={data => this.closeNewModal(data)} simulators={this.state.simulators} />
|
||||
<EditSimulationModelDialog show={this.state.editModal} onClose={data => this.closeEditModal(data)} data={this.state.modalData} simulators={this.state.simulators} />
|
||||
<ImportSimulationModelDialog show={this.state.importModal} onClose={data => this.closeImportModal(data)} simulators={this.state.simulators} />
|
||||
|
||||
<DeleteDialog title="simulation model" name={this.state.modalData.name} show={this.state.deleteModal} onClose={this.closeDeleteModal} />
|
||||
<div style={{ float: 'left' }}>
|
||||
<SimulatorAction
|
||||
runDisabled={this.state.selectedSimulationModels.length === 0}
|
||||
runAction={this.runAction}
|
||||
actions={[
|
||||
{ id: '0', title: 'Start', data: { action: 'start' } },
|
||||
{ id: '1', title: 'Stop', data: { action: 'stop' } },
|
||||
{ id: '2', title: 'Pause', data: { action: 'pause' } },
|
||||
{ id: '3', title: 'Resume', data: { action: 'resume' } }
|
||||
]}/>
|
||||
</div>
|
||||
);
|
||||
|
||||
<div style={{ float: 'right' }}>
|
||||
<Button onClick={this.addSimulationModel} style={buttonStyle}><Glyphicon glyph="plus" /> Simulation Model</Button>
|
||||
<Button onClick={() => this.setState({ importModal: true })} style={buttonStyle}><Glyphicon glyph="import" /> Import</Button>
|
||||
</div>
|
||||
|
||||
<div style={{ clear: 'both' }} />
|
||||
|
||||
<ImportSimulationModelDialog show={this.state.importModal} onClose={this.importSimulationModel} simulators={this.state.simulators} />
|
||||
|
||||
<DeleteDialog title="simulation model" name={this.state.modalData.name} show={this.state.deleteModal} onClose={this.closeDeleteModal} />
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,8 +30,10 @@ class FilesDataManager extends RestDataManager {
|
|||
|
||||
upload(file, token = null, progressCallback = null, finishedCallback = null) {
|
||||
RestAPI.upload(this.makeURL('/upload'), file, token, progressCallback).then(response => {
|
||||
console.log(response);
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'files/uploaded'
|
||||
type: 'files/uploaded',
|
||||
});
|
||||
|
||||
// Trigger a files reload
|
||||
|
|
|
@ -386,3 +386,8 @@ body {
|
|||
.section-buttons-group-right .rc-slider {
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.form-horizontal .form-group {
|
||||
margin-left: 0 !important;
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue