diff --git a/package-lock.json b/package-lock.json index 5ce72e9..b8e396b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index f09ae20..f54e1a9 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/src/__tests__/components/dialog/edit-widget-control-creator.js b/src/__tests__/components/dialog/edit-widget-control-creator.js index ef02578..d9915bb 100644 --- a/src/__tests__/components/dialog/edit-widget-control-creator.js +++ b/src/__tests__/components/dialog/edit-widget-control-creator.js @@ -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] } }, diff --git a/src/components/dialog/edit-simulation-model.js b/src/components/dialog/edit-simulation-model.js deleted file mode 100644 index a194ea6..0000000 --- a/src/components/dialog/edit-simulation-model.js +++ /dev/null @@ -1,186 +0,0 @@ -/** - * File: edit-simulation-model.js - * Author: Markus Grigull - * 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 . - ******************************************************************************/ - -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 ( - this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}> -
- - Name - this.handleChange(e)} /> - - - - Simulator - this.handleChange(e)}> - {this.props.simulators.map(simulator => ( - - ))} - - - - Output Length - this.handleChange(e)} /> - - - - Output Mapping - Click Name or Type cell to edit - - - this.handleMappingChange('outputMapping', event, row, column)} /> - this.handleMappingChange('outputMapping', event, row, column)} /> -
-
- - Input Length - this.handleChange(e)} /> - - - - Input Mapping - Click Name or Type cell to edit - - - this.handleMappingChange('inputMapping', event, row, column)} /> - this.handleMappingChange('inputMapping', event, row, column)} /> -
-
-
-
- ); - } -} - -export default EditSimulationModelDialog; diff --git a/src/components/dialog/import-simulation-model.js b/src/components/dialog/import-simulation-model.js index 1f9a2ff..137dfe9 100644 --- a/src/components/dialog/import-simulation-model.js +++ b/src/components/dialog/import-simulation-model.js @@ -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 ( - this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}> +
- + Simulation Model File - this.loadFile(e.target.files)} /> + - - Name - this.handleChange(e)} /> - - - + Simulator - this.handleChange(e)}> + {this.props.simulators.map(simulator => ( - + ))} - - Output Length - this.handleChange(e)} /> - - - - Output Mapping - Click Name or Type cell to edit - - - this.handleMappingChange('outputMapping', event, row, column)} /> - this.handleMappingChange('outputMapping', event, row, column)} /> -
-
- - Input Length - this.handleChange(e)} /> - - - - Input Mapping - Click Name or Type cell to edit - - - this.handleMappingChange('inputMapping', event, row, column)} /> - this.handleMappingChange('inputMapping', event, row, column)} /> -
-
); diff --git a/src/components/dialog/new-simulation-model.js b/src/components/dialog/new-simulation-model.js deleted file mode 100644 index 40daf9c..0000000 --- a/src/components/dialog/new-simulation-model.js +++ /dev/null @@ -1,182 +0,0 @@ -/** - * File: new-simulation-model.js - * Author: Markus Grigull - * 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 . - ******************************************************************************/ - -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 ( - this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}> -
- - Name - this.handleChange(e)} /> - - - - Simulator - this.handleChange(e)}> - {this.props.simulators.map(simulator => ( - - ))} - - - - Output Length - this.handleChange(e)} /> - - - - Output Mapping - Click Name or Type cell to edit - - - this.handleMappingChange('outputMapping', event, row, column)} /> - this.handleMappingChange('outputMapping', event, row, column)} /> -
-
- - Input Length - this.handleChange(e)} /> - - - - Input Mapping - Click Name or Type cell to edit - - - this.handleMappingChange('inputMapping', event, row, column)} /> - this.handleMappingChange('inputMapping', event, row, column)} /> -
-
-
-
- ); - } -} - -export default NewSimulationModelDialog; diff --git a/src/components/editable-header.js b/src/components/editable-header.js new file mode 100644 index 0000000..77a4293 --- /dev/null +++ b/src/components/editable-header.js @@ -0,0 +1,107 @@ +/** + * File: header.js + * Author: Markus Grigull + * 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 . + ******************************************************************************/ + +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
+
+ + + + + +
; + } + + return
+

+ {this.state.title} +

+ + +
; + } +} + +EditableHeader.PropTypes = { + title: PropTypes.string.isRequired, + onChange: PropTypes.func +}; + +export default EditableHeader; diff --git a/src/components/signal-mapping.js b/src/components/signal-mapping.js new file mode 100644 index 0000000..3c975b8 --- /dev/null +++ b/src/components/signal-mapping.js @@ -0,0 +1,125 @@ +/** + * File: signalMapping.js + * Author: Markus Grigull + * 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 . + ******************************************************************************/ + +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
+ + {this.props.name} Length + + + + + + {this.props.name} Mapping + Click name or type cell to edit + + + + +
+
+
; + } +} + +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; diff --git a/src/components/table.js b/src/components/table.js index df2b7fe..9d593fe 100644 --- a/src/components/table.js +++ b/src/components/table.js @@ -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; diff --git a/src/containers/app.js b/src/containers/app.js index bb70935..4199935 100644 --- a/src/containers/app.js +++ b/src/containers/app.js @@ -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 { + diff --git a/src/containers/select-file.js b/src/containers/select-file.js new file mode 100644 index 0000000..755356d --- /dev/null +++ b/src/containers/select-file.js @@ -0,0 +1,151 @@ +/** + * File: selectFile.js + * Author: Markus Grigull + * 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 . + ******************************************************************************/ + +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 => + + ); + + const progressBarStyle = { + marginLeft: '100px', + marginTop: '-25px' + }; + + return
+ + + {this.props.name} + + + + + {fileOptions} + + + + + + + + + + + + + + + + + +
; + } +} + +export default Container.create(SelectFile); diff --git a/src/containers/select-simulator.js b/src/containers/select-simulator.js new file mode 100644 index 0000000..bcf7719 --- /dev/null +++ b/src/containers/select-simulator.js @@ -0,0 +1,88 @@ +/** + * File: selectSimulator.js + * Author: Markus Grigull + * 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 . + ******************************************************************************/ + +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 => + + ); + + return + + Simulator + + + + + {simulatorOptions} + + + ; + } +} + +export default Container.create(SelectSimulator); diff --git a/src/containers/simulation-model.js b/src/containers/simulation-model.js new file mode 100644 index 0000000..172ef72 --- /dev/null +++ b/src/containers/simulation-model.js @@ -0,0 +1,151 @@ +/** + * File: simulationModel.js + * Author: Markus Grigull + * 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 . + ******************************************************************************/ + +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
+ + +
+ + + + + + + + + + + + + + + + +
+ + + + +
; + } +} + +export default Container.create(SimulationModel, { withProps: true }); diff --git a/src/containers/simulation.js b/src/containers/simulation.js index d772a61..77d6d1d 100644 --- a/src/containers/simulation.js +++ b/src/containers/simulation.js @@ -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 ( -
-

{this.state.simulation.name}

+ const buttonStyle = { + marginLeft: '10px' + }; - - this.onSimulationModelChecked(index, event)} width='30' /> - - this.getSimulatorName(simulator)} /> - - - 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)} - /> -
+ return
+

{this.state.simulation.name}

-
- -
+ + this.onSimulationModelChecked(index, event)} width='30' /> + + this.getSimulatorName(simulator)} /> + + + this.setState({ deleteModal: true, modalData: this.state.simulationModels[index], modalIndex: index })} + onExport={index => this.exportModel(index)} + /> +
-
- - -
- - this.closeNewModal(data)} simulators={this.state.simulators} /> - this.closeEditModal(data)} data={this.state.modalData} simulators={this.state.simulators} /> - this.closeImportModal(data)} simulators={this.state.simulators} /> - - +
+
- ); + +
+ + +
+ +
+ + + + +
; } } diff --git a/src/data-managers/files-data-manager.js b/src/data-managers/files-data-manager.js index c63f895..60cbdad 100644 --- a/src/data-managers/files-data-manager.js +++ b/src/data-managers/files-data-manager.js @@ -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 diff --git a/src/styles/app.css b/src/styles/app.css index 16f8a65..52cb3bd 100644 --- a/src/styles/app.css +++ b/src/styles/app.css @@ -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; +}