Commit 87be9069 authored by Sonja Happ's avatar Sonja Happ

WIP resolving dependencies of widgets on simulation models (use signals instead)

parent 937bda8f
......@@ -33,7 +33,8 @@ import DashboardButtonGroup from './dashboard-button-group';
import LoginStore from '../user/login-store';
import DashboardStore from './dashboard-store';
import SimulationModelStore from '../simulationmodel/simulation-model-store';
//import SimulationModelStore from '../simulationmodel/simulation-model-store';
import SignalStore from '../signal/signal-store'
import FileStore from '../file/file-store';
import WidgetStore from '../widget/widget-store';
import AppDispatcher from '../common/app-dispatcher';
......@@ -44,7 +45,7 @@ class Dashboard extends Component {
static lastWidgetKey = 0;
static getStores() {
return [ DashboardStore, SimulationModelStore, FileStore, LoginStore, WidgetStore ];
return [ DashboardStore, FileStore, LoginStore, WidgetStore, SignalStore ];
}
static calculateState(prevState, props) {
......@@ -75,15 +76,18 @@ class Dashboard extends Component {
return thisWidgetHeight > maxHeightSoFar? thisWidgetHeight : maxHeightSoFar;
}, 0);
let simulationModels = [];
//let simulationModels = [];
//if (prevState.simulation != null) {
// simulationModels = SimulationModelStore.getState().filter(m => prevState.simulation.models.includes(m._id));
//}
// TODO filter signals to the ones belonging to the scenario at hand!
let signals = SignalStore.getState();
return {
dashboard,
widgets,
simulationModels,
signals,
sessionToken: sessionToken,
files: null,
......@@ -398,7 +402,14 @@ class Dashboard extends Component {
)}
<EditWidget sessionToken={this.state.sessionToken} show={this.state.editModal} onClose={this.closeEdit.bind(this)} widget={this.state.modalData} simulationModels={this.state.simulationModels} files={this.state.files} />
<EditWidget
sessionToken={this.state.sessionToken}
show={this.state.editModal}
onClose={this.closeEdit.bind(this)}
widget={this.state.modalData}
signals={this.state.signals}
files={this.state.files}
/>
</div>
......
......@@ -27,9 +27,7 @@ class EditWidgetSignalControl extends Component {
super(props);
this.state = {
widget: {
simulationModel: ''
}
widget: {}
};
}
......@@ -40,28 +38,17 @@ class EditWidgetSignalControl extends Component {
}
render() {
const simulationModel = this.props.simulationModels.find(m => m._id === this.state.widget.simulationModel);
let signalsToRender = [];
if (simulationModel != null) {
if (this.props.input) {
signalsToRender = simulationModel ? simulationModel.inputMapping : [];
} else {
signalsToRender = simulationModel ? simulationModel.outputMapping : [];
}
}
return (
<FormGroup controlId="signal">
<FormLabel>Signal</FormLabel>
<FormControl as="select" placeholder="Select signal" value={this.state.widget.signal} onChange={(e) => this.props.handleChange(e)}>
<FormControl as="select" placeholder="Select signal" value={this.props.widget.signalIDs[0]} onChange={(e) => this.props.handleChange(e)}>
{
signalsToRender.length === 0 ? (
this.props.signals.length === 0 ? (
<option disabled value style={{ display: 'none' }}>No signals available.</option>
) : (
signalsToRender.map((signal, index) => (
<option key={index} value={index}>{signal.name}</option>
this.props.signals.map((signal, index) => (
<option key={index} value={signal.id}>{signal.name}</option>
))
)
}
......
......@@ -27,9 +27,7 @@ class EditWidgetSignalsControl extends Component {
super(props);
this.state = {
widget: {
simulator: {}
}
widget: { }
};
}
......@@ -39,40 +37,31 @@ class EditWidgetSignalsControl extends Component {
};
}
handleSignalChange(checked, index) {
var signals = this.state.widget[this.props.controlId];
handleSignalChange(checked, signalID) {
var signals = this.state.widget.signalIDs;
var new_signals;
if (checked) {
// add signal
new_signals = signals.concat(index);
new_signals = signals.concat(signalID);
} else {
// remove signal
new_signals = signals.filter( (idx) => idx !== index );
new_signals = signals.filter( (id) => id !== signalID );
}
this.props.handleChange({ target: { id: this.props.controlId, value: new_signals } });
}
render() {
const simulationModel = this.props.simulationModels.find(m => m._id === this.state.widget.simulationModel);
let signalsToRender = [];
if (simulationModel != null) {
// If simulation model update the signals to render
signalsToRender = simulationModel ? simulationModel.outputMapping : [];
}
return (
<FormGroup>
<FormLabel>Signals</FormLabel>
{
signalsToRender.length === 0 || !this.state.widget.hasOwnProperty(this.props.controlId)? (
this.props.signals === 0 || !this.state.widget.hasOwnProperty(this.props.controlId)? (
<FormLabel>No signals available</FormLabel>
) : (
signalsToRender.map((signal, index) => (
<FormCheck key={index} checked={this.state.widget[this.props.controlId].indexOf(index) !== -1} onChange={(e) => this.handleSignalChange(e.target.checked, index)}>{signal.name}</FormCheck>
this.props.signals.map((signal, index) => (
<FormCheck key={signal.id} checked={this.state.widget.signalIDs.indexOf(signal.id) !== -1} onChange={(e) => this.handleSignalChange(e.target.checked, signal.id)}>{signal.name}</FormCheck>
))
)
}
......
......@@ -69,7 +69,7 @@ class EditWidgetDialog extends React.Component {
handleChange(e) {
// TODO: check what we really need in this function. Can we reduce its complexity?
if (e.constructor === Array) {
if (e.constructor === Array) {
// Every property in the array will be updated
let changes = e.reduce( (changesObject, event) => {
changesObject[event.target.id] = event.target.value;
......@@ -80,46 +80,42 @@ class EditWidgetDialog extends React.Component {
this.setState({ temporal: Object.assign({}, this.state.temporal, changes ) });
}
if(e.target.type !== 'text'){
let changeObject = {};
if (e.target.id === 'lockAspect') {
changeObject[e.target.id] = e.target.checked;
// correct image aspect if turned on
if (e.target.checked) {
changeObject = this.assignAspectRatio(changeObject, this.state.temporal.file);
}
} else if (e.target.id === 'file') {
changeObject[e.target.id] = e.target.value;
// get file and update size (if it's an image)
if ('lockAspect' in this.state.temporal && this.state.temporal.lockAspect) {
changeObject = this.assignAspectRatio(changeObject, e.target.value);
}
} else if (e.target.type === 'checkbox') {
changeObject[e.target.id] = e.target.checked;
} else if (e.target.type === 'number') {
changeObject[e.target.id] = Number(e.target.value);
if(e.target.type !== 'text'){
let changeObject = {};
if (e.target.id === 'lockAspect') {
changeObject[e.target.id] = e.target.checked;
// correct image aspect if turned on
if (e.target.checked) {
changeObject = this.assignAspectRatio(changeObject, this.state.temporal.file);
}
} else if (e.target.id === 'file') {
changeObject[e.target.id] = e.target.value;
else {
changeObject[e.target.id] = e.target.value;
// get file and update size (if it's an image)
if ('lockAspect' in this.state.temporal && this.state.temporal.lockAspect) {
changeObject = this.assignAspectRatio(changeObject, e.target.value);
}
} else if (e.target.type === 'checkbox') {
changeObject[e.target.id] = e.target.checked;
} else if (e.target.type === 'number') {
changeObject[e.target.id] = Number(e.target.value);
} else {
changeObject[e.target.id] = e.target.value;
}
let finalChange = this.state.temporal;
finalChange.customProperties[e.target.id] = changeObject[e.target.id];
this.setState({ temporal: finalChange});
} else {
if(this.state.temporal[e.target.id]){
let finalChange = this.state.temporal;
finalChange.customProperties[e.target.id] = changeObject[e.target.id];
finalChange[e.target.id] = e.target.value;
this.setState({ temporal: finalChange});
}
else{
if(this.state.temporal[e.target.id]){
let finalChange = this.state.temporal;
finalChange[e.target.id] = e.target.value;
this.setState({ temporal: finalChange});
}
}
}
}
resetState() {
......@@ -151,7 +147,7 @@ class EditWidgetDialog extends React.Component {
this.props.sessionToken,
this.props.files,
(id) => this.validateForm(id),
this.props.simulationModels,
this.props.signals,
(e) => this.handleChange(e));
}
......
......@@ -40,7 +40,7 @@ class WidgetArea extends React.Component {
position.x = this.snapToGrid(position.x);
position.y = this.snapToGrid(position.y);
const widget = WidgetFactory.createWidgetOfType(item.name, position, this.props.defaultSimulationModel);
const widget = WidgetFactory.createWidgetOfType(item.name, position);
if (this.props.onWidgetAdded != null) {
this.props.onWidgetAdded(widget);
......@@ -66,7 +66,6 @@ WidgetArea.propTypes = {
children: PropTypes.node, //TODO is .node correct here? Was .children before leading to compile error
editing: PropTypes.bool,
grid: PropTypes.number,
defaultSimulationModel: PropTypes.string,
//widgets: PropTypes.array,
onWidgetAdded: PropTypes.func
};
......
......@@ -25,7 +25,7 @@ import WidgetSlider from './widgets/slider';
class WidgetFactory {
static createWidgetOfType(type, position, defaultSimulationModel = null) {
static createWidgetOfType(type, position) {
let widget = {
name: 'Name',
......@@ -36,7 +36,8 @@ class WidgetFactory {
y: position.y,
z: position.z,
locked: false,
customProperties: {}
customProperties: {},
signalIDs: [],
};
// set type specific properties
......@@ -60,14 +61,10 @@ class WidgetFactory {
widget.customProperties.icon = 'star';
widget.width = 100;
widget.height = 50;
widget.customProperties.simulationModel = defaultSimulationModel;
break;
case 'Action':
widget.customProperties.simulationModel = defaultSimulationModel;
break;
case 'Lamp':
widget.customProperties.simulationModel = defaultSimulationModel;
widget.customProperties.signal = 0;
widget.minWidth = 5;
widget.minHeight = 5;
widget.width = 20;
......@@ -77,8 +74,6 @@ class WidgetFactory {
widget.customProperties.threshold = 0.5;
break;
case 'Value':
widget.customProperties.simulationModel = defaultSimulationModel;
widget.customProperties.signal = 0;
widget.minWidth = 70;
widget.minHeight = 20;
widget.width = 120;
......@@ -88,8 +83,6 @@ class WidgetFactory {
widget.customProperties.showUnit = false;
break;
case 'Plot':
widget.customProperties.simulationModel = defaultSimulationModel;
widget.customProperties.signals = [ 0 ];
widget.customProperties.ylabel = '';
widget.customProperties.time = 60;
widget.minWidth = 400;
......@@ -101,7 +94,6 @@ class WidgetFactory {
widget.customProperties.yUseMinMax = false;
break;
case 'Table':
widget.customProperties.simulationModel = defaultSimulationModel;
widget.minWidth = 200;
widget.width = 300;
widget.height = 200;
......@@ -116,9 +108,6 @@ class WidgetFactory {
widget.customProperties.fontColor = 0;
break;
case 'PlotTable':
widget.customProperties.simulationModel = defaultSimulationModel;
widget.customProperties.preselectedSignals = [];
widget.customProperties.signals = []; // initialize selected signals
widget.customProperties.ylabel = '';
widget.minWidth = 200;
widget.minHeight = 100;
......@@ -143,16 +132,12 @@ class WidgetFactory {
widget.height = 100;
widget.customProperties.background_color = 1;
widget.customProperties.font_color = 0;
widget.customProperties.simulationModel = defaultSimulationModel;
widget.customProperties.signal = 0;
break;
case 'Input':
widget.minWidth = 200;
widget.minHeight = 50;
widget.width = 200;
widget.height = 50;
widget.customProperties.simulationModel = defaultSimulationModel;
widget.customProperties.signal = 0;
break;
case 'Slider':
widget.minWidth = 380;
......@@ -160,17 +145,13 @@ class WidgetFactory {
widget.width = 400;
widget.height = 50;
widget.customProperties.orientation = WidgetSlider.OrientationTypes.HORIZONTAL.value; // Assign default orientation
widget.customProperties.simulationModel = defaultSimulationModel;
widget.customProperties.signal = 0;
widget.customProperties.rangeMin = 0;
widget.customProperties.rangeMax = 200;
widget.customProperties.rangeUseMinMax = true;
widget.customProperties.showUnit = true
widget.customProperties.showUnit = true;
break;
case 'Gauge':
widget.customProperties.simulationModel = defaultSimulationModel;
widget.customProperties.signal = 0;
widget.minWidth = 100;
widget.minHeight = 150;
widget.width = 150;
......
......@@ -27,6 +27,7 @@ import LoginStore from '../user/login-store';
import SimulatorDataStore from '../simulator/simulator-data-store';
import SimulationModelStore from '../simulationmodel/simulation-model-store';
import FileStore from '../file/file-store';
import SignalStore from '../signal/signal-store'
import EditableWidgetContainer from './editable-widget-container';
import WidgetContainer from './widget-container';
......@@ -52,7 +53,7 @@ import '../styles/widgets.css';
class Widget extends React.Component {
static getStores() {
return [ SimulatorDataStore, SimulationModelStore, FileStore, LoginStore ];
return [ SimulatorDataStore, SimulationModelStore, FileStore, LoginStore, SignalStore];
}
static calculateState(prevState, props) {
......@@ -67,10 +68,25 @@ class Widget extends React.Component {
simulatorData = SimulatorDataStore.getState();
}
// Get the simulator IDs and signal indexes for all signals of the widget
let simulationModels = SimulationModelStore.getState();
// TODO make sure that the signals are only the signals that belong to the scenario at hand
let signals = SignalStore.getState();
let simulatorIDs = [];
if ( props.data.signalIDs.length > 0){
for (let i in props.data.signalIDs.length){
let signal = signals.find(s => s.id === props.data.signalIDs[i]);
let model = simulationModels.find(m => m.id === signal.simulationModelID);
simulatorIDs[i] = model.simulatorID;
}
}
return {
simulatorData,
signals: signals,
simulatorIDs: simulatorIDs,
files: FileStore.getState(),
simulationModels: SimulationModelStore.getState(),
sequence: prevState != null ? prevState.sequence + 1 : 0,
......@@ -90,71 +106,53 @@ class Widget extends React.Component {
});*/
// TODO no not load simulation models here, since they are loaded via the scenario, pass them as props
/*
AppDispatcher.dispatch({
type: 'simulationModels/start-load',
token: this.state.sessionToken,
param: '?scenarioID=1' // TODO do not hardcode scenarioID!
});
*/
}
inputDataChanged(widget, data) {
let simulationModel = null;
for (let model of this.state.simulationModels) {
if (model.id !== widget.simulationModel) {
continue;
}
simulationModel = model;
}
// The following assumes that a widget modifies/ uses exactly one signal
AppDispatcher.dispatch({
type: 'simulatorData/inputChanged',
simulator: simulationModel.simulator,
signal: widget.signal,
simulator: this.state.simulatorIDs[0],
signal: this.state.signals[0].index,
data
});
}
createWidget(widget) {
let simulationModel = null;
for (let model of this.state.simulationModels) {
if (model.id !== widget.simulationModel) {
continue;
}
simulationModel = model;
}
if (widget.type === 'CustomAction') {
return <WidgetCustomAction widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel} />
return <WidgetCustomAction widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} signals={this.state.signals} simulatorIDs={this.state.simulatorIDs} />
} else if (widget.type === 'Action') {
return <WidgetAction widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel}/>
return <WidgetAction widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} />
} else if (widget.type === 'Lamp') {
return <WidgetLamp widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel} />
return <WidgetLamp widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} signals={this.state.signals} simulatorIDs={this.state.simulatorIDs} />
} else if (widget.type === 'Value') {
return <WidgetValue widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel} />
return <WidgetValue widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} signals={this.state.signals} simulatorIDs={this.state.simulatorIDs} />
} else if (widget.type === 'Plot') {
return <WidgetPlot widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel} paused={this.props.paused} />
return <WidgetPlot widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} paused={this.props.paused} />
} else if (widget.type === 'Table') {
return <WidgetTable widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel} />
return <WidgetTable widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} signals={this.state.signals} simulatorIDs={this.state.simulatorIDs} />
} else if (widget.type === 'Label') {
return <WidgetLabel widget={widget} />
} else if (widget.type === 'PlotTable') {
return <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} />
return <WidgetPlotTable widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} editing={this.props.editing} onWidgetChange={(w) => this.props.onWidgetStatusChange(w, this.props.index)} paused={this.props.paused} />
} else if (widget.type === 'Image') {
return <WidgetImage widget={widget} files={this.state.files} token={this.state.sessionToken} />
} else if (widget.type === 'Button') {
return <WidgetButton widget={widget} editing={this.props.editing} simulationModel={simulationModel} onInputChanged={(value) => this.inputDataChanged(widget, value)} />
return <WidgetButton widget={widget} editing={this.props.editing} onInputChanged={(value) => this.inputDataChanged(widget, value)} signals={this.state.signals} />
} else if (widget.type === 'NumberInput') {
return <WidgetInput widget={widget} editing={this.props.editing} simulationModel={simulationModel} onInputChanged={(value) => this.inputDataChanged(widget, value)} />
return <WidgetInput widget={widget} editing={this.props.editing} onInputChanged={(value) => this.inputDataChanged(widget, value)} />
} else if (widget.type === 'Slider') {
return <WidgetSlider widget={widget} editing={this.props.editing} simulationModel={simulationModel} onWidgetChange={(w) => this.props.onWidgetStatusChange(w, this.props.index) } onInputChanged={value => this.inputDataChanged(widget, value)} />
return <WidgetSlider widget={widget} editing={this.props.editing} onWidgetChange={(w) => this.props.onWidgetStatusChange(w, this.props.index) } onInputChanged={value => this.inputDataChanged(widget, value)} signals={this.state.signals}/>
} else if (widget.type === 'Gauge') {
return <WidgetGauge widget={widget} data={this.state.simulatorData} editing={this.props.editing} simulationModel={simulationModel} />
return <WidgetGauge widget={widget} data={this.state.simulatorData} editing={this.props.editing} signals={this.state.signals} simulatorIDs={this.state.simulatorIDs} />
} else if (widget.type === 'Box') {
return <WidgetBox widget={widget} editing={this.props.editing} />
} else if (widget.type === 'HTML') {
......
......@@ -41,11 +41,12 @@ class WidgetCustomAction extends Component {
}
static getDerivedStateFromProps(props, state){
if (props.simulationModel === null)
return null; //no change
if(props.widget.signalIDs.length === 0){
return null;
}
return{
simulator: SimulatorStore.getState().find(s => s._id === props.simulationModel.simulator),
simulator: SimulatorStore.getState().find(s => s.id === props.simulatorIDs[0]),
sessionToken: LoginStore.getState().token
};
}
......
......@@ -33,6 +33,7 @@ class WidgetGauge extends Component {
this.state = {
value: 0,
unit: '',
minValue: null,
maxValue: null,
};
......@@ -69,11 +70,21 @@ class WidgetGauge extends Component {
}
static getDerivedStateFromProps(props, state){
if (props.simulationModel == null) {
return{value:0};
if(props.widget.signalIDs.length === 0){
return null;
}
let returnState = {}
// Update unit (assuming there is exactly one signal for this widget)
let signalID = props.widget.signalIDs[0];
let widgetSignal = props.signals.find(sig => sig.id === signalID);
if(widgetSignal !== undefined){
returnState["unit"] = widgetSignal.unit;
}
const simulator = props.simulationModel.simulator;
const simulator = props.simulatorIDs[0];
// update value
if (props.data == null
......@@ -82,7 +93,7 @@ class WidgetGauge extends Component {
|| props.data[simulator].output.values == null
|| props.data[simulator].output.values.length === 0
|| props.data[simulator].output.values[0].length === 0) {
return{value:0};
returnState["value"] = 0;
}
// memorize if min or max value is updated
......@@ -91,11 +102,11 @@ class WidgetGauge extends Component {
let updateMaxValue = false;
// check if value has changed
const signal = props.data[simulator].output.values[props.widget.customProperties.signal];
const signalData = props.data[simulator].output.values[widgetSignal.index];
// 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) {
const value = Math.round(signal[signal.length - 1].y * 1e3) / 1e3;
if (signalData != null) {
const value = Math.round(signalData[signalData.length - 1].y * 1e3) / 1e3;
let minValue = null;
let maxValue = null;
if (state.value !== value && value != null) {
......@@ -149,7 +160,6 @@ class WidgetGauge extends Component {
}
// prepare returned state
let returnState = null;
if(updateValue === true){
returnState["value"] = value;
}
......@@ -160,8 +170,13 @@ class WidgetGauge extends Component {
returnState["maxValue"] = maxValue;
}
return returnState
} // if there is a signal
if (returnState !== {}){
return returnState;
}
else{
return null;
}
} // if there is signal data