1
0
Fork 0
mirror of https://git.rwth-aachen.de/acs/public/villas/web/ synced 2025-03-09 00:00:01 +01:00

Merge branch 'feature-automatic-simulator-detection' into 'develop'

Feature automatic simulator detection

See merge request acs/public/villas/VILLASweb!26
This commit is contained in:
Markus Grigull 2018-05-04 11:10:44 +02:00
commit 8ccc1791a6
41 changed files with 7883 additions and 4407 deletions

10383
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -16,6 +16,7 @@
"flux": "^3.1.2",
"gaugeJS": "^1.3.2",
"immutable": "^3.8.1",
"lodash": "^4.17.5",
"rc-slider": "^8.3.0",
"react": "^15.4.2",
"react-bootstrap": "^0.31.1",

View file

@ -20,9 +20,9 @@
******************************************************************************/
class WebsocketAPI {
addSocket(node, callbacks) {
addSocket(endpoint, callbacks) {
// create web socket client
const socket = new WebSocket(this.getURL(node), 'live');
const socket = new WebSocket(this.getURL(endpoint), 'live');
socket.binaryType = 'arraybuffer';
// register callbacks
@ -34,10 +34,10 @@ class WebsocketAPI {
return socket;
}
getURL(node) {
getURL(endpoint) {
// create an anchor element (note: no need to append this element to the document)
var link = document.createElement('a');
link.href = node.endpoint;
link.href = endpoint;
if (link.protocol === 'https:')
link.protocol = 'wss:';

View file

@ -0,0 +1,52 @@
/**
* File: edit-user.js
* Author: Ricardo Hernandez-Montoya <rhernandez@gridhound.de>
* Date: 02.05.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 { Button, Modal } from 'react-bootstrap';
class DeleteDialog extends React.Component {
onModalKeyPress = (event) => {
if (event.key === 'Enter') {
event.preventDefault();
this.props.onClose(false);
}
}
render() {
return <Modal keyboard show={this.props.show} onHide={() => this.props.onClose(false)} onKeyPress={this.onModalKeyPress}>
<Modal.Header>
<Modal.Title>Delete {this.props.title}</Modal.Title>
</Modal.Header>
<Modal.Body>
Are you sure you want to delete the {this.props.title} <strong>'{this.props.name}'</strong>?
</Modal.Body>
<Modal.Footer>
<Button onClick={() => this.props.onClose(false)}>Cancel</Button>
<Button bsStyle="danger" onClick={() => this.props.onClose(true)}>Delete</Button>
</Modal.Footer>
</Modal>;
}
}
export default DeleteDialog;

View file

@ -21,20 +21,23 @@
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;
valid = false;
constructor(props) {
super(props);
this.state = {
_id: '',
name: '',
simulator: { node: '', simulator: '' },
simulator: '',
simulation: '',
outputLength: 1,
inputLength: 1,
outputMapping: [{ name: 'Signal', type: 'Type' }],
@ -74,12 +77,7 @@ class EditSimulationModelDialog extends React.Component {
}
}
if (e.target.id === 'simulator') {
var value = e.target.value.split("/");
this.setState({ simulator: { node: value[0], simulator: value[1] } });
} else {
this.setState({ [e.target.id]: e.target.value });
}
this.setState({ [e.target.id]: e.target.value });
}
handleMappingChange(key, event, row, column) {
@ -96,6 +94,8 @@ class EditSimulationModelDialog extends React.Component {
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,
@ -143,11 +143,9 @@ class EditSimulationModelDialog extends React.Component {
</FormGroup>
<FormGroup controlId="simulator" validationState={this.validateForm('simulator')}>
<ControlLabel>Simulator</ControlLabel>
<FormControl componentClass="select" placeholder="Select simulator" value={this.state.simulator.node + '/' + this.state.simulator.simulator} onChange={(e) => this.handleChange(e)}>
{this.props.nodes.map(node => (
node.simulators.map((simulator, index) => (
<option key={node._id + index} value={node.name + '/' + simulator.name}>{node.name}/{simulator.name}</option>
))
<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>

View file

@ -21,24 +21,36 @@
import React from 'react';
import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
import _ from 'lodash';
import Dialog from './dialog';
class EditSimulatorDialog extends React.Component {
valid: false;
valid = true;
constructor(props) {
super(props);
this.state = {
name: ''
name: '',
endpoint: ''
};
}
onClose(canceled) {
if (canceled === false) {
if (this.valid) {
this.props.onClose(this.state);
let data = {};
if (this.state.name != null && this.state.name !== "" && this.state.name !== _.get(this.props.simulator, 'rawProperties.name')) {
data.name = this.state.name;
}
if (this.state.endpoint != null && this.state.endpoint !== "" && this.state.endpoint !== "http://" && this.state.endpoint !== _.get(this.props.simulator, 'rawProperties.endpoint')) {
data.endpoint = this.state.endpoint;
}
this.props.onClose(data);
}
} else {
this.props.onClose();
@ -51,31 +63,23 @@ class EditSimulatorDialog extends React.Component {
resetState() {
this.setState({
name: this.props.simulator.name
name: _.get(this.props.simulator, 'properties.name') || _.get(this.props.simulator, 'rawProperties.name'),
endpoint: _.get(this.props.simulator, 'properties.endpoint') || _.get(this.props.simulator, 'rawProperties.endpoint')
});
}
validateForm(target) {
// check all controls
var name = true;
if (this.state.name === '' || this.props.node.simulators.find(simulator => this.props.simulator.name !== this.state.name && simulator.name === this.state.name) !== undefined) {
name = false;
}
this.valid = name;
// return state to control
if (target === 'name') return name ? "success" : "error";
}
render() {
return (
<Dialog show={this.props.show} title="Edit Simulator" buttonTitle="Save" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
<form>
<FormGroup controlId="name" validationState={this.validateForm('name')}>
<FormGroup controlId="name">
<ControlLabel>Name</ControlLabel>
<FormControl type="text" placeholder="Enter name" value={this.state.name} onChange={(e) => this.handleChange(e)} />
<FormControl type="text" placeholder={_.get(this.props.simulator, 'rawProperties.name')} value={this.state.name} onChange={(e) => this.handleChange(e)} />
<FormControl.Feedback />
</FormGroup>
<FormGroup controlId="endpoint">
<ControlLabel>Endpoint</ControlLabel>
<FormControl type="text" placeholder={_.get(this.props.simulator, 'rawProperties.endpoint')} value={this.state.endpoint || 'http://' } onChange={(e) => this.handleChange(e)} />
<FormControl.Feedback />
</FormGroup>
</form>

View file

@ -36,7 +36,7 @@ import EditWidgetColorZonesControl from './edit-widget-color-zones-control';
import EditWidgetMinMaxControl from './edit-widget-min-max-control';
import EditWidgetHTMLContent from './edit-widget-html-content';
export default function createControls(widgetType = null, widget = null, sessionToken = null, files = null, validateForm, simulation, handleChange) {
export default function createControls(widgetType = null, widget = null, sessionToken = null, files = null, validateForm, simulationModels, handleChange) {
// Use a list to concatenate the controls according to the widget type
var dialogControls = [];
@ -47,8 +47,8 @@ export default function createControls(widgetType = null, widget = null, session
}
dialogControls.push(
<EditWidgetTextControl key={0} widget={widget} controlId={'name'} label={'Text'} placeholder={'Enter text'} validate={id => validateForm(id)} handleChange={e => handleChange(e)} />,
<EditWidgetSimulatorControl key={1} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => valueBoundOnChange(e)} />,
<EditWidgetSignalControl key={2} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />,
<EditWidgetSimulatorControl key={1} widget={widget} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => valueBoundOnChange(e)} />,
<EditWidgetSignalControl key={2} widget={widget} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => handleChange(e)} />,
<EditWidgetTextSizeControl key={3} widget={widget} handleChange={e => handleChange(e)} />,
<EditWidgetCheckboxControl key={4} widget={widget} controlId={'showUnit'} text="Show unit" handleChange={e => handleChange(e)} />
);
@ -58,8 +58,8 @@ export default function createControls(widgetType = null, widget = null, session
handleChange([e, {target: {id: 'signal', value: 0}}]);
}
dialogControls.push(
<EditWidgetSimulatorControl key={0} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => lampBoundOnChange(e)} />,
<EditWidgetSignalControl key={1} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />,
<EditWidgetSimulatorControl key={0} widget={widget} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => lampBoundOnChange(e)} />,
<EditWidgetSignalControl key={1} widget={widget} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => handleChange(e)} />,
<EditWidgetTextControl key={2} widget={widget} controlId={'threshold'} label={'Threshold'} placeholder={'0.5'} validate={id => validateForm(id)} handleChange={e => handleChange(e)} />,
<EditWidgetColorControl key={3} widget={widget} controlId={'on_color'} label={'Color On'} validate={(id) => validateForm(id)} handleChange={(e) => handleChange(e)} />,
<EditWidgetColorControl key={4} widget={widget} controlId={'off_color'} label={'Color Off'} validate={(id) => validateForm(id)} handleChange={(e) => handleChange(e)} />,
@ -70,23 +70,23 @@ export default function createControls(widgetType = null, widget = null, session
handleChange([e, {target: {id: 'signals', value: []}}]);
}
dialogControls.push(
<EditWidgetTimeControl key={0} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />,
<EditWidgetSimulatorControl key={1} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => plotBoundOnChange(e)} />,
<EditWidgetSignalsControl key={2} controlId={'signals'} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />,
<EditWidgetTimeControl key={0} widget={widget} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => handleChange(e)} />,
<EditWidgetSimulatorControl key={1} widget={widget} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => plotBoundOnChange(e)} />,
<EditWidgetSignalsControl key={2} controlId={'signals'} widget={widget} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => handleChange(e)} />,
<EditWidgetTextControl key={3} controlId={'ylabel'} label={'Y-Axis name'} placeholder={'Enter a name for the y-axis'} widget={widget} handleChange={(e) => handleChange(e)} />,
<EditWidgetMinMaxControl key={4} widget={widget} controlId="y" handleChange={e => handleChange(e)} />
);
break;
case 'Table':
dialogControls.push(
<EditWidgetSimulatorControl key={0} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />
<EditWidgetSimulatorControl key={0} widget={widget} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => handleChange(e)} />
);
break;
case 'Image':
// Restrict to only image file types (MIME)
let imageControlFiles = files == null? [] : files.filter(file => file.type.includes('image'));
dialogControls.push(
<EditImageWidgetControl key={0} sessionToken={sessionToken} widget={widget} files={imageControlFiles} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />,
<EditImageWidgetControl key={0} sessionToken={sessionToken} widget={widget} files={imageControlFiles} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => handleChange(e)} />,
<EditWidgetAspectControl key={1} widget={widget} handleChange={e => handleChange(e)} />
);
break;
@ -96,8 +96,8 @@ export default function createControls(widgetType = null, widget = null, session
}
dialogControls.push(
<EditWidgetTextControl key={0} widget={widget} controlId={'name'} label={'Text'} placeholder={'Enter text'} validate={id => validateForm(id)} handleChange={e => handleChange(e)} />,
<EditWidgetSimulatorControl key={1} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => gaugeBoundOnChange(e) } />,
<EditWidgetSignalControl key={2} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />,
<EditWidgetSimulatorControl key={1} widget={widget} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => gaugeBoundOnChange(e) } />,
<EditWidgetSignalControl key={2} widget={widget} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => handleChange(e)} />,
<EditWidgetCheckboxControl key={3} widget={widget} controlId="colorZones" text="Show color zones" handleChange={e => handleChange(e)} />,
<EditWidgetColorZonesControl key={4} widget={widget} handleChange={e => handleChange(e)} />,
<EditWidgetMinMaxControl key={5} widget={widget} controlId="value" handleChange={e => handleChange(e)} />
@ -108,26 +108,26 @@ export default function createControls(widgetType = null, widget = null, session
handleChange([e, {target: {id: 'preselectedSignals', value: []}}]);
}
dialogControls.push(
<EditWidgetSimulatorControl key={0} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => plotTableBoundOnChange(e)} />,
<EditWidgetSignalsControl key={1} controlId={'preselectedSignals'} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />,
<EditWidgetSimulatorControl key={0} widget={widget} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => plotTableBoundOnChange(e)} />,
<EditWidgetSignalsControl key={1} controlId={'preselectedSignals'} widget={widget} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => handleChange(e)} />,
<EditWidgetTextControl key={2} controlId={'ylabel'} label={'Y-Axis'} placeholder={'Enter a name for the Y-axis'} widget={widget} handleChange={(e) => handleChange(e)} />,
<EditWidgetTimeControl key={3} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />,
<EditWidgetTimeControl key={3} widget={widget} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => handleChange(e)} />,
<EditWidgetMinMaxControl key={4} widget={widget} controlId="y" handleChange={e => handleChange(e)} />
);
break;
case 'Slider':
dialogControls.push(
<EditWidgetOrientation key={0} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />,
<EditWidgetSimulatorControl key={1} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => valueBoundOnChange(e)} />,
<EditWidgetSignalControl key={2} widget={widget} input validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />
<EditWidgetOrientation key={0} widget={widget} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => handleChange(e)} />,
<EditWidgetSimulatorControl key={1} widget={widget} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => valueBoundOnChange(e)} />,
<EditWidgetSignalControl key={2} widget={widget} input validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => handleChange(e)} />
);
break;
case 'Button':
dialogControls.push(
<EditWidgetColorControl key={0} widget={widget} controlId={'background_color'} label={'Background'} validate={(id) => validateForm(id)} handleChange={(e) => handleChange(e)} />,
<EditWidgetColorControl key={1} widget={widget} controlId={'font_color'} label={'Font color'} validate={(id) => validateForm(id)} handleChange={(e) => handleChange(e)} />,
<EditWidgetSimulatorControl key={2} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => valueBoundOnChange(e)} />,
<EditWidgetSignalControl key={3} widget={widget} input validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />
<EditWidgetSimulatorControl key={2} widget={widget} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => valueBoundOnChange(e)} />,
<EditWidgetSignalControl key={3} widget={widget} input validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => handleChange(e)} />
);
break;
case 'Box':
@ -151,15 +151,15 @@ export default function createControls(widgetType = null, widget = null, session
// Restrict to only xml files (MIME)
let topologyControlFiles = files == null? [] : files.filter( file => file.type.includes('xml'));
dialogControls.push(
<EditImageWidgetControl key={0} sessionToken={sessionToken} widget={widget} files={topologyControlFiles} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />
<EditImageWidgetControl key={0} sessionToken={sessionToken} widget={widget} files={topologyControlFiles} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => handleChange(e)} />
);
break;
case 'NumberInput':
dialogControls.push(
<EditWidgetTextControl key={0} widget={widget} controlId={'name'} label={'Text'} placeholder={'Enter text'} validate={id => validateForm(id)} handleChange={e => handleChange(e)} />,
<EditWidgetSimulatorControl key={1} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => valueBoundOnChange(e)} />,
<EditWidgetSignalControl key={2} widget={widget} input validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />
<EditWidgetSimulatorControl key={1} widget={widget} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => valueBoundOnChange(e)} />,
<EditWidgetSignalControl key={2} widget={widget} input validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => handleChange(e)} />
);
break;

View file

@ -28,7 +28,7 @@ class EditWidgetSignalControl extends Component {
this.state = {
widget: {
simulator: {}
simulationModel: ''
}
};
}
@ -39,19 +39,16 @@ class EditWidgetSignalControl extends Component {
}
render() {
const simulationModel = this.props.simulationModels.find(m => m._id === this.state.widget.simulationModel);
let signalsToRender = [];
if (this.props.simulation) {
// get selected simulation model
const simulationModel = this.props.simulation.models.find( model => model.simulator.node === this.state.widget.simulator.node && model.simulator.simulator === this.state.widget.simulator.simulator );
// If simulation model update the signals to render
if (simulationModel != null) {
if (this.props.input) {
signalsToRender = simulationModel ? simulationModel.inputMapping : [];
} else {
signalsToRender = simulationModel ? simulationModel.outputMapping : [];
}
}
return (

View file

@ -54,14 +54,13 @@ class EditWidgetSignalsControl extends Component {
}
render() {
const simulationModel = this.props.simulationModels.find(m => m._id === this.state.widget.simulationModel);
let signalsToRender = [];
if (this.props.simulation) {
// get selected simulation model
const simulationModel = this.props.simulation.models.find( model => model.simulator.node === this.state.widget.simulator.node && model.simulator.simulator === this.state.widget.simulator.simulator );
if (simulationModel != null) {
// If simulation model update the signals to render
signalsToRender = simulationModel? simulationModel.outputMapping : [];
signalsToRender = simulationModel ? simulationModel.outputMapping : [];
}
return (

View file

@ -28,7 +28,7 @@ class EditWidgetSimulatorControl extends Component {
this.state = {
widget: {
simulator: {}
simulationModel: ''
}
};
}
@ -40,15 +40,15 @@ class EditWidgetSimulatorControl extends Component {
render() {
return (
<FormGroup controlId="simulator">
<FormGroup controlId="simulationModel">
<ControlLabel>Simulation Model</ControlLabel>
<FormControl componentClass="select" placeholder="Select simulation model" value={JSON.stringify(this.state.widget.simulator) || '' } onChange={(e) => this.props.handleChange(e)}>
<FormControl componentClass="select" placeholder="Select simulation model" value={this.state.widget.simulationModel || '' } onChange={(e) => this.props.handleChange(e)}>
{
this.props.simulation.models.length === 0? (
this.props.simulationModels.length === 0 ? (
<option disabled value style={{ display: 'none' }}> No simulation models available. </option>
) : (
this.props.simulation.models.map((model, index) => (
<option key={index} value={JSON.stringify(model.simulator)}>{model.name}</option>
this.props.simulationModels.map((model, index) => (
<option key={index} value={model._id}>{model.name}</option>
)))
}
</FormControl>

View file

@ -35,7 +35,7 @@ class EditWidgetDialog extends React.Component {
this.state = {
temporal: {
name: '',
simulator: {},
simulationModel: '',
signal: 0
}
};
@ -66,11 +66,7 @@ class EditWidgetDialog extends React.Component {
if (e.constructor === Array) {
// Every property in the array will be updated
let changes = e.reduce( (changesObject, event) => {
if (event.target.id === 'simulator') {
changesObject[event.target.id] = JSON.parse(event.target.value);
} else {
changesObject[event.target.id] = event.target.value;
}
changesObject[event.target.id] = event.target.value;
return changesObject;
}, {});
@ -78,9 +74,7 @@ class EditWidgetDialog extends React.Component {
this.setState({ temporal: Object.assign({}, this.state.temporal, changes ) });
} else {
let changeObject = {};
if (e.target.id === 'simulator') {
changeObject[e.target.id] = JSON.parse(e.target.value);
} else if (e.target.id === 'lockAspect') {
if (e.target.id === 'lockAspect') {
changeObject[e.target.id] = e.target.checked;
// correct image aspect if turned on
@ -135,7 +129,7 @@ class EditWidgetDialog extends React.Component {
this.props.sessionToken,
this.props.files,
(id) => this.validateForm(id),
this.props.simulation,
this.props.simulationModels,
(e) => this.handleChange(e));
}

View file

@ -68,8 +68,8 @@ class ImportNodeDialog extends React.Component {
}
// create file reader
var reader = new FileReader();
var self = this;
const reader = new FileReader();
const self = this;
reader.onload = function(event) {
// read simulator

View file

@ -21,6 +21,7 @@
import React from 'react';
import { FormGroup, FormControl, ControlLabel, HelpBlock } from 'react-bootstrap';
import _ from 'lodash';
import Table from '../table';
import TableColumn from '../table-column';
@ -35,7 +36,7 @@ class ImportSimulationModelDialog extends React.Component {
this.state = {
name: '',
simulator: { node: '', simulator: '' },
simulator: '',
outputLength: '1',
inputLength: '1',
outputMapping: [ { name: 'Signal', type: 'Type' } ],
@ -54,7 +55,7 @@ class ImportSimulationModelDialog extends React.Component {
resetState() {
this.setState({
name: '',
simulator: { node: this.props.nodes[0] ? this.props.nodes[0]._id : '', simulator: this.props.nodes[0].simulators[0] ? 0 : '' },
simulator: '',
outputLength: '1',
inputLength: '1',
outputMapping: [{ name: 'Signal', type: 'Type' }],
@ -86,11 +87,7 @@ class ImportSimulationModelDialog extends React.Component {
}
}
if (e.target.id === 'simulator') {
this.setState({ simulator: JSON.parse(e.target.value) });
} else {
this.setState({ [e.target.id]: e.target.value });
}
this.setState({ [e.target.id]: e.target.value });
}
handleMappingChange(key, event, row, column) {
@ -177,11 +174,9 @@ class ImportSimulationModelDialog extends React.Component {
</FormGroup>
<FormGroup controlId="simulator">
<ControlLabel>Simulator</ControlLabel>
<FormControl readOnly={!this.imported} componentClass="select" placeholder="Select simulator" value={JSON.stringify({ node: this.state.simulator.node, simulator: this.state.simulator.simulator})} onChange={(e) => this.handleChange(e)}>
{this.props.nodes.map(node => (
node.simulators.map((simulator, index) => (
<option key={node._id + index} value={JSON.stringify({ node: node.name, simulator: simulator.name })}>{node.name}/{simulator.name}</option>
))
<FormControl readOnly={!this.imported} 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}>{_.get(simulator, 'properties.name') || _.get(simulator, 'rawProperties.name')}</option>
))}
</FormControl>
</FormGroup>

View file

@ -0,0 +1,146 @@
/**
* File: new-simulator.js
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
* Date: 27.03.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 { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
import _ from 'lodash';
import Dialog from './dialog';
class ImportSimulatorDialog extends React.Component {
valid = false;
imported = false;
constructor(props) {
super(props);
this.state = {
name: '',
endpoint: '',
uuid: ''
};
}
onClose(canceled) {
if (canceled === false) {
if (this.valid) {
const data = {
properties: {
name: this.state.name
},
uuid: this.state.uuid
};
if (this.state.endpoint != null && this.state.endpoint !== "" && this.state.endpoint !== 'http://') {
data.properties.endpoint = this.state.endpoint;
}
this.props.onClose(data);
}
} else {
this.props.onClose();
}
}
handleChange(e) {
this.setState({ [e.target.id]: e.target.value });
}
resetState() {
this.setState({ name: '', endpoint: 'http://', uuid: '' });
}
loadFile(fileList) {
// get file
const file = fileList[0];
if (!file.type.match('application/json')) {
return;
}
// create file reader
const reader = new FileReader();
const self = this;
reader.onload = function(event) {
// read simulator
const simulator = JSON.parse(event.target.result);
self.imported = true;
self.setState({
name: _.get(simulator, 'properties.name') || _.get(simulator, 'rawProperties.name'),
endpoint: _.get(simulator, 'properties.endpoint') || _.get(simulator, 'rawProperties.endpoint'),
uuid: simulator.uuid
});
};
reader.readAsText(file);
}
validateForm(target) {
// check all controls
let name = true;
let uuid = true;
if (this.state.name === '') {
name = false;
}
if (this.state.uuid === '') {
uuid = false;
}
this.valid = name || uuid;
// return state to control
if (target === 'name') return name ? "success" : "error";
if (target === 'uuid') return uuid ? "success" : "error";
}
render() {
return (
<Dialog show={this.props.show} title="New Simulator" buttonTitle="Add" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
<form>
<FormGroup controlId="file">
<ControlLabel>Simulator File</ControlLabel>
<FormControl type="file" onChange={(e) => this.loadFile(e.target.files)} />
</FormGroup>
<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="endpoint">
<ControlLabel>Endpoint</ControlLabel>
<FormControl type="text" placeholder="Enter endpoint" value={this.state.endpoint} onChange={(e) => this.handleChange(e)} />
<FormControl.Feedback />
</FormGroup>
<FormGroup controlId="uuid" validationState={this.validateForm('uuid')}>
<ControlLabel>UUID</ControlLabel>
<FormControl type="text" placeholder="Enter uuid" value={this.state.uuid} onChange={(e) => this.handleChange(e)} />
<FormControl.Feedback />
</FormGroup>
</form>
</Dialog>
);
}
}
export default ImportSimulatorDialog;

View file

@ -21,6 +21,7 @@
import React from 'react';
import { FormGroup, FormControl, ControlLabel, HelpBlock } from 'react-bootstrap';
import _ from 'lodash';
import Table from '../table';
import TableColumn from '../table-column';
@ -34,7 +35,7 @@ class NewSimulationModelDialog extends React.Component {
this.state = {
name: '',
simulator: { node: '', simulator: '' },
simulator: '',
outputLength: '1',
inputLength: '1',
outputMapping: [ { name: 'Signal', type: 'Type' } ],
@ -74,11 +75,7 @@ class NewSimulationModelDialog extends React.Component {
}
}
if (e.target.id === 'simulator') {
this.setState({ simulator: JSON.parse(e.target.value) });
} else {
this.setState({ [e.target.id]: e.target.value });
}
this.setState({ [e.target.id]: e.target.value });
}
handleMappingChange(key, event, row, column) {
@ -96,7 +93,7 @@ class NewSimulationModelDialog extends React.Component {
resetState() {
this.setState({
name: '',
simulator: { node: this.props.nodes[0] ? this.props.nodes[0]._id : '', simulator: this.props.nodes[0].simulators[0] ? 0 : '' },
simulator: this.props.simulators[0]._id || '',
outputLength: '1',
inputLength: '1',
outputMapping: [{ name: 'Signal', type: 'Type' }],
@ -109,16 +106,11 @@ class NewSimulationModelDialog extends React.Component {
let name = true;
let inputLength = true;
let outputLength = true;
let simulator = true;
if (this.state.name === '') {
name = false;
}
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;
@ -128,13 +120,12 @@ class NewSimulationModelDialog extends React.Component {
inputLength = false;
}
this.valid = name && inputLength && outputLength && simulator;
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";
else if (target === 'simulator') return simulator ? "success" : "error";
}
render() {
@ -146,13 +137,11 @@ class NewSimulationModelDialog extends React.Component {
<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')}>
<FormGroup controlId="simulator">
<ControlLabel>Simulator</ControlLabel>
<FormControl componentClass="select" placeholder="Select simulator" value={JSON.stringify({ node: this.state.simulator.node, simulator: this.state.simulator.simulator})} onChange={(e) => this.handleChange(e)}>
{this.props.nodes.map(node => (
node.simulators.map((simulator, index) => (
<option key={node._id + index} value={JSON.stringify({ node: node._id, simulator: index })}>{node.name}/{simulator.name}</option>
))
<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>

View file

@ -25,20 +25,33 @@ import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
import Dialog from './dialog';
class NewSimulatorDialog extends React.Component {
valid: false;
valid = false;
constructor(props) {
super(props);
this.state = {
name: ''
name: '',
endpoint: '',
uuid: ''
};
}
onClose(canceled) {
if (canceled === false) {
if (this.valid) {
this.props.onClose(this.state);
const data = {
properties: {
name: this.state.name
},
uuid: this.state.uuid
};
if (this.state.endpoint != null && this.state.endpoint !== "" && this.state.endpoint !== 'http://') {
data.properties.endpoint = this.state.endpoint;
}
this.props.onClose(data);
}
} else {
this.props.onClose();
@ -50,21 +63,27 @@ class NewSimulatorDialog extends React.Component {
}
resetState() {
this.setState({ name: '' });
this.setState({ name: '', endpoint: 'http://', uuid: '' });
}
validateForm(target) {
// check all controls
var name = true;
let name = true;
let uuid = true;
if (this.state.name === '' || this.props.node.simulators == null || this.props.node.simulators.find(simulator => simulator.name === this.state.name) !== undefined) {
if (this.state.name === '') {
name = false;
}
this.valid = name;
if (this.state.uuid === '') {
uuid = false;
}
this.valid = name || uuid;
// return state to control
if (target === 'name') return name ? "success" : "error";
if (target === 'uuid') return uuid ? "success" : "error";
}
render() {
@ -76,6 +95,16 @@ class NewSimulatorDialog extends React.Component {
<FormControl type="text" placeholder="Enter name" value={this.state.name} onChange={(e) => this.handleChange(e)} />
<FormControl.Feedback />
</FormGroup>
<FormGroup controlId="endpoint">
<ControlLabel>Endpoint</ControlLabel>
<FormControl type="text" placeholder="Enter endpoint" value={this.state.endpoint} onChange={(e) => this.handleChange(e)} />
<FormControl.Feedback />
</FormGroup>
<FormGroup controlId="uuid" validationState={this.validateForm('uuid')}>
<ControlLabel>UUID</ControlLabel>
<FormControl type="text" placeholder="Enter uuid" value={this.state.uuid} onChange={(e) => this.handleChange(e)} />
<FormControl.Feedback />
</FormGroup>
</form>
</Dialog>
);

View file

@ -0,0 +1,68 @@
/**
* File: simulator-actionm.js
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
* Date: 12.04.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 { Button, DropdownButton, MenuItem } from 'react-bootstrap';
class SimulatorAction extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedAction: null
};
}
componentWillReceiveProps(nextProps) {
if (this.state.selectedAction == null) {
if (nextProps.actions != null && nextProps.actions.length > 0) {
this.setState({ selectedAction: nextProps.actions[0] });
}
}
}
setAction = id => {
// search action
for (let action of this.props.actions) {
if (action.id === id) {
this.setState({ selectedAction: action });
}
}
}
render() {
const actionList = this.props.actions.map(action => (
<MenuItem key={action.id} eventKey={action.id} active={this.state.selectedAction === action.id}>
{action.title}
</MenuItem>
));
return <div>
<DropdownButton title={this.state.selectedAction != null ? this.state.selectedAction.title : ''} id="action-dropdown" onSelect={this.setAction}>
{actionList}
</DropdownButton>
<Button style={{ marginLeft: '5px' }} disabled={this.props.runDisabled} onClick={() => this.props.runAction(this.state.selectedAction)}>Run</Button>
</div>;
}
}
export default SimulatorAction;

View file

@ -20,6 +20,7 @@
******************************************************************************/
import React, { Component } from 'react';
import _ from 'lodash';
import { Table, Button, Glyphicon, FormControl, Label, Checkbox } from 'react-bootstrap';
import { Link } from 'react-router-dom';
@ -46,19 +47,27 @@ class CustomTable extends Component {
addCell(data, index, child) {
// add data to cell
const dataKey = child.props.dataKey;
var cell = [];
let content = null;
if (dataKey && data[dataKey] != null) {
// get content
var content;
const modifier = child.props.modifier;
if (modifier) {
content = modifier(data[dataKey]);
} else {
content = data[dataKey].toString();
if ('dataKeys' in child.props) {
for (let key of child.props.dataKeys) {
if (_.get(data, key) != null) {
content = _.get(data, key);
break;
}
}
} else if ('dataKey' in child.props) {
content = _.get(data, child.props.dataKey);
}
const modifier = child.props.modifier;
if (modifier && content != null) {
content = modifier(content);
}
let cell = [];
if (content != null) {
content = content.toString();
// check if cell should be a link
const linkKey = child.props.linkKey;
@ -89,21 +98,21 @@ class CustomTable extends Component {
// add buttons
if (child.props.editButton) {
cell.push(<Button bsClass='table-control-button' onClick={() => child.props.onEdit(index)}><Glyphicon glyph='pencil' /></Button>);
cell.push(<Button bsClass='table-control-button' onClick={() => child.props.onEdit(index)} disabled={child.props.onEdit == null}><Glyphicon glyph='pencil' /></Button>);
}
if (child.props.deleteButton) {
cell.push(<Button bsClass='table-control-button' onClick={() => child.props.onDelete(index)}><Glyphicon glyph='remove' /></Button>);
cell.push(<Button bsClass='table-control-button' onClick={() => child.props.onDelete(index)} disabled={child.props.onDelete == null}><Glyphicon glyph='remove' /></Button>);
}
if (child.props.checkbox) {
const checkboxKey = this.props.checkboxKey;
cell.push(<Checkbox className="table-control-checkbox" inline checked={checkboxKey ? data[checkboxKey] : null} onChange={e => child.props.onChecked(index, e)}></Checkbox>);
cell.push(<Checkbox className="table-control-checkbox" inline checked={checkboxKey ? data[checkboxKey] : null} onChange={e => child.props.onChecked(index, e)} />);
}
if (child.props.exportButton) {
cell.push(<Button bsClass='table-control-button' onClick={() => child.props.onExport(index)}><Glyphicon glyph='export' /></Button>);
cell.push(<Button bsClass='table-control-button' onClick={() => child.props.onExport(index)} disabled={child.props.onExport == null}><Glyphicon glyph='export' /></Button>);
}
return cell;
@ -155,7 +164,7 @@ class CustomTable extends Component {
render() {
// get children
var children = this.props.children;
let children = this.props.children;
if (Array.isArray(this.props.children) === false) {
children = [ children ];
}

View file

@ -12,7 +12,7 @@ import WidgetSlider from './widget-slider';
class WidgetFactory {
static createWidgetOfType(type, position, defaultSimulator = null) {
static createWidgetOfType(type, position, defaultSimulationModel = null) {
let widget = {
name: 'Name',
@ -28,7 +28,7 @@ class WidgetFactory {
// set type specific properties
switch(type) {
case 'Lamp':
widget.simulator = defaultSimulator;
widget.simulationModel = defaultSimulationModel;
widget.signal = 0;
widget.minWidth = 5;
widget.minHeight = 5;
@ -39,7 +39,7 @@ class WidgetFactory {
widget.threshold = 0.5;
break;
case 'Value':
widget.simulator = defaultSimulator;
widget.simulationModel = defaultSimulationModel;
widget.signal = 0;
widget.minWidth = 70;
widget.minHeight = 20;
@ -50,7 +50,7 @@ class WidgetFactory {
widget.showUnit = false;
break;
case 'Plot':
widget.simulator = defaultSimulator;
widget.simulationModel = defaultSimulationModel;
widget.signals = [ 0 ];
widget.ylabel = '';
widget.time = 60;
@ -63,7 +63,7 @@ class WidgetFactory {
widget.yUseMinMax = false;
break;
case 'Table':
widget.simulator = defaultSimulator;
widget.simulationModel = defaultSimulationModel;
widget.minWidth = 200;
widget.width = 300;
widget.height = 200;
@ -78,7 +78,7 @@ class WidgetFactory {
widget.fontColor = 0;
break;
case 'PlotTable':
widget.simulator = defaultSimulator;
widget.simulationModel = defaultSimulationModel;
widget.preselectedSignals = [];
widget.signals = []; // initialize selected signals
widget.ylabel = '';
@ -105,7 +105,7 @@ class WidgetFactory {
widget.height = 100;
widget.background_color = 1;
widget.font_color = 0;
widget.simulator = defaultSimulator;
widget.simulationModel = defaultSimulationModel;
widget.signal = 0;
break;
case 'NumberInput':
@ -113,7 +113,7 @@ class WidgetFactory {
widget.minHeight = 50;
widget.width = 200;
widget.height = 50;
widget.simulator = defaultSimulator;
widget.simulationModel = defaultSimulationModel;
widget.signal = 0;
break;
case 'Slider':
@ -122,11 +122,11 @@ class WidgetFactory {
widget.width = 400;
widget.height = 50;
widget.orientation = WidgetSlider.OrientationTypes.HORIZONTAL.value; // Assign default orientation
widget.simulator = defaultSimulator;
widget.simulationModel = defaultSimulationModel;
widget.signal = 0;
break;
case 'Gauge':
widget.simulator = defaultSimulator;
widget.simulationModel = defaultSimulationModel;
widget.signal = 0;
widget.minWidth = 100;
widget.minHeight = 150;

View file

@ -35,19 +35,23 @@ class WidgetGauge extends Component {
}
componentWillReceiveProps(nextProps) {
// update value
const simulator = nextProps.widget.simulator;
if (nextProps.simulationModel == null) {
this.setState({ value: 0 });
return;
}
if (nextProps.data == null || nextProps.data[simulator.node] == null
|| nextProps.data[simulator.node][simulator.simulator] == null
|| nextProps.data[simulator.node][simulator.simulator].output.values.length === 0
|| nextProps.data[simulator.node][simulator.simulator].output.values[0].length === 0) {
const simulator = nextProps.simulationModel.simulator;
// update value
if (nextProps.data == null || nextProps.data[simulator] == null
|| nextProps.data[simulator].output.values.length === 0
|| nextProps.data[simulator].output.values[0].length === 0) {
this.setState({ value: 0 });
return;
}
// check if value has changed
const signal = nextProps.data[simulator.node][simulator.simulator].output.values[nextProps.widget.signal];
const signal = nextProps.data[simulator].output.values[nextProps.widget.signal];
// Take just 3 decimal positions
// Note: Favor this method over Number.toFixed(n) in order to avoid a type conversion, since it returns a String
if (signal != null) {
@ -177,9 +181,8 @@ class WidgetGauge extends Component {
const componentClass = this.props.editing ? "gauge-widget editing" : "gauge-widget";
let signalType = null;
if (this.props.simulation) {
const simulationModel = this.props.simulation.models.filter((model) => model.simulator.node === this.props.widget.simulator.node && model.simulator.simulator === this.props.widget.simulator.simulator)[0];
signalType = (simulationModel != null && simulationModel.length > 0 && this.props.widget.signal < simulationModel.length) ? simulationModel.outputMapping[this.props.widget.signal].type : '';
if (this.props.simulationModel != null) {
signalType = (this.props.simulationModel != null && this.props.simulationModel.outputLength > 0 && this.props.widget.signal < this.props.simulationModel.outputLength) ? this.props.simulationModel.outputMapping[this.props.widget.signal].type : '';
}
return (

View file

@ -34,17 +34,21 @@ class WidgetLamp extends Component {
}
componentWillReceiveProps(nextProps) {
// update value
const simulator = nextProps.widget.simulator.simulator;
const node = nextProps.widget.simulator.node;
if (nextProps.simulationModel == null) {
this.setState({ value: '' });
return;
}
if (nextProps.data == null || nextProps.data[node] == null || nextProps.data[node][simulator] == null || nextProps.data[node][simulator].output.values == null) {
const simulator = nextProps.simulationModel.simulator;
// update value
if (nextProps.data == null || nextProps.data[simulator] == null || nextProps.data[simulator].output.values == null) {
this.setState({ value: '' });
return;
}
// check if value has changed
const signal = nextProps.data[node][simulator].output.values[nextProps.widget.signal];
const signal = nextProps.data[simulator].output.values[nextProps.widget.signal];
if (signal != null && this.state.value !== signal[signal.length - 1].y) {
this.setState({ value: signal[signal.length - 1].y });
}

View file

@ -37,13 +37,17 @@ class WidgetPlotTable extends Component {
}
componentWillReceiveProps(nextProps) {
if (nextProps.simulationModel == null) {
return;
}
// Update internal selected signals state with props (different array objects)
if (this.props.widget.signals !== nextProps.widget.signals) {
this.setState( {signals: nextProps.widget.signals});
}
// Identify if there was a change in the preselected signals
if (nextProps.simulation && (JSON.stringify(nextProps.widget.preselectedSignals) !== JSON.stringify(this.props.widget.preselectedSignals) || this.state.preselectedSignals.length === 0)) {
if (JSON.stringify(nextProps.widget.preselectedSignals) !== JSON.stringify(this.props.widget.preselectedSignals) || this.state.preselectedSignals.length === 0) {
// Update the currently selected signals by intersecting with the preselected signals
// Do the same with the plot values
var intersection = this.computeIntersection(nextProps.widget.preselectedSignals, nextProps.widget.signals);
@ -60,35 +64,24 @@ class WidgetPlotTable extends Component {
}
updatePreselectedSignalsState(nextProps) {
const simulator = nextProps.widget.simulator;
// Create checkboxes using the signal indices from simulation model
const preselectedSignals = nextProps.simulationModel.outputMapping.reduce(
// Loop through simulation model signals
(accum, model_signal, signal_index) => {
// Append them if they belong to the current selected type
if (nextProps.widget.preselectedSignals.indexOf(signal_index) > -1) {
accum.push(
{
index: signal_index,
name: model_signal.name,
type: model_signal.type
}
)
}
return accum;
}, []);
// get simulation model
const simulationModel = nextProps.simulation.models.find((model) => {
return (model.simulator.node === simulator.node && model.simulator.simulator === simulator.simulator);
});
let preselectedSignals = [];
// Proceed if a simulation model is available
if (simulationModel) {
// Create checkboxes using the signal indices from simulation model
preselectedSignals = simulationModel.outputMapping.reduce(
// Loop through simulation model signals
(accum, model_signal, signal_index) => {
// Append them if they belong to the current selected type
if (nextProps.widget.preselectedSignals.indexOf(signal_index) > -1) {
accum.push(
{
index: signal_index,
name: model_signal.name,
type: model_signal.type
}
)
}
return accum;
}, []);
}
this.setState({ preselectedSignals: preselectedSignals });
this.setState({ preselectedSignals });
}
updateSignalSelection(signal_index, checked) {
@ -100,14 +93,18 @@ class WidgetPlotTable extends Component {
}
render() {
var checkBoxes = [];
let checkBoxes = [];
// Data passed to plot
let simulator = this.props.widget.simulator;
if (this.props.simulationModel == null) {
return <div />;
}
const simulator = this.props.simulationModel.simulator;
let simulatorData = [];
if (this.props.data[simulator.node] != null && this.props.data[simulator.node][simulator.simulator] != null) {
simulatorData = this.props.data[simulator.node][simulator.simulator].output.values.filter((values, index) => (
if (this.props.data[simulator] != null) {
simulatorData = this.props.data[simulator].output.values.filter((values, index) => (
this.props.widget.signals.findIndex(value => value === index) !== -1
));
}

View file

@ -35,20 +35,23 @@ class WidgetPlot extends React.Component {
}
componentWillReceiveProps(nextProps) {
const simulator = nextProps.widget.simulator;
const simulation = nextProps.simulation;
if (nextProps.simulationModel == null) {
this.setState({ data: [], legend: [] });
return;
}
const simulator = nextProps.simulationModel.simulator;
// Proceed if a simulation with models and a simulator are available
if (simulator && nextProps.data[simulator.node] != null && nextProps.data[simulator.node][simulator.simulator] != null && simulation && simulation.models.length > 0) {
const model = simulation.models.find(model => model.simulator.node === simulator.node && model.simulator.simulator === simulator.simulator);
if (simulator && nextProps.data[simulator] != null && nextProps.data[simulator] != null) {
const chosenSignals = nextProps.widget.signals;
const data = nextProps.data[simulator.node][simulator.simulator].output.values.filter((values, index) => (
const data = nextProps.data[simulator].output.values.filter((values, index) => (
nextProps.widget.signals.findIndex(value => value === index) !== -1
));
// Query the signals that will be displayed in the legend
const legend = model.outputMapping.reduce( (accum, model_signal, signal_index) => {
const legend = nextProps.simulationModel.outputMapping.reduce( (accum, model_signal, signal_index) => {
if (chosenSignals.includes(signal_index)) {
accum.push({ index: signal_index, name: model_signal.name, type: model_signal.type });
}

View file

@ -35,14 +35,20 @@ class WidgetTable extends Component {
}
componentWillReceiveProps(nextProps) {
// check data
const simulator = nextProps.widget.simulator;
if (nextProps.simulationModel == null) {
this.setState({ rows: [], sequence: null });
return;
}
if (nextProps.simulation == null || nextProps.data == null || nextProps.data[simulator.node] == null
|| nextProps.data[simulator.node][simulator.simulator] == null
|| nextProps.data[simulator.node][simulator.simulator].output.length === 0
|| nextProps.data[simulator.node][simulator.simulator].output.values.length === 0
|| nextProps.data[simulator.node][simulator.simulator].output.values[0].length === 0) {
const simulator = nextProps.simulationModel.simulator;
// check data
if (nextProps.data == null
|| nextProps.data[simulator] == null
|| nextProps.data[simulator].output == null
|| nextProps.data[simulator].output.length === 0
|| nextProps.data[simulator].output.values.length === 0
|| nextProps.data[simulator].output.values[0].length === 0) {
// clear values
this.setState({ rows: [], sequence: null });
return;
@ -53,24 +59,19 @@ class WidgetTable extends Component {
return;
}*/
// get simulation model
const simulationModel = nextProps.simulation.models.find((model) => {
return (model.simulator.node === simulator.node && model.simulator.simulator === simulator.simulator);
});
// get rows
var rows = [];
const rows = [];
nextProps.data[simulator.node][simulator.simulator].output.values.forEach((signal, index) => {
if (index < simulationModel.outputMapping.length) {
nextProps.data[simulator].output.values.forEach((signal, index) => {
if (index < nextProps.simulationModel.outputMapping.length) {
rows.push({
name: simulationModel.outputMapping[index].name,
name: nextProps.simulationModel.outputMapping[index].name,
value: signal[signal.length - 1].y.toFixed(3)
});
}
});
this.setState({ rows: rows, sequence: nextProps.data[simulator.node][simulator.simulator].output.sequence });
this.setState({ rows: rows, sequence: nextProps.data[simulator].output.sequence });
}
render() {

View file

@ -33,27 +33,15 @@ class WidgetValue extends Component {
componentWillReceiveProps(nextProps) {
// update value
const simulator = nextProps.widget.simulator.simulator;
const node = nextProps.widget.simulator.node;
if (nextProps.data == null || nextProps.data[node] == null || nextProps.data[node][simulator] == null || nextProps.data[node][simulator].output.values == null) {
if (nextProps.data == null || nextProps.simulationModel == null || nextProps.data[nextProps.simulationModel.simulator] == null || nextProps.data[nextProps.simulationModel.simulator].output == null || nextProps.data[nextProps.simulationModel.simulator].output.values == null) {
this.setState({ value: '' });
return;
}
// get unit from simulation model
let unit = '';
if (nextProps.simulation) {
const simulationModel = nextProps.simulation.models.find(model => model.simulator.node === node && model.simulator.simulator === simulator);
if (nextProps.widget.signal < simulationModel.outputMapping.length) {
unit = simulationModel.outputMapping[nextProps.widget.signal].type;
}
}
const unit = nextProps.simulationModel.outputMapping[nextProps.widget.signal].type;
// check if value has changed
const signal = nextProps.data[node][simulator].output.values[nextProps.widget.signal];
const signal = nextProps.data[nextProps.simulationModel.simulator].output.values[nextProps.widget.signal];
if (signal != null && this.state.value !== signal[signal.length - 1].y) {
this.setState({ value: signal[signal.length - 1].y, unit });
}

View file

@ -29,7 +29,7 @@ import { Col } from 'react-bootstrap';
import AppDispatcher from '../app-dispatcher';
import SimulationStore from '../stores/simulation-store';
import NodeStore from '../stores/node-store';
import SimulatorStore from '../stores/simulator-store';
import UserStore from '../stores/user-store';
import NotificationsDataManager from '../data-managers/notifications-data-manager';
@ -51,14 +51,14 @@ import '../styles/app.css';
class App extends React.Component {
static getStores() {
return [ NodeStore, UserStore, SimulationStore ];
return [ SimulatorStore, UserStore, SimulationStore ];
}
static calculateState(prevState) {
let currentUser = UserStore.getState().currentUser;
return {
nodes: NodeStore.getState(),
simulators: SimulatorStore.getState(),
simulations: SimulationStore.getState(),
currentRole: currentUser ? currentUser.role : '',
token: UserStore.getState().token,
@ -85,7 +85,7 @@ class App extends React.Component {
componentDidMount() {
// load all simulators and simulations to fetch simulation data
AppDispatcher.dispatch({
type: 'nodes/start-load',
type: 'simulators/start-load',
token: this.state.token
});

View file

@ -21,7 +21,7 @@
import React, { Component } from 'react';
import { Container } from 'flux/utils';
import { Button, Modal, Glyphicon } from 'react-bootstrap';
import { Button, Glyphicon } from 'react-bootstrap';
import FileSaver from 'file-saver';
import AppDispatcher from '../app-dispatcher';
@ -36,6 +36,8 @@ import NewVisualzationDialog from '../components/dialog/new-visualization';
import EditVisualizationDialog from '../components/dialog/edit-visualization';
import ImportVisualizationDialog from '../components/dialog/import-visualization';
import DeleteDialog from '../components/dialog/delete-dialog';
class Visualizations extends Component {
static getStores() {
return [ ProjectStore, VisualizationStore, UserStore, SimulationStore ];
@ -99,6 +101,8 @@ class Visualizations extends Component {
}
closeNewModal(data) {
this.setState({ newModal: false });
if (data) {
// add project to visualization
data.project = this.state.project._id;
@ -108,14 +112,24 @@ class Visualizations extends Component {
data: data,
token: this.state.sessionToken
});
}
this.setState({ newModal: false });
this.setState({ project: {} }, () => {
AppDispatcher.dispatch({
type: 'projects/start-load',
data: this.props.match.params.project,
token: this.state.sessionToken
});
});
}
}
confirmDeleteModal() {
closeDeleteModal = confirmDelete => {
this.setState({ deleteModal: false });
if (confirmDelete === false) {
return;
}
AppDispatcher.dispatch({
type: 'visualizations/start-remove',
data: this.state.modalData,
@ -206,20 +220,7 @@ class Visualizations extends Component {
<EditVisualizationDialog show={this.state.editModal} onClose={(data) => this.closeEditModal(data)} visualization={this.state.modalData} />
<ImportVisualizationDialog show={this.state.importModal} onClose={data => this.closeImportModal(data)} simulation={this.state.simulation} />
<Modal keyboard show={this.state.deleteModal} onHide={() => this.setState({ deleteModal: false })} onKeyPress={this.onModalKeyPress}>
<Modal.Header>
<Modal.Title>Delete Visualization</Modal.Title>
</Modal.Header>
<Modal.Body>
Are you sure you want to delete the visualization <strong>'{this.state.modalData.name}'</strong>?
</Modal.Body>
<Modal.Footer>
<Button onClick={() => this.setState({ deleteModal: false })}>Cancel</Button>
<Button bsStyle="danger" onClick={() => this.confirmDeleteModal()}>Delete</Button>
</Modal.Footer>
</Modal>
<DeleteDialog title="visualization" name={this.state.modalData.name} show={this.state.deleteModal} onClose={this.closeDeleteModal} />
</div>
);
}

View file

@ -21,7 +21,7 @@
import React from 'react';
import { Container } from 'flux/utils';
import { Button, Modal, Glyphicon } from 'react-bootstrap';
import { Button, Glyphicon } from 'react-bootstrap';
import AppDispatcher from '../app-dispatcher';
import ProjectStore from '../stores/project-store';
@ -33,6 +33,8 @@ import TableColumn from '../components/table-column';
import NewProjectDialog from '../components/dialog/new-project';
import EditProjectDialog from '../components/dialog/edit-project';
import DeleteDialog from '../components/dialog/delete-dialog';
class Projects extends React.Component {
static getStores() {
return [ ProjectStore, SimulationStore, UserStore ];
@ -75,9 +77,13 @@ class Projects extends React.Component {
}
}
confirmDeleteModal() {
closeDeleteModal = confirmDelete => {
this.setState({ deleteModal: false });
if (confirmDelete === false) {
return;
}
AppDispatcher.dispatch({
type: 'projects/start-remove',
data: this.state.modalData,
@ -143,20 +149,7 @@ class Projects extends React.Component {
<NewProjectDialog show={this.state.newModal} onClose={(data) => this.closeNewModal(data)} simulations={this.state.simulations} />
<EditProjectDialog show={this.state.editModal} onClose={(data) => this.closeEditModal(data)} project={this.state.modalData} simulations={this.state.simulations} />
<Modal keyboard show={this.state.deleteModal} onHide={() => this.setState({ deleteModal: false })} onKeyPress={this.onModalKeyPress}>
<Modal.Header>
<Modal.Title>Delete Project</Modal.Title>
</Modal.Header>
<Modal.Body>
Are you sure you want to delete the project <strong>'{this.state.modalData.name}'</strong>?
</Modal.Body>
<Modal.Footer>
<Button onClick={() => this.setState({ deleteModal: false})}>Cancel</Button>
<Button bsStyle='danger' onClick={() => this.confirmDeleteModal()}>Delete</Button>
</Modal.Footer>
</Modal>
<DeleteDialog title="project" name={this.state.modalData.name} show={this.state.deleteModal} onClose={this.closeDeleteModal} />
</div>
);
}

View file

@ -21,11 +21,13 @@
import React from 'react';
import { Container } from 'flux/utils';
import { Button, Modal, Glyphicon } from 'react-bootstrap';
import { Button, Glyphicon } from 'react-bootstrap';
import FileSaver from 'file-saver';
import _ from 'lodash';
import SimulationStore from '../stores/simulation-store';
import NodeStore from '../stores/node-store';
import SimulatorStore from '../stores/simulator-store';
import SimulationModelStore from '../stores/simulation-model-store';
import UserStore from '../stores/user-store';
import AppDispatcher from '../app-dispatcher';
@ -35,16 +37,41 @@ 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';
import DeleteDialog from '../components/dialog/delete-dialog';
class Simulation extends React.Component {
static getStores() {
return [ SimulationStore, NodeStore, UserStore ];
return [ SimulationStore, SimulatorStore, SimulationModelStore, UserStore ];
}
static calculateState() {
static calculateState(prevState, props) {
// get selected simulation
const sessionToken = UserStore.getState().token;
let simulation = SimulationStore.getState().find(s => s._id === props.match.params.simulation);
if (simulation == null) {
AppDispatcher.dispatch({
type: 'simulations/start-load',
data: props.match.params.simulation,
token: sessionToken
});
simulation = {};
}
// load models
let simulationModels = [];
if (simulation.models != null) {
simulationModels = SimulationModelStore.getState().filter(m => simulation.models.includes(m._id));
}
return {
simulations: SimulationStore.getState(),
nodes: NodeStore.getState(),
sessionToken: UserStore.getState().token,
simulationModels,
simulation,
simulators: SimulatorStore.getState(),
sessionToken,
newModal: false,
deleteModal: false,
@ -53,7 +80,7 @@ class Simulation extends React.Component {
modalData: {},
modalIndex: null,
simulation: {}
selectedSimulationModels: []
}
}
@ -64,24 +91,13 @@ class Simulation extends React.Component {
});
AppDispatcher.dispatch({
type: 'nodes/start-load',
type: 'simulationModels/start-load',
token: this.state.sessionToken
});
}
componentDidUpdate() {
if (this.state.simulation._id !== this.props.match.params.simulation) {
this.reloadSimulation();
}
}
reloadSimulation() {
// select simulation by param id
this.state.simulations.forEach((simulation) => {
if (simulation._id === this.props.match.params.simulation) {
// JSON.parse(JSON.stringify(obj)) = deep clone to make also copy of widget objects inside
this.setState({ simulation: JSON.parse(JSON.stringify(simulation)) });
}
AppDispatcher.dispatch({
type: 'simulators/start-load',
token: this.state.sessionToken
});
}
@ -89,26 +105,34 @@ class Simulation extends React.Component {
this.setState({ newModal : false });
if (data) {
this.state.simulation.models.push(data);
data.simulation = this.state.simulation._id;
AppDispatcher.dispatch({
type: 'simulations/start-edit',
data: this.state.simulation,
type: 'simulationModels/start-add',
data,
token: this.state.sessionToken
});
this.setState({ simulation: {} }, () => {
AppDispatcher.dispatch({
type: 'simulations/start-load',
data: this.props.match.params.simulation,
token: this.state.sessionToken
});
});
}
}
confirmDeleteModal() {
// remove model from simulation
var simulation = this.state.simulation;
simulation.models.splice(this.state.modalIndex, 1);
closeDeleteModal = confirmDelete => {
this.setState({ deleteModal: false });
this.setState({ deleteModal: false, simulation: simulation });
if (confirmDelete === false) {
return;
}
AppDispatcher.dispatch({
type: 'simulations/start-edit',
data: simulation,
type: 'simulationModels/start-remove',
data: this.state.modalData,
token: this.state.sessionToken
});
}
@ -117,13 +141,9 @@ class Simulation extends React.Component {
this.setState({ editModal : false });
if (data) {
var simulation = this.state.simulation;
simulation.models[this.state.modalIndex] = data;
this.setState({ simulation: simulation });
AppDispatcher.dispatch({
type: 'simulations/start-edit',
data: simulation,
type: 'simulationModels/start-edit',
data,
token: this.state.sessionToken
});
}
@ -133,43 +153,92 @@ class Simulation extends React.Component {
this.setState({ importModal: false });
if (data) {
this.state.simulation.models.push(data);
data.simulation = this.state.simulation._id;
AppDispatcher.dispatch({
type: 'simulations/start-edit',
data: this.state.simulation,
type: 'simulationModels/start-add',
data,
token: this.state.sessionToken
});
this.setState({ simulation: {} }, () => {
AppDispatcher.dispatch({
type: 'simulations/start-load',
data: this.props.match.params.simulation,
token: this.state.sessionToken
});
});
}
}
getSimulatorName(simulator) {
var name = "undefined";
this.state.nodes.forEach(node => {
if (node._id === simulator.node) {
name = node.name + '/' + node.simulators[simulator.simulator].name;
getSimulatorName(simulatorId) {
for (let simulator of this.state.simulators) {
if (simulator._id === simulatorId) {
if ('name' in simulator.rawProperties) {
return _.get(simulator, 'properties.name') || _.get(simulator, 'rawProperties.name');
} else {
return simulator.uuid;
}
}
});
return name;
}
}
exportModel(index) {
// filter properties
let simulationModel = Object.assign({}, this.state.simulation.models[index]);
delete simulationModel.simulator;
const model = Object.assign({}, this.state.simulationModels[index]);
delete model.simulator;
delete model.simulation;
// show save dialog
const blob = new Blob([JSON.stringify(simulationModel, null, 2)], { type: 'application/json' });
FileSaver.saveAs(blob, 'simulation model - ' + simulationModel.name + '.json');
const blob = new Blob([JSON.stringify(model, null, 2)], { type: 'application/json' });
FileSaver.saveAs(blob, 'simulation model - ' + model.name + '.json');
}
onModalKeyPress = (event) => {
if (event.key === 'Enter') {
event.preventDefault();
this.confirmDeleteModal();
onSimulationModelChecked(index, event) {
const selectedSimulationModels = Object.assign([], this.state.selectedSimulationModels);
for (let key in selectedSimulationModels) {
if (selectedSimulationModels[key] === index) {
// update existing entry
if (event.target.checked) {
return;
}
selectedSimulationModels.splice(key, 1);
this.setState({ selectedSimulationModels });
return;
}
}
// add new entry
if (event.target.checked === false) {
return;
}
selectedSimulationModels.push(index);
this.setState({ selectedSimulationModels });
}
runAction = action => {
for (let index of this.state.selectedSimulationModels) {
// get simulator for model
let simulator = null;
for (let sim of this.state.simulators) {
if (sim._id === this.state.simulationModels[index].simulator) {
simulator = sim;
}
}
if (simulator == null) {
continue;
}
AppDispatcher.dispatch({
type: 'simulators/start-action',
simulator,
data: action.data,
token: this.state.sessionToken
});
}
}
@ -178,46 +247,49 @@ class Simulation extends React.Component {
<div className='section'>
<h1>{this.state.simulation.name}</h1>
<Table data={this.state.simulation.models}>
<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='Length' dataKey='length' width='100' />
<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.simulation.models[index], modalIndex: index })}
onDelete={(index) => this.setState({ deleteModal: true, modalData: this.state.simulation.models[index], modalIndex: index })}
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>
<Button onClick={() => this.setState({ newModal: true })}><Glyphicon glyph="plus" /> Simulation Model</Button>
<Button onClick={() => this.setState({ importModal: true })}><Glyphicon glyph="import" /> Import</Button>
<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>
<NewSimulationModelDialog show={this.state.newModal} onClose={(data) => this.closeNewModal(data)} nodes={this.state.nodes} />
<EditSimulationModelDialog show={this.state.editModal} onClose={(data) => this.closeEditModal(data)} data={this.state.modalData} nodes={this.state.nodes} />
<ImportSimulationModelDialog show={this.state.importModal} onClose={data => this.closeImportModal(data)} nodes={this.state.nodes} />
<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>
<Modal keyboard show={this.state.deleteModal} onHide={() => this.setState({ deleteModal: false })} onKeyPress={this.onModalKeyPress}>
<Modal.Header>
<Modal.Title>Delete Simulation Model</Modal.Title>
</Modal.Header>
<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} />
<Modal.Body>
Are you sure you want to delete the simulation model <strong>'{this.state.modalData.name}'</strong>?
</Modal.Body>
<Modal.Footer>
<Button onClick={() => this.setState({ deleteModal: false })}>Cancel</Button>
<Button bsStyle="danger" onClick={() => this.confirmDeleteModal()}>Delete</Button>
</Modal.Footer>
</Modal>
<DeleteDialog title="simulation model" name={this.state.modalData.name} show={this.state.deleteModal} onClose={this.closeDeleteModal} />
</div>
);
}
}
export default Container.create(Simulation);
export default Container.create(Simulation, { withProps: true });

View file

@ -21,13 +21,13 @@
import React, { Component } from 'react';
import { Container } from 'flux/utils';
import { Button, Modal, Glyphicon } from 'react-bootstrap';
import { Button, Glyphicon } from 'react-bootstrap';
import FileSaver from 'file-saver';
import AppDispatcher from '../app-dispatcher';
import SimulationStore from '../stores/simulation-store';
import UserStore from '../stores/user-store';
import NodeStore from '../stores/node-store';
import SimulatorStore from '../stores/simulator-store';
import Table from '../components/table';
import TableColumn from '../components/table-column';
@ -35,22 +35,27 @@ import NewSimulationDialog from '../components/dialog/new-simulation';
import EditSimulationDialog from '../components/dialog/edit-simulation';
import ImportSimulationDialog from '../components/dialog/import-simulation';
import SimulatorAction from '../components/simulator-action';
import DeleteDialog from '../components/dialog/delete-dialog';
class Simulations extends Component {
static getStores() {
return [ SimulationStore, UserStore, NodeStore ];
return [ SimulationStore, UserStore, SimulatorStore ];
}
static calculateState() {
return {
simulations: SimulationStore.getState(),
nodes: NodeStore.getState(),
simulators: SimulatorStore.getState(),
sessionToken: UserStore.getState().token,
newModal: false,
deleteModal: false,
editModal: false,
importModal: false,
modalSimulation: {}
modalSimulation: {},
selectedSimulations: []
};
}
@ -86,9 +91,13 @@ class Simulations extends Component {
this.setState({ deleteModal: true, modalSimulation: deleteSimulation });
}
confirmDeleteModal() {
closeDeleteModal = confirmDelete => {
this.setState({ deleteModal: false });
if (confirmDelete === false) {
return;
}
AppDispatcher.dispatch({
type: 'simulations/start-remove',
data: this.state.modalSimulation,
@ -158,12 +167,63 @@ class Simulations extends Component {
FileSaver.saveAs(blob, 'simulation - ' + simulation.name + '.json');
}
onSimulationChecked(index, event) {
const selectedSimulations = Object.assign([], this.state.selectedSimulations);
for (let key in selectedSimulations) {
if (selectedSimulations[key] === index) {
// update existing entry
if (event.target.checked) {
return;
}
selectedSimulations.splice(key, 1);
this.setState({ selectedSimulations });
return;
}
}
// add new entry
if (event.target.checked === false) {
return;
}
selectedSimulations.push(index);
this.setState({ selectedSimulations });
}
runAction = action => {
for (let index of this.state.selectedSimulations) {
for (let model of this.state.simulations[index].models) {
// get simulator for model
let simulator = null;
for (let sim of this.state.simulators) {
if (sim._id === model.simulator) {
simulator = sim;
}
}
if (simulator == null) {
continue;
}
AppDispatcher.dispatch({
type: 'simulators/start-action',
simulator,
data: action.data,
token: this.state.sessionToken
});
}
}
}
render() {
return (
<div className='section'>
<h1>Simulations</h1>
<Table data={this.state.simulations}>
<TableColumn checkbox onChecked={(index, event) => this.onSimulationChecked(index, event)} width='30' />
<TableColumn title='Name' dataKey='name' link='/simulations/' linkKey='_id' />
<TableColumn
width='100'
@ -176,27 +236,28 @@ class Simulations extends Component {
/>
</Table>
<Button onClick={() => this.setState({ newModal: true })}><Glyphicon glyph="plus" /> Simulation</Button>
<Button onClick={() => this.setState({ importModal: true })}><Glyphicon glyph="import" /> Import</Button>
<div style={{ float: 'left' }}>
<SimulatorAction
runDisabled={this.state.selectedSimulations.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.setState({ newModal: true })}><Glyphicon glyph="plus" /> Simulation</Button>
<Button onClick={() => this.setState({ importModal: true })}><Glyphicon glyph="import" /> Import</Button>
</div>
<NewSimulationDialog show={this.state.newModal} onClose={(data) => this.closeNewModal(data)} />
<EditSimulationDialog show={this.state.editModal} onClose={(data) => this.closeEditModal(data)} simulation={this.state.modalSimulation} />
<ImportSimulationDialog show={this.state.importModal} onClose={data => this.closeImportModal(data)} nodes={this.state.nodes} />
<Modal keyboard show={this.state.deleteModal} onHide={() => this.setState({ deleteModal: false })} onKeyPress={this.onModalKeyPress}>
<Modal.Header>
<Modal.Title>Delete Simulation</Modal.Title>
</Modal.Header>
<Modal.Body>
Are you sure you want to delete the simulation <strong>'{this.state.modalSimulation.name}'</strong>?
</Modal.Body>
<Modal.Footer>
<Button onClick={() => this.setState({ deleteModal: false })}>Cancel</Button>
<Button bsStyle="danger" onClick={() => this.confirmDeleteModal()}>Delete</Button>
</Modal.Footer>
</Modal>
<DeleteDialog title="simulation" name={this.state.modalSimulation.name} show={this.state.deleteModal} onClose={this.closeDeleteModal} />
</div>
);
}

View file

@ -21,256 +21,145 @@
import React, { Component } from 'react';
import { Container } from 'flux/utils';
import { Button, Modal, Glyphicon } from 'react-bootstrap';
import { Button, Glyphicon } from 'react-bootstrap';
import FileSaver from 'file-saver';
import _ from 'lodash';
import AppDispatcher from '../app-dispatcher';
import NodeStore from '../stores/node-store';
import SimulatorStore from '../stores/simulator-store';
import UserStore from '../stores/user-store';
import NewNodeDialog from '../components/dialog/new-node';
import EditNodeDialog from '../components/dialog/edit-node';
import Table from '../components/table';
import TableColumn from '../components/table-column';
import NewSimulatorDialog from '../components/dialog/new-simulator';
import EditSimulatorDialog from '../components/dialog/edit-simulator';
import NodeTree from '../components/node-tree';
import ImportNodeDialog from '../components/dialog/import-node';
import ImportSimulatorDialog from '../components/dialog/import-simulator';
import SimulatorAction from '../components/simulator-action';
import DeleteDialog from '../components/dialog/delete-dialog';
class Simulators extends Component {
static getStores() {
return [ NodeStore, UserStore ];
return [ UserStore, SimulatorStore ];
}
static calculateState() {
return {
nodes: NodeStore.getState(),
sessionToken: UserStore.getState().token,
simulators: SimulatorStore.getState(),
newNodeModal: false,
deleteNodeModal: false,
editNodeModal: false,
importModal: false,
exportModal: false,
modalSimulator: {},
deleteModal: false,
addSimulatorModal: false,
editSimulatorModal: false,
deleteSimulatorModal: false,
modalData: {},
modalIndex: 0,
modalName: ''
selectedSimulators: []
};
}
componentWillMount() {
AppDispatcher.dispatch({
type: 'nodes/start-load',
type: 'simulators/start-load',
token: this.state.sessionToken
});
}
closeNewNodeModal(data) {
this.setState({ newNodeModal: false });
closeNewModal(data) {
this.setState({ newModal : false });
if (data) {
AppDispatcher.dispatch({
type: 'nodes/start-add',
data: data,
token: this.state.sessionToken
});
}
}
showEditNodeModal(data) {
// find node with id
var node = this.state.nodes.find((element) => {
return element._id === data.id;
});
this.setState({ editNodeModal: true, modalData: node });
}
closeEditNodeModal(data) {
this.setState({ editNodeModal: false });
if (data) {
AppDispatcher.dispatch({
type: 'nodes/start-edit',
data: data,
token: this.state.sessionToken
});
}
}
showDeleteNodeModal(data) {
// find node with id
var node = this.state.nodes.find((element) => {
return element._id === data.id;
});
this.setState({ deleteNodeModal: true, modalData: node });
}
confirmDeleteNodeModal() {
this.setState({ deleteModal: false });
AppDispatcher.dispatch({
type: 'nodes/start-remove',
data: this.state.modalData,
token: this.state.sessionToken
});
}
showAddSimulatorModal(data) {
// find node with id
var node = this.state.nodes.find((element) => {
return element._id === data.id;
});
this.setState({ addSimulatorModal: true, modalData: node });
}
closeAddSimulatorModal(data) {
this.setState({ addSimulatorModal: false });
if (data) {
var node = this.state.modalData;
node.simulators.push(data);
AppDispatcher.dispatch({
type: 'nodes/start-edit',
data: node,
token: this.state.sessionToken
});
}
}
showEditSimulatorModal(data, index) {
// find node with id
var node = this.state.nodes.find((element) => {
return element._id === data.id;
});
this.setState({ editSimulatorModal: true, modalData: node, modalIndex: index });
}
closeEditSimulatorModal(data) {
this.setState({ editSimulatorModal: false });
if (data) {
var node = this.state.modalData;
node.simulators[this.state.modalIndex] = data;
AppDispatcher.dispatch({
type: 'nodes/start-edit',
data: node,
token: this.state.sessionToken
});
}
}
showDeleteSimulatorModal(data, index) {
// find node with id
var node = this.state.nodes.find((element) => {
return element._id === data.id;
});
this.setState({ deleteSimulatorModal: true, modalData: node, modalIndex: index, modalName: data.children[index].title });
}
confirmDeleteSimulatorModal() {
this.setState({ deleteSimulatorModal: false });
// remove simulator
var node = this.state.modalData;
node.simulators.splice(this.state.modalIndex);
AppDispatcher.dispatch({
type: 'nodes/start-edit',
data: node,
token: this.state.sessionToken
});
}
closeImportNodeModal(data) {
this.setState({ importNodeModal: false });
if (data) {
AppDispatcher.dispatch({
type: 'nodes/start-add',
type: 'simulators/start-add',
data,
token: this.state.sessionToken
});
}
}
exportNode(data) {
const node = this.state.nodes.find((element) => {
return element._id === data.id;
});
closeEditModal(data) {
this.setState({ editModal : false });
// filter properties
let simulator = Object.assign({}, node);
delete simulator._id;
if (data) {
let simulator = this.state.simulators[this.state.modalIndex];
simulator.properties = data;
this.setState({ simulator });
simulator.simulators.forEach(simulator => {
delete simulator.id;
});
// show save dialog
const blob = new Blob([JSON.stringify(simulator, null, 2)], { type: 'application/json' });
FileSaver.saveAs(blob, 'node - ' + node.name + '.json');
}
labelStyle(value) {
if (value === true) return 'success';
else return 'warning';
}
onTreeDataChange(nodes) {
// update all at once
nodes.forEach((node) => {
AppDispatcher.dispatch({
type: 'nodes/start-edit',
data: node,
type: 'simulators/start-edit',
data: simulator,
token: this.state.sessionToken
});
});
}
onNodeModalKeyPress = (event) => {
if (event.key === 'Enter') {
event.preventDefault();
this.confirmDeleteNodeModal();
}
}
onSimulatorModalKeyPress = (event) => {
if (event.key === 'Enter') {
event.preventDefault();
this.confirmDeleteSimulatorModal();
}
}
closeDeleteModal = confirmDelete => {
this.setState({ deleteModal: false });
loadFile(fileList) {
// get file
const file = fileList[0];
if (!file.type.match('application/json')) {
if (confirmDelete === false) {
return;
}
// create file reader
var reader = new FileReader();
var self = this;
AppDispatcher.dispatch({
type: 'simulators/start-remove',
data: this.state.modalSimulator,
token: this.state.sessionToken
});
}
reader.onload = function(event) {
// read simulator
const simulator = JSON.parse(event.target.result);
self.setState({ importModal: true, modalSimulator: simulator });
};
exportSimulator(index) {
// filter properties
let simulator = Object.assign({}, this.state.simulators[index]);
delete simulator._id;
reader.readAsText(file);
// show save dialog
const blob = new Blob([JSON.stringify(simulator, null, 2)], { type: 'application/json' });
FileSaver.saveAs(blob, 'simulator - ' + (simulator.properties.name || simulator.rawProperties.name || 'undefined') + '.json');
}
closeImportModal(data) {
this.setState({ importModal: false });
if (data) {
AppDispatcher.dispatch({
type: 'simulators/start-add',
data,
token: this.state.sessionToken
});
}
}
onSimulatorChecked(index, event) {
const selectedSimulators = Object.assign([], this.state.selectedSimulators);
for (let key in selectedSimulators) {
if (selectedSimulators[key] === index) {
// update existing entry
if (event.target.checked) {
return;
}
selectedSimulators.splice(key, 1);
this.setState({ selectedSimulators });
return;
}
}
// add new entry
if (event.target.checked === false) {
return;
}
selectedSimulators.push(index);
this.setState({ selectedSimulators });
}
runAction = action => {
for (let index of this.state.selectedSimulators) {
AppDispatcher.dispatch({
type: 'simulators/start-action',
simulator: this.state.simulators[index],
data: action.data,
token: this.state.sessionToken
});
}
}
render() {
@ -278,63 +167,42 @@ class Simulators extends Component {
<div className='section'>
<h1>Simulators</h1>
<Button onClick={() => this.setState({ newNodeModal: true })}><Glyphicon glyph="plus" /> Node</Button>
<Button onClick={() => this.setState({ importNodeModal: true })}><Glyphicon glyph="import" /> Import</Button>
<Table data={this.state.simulators}>
<TableColumn checkbox onChecked={(index, event) => this.onSimulatorChecked(index, event)} width='30' />
<TableColumn title='Name' dataKeys={['properties.name', 'rawProperties.name']} />
<TableColumn title='State' dataKey='state' />
<TableColumn title='Model' dataKey='model' />
<TableColumn title='Endpoint' dataKeys={['properties.endpoint', 'rawProperties.endpoint']} />
<TableColumn title='Host' dataKey='host' />
<TableColumn title='UUID' dataKey='uuid' />
<TableColumn
width='100'
editButton
exportButton
deleteButton
onEdit={index => this.setState({ editModal: true, modalSimulator: this.state.simulators[index], modalIndex: index })}
onExport={index => this.exportSimulator(index)}
onDelete={index => this.setState({ deleteModal: true, modalSimulator: this.state.simulators[index], modalIndex: index })}
/>
</Table>
<br />
<small><i>Hint: Node names must be unique. Simulator names must be unique on a node.</i></small>
<div style={{ float: 'left' }}>
<SimulatorAction
runDisabled={this.state.selectedSimulators.length === 0}
runAction={this.runAction}
actions={[ { id: '0', title: 'Reset', data: { action: 'reset' } }, { id: '1', title: 'Shutdown', data: { action: 'shutdown' } } ]}/>
</div>
<NodeTree
data={this.state.nodes}
onDataChange={(treeData) => this.onTreeDataChange(treeData)}
onNodeDelete={(node) => this.showDeleteNodeModal(node)}
onNodeEdit={(node) => this.showEditNodeModal(node)}
onNodeAdd={(node) => this.showAddSimulatorModal(node)}
onNodeExport={node => this.exportNode(node)}
onSimulatorEdit={(node, index) => this.showEditSimulatorModal(node, index)}
onSimulatorDelete={(node, index) => this.showDeleteSimulatorModal(node, index)}
/>
<div style={{ float: 'right' }}>
<Button onClick={() => this.setState({ newModal: true })}><Glyphicon glyph="plus" /> Simulator</Button>
<Button onClick={() => this.setState({ importModal: true })}><Glyphicon glyph="import" /> Import</Button>
</div>
<NewNodeDialog show={this.state.newNodeModal} onClose={(data) => this.closeNewNodeModal(data)} nodes={this.state.nodes} />
<EditNodeDialog node={this.state.modalData} show={this.state.editNodeModal} onClose={(data) => this.closeEditNodeModal(data)} nodes={this.state.nodes} />
<NewSimulatorDialog show={this.state.addSimulatorModal} onClose={(data) => this.closeAddSimulatorModal(data)} node={this.state.modalData}/>
<ImportNodeDialog show={this.state.importNodeModal} onClose={data => this.closeImportNodeModal(data)} nodes={this.state.nodes} />
<NewSimulatorDialog show={this.state.newModal} onClose={data => this.closeNewModal(data)} />
<EditSimulatorDialog show={this.state.editModal} onClose={data => this.closeEditModal(data)} simulator={this.state.modalSimulator} />
<ImportSimulatorDialog show={this.state.importModal} onClose={data => this.closeImportModal(data)} />
{this.state.editSimulatorModal &&
<EditSimulatorDialog simulator={this.state.modalData.simulators[this.state.modalIndex]} show={this.state.editSimulatorModal} onClose={(data) => this.closeEditSimulatorModal(data)} node={this.state.modalData} />
}
<Modal keyboard show={this.state.deleteNodeModal} onHide={() => this.setState({ deleteNodeModal: false })} onKeyPress={this.onNodeModalKeyPress}>
<Modal.Header>
<Modal.Title>Delete Node</Modal.Title>
</Modal.Header>
<Modal.Body>
Are you sure you want to delete the node <strong>'{this.state.modalData.name}'</strong>?
<br />
This will delete all simulators assigned to this node.
</Modal.Body>
<Modal.Footer>
<Button onClick={() => this.setState({ deleteNodeModal: false })}>Cancel</Button>
<Button bsStyle="danger" onClick={() => this.confirmDeleteNodeModal()}>Delete</Button>
</Modal.Footer>
</Modal>
<Modal keyboard show={this.state.deleteSimulatorModal} onHide={() => this.setState({ deleteSimulatorModal: false })} onKeyPress={this.onSimulatorModalKeyPress}>
<Modal.Header>
<Modal.Title>Delete Simulator</Modal.Title>
</Modal.Header>
<Modal.Body>
Are you sure you want to delete the simulator <strong>'{this.state.modalName}'</strong>?
</Modal.Body>
<Modal.Footer>
<Button onClick={() => this.setState({ deleteSimulatorModal: false })}>Cancel</Button>
<Button bsStyle="danger" onClick={() => this.confirmDeleteSimulatorModal()}>Delete</Button>
</Modal.Footer>
</Modal>
<DeleteDialog title="simulator" name={_.get(this.state.modalSimulator, 'properties.name') || _.get(this.state.modalSimulator, 'rawProperties.name') || 'Unknown'} show={this.state.deleteModal} onClose={this.closeDeleteModal} />
</div>
);
}

View file

@ -21,7 +21,7 @@
import React, { Component } from 'react';
import { Container } from 'flux/utils';
import { Button, Modal, Glyphicon } from 'react-bootstrap';
import { Button, Glyphicon } from 'react-bootstrap';
import AppDispatcher from '../app-dispatcher';
import UserStore from '../stores/user-store';
@ -32,6 +32,8 @@ import TableColumn from '../components/table-column';
import NewUserDialog from '../components/dialog/new-user';
import EditUserDialog from '../components/dialog/edit-user';
import DeleteDialog from '../components/dialog/delete-dialog';
class Users extends Component {
static getStores() {
return [ UserStore, UsersStore ];
@ -72,9 +74,13 @@ class Users extends Component {
}
}
confirmDeleteModal() {
closeDeleteModal = confirmDelete => {
this.setState({ deleteModal: false });
if (confirmDelete === false) {
return;
}
AppDispatcher.dispatch({
type: 'users/start-remove',
data: this.state.modalData,
@ -124,23 +130,9 @@ class Users extends Component {
<Button onClick={() => this.setState({ newModal: true })}><Glyphicon glyph='plus' /> User</Button>
<NewUserDialog show={this.state.newModal} onClose={(data) => this.closeNewModal(data)} />
<EditUserDialog show={this.state.editModal} onClose={(data) => this.closeEditModal(data)} user={this.state.modalData} />
<Modal keyboard show={this.state.deleteModal} onHide={() => this.setState({ deleteModal: false })} onKeyPress={this.onModalKeyPress}>
<Modal.Header>
<Modal.Title>Delete user</Modal.Title>
</Modal.Header>
<Modal.Body>
Are you sure you want to delete the user <strong>'{this.state.modalData.username}'</strong>?
</Modal.Body>
<Modal.Footer>
<Button onClick={() => this.setState({ deleteModal: false})}>Cancel</Button>
<Button bsStyle='danger' onClick={() => this.confirmDeleteModal()}>Delete</Button>
</Modal.Footer>
</Modal>
<DeleteDialog title="user" name={this.state.modalData.username} show={this.state.deleteModal} onClose={this.closeDeleteModal} />
</div>
);
}

View file

@ -38,6 +38,7 @@ import UserStore from '../stores/user-store';
import VisualizationStore from '../stores/visualization-store';
import ProjectStore from '../stores/project-store';
import SimulationStore from '../stores/simulation-store';
import SimulationModelStore from '../stores/simulation-model-store';
import FileStore from '../stores/file-store';
import AppDispatcher from '../app-dispatcher';
import NotificationsDataManager from '../data-managers/notifications-data-manager';
@ -47,14 +48,19 @@ import '../styles/context-menu.css';
class Visualization extends React.Component {
static getStores() {
return [ VisualizationStore, ProjectStore, SimulationStore, FileStore, UserStore ];
return [ VisualizationStore, ProjectStore, SimulationStore, SimulationModelStore, FileStore, UserStore ];
}
static calculateState(prevState) {
static calculateState(prevState, props) {
if (prevState == null) {
prevState = {};
}
let simulationModels = [];
if (prevState.simulation != null) {
simulationModels = SimulationModelStore.getState().filter(m => prevState.simulation.models.includes(m._id));
}
return {
sessionToken: UserStore.getState().token,
visualizations: VisualizationStore.getState(),
@ -65,6 +71,7 @@ class Visualization extends React.Component {
visualization: prevState.visualization || {},
project: prevState.project || null,
simulation: prevState.simulation || null,
simulationModels,
editing: prevState.editing || false,
paused: prevState.paused || false,
@ -192,12 +199,12 @@ class Visualization extends React.Component {
handleDrop(item, position) {
let widget = null;
let defaultSimulator = null;
let defaultSimulationModel = null;
if (this.state.simulation.models && this.state.simulation.models.length === 0) {
NotificationsDataManager.addNotification(NotificationsFactory.NO_SIM_MODEL_AVAILABLE);
} else {
defaultSimulator = this.state.simulation.models[0].simulator;
defaultSimulationModel = this.state.simulation.models[0];
}
// snap position to grid
@ -205,7 +212,7 @@ class Visualization extends React.Component {
position.y = this.snapToGrid(position.y);
// create new widget
widget = WidgetFactory.createWidgetOfType(item.name, position, defaultSimulator);
widget = WidgetFactory.createWidgetOfType(item.name, position, defaultSimulationModel);
var new_widgets = this.state.visualization.widgets;
@ -529,11 +536,11 @@ class Visualization extends React.Component {
</ContextMenu>
))}
<EditWidget sessionToken={this.state.sessionToken} show={this.state.editModal} onClose={(data) => this.closeEdit(data)} widget={this.state.modalData} simulation={this.state.simulation} files={this.state.files} />
<EditWidget sessionToken={this.state.sessionToken} show={this.state.editModal} onClose={(data) => this.closeEdit(data)} widget={this.state.modalData} simulationModels={this.state.simulationModels} files={this.state.files} />
</div>
</div>
);
}
}
export default Fullscreenable()(Container.create(Visualization));
export default Fullscreenable()(Container.create(Visualization, { withProps: true }));

View file

@ -28,6 +28,7 @@ import classNames from 'classnames';
import AppDispatcher from '../app-dispatcher';
import UserStore from '../stores/user-store';
import SimulatorDataStore from '../stores/simulator-data-store';
import SimulationModelStore from '../stores/simulation-model-store';
import FileStore from '../stores/file-store';
import WidgetLamp from '../components/widget-lamp';
@ -49,7 +50,7 @@ import '../styles/widgets.css';
class Widget extends React.Component {
static getStores() {
return [ SimulatorDataStore, FileStore, UserStore ];
return [ SimulatorDataStore, SimulationModelStore, FileStore, UserStore ];
}
static calculateState(prevState, props) {
@ -70,14 +71,18 @@ class Widget extends React.Component {
sessionToken,
simulatorData,
files: FileStore.getState(),
sequence: prevState.sequence + 1
sequence: prevState.sequence + 1,
simulationModels: SimulationModelStore.getState()
};
} else {
return {
sessionToken,
simulatorData,
files: FileStore.getState(),
sequence: 0
sequence: 0,
simulationModels: SimulationModelStore.getState()
};
}
}
@ -96,6 +101,11 @@ class Widget extends React.Component {
type: 'files/start-load',
token: this.state.sessionToken
});
AppDispatcher.dispatch({
type: 'simulationModels/start-load',
token: this.state.sessionToken
});
}
}
@ -171,19 +181,29 @@ class Widget extends React.Component {
let borderedWidget = false;
let element = null;
let simulationModel = null;
for (let model of this.state.simulationModels) {
if (model._id !== widget.simulationModel) {
continue;
}
simulationModel = model;
}
// dummy is passed to widgets to keep updating them while in edit mode
if (widget.type === 'Lamp') {
element = <WidgetLamp widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulation={this.props.simulation} />
element = <WidgetLamp widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel} />
} else if (widget.type === 'Value') {
element = <WidgetValue widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulation={this.props.simulation} />
element = <WidgetValue widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel} />
} else if (widget.type === 'Plot') {
element = <WidgetPlot widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulation={this.props.simulation} paused={this.props.paused} />
element = <WidgetPlot widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel} paused={this.props.paused} />
} else if (widget.type === 'Table') {
element = <WidgetTable widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulation={this.props.simulation} />
element = <WidgetTable widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel} />
} else if (widget.type === 'Label') {
element = <WidgetLabel widget={widget} />
} else if (widget.type === 'PlotTable') {
element = <WidgetPlotTable widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulation={this.props.simulation} editing={this.props.editing} onWidgetChange={(w) => this.props.onWidgetStatusChange(w, this.props.index)} paused={this.props.paused} />
element = <WidgetPlotTable widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel} editing={this.props.editing} onWidgetChange={(w) => this.props.onWidgetStatusChange(w, this.props.index)} paused={this.props.paused} />
} else if (widget.type === 'Image') {
element = <WidgetImage widget={widget} files={this.state.files} token={this.state.sessionToken} />
} else if (widget.type === 'Button') {
@ -193,7 +213,7 @@ class Widget extends React.Component {
} else if (widget.type === 'Slider') {
element = <WidgetSlider widget={widget} editing={this.props.editing} onWidgetChange={(w) => this.props.onWidgetStatusChange(w, this.props.index) } onInputChanged={(value) => this.inputDataChanged(widget, value)} />
} else if (widget.type === 'Gauge') {
element = <WidgetGauge widget={widget} data={this.state.simulatorData} editing={this.props.editing} simulation={this.props.simulation} />
element = <WidgetGauge widget={widget} data={this.state.simulatorData} editing={this.props.editing} simulationModel={simulationModel} />
} else if (widget.type === 'Box') {
element = <WidgetBox widget={widget} editing={this.props.editing} />
} else if (widget.type === 'HTML') {

View file

@ -0,0 +1,24 @@
/**
* File: simulation-models-data-manager.js
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
* Date: 20.04.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 RestDataManager from './rest-data-manager';
export default new RestDataManager('simulationModel', '/models');

View file

@ -29,13 +29,23 @@ class SimulationsDataManager extends RestDataManager {
this.onLoad = this.onSimulationsLoad;
}
onSimulationsLoad(simulation) {
onSimulationsLoad(data) {
if (Array.isArray(data)) {
for (let simulation of data) {
this.loadSimulationData(simulation);
}
} else {
this.loadSimulationData(data);
}
}
loadSimulationData(simulation) {
for (let model of simulation.models) {
AppDispatcher.dispatch({
type: 'simulatorData/prepare',
inputLength: parseInt(model.inputLength, 10),
outputLength: parseInt(model.outputLength, 10),
node: model.simulator
id: model.simulator
});
}
}

View file

@ -30,22 +30,17 @@ class SimulatorDataDataManager {
this._sockets = {};
}
open(endpoint, node) {
open(endpoint, identifier) {
// pass signals to onOpen callback
if (this._sockets[node._id] != null) {
if (this._sockets[node._id].url !== WebsocketAPI.getURL(node)) {
if (this._sockets[identifier] != null) {
if (this._sockets[identifier].url !== WebsocketAPI.getURL(endpoint)) {
// replace connection, since endpoint changed
this._sockets.close();
this._sockets[node._id] = WebsocketAPI.addSocket(node, { onOpen: (event) => this.onOpen(event, node), onClose: (event) => this.onClose(event, node), onMessage: (event) => this.onMessage(event, node), onError: (error) => this.onError(error, node) });
this._sockets[identifier] = WebsocketAPI.addSocket(endpoint, { onOpen: (event) => this.onOpen(event, identifier), onClose: (event) => this.onClose(event, identifier), onMessage: (event) => this.onMessage(event, identifier), onError: (error) => this.onError(error, identifier) });
}
} else {
// set flag if a socket to this simulator was already create before
if (this._sockets[node._id] === null) {
this._sockets[node._id] = WebsocketAPI.addSocket(node, { onOpen: (event) => this.onOpen(event, node, false), onClose: (event) => this.onClose(event, node), onMessage: (event) => this.onMessage(event, node), onError: (error) => this.onError(error, node) });
} else {
this._sockets[node._id] = WebsocketAPI.addSocket(node, { onOpen: (event) => this.onOpen(event, node, true), onClose: (event) => this.onClose(event, node), onMessage: (event) => this.onMessage(event, node), onError: (error) => this.onError(error, node) });
}
this._sockets[identifier] = WebsocketAPI.addSocket(endpoint, { onOpen: (event) => this.onOpen(event, identifier, false), onClose: (event) => this.onClose(event, identifier), onMessage: (event) => this.onMessage(event, identifier), onError: (error) => this.onError(error, identifier) });
}
}
@ -59,8 +54,8 @@ class SimulatorDataDataManager {
}
}
send(message, nodeId) {
const socket = this._sockets[nodeId];
send(message, identifier) {
const socket = this._sockets[identifier];
if (socket == null) {
return false;
}
@ -68,40 +63,42 @@ class SimulatorDataDataManager {
const data = this.messageToBuffer(message);
socket.send(data);
console.log(data);
return true;
}
onOpen(event, node, firstOpen) {
onOpen(event, identifier, firstOpen) {
AppDispatcher.dispatch({
type: 'simulatorData/opened',
node: node,
id: identifier,
firstOpen: firstOpen
});
}
onClose(event, node) {
onClose(event, identifier) {
AppDispatcher.dispatch({
type: 'simulatorData/closed',
node: node,
id: identifier,
notification: (event.code !== 4000)
});
// remove from list, keep null reference for flag detection
delete this._sockets[node._id];
delete this._sockets[identifier];
}
onError(error, node) {
console.error('Error on ' + node._id + ':' + error);
onError(error, identifier) {
console.error('Error on ' + identifier + ':' + error);
}
onMessage(event, node) {
onMessage(event, identifier) {
var msgs = this.bufferToMessageArray(event.data);
if (msgs.length > 0) {
AppDispatcher.dispatch({
type: 'simulatorData/data-changed',
data: msgs,
node: node
id: identifier
});
}
}

View file

@ -0,0 +1,47 @@
/**
* File: simulator-data-manager.js
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
* Date: 03.03.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 RestDataManager from './rest-data-manager';
import RestAPI from '../api/rest-api';
import AppDispatcher from '../app-dispatcher';
class SimulatorsDataManager extends RestDataManager {
constructor() {
super('simulator', '/simulators');
}
doAction(simulator, action, token = null) {
// TODO: Make only simulator id dependent
RestAPI.post(this.makeURL(this.url + '/' + simulator._id), action, token).then(response => {
AppDispatcher.dispatch({
type: 'simulators/action-started',
data: response
});
}).catch(error => {
AppDispatcher.dispatch({
type: 'simulators/action-error',
error
});
});
}
}
export default new SimulatorsDataManager();

View file

@ -0,0 +1,25 @@
/**
* File: simulation-model-store.js
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
* Date: 20.04.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 ArrayStore from './array-store';
import SimulationModelsDataManager from '../data-managers/simulation-models-data-manager';
export default new ArrayStore('simulationModels', SimulationModelsDataManager);

View file

@ -36,24 +36,14 @@ class SimulationDataStore extends ReduceStore {
}
reduce(state, action) {
var i, j;
switch (action.type) {
case 'simulatorData/open':
SimulatorDataDataManager.open(action.endpoint, action.node);
return state;
case 'simulatorData/opened':
// create entry for simulator
state[action.node._id] = {};
state[action.id] = {};
return state;
case 'simulatorData/prepare':
if (state[action.node.node] == null) {
return state;
}
state[action.node.node][action.node.simulator] = {
state[action.id] = {
output: {
sequence: -1,
length: action.outputLength,
@ -64,47 +54,49 @@ class SimulationDataStore extends ReduceStore {
length: action.inputLength,
version: 2,
type: 0,
id: action.node.simulator,
id: 0,
timestamp: Date.now(),
values: new Array(action.inputLength).fill(0)
}
};
this.__emitChange();
return state;
case 'simulatorData/data-changed':
// get index for simulator id
if (state[action.node._id] == null) {
if (state[action.id] == null) {
return state;
}
if (state[action.id].output == null) {
state[action.id].output = {
values: []
};
}
// loop over all samples in a vector
for (j = 0; j < action.data.length; j++) {
for (let j = 0; j < action.data.length; j++) {
let smp = action.data[j];
let index = action.node.simulators.findIndex(simulator => simulator.id === smp.id);
if (index === -1 || state[action.node._id][index] == null) {
return state;
}
// add data to simulator
for (i = 0; i < smp.length; i++) {
while (state[action.node._id][index].output.values.length < i + 1) {
state[action.node._id][index].output.values.push([]);
for (let i = 0; i < smp.length; i++) {
while (state[action.id].output.values.length < i + 1) {
state[action.id].output.values.push([]);
}
state[action.node._id][index].output.values[i].push({ x: smp.timestamp, y: smp.values[i] });
state[action.id].output.values[i].push({ x: smp.timestamp, y: smp.values[i] });
// erase old values
if (state[action.node._id][index].output.values[i].length > MAX_VALUES) {
const pos = state[action.node._id][index].output.values[i].length - MAX_VALUES;
state[action.node._id][index].output.values[i].splice(0, pos);
if (state[action.id].output.values[i].length > MAX_VALUES) {
const pos = state[action.id].output.values[i].length - MAX_VALUES;
state[action.id].output.values[i].splice(0, pos);
}
}
// update metadata
state[action.node._id][index].output.timestamp = smp.timestamp;
state[action.node._id][index].output.sequence = smp.sequence;
state[action.id].output.timestamp = smp.timestamp;
state[action.id].output.sequence = smp.sequence;
}
// explicit call to prevent array copy

View file

@ -0,0 +1,73 @@
/**
* File: simulator-store.js
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
* Date: 03.03.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 _ from 'lodash';
import ArrayStore from './array-store';
import SimulatorsDataManager from '../data-managers/simulators-data-manager';
import SimulatorDataDataManager from '../data-managers/simulator-data-data-manager';
class SimulatorStore extends ArrayStore {
constructor() {
super('simulators', SimulatorsDataManager);
}
reduce(state, action) {
switch(action.type) {
case 'simulators/loaded':
// connect to each simulator
for (let simulator of action.data) {
const endpoint = _.get(simulator, 'properties.endpoint') || _.get(simulator, 'rawProperties.endpoint');
if (endpoint != null && endpoint !== '') {
SimulatorDataDataManager.open(endpoint, simulator._id);
} else {
// console.warn('Endpoint not found for simulator at ' + endpoint);
// console.log(simulator);
}
}
return super.reduce(state, action);
case 'simulators/edited':
return super.reduce(state, action);
case 'simulators/fetched':
return this.updateElements(state, [action.data]);
case 'simulators/fetch-error':
return state;
case 'simulators/start-action':
SimulatorsDataManager.doAction(action.simulator, action.data, action.token);
return state;
case 'simulators/action-error':
console.log(action.error);
return state;
default:
return super.reduce(state, action);
}
}
}
export default new SimulatorStore();