diff --git a/doc/Requirements.md b/doc/Requirements.md
new file mode 100644
index 0000000..c53871b
--- /dev/null
+++ b/doc/Requirements.md
@@ -0,0 +1,16 @@
+# Requirements {#web-requirements}
+
+## Services
+ - NodeJS: Runs VILLASweb frontend
+ - Go: Runs VILLASweb backend
+ - PostgreSQL database (min version 11): Backend database
+ - [swag](https://github.com/swaggo/swag): For automated API documentation creation
+ - NGinX: Webserver and reverse proxy for backends (only for production)
+ - Docker: Container management system
+
+## Installed on your local computer
+ - NodeJS with npm
+ - Go (at least version 1.11)
+ - [swag](https://github.com/swaggo/swag)
+ - Docker
+
diff --git a/doc/Structure.md b/doc/Structure.md
new file mode 100644
index 0000000..4399f0c
--- /dev/null
+++ b/doc/Structure.md
@@ -0,0 +1,79 @@
+# VILLASweb data structure {#web-datastructure}
+
+This document describes how data (scenarios, infrastructure components, users etc., not only live data) is structured in VILLASweb.
+
+## Data model
+
+
+
+VILLASweb features the following data classes:
+
+ - Users
+ - Infrastructure Components
+ - Scenarios
+ * Component Configurations and Signals
+ * Dashboards and Widgets
+ * Files
+
+### Users
+- You need a username and a password to authenticate in VILLASweb
+- There exist three categories of users: Guest, User, and Admin
+- Guests have only read access and cannot modify anything
+- Users are normal users, they have access to their scenarios, can see available infrastructure components, and modify their accounts (except for their role)
+- Admin users have full access to everything, they are the only users that can create new users or change the role of existing users. Only admin users can add or modify infrastructure components.
+
+### Infrastructure Components
+- Components of research infrastructure
+- Category: for example simulator, gateway, amplifier, database, etc.
+- Type: for example RTDS, OpalRT, VILLASnode, Cassandra
+- Can only be added/ modified by admin users
+
+### Scenarios
+- A collection of component configurations, dashboards, and files for a specific experiment
+- Users can have access to multiple scenarios
+- Users can be added to and removed from scenarios
+
+### Component Configurations and Signals
+- Configure an infrastructure component for the use in a specific scenario
+- Input signals: Signals that can be modified in VILLASweb
+- Output signals: Signals that can be visualized on dashboards of VILLASweb
+- Parameters: Additional configuration parameters of the infrastructure component
+- Signals are the actual live data that is displayed or modified through VILLASweb dashboards
+
+### Dashboards and Widgets
+- Visualize ongoing experiments in real-time
+- Interact with ongoing experiments in real-time
+- Use widgets to design the dashboard according to the needs
+
+### Files
+- Files can be added to scenarios optionally
+- Can be images, model files, CIM xml files
+- Can be used in widgets or component configurations
+
+## Setup strategy
+
+The easiest way to start from scratch is the following (assuming the infrastructure components are already configured by an admin user, see below):
+
+1. Create a new scenario.
+2. Create and configure a new component configuration and link it with an available infrastructure component.
+3. Configure the input and output signals of the component configuration according to the signals provided by the selected infrastructure component. The number of signals and their order (index starting at 1) must match.
+4. Create a new dashboard and add widgets as desired. Configure the widgets by right-clicking to open the edit menu
+5. If needed, files can be added to the scenario and used by component configurations or widgets (models, images, CIM-files, etc.)
+6. For collaboration with other users, users can be added to a scenario
+
+### Setup of infrastructure components
+
+In the "Infrastructure Components" menu point admin users can create and edit components to be used in experiments. Normal uses can view the available components, but not edit them.
+The components are global at any time and are shared among all users of VILLASweb.
+
+To create a new infrastructure component, you need to provide:
+- Name
+- Category (see above for examples)
+- Type (see above for examples)
+- Location
+- Host (network address of the component)
+
+At the moment, you need to know the input and output signals of the infrastructure component a priori to be able to create compatible component configurations by hand.
+An auto-detection mechanism for signals is planned for future releases.
+
+> Hint: At least one infrastructure component is required to receive data in VILLASweb.
diff --git a/doc/development.md b/doc/development.md
new file mode 100644
index 0000000..259fb19
--- /dev/null
+++ b/doc/development.md
@@ -0,0 +1,69 @@
+# Development {#web-development}
+
+- @subpage web-datastructure
+
+In order to get started with VILLASweb, you might also want to check our our [demo project](https://git.rwth-aachen.de/acs/public/villas/Demo) which is simple to setup using Docker Compose.
+
+## Frontend
+
+### Description
+
+The website itself based on the React JavaScript framework.
+
+### Required
+
+ - NodeJS with npm
+
+### Setup
+
+ - `git clone git@git.rwth-aachen.de/acs/public/villas/web.git` to copy the project on your computer
+ - `cd VILLASweb`
+ - `npm install`
+
+### Running
+
+ - `npm start`
+
+This runs the development server for the website on your local computer at port 3000.
+The backend must be running to make the website work.
+
+## Backend
+
+### Description
+
+The backend of VILLASweb uses the programming language Go and a PostgreSQL data base.
+
+### Required
+
+ - Go (min version 1.11)
+ - Running PostgreSQL data base (min version 11)
+ - [swag](https://github.com/swaggo/swag)
+
+### Setup and Running
+
+ - `git clone git@git.rwth-aachen.de/acs/public/villas/web-backend-go.git` to copy the project on your computer
+ - `cd VILLASweb-backend-go`
+ - `go mod tidy`
+ - `go run start.go [params]`
+
+To obtain a list of available parameters use `go run start.go --help`.
+To run the tests use `go test $(go list ./... ) -p 1` in the top-level folder of the repo.
+
+Running the backend will only work if the PostgreSQL database is setup properly. Otherwise, you will get error messages.
+
+### Auto-generate the API documentation
+
+The documentation of the VILLASweb API in the OpenAPI format can be auto-generated from the source code documentation using the tool swag.
+To do this run the following in the top-level folder of the repo:
+
+- `go mod tidy`
+- `go install github.com/swaggo/swag/cmd/swag`
+- `swag init -p pascalcase -g "start.go" -o "./doc/api/"`
+
+The `.yaml` and `.json` files in OpenAPI swagger format are created in the output folder `doc/api`.
+
+### PostgreSQL database setup
+
+Please check the [Readme file in the backend repository](https://git.rwth-aachen.de/acs/public/villas/web-backend-go) for some useful hints on the local setup of the PostreSQL database.
+
+
diff --git a/src/__tests__/widget/edit-widget-control-creator.js b/src/__tests__/widget/edit-widget-control-creator.js
index 73efcd9..7f1dded 100644
--- a/src/__tests__/widget/edit-widget-control-creator.js
+++ b/src/__tests__/widget/edit-widget-control-creator.js
@@ -30,7 +30,6 @@ describe('edit widget control creator', () => {
{ args: { widgetType: 'Table' }, result: { controlNumber: 2, controlTypes: [EditWidgetCheckboxControl] } },
{ args: { widgetType: 'Image' }, result: { controlNumber: 2, controlTypes: [EditFileWidgetControl, EditWidgetAspectControl] } },
{ args: { widgetType: 'Gauge' }, result: { controlNumber: 6, controlTypes: [EditWidgetTextControl, EditWidgetSignalControl, EditWidgetCheckboxControl, EditWidgetColorZonesControl, EditWidgetMinMaxControl] } },
- { args: { widgetType: 'PlotTable' }, result: { controlNumber: 5, controlTypes: [EditWidgetSignalsControl, EditWidgetTextControl, EditWidgetTimeControl, EditWidgetMinMaxControl] } },
{ args: { widgetType: 'Slider' }, result: { controlNumber: 9, controlTypes: [EditWidgetTextControl, EditWidgetOrientation, EditWidgetSignalControl, EditWidgetCheckboxControl, EditWidgetCheckboxControl, EditWidgetMinMaxControl, EditWidgetNumberControl, EditWidgetNumberControl] } },
{ args: { widgetType: 'Button' }, result: { controlNumber: 6, controlTypes: [EditWidgetTextControl, EditWidgetSignalControl, EditWidgetCheckboxControl, EditWidgetNumberControl, EditWidgetNumberControl] } },
{ args: { widgetType: 'Box' }, result: { controlNumber: 2, controlTypes: [EditWidgetColorControl, EditWidgetColorControl] } },
diff --git a/src/common/dialogs/dialog.js b/src/common/dialogs/dialog.js
index cc387d9..9b3a2c8 100644
--- a/src/common/dialogs/dialog.js
+++ b/src/common/dialogs/dialog.js
@@ -56,7 +56,7 @@ class Dialog extends React.Component {
-
+ {this.props.blendOutCancel? : }
diff --git a/src/common/table.js b/src/common/table.js
index 020f9bd..b4edea3 100644
--- a/src/common/table.js
+++ b/src/common/table.js
@@ -63,7 +63,7 @@ class CustomTable extends Component {
let cell = [];
if (content != null) {
- content = content.toString();
+ //content = content.toString();
// check if cell should be a link
const linkKey = child.props.linkKey;
@@ -79,21 +79,24 @@ class CustomTable extends Component {
// add label to content
const labelKey = child.props.labelKey;
if (labelKey && data[labelKey] != null) {
- var labelContent = data[labelKey];
+ let labelContent = data[labelKey];
if (child.props.labelModifier) {
labelContent = child.props.labelModifier(labelContent, data);
}
+ let labelStyle = child.props.labelStyle(data[labelKey], data)
+
cell.push(
-
- {labelContent.toString()}
+
+ {labelContent}
);
}
+
if (child.props.dataIndex) {
cell.push(index);
}
diff --git a/src/dashboard/dashboard-button-group.js b/src/dashboard/dashboard-button-group.js
index 9fda299..8baeece 100644
--- a/src/dashboard/dashboard-button-group.js
+++ b/src/dashboard/dashboard-button-group.js
@@ -65,11 +65,18 @@ class DashboardButtonGroup extends React.Component {
);
}
+ buttons.push(
+
+ );
+
buttons.push(
);
+
}
return
, prevState: Readonly, snapshot: SS): void {
- if(prevState.minValue !== this.state.minValue){
- this.gauge.setMinValue(this.state.minValue);
- }
- if(prevState.maxValue !== this.state.maxValue){
- this.gauge.maxValue = this.state.maxValue
- }
-
+
// update gauge's value
if(prevState.value !== this.state.value){
this.gauge.set(this.state.value)
@@ -65,8 +60,8 @@ class WidgetGauge extends Component {
}
// update labels
- if(prevState.minValue !== this.state.minValue || prevState.maxValue !== this.state.maxValue || prevState.useColorZones !== this.state.useColorZones
- || prevState.useMinMax !== this.state.useMinMax){
+ if(prevState.minValue !== this.state.minValue || prevState.maxValue !== this.state.maxValue || prevState.colorZones !== this.state.colorZones
+ || prevState.useMinMax !== this.state.useMinMax || prevState.signalID !== this.state.signalID){
this.gauge = new Gauge(this.gaugeCanvas).setOptions(this.computeGaugeOptions(this.props.widget));
this.gauge.maxValue = this.state.maxValue;
this.gauge.setMinValue(this.state.minValue);
@@ -80,30 +75,35 @@ class WidgetGauge extends Component {
static getDerivedStateFromProps(props, state){
if(props.widget.signalIDs.length === 0){
- return null;
+ return{ value: 0, minValue: 0, maxValue: 10};
}
+
+ // get the signal with the selected signal ID
+ let signalID = props.widget.signalIDs[0];
+ let signal = props.signals.filter(s => s.id === signalID)
+ // determine ID of infrastructure component related to signal[0] (there is only one signal for a lamp widget)
+ let icID = props.icIDs[signal[0].id];
+
let returnState = {}
- returnState["useColorZones"] = props.widget.customProperties.colorZones;
+ returnState["colorZones"] = props.widget.customProperties.zones;
+ if(signalID){
+ returnState["signalID"] = signalID;
+ }
// 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;
+ if(signal !== undefined){
+ returnState["unit"] = signal[0].unit;
}
- const ICid = props.icIDs[0];
-
// update value
+
+ // check if data available
if (props.data == null
- || props.data[ICid] == null
- || props.data[ICid].output == null
- || props.data[ICid].output.values == null
- || props.data[ICid].output.values.length === 0
- || props.data[ICid].output.values[0].length === 0) {
- returnState["value"] = 0;
- return returnState;
+ || props.data[icID] == null
+ || props.data[icID].output == null
+ || props.data[icID].output.values == null) {
+ return{ value: 0, minValue: 0, maxValue: 10};
}
// memorize if min or max value is updated
@@ -112,14 +112,14 @@ class WidgetGauge extends Component {
let updateMaxValue = false;
// check if value has changed
- const signalData = props.data[ICid].output.values[widgetSignal.index];
+ const data = props.data[icID].output.values[signal[0].index-1];
// 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 (signalData != null) {
- const value = Math.round(signalData[signalData.length - 1].y * 1e3) / 1e3;
+ if (data != null) {
+ const value = Math.round(data[data.length - 1].y * 1e3) / 1e3;
let minValue = null;
let maxValue = null;
-
+
if ((state.value !== value && value != null) || props.widget.customProperties.valueUseMinMax || state.useMinMaxChange) {
//value has changed
updateValue = true;
@@ -129,14 +129,14 @@ class WidgetGauge extends Component {
minValue = state.minValue;
maxValue = state.maxValue;
-
- if (minValue == null || state.useMinMaxChange) {
+
+ if (minValue == null || (!props.widget.customProperties.valueUseMinMax && (value < minValue || signalID !== state.signalID)) ||state.useMinMaxChange) {
minValue = value - 0.5;
updateLabels = true;
updateMinValue = true;
}
- if (maxValue == null || state.useMinMaxChange) {
+ if (maxValue == null || (!props.widget.customProperties.valueUseMinMax && (value > maxValue || signalID !== state.signalID)) || state.useMinMaxChange) {
maxValue = value + 0.5;
updateLabels = true;
updateMaxValue = true;
@@ -144,17 +144,12 @@ class WidgetGauge extends Component {
}
if (props.widget.customProperties.valueUseMinMax) {
- if (state.minValue > props.widget.customProperties.valueMin) {
minValue = props.widget.customProperties.valueMin;
updateMinValue = true;
- updateLabels = true;
- }
-
- if (state.maxValue < props.widget.customProperties.valueMax) {
maxValue = props.widget.customProperties.valueMax;
updateMaxValue = true;
updateLabels = true;
- }
+
}
if (updateLabels === false && state.gauge) {
@@ -174,10 +169,7 @@ class WidgetGauge extends Component {
if(props.widget.customProperties.valueUseMinMax !== state.useMinMax){
returnState["useMinMax"] = props.widget.customProperties.valueUseMinMax;
}
- if(props.widget.customProperties.colorZones !== state.useColorZones){
- returnState["useColorZones"] = props.widget.customProperties.colorZones;
- }
-
+
// prepare returned state
if(updateValue === true){
returnState["value"] = value;
@@ -209,18 +201,19 @@ class WidgetGauge extends Component {
for (let i = 0; i < labelCount; i++) {
labels.push(minValue + labelStep * i);
}
-
+
// calculate zones
let zones = this.props.widget.customProperties.colorZones ? this.props.widget.customProperties.zones : null;
if (zones != null) {
// adapt range 0-100 to actual min-max
const step = (maxValue - minValue) / 100;
-
+
zones = zones.map(zone => {
return Object.assign({}, zone, { min: (zone.min * step) + +minValue, max: zone.max * step + +minValue, strokeStyle: '#' + zone.strokeStyle });
});
}
+ if(this.state.signalID !== ''){
this.gauge.setOptions({
staticLabels: {
font: '10px "Helvetica Neue"',
@@ -231,6 +224,7 @@ class WidgetGauge extends Component {
staticZones: zones
});
}
+ }
computeGaugeOptions(widget) {
return {
@@ -245,8 +239,8 @@ class WidgetGauge extends Component {
colorStop: '#6EA2B0',
strokeColor: '#E0E0E0',
highDpiSupport: true,
- limitMax: false,
- limitMin: false
+ limitMax: widget.customProperties.valueUseMinMax || false,
+ limitMin: widget.customProperties.valueUseMinMax || false
};
}
diff --git a/src/widget/widgets/input.js b/src/widget/widgets/input.js
index 58273ad..7fe7c38 100644
--- a/src/widget/widgets/input.js
+++ b/src/widget/widgets/input.js
@@ -31,53 +31,47 @@ class WidgetInput extends Component {
static getDerivedStateFromProps(props, state){
- let returnState = {};
+ let value = ''
+ let unit = ''
- if(props.widget.customProperties.value !== ''){
- returnState["value"] = props.widget.customProperties.value;
- }
-
- if(props.widget.signalIDs.length === 0){
- if (props.widget.customProperties.default_value && state.value === undefined && props.widget.customProperties.value === '') {
- returnState["value"] = props.widget.customProperties.default_value;
- } else { // if no default available
- if (returnState !== {}){
- return returnState;
- }
- else{
- return null;
- }
- }
- }
-
- // Update value
- if (props.widget.customProperties.default_value && this.state.value === undefined && props.widget.customProperties.value === '') {
- returnState["value"] = props.widget.customProperties.default_value;
+ if(props.widget.customProperties.hasOwnProperty('value') && props.widget.customProperties.value !== state.value){
+ // set value to customProperties.value if this property exists and the value is different from current state
+ value = Number(props.widget.customProperties.value);
+ } else if (props.widget.customProperties.hasOwnProperty('default_value') && state.value === ''){
+ // if customProperties.default_value exists and value has been assigned yet, set the value to the default_value
+ value = Number(props.widget.customProperties.default_value)
}
// Update unit (assuming there is exactly one signal for this widget)
let signalID = props.widget.signalIDs[0];
let signal = props.signals.find(sig => sig.id === signalID);
if(signal !== undefined){
- returnState["unit"] = signal.unit;
+ unit = signal.unit;
}
- if (returnState !== {}){
- return returnState;
- }
- else{
- return null;
+ if (unit !== '' && value !== ''){
+ // unit and value have changed
+ return {unit: unit, value: value};
+ } else if (unit !== ''){
+ // only unit has changed
+ return {unit: unit}
+ } else if (value !== ''){
+ // only value has changed
+ return {value: value}
+ } else{
+ // nothing has changed
+ return null
}
}
valueIsChanging(newValue) {
- this.setState({ value: newValue });
- this.props.widget.customProperties.value = newValue;
+ this.setState({ value: Number(newValue) });
+ this.props.widget.customProperties.value = Number(newValue);
}
valueChanged(newValue) {
if (this.props.onInputChanged) {
- this.props.onInputChanged(newValue);
+ this.props.onInputChanged(Number(newValue), 'value');
}
}
@@ -97,7 +91,16 @@ class WidgetInput extends Component {
- this.handleKeyPress(e) } onBlur={ (e) => this.valueChanged(this.state.value) } onChange={ (e) => this.valueIsChanging(e.target.value) } placeholder="Enter value" value={ this.state.value } />
+ this.handleKeyPress(e) }
+ onBlur={ (e) => this.valueChanged(this.state.value) }
+ onChange={ (e) => this.valueIsChanging(e.target.value) }
+ placeholder="Enter value"
+ value={ this.state.value }
+ />
{this.props.widget.customProperties.showUnit? (
{this.state.unit}
diff --git a/src/widget/widgets/lamp.js b/src/widget/widgets/lamp.js
index 7a89c04..939caed 100644
--- a/src/widget/widgets/lamp.js
+++ b/src/widget/widgets/lamp.js
@@ -25,7 +25,6 @@ class WidgetLamp extends Component {
this.state = {
value: '',
- threshold: 0
};
}
@@ -34,28 +33,31 @@ class WidgetLamp extends Component {
return{ value: ''};
}
- const ic = props.icIDs[0];
+ // get the signal with the selected signal ID
let signalID = props.widget.signalIDs[0];
- let widgetSignal = props.signals.find(sig => sig.id === signalID);
+ let signal = props.signals.filter(s => s.id === signalID)
+ // determine ID of infrastructure component related to signal[0] (there is only one signal for a lamp widget)
+ let icID = props.icIDs[signal[0].id];
- // update value
+ // check if data available
if (props.data == null
- || props.data[ic] == null
- || props.data[ic].output == null
- || props.data[ic].output.values == null) {
+ || props.data[icID] == null
+ || props.data[icID].output == null
+ || props.data[icID].output.values == null) {
return{value:''};
}
// check if value has changed
- const signalData = props.data[ic].output.values[widgetSignal.index];
- if (signalData != null && state.value !== signalData[signalData.length - 1].y) {
- return { value: signalData[signalData.length - 1].y };
+ const data = props.data[icID].output.values[signal[0].index-1];
+ if (data != null && Number(state.value) !== data[data.length - 1].y) {
+ return { value: data[data.length - 1].y };
}
return null;
}
render() {
+
let colors = EditWidgetColorControl.ColorPalette;
let color;
diff --git a/src/widget/widgets/plot-table.js b/src/widget/widgets/plot-table.js
deleted file mode 100644
index 155131e..0000000
--- a/src/widget/widgets/plot-table.js
+++ /dev/null
@@ -1,130 +0,0 @@
-/**
- * This file is part of VILLASweb.
- *
- * VILLASweb is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * VILLASweb is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with VILLASweb. If not, see .
- ******************************************************************************/
-
-import React, { Component } from 'react';
-import { FormGroup } from 'react-bootstrap';
-import Plot from '../widget-plot/plot';
-import PlotLegend from '../widget-plot/plot-legend';
-
-class WidgetPlotTable extends Component {
- constructor(props) {
- super(props);
- this.state = {
- signals: [],
- data: []
- };
- }
-
- static getDerivedStateFromProps(props, state){
- let intersection = []
- let data = [];
- let signalID, sig;
- for (signalID of props.widget.signalIDs) {
- for (sig of props.signals) {
- if (signalID === sig.id) {
- intersection.push(sig);
-
- // sig is a selected signal, get data
- // determine ID of infrastructure component related to signal (via config)
- let icID = props.icIDs[sig.id]
-
- // distinguish between input and output signals
- if (sig.direction === "out") {
- if (props.data[icID] != null && props.data[icID].output != null && props.data[icID].output.values != null) {
- if (props.data[icID].output.values[sig.index-1] !== undefined) {
- data.push(props.data[icID].output.values[sig.index-1]);
- }
- }
- } else if (sig.direction === "in") {
- if (props.data[icID] != null && props.data[icID].input != null && props.data[icID].input.values != null) {
- if (props.data[icID].input.values[sig.index-1] !== undefined) {
- data.push(props.data[icID].input.values[sig.index-1]);
- }
- }
- }
- } // sig is selected signal
- } // loop over props.signals
- } // loop over selected signals
-
- return {signals: intersection, data: data}
- }
-
- // updateSignalSelection(signal, checked) {
- // // Update the selected signals and propagate to parent component
- // var new_widget = Object.assign({}, this.props.widget, {
- // checkedSignals: checked ? this.state.signals.concat(signal) : this.state.signals.filter((idx) => idx !== signal)
- // });
- // this.props.onWidgetChange(new_widget);
- // }
-
- render() {
- let checkBoxes = [];
-
- let showLegend = false;
- if (this.state.signals.length > 0) {
-
- showLegend = true;
-
- // Create checkboxes using the signal indices from component config
- // checkBoxes = this.state.signals.map((signal) => {
- // let checked = this.state.signals.indexOf(signal) > -1;
- // let chkBxClasses = classNames({
- // 'btn': true,
- // 'btn-default': true,
- // 'active': checked
- // });
- // return this.updateSignalSelection(signal, e.target.checked)}> {signal.name}
- // });
- }
-
- return (
-
- );
- }
-}
-export default WidgetPlotTable;
diff --git a/src/widget/widgets/slider.js b/src/widget/widgets/slider.js
index ce76fc6..89f4da7 100644
--- a/src/widget/widgets/slider.js
+++ b/src/widget/widgets/slider.js
@@ -40,47 +40,38 @@ class WidgetSlider extends Component {
}
static getDerivedStateFromProps(props, state){
- let returnState = {};
- if(props.widget.customProperties.value !== ''){
- returnState["value"] = props.widget.customProperties.value;
+ let value = ''
+ let unit = ''
+
+ if(props.widget.customProperties.hasOwnProperty('value') && props.widget.customProperties.value !== state.value){
+ // set value to customProperties.value if this property exists and the value is different from current state
+ value = Number(props.widget.customProperties.value);
+ } else if (props.widget.customProperties.hasOwnProperty('default_value') && state.value === ''){
+ // if customProperties.default_value exists and value has been assigned yet, set the value to the default_value
+ value = Number(props.widget.customProperties.default_value)
}
- if(props.widget.signalIDs.length === 0){
-
- // set value to default
- if (props.widget.customProperties.default_value && state.value === undefined && props.widget.customProperties.value === '') {
- returnState["value"] = props.widget.customProperties.default_value;
- } else { // if no default available
- if (returnState !== {}){
- return returnState;
- }
- else{
- return null;
- }
- }
-
- }
-
- // Update value
- if (props.widget.customProperties.default_value && state.value === undefined && props.widget.customProperties.value === '') {
- returnState["value"] = props.widget.customProperties.default_value;
- }
-
// Update unit (assuming there is exactly one signal for this widget)
let signalID = props.widget.signalIDs[0];
let signal = props.signals.find(sig => sig.id === signalID);
if(signal !== undefined){
- returnState["unit"] = signal.unit;
+ unit = signal.unit;
}
- if (returnState !== {}){
- return returnState;
+ if (unit !== '' && value !== ''){
+ // unit and value have changed
+ return {unit: unit, value: value};
+ } else if (unit !== ''){
+ // only unit has changed
+ return {unit: unit}
+ } else if (value !== ''){
+ // only value has changed
+ return {value: value}
+ } else {
+ // nothing has changed
+ return null
}
- else{
- return null;
- }
-
}
componentDidUpdate(prevProps: Readonly
, prevState: Readonly, snapshot: SS): void {
@@ -114,7 +105,7 @@ class WidgetSlider extends Component {
valueChanged(newValue) {
if (this.props.onInputChanged) {
- this.props.onInputChanged(newValue);
+ this.props.onInputChanged(newValue, 'value');
}
}
@@ -124,7 +115,7 @@ class WidgetSlider extends Component {
let fields = {
name: this.props.widget.name,
control: this.valueIsChanging(v) } onAfterChange={ (v) => this.valueChanged(v) }/>,
- value: { format('.3s')(Number.parseFloat(this.state.value)) },
+ value: { format('.2f')(Number.parseFloat(this.state.value)) },
unit: { this.state.unit }
}
diff --git a/src/widget/widgets/table.js b/src/widget/widgets/table.js
index 54383a8..089eec0 100644
--- a/src/widget/widgets/table.js
+++ b/src/widget/widgets/table.js
@@ -27,72 +27,55 @@ class WidgetTable extends Component {
this.state = {
rows: [],
- sequence: null,
- showUnit: false
};
}
-
static getDerivedStateFromProps(props, state){
- if(props.widget.signalIDs.length === 0){
- return{
- rows: [],
- sequence: null,
- };
- }
+ let rows = [];
+ let signalID, sig;
+ for (signalID of props.widget.signalIDs) {
+ for (sig of props.signals) {
+ if (signalID === sig.id) {
+ // sig is a selected signal, get data
+ // determine ID of infrastructure component related to signal (via config)
+ let icID = props.icIDs[sig.id]
+ // distinguish between input and output signals
+ if (sig.direction === "out") {
+ if (props.data[icID] != null && props.data[icID].output != null && props.data[icID].output.values != null) {
+ if (props.data[icID].output.values[sig.index-1] !== undefined) {
+ let data = props.data[icID].output.values[sig.index-1];
+ rows.push({
+ name: sig.name,
+ unit: sig.unit,
+ value: data[data.length - 1].y
+ });
- const ICid = props.icIDs[0];
- let widgetSignals = props.signals.find(sig => {
- for (let id of props.widget.signalIDs){
- if (id === sig.id){
- return true;
- }
- }
- return false;
- });
+ }
+ }
+ } else if (sig.direction === "in") {
+ if (props.data[icID] != null && props.data[icID].input != null && props.data[icID].input.values != null) {
+ if (props.data[icID].input.values[sig.index-1] !== undefined) {
+ let data = props.data[icID].input.values[sig.index-1];
+ rows.push({
+ name: sig.name,
+ unit: sig.unit,
+ value: data[data.length - 1].y
+ });
+ }
+ }
+ }
+ } // sig is selected signal
+ } // loop over props.signals
+ } // loop over selected signals
- // check data
- if (props.data == null
- || props.data[ICid] == null
- || props.data[ICid].output == null
- || props.data[ICid].output.values.length === 0
- || props.data[ICid].output.values[0].length === 0) {
+ return {rows: rows}
- // clear values
- return{
- rows: [],
- sequence: null,
- showUnit: false,
- };
- }
-
- // get rows
- const rows = [];
-
- props.data[ICid].output.values.forEach((signal, index) => {
- let s = widgetSignals.find( sig => sig.index === index);
- // if the signal is used by the widget
- if (s !== undefined) {
- // push data of the signal
- rows.push({
- name: s.name,
- unit: s.unit,
- value: signal[signal.length - 1].y
- });
- }
- });
-
- return {
- showUnit: props.showUnit,
- rows: rows,
- sequence: props.data[ICid].output.sequence
- };
}
render() {
-
+
let rows = this.state.rows;
if(rows.length === 0){
diff --git a/src/widget/widgets/value.js b/src/widget/widgets/value.js
index f143cd0..0468b3e 100644
--- a/src/widget/widgets/value.js
+++ b/src/widget/widgets/value.js
@@ -33,31 +33,28 @@ class WidgetValue extends Component {
return null;
}
- // TODO does the following line make sense?
- const ICid = props.icIDs[0];
+ // get the signal with the selected signal ID
let signalID = props.widget.signalIDs[0];
- let signal = props.signals.find(sig => sig.id === signalID);
+ let signal = props.signals.filter(s => s.id === signalID)
+ // determine ID of infrastructure component related to signal[0] (there is only one signal for a value widget)
+ let icID = props.icIDs[signal[0].id];
-
- // update value
- let value = '';
- if (props.data == null
- || props.data[ICid] == null
- || props.data[ICid].output == null
- || props.data[ICid].output.values == null) {
+ // check if data available
+ let value = ''
+ if (props.data == null || props.data[icID] == null || props.data[icID].output == null || props.data[icID].output.values == null) {
value = '';
} else {
// check if value has changed
- const signalData = props.data[ICid].output.values[signal.index];
- if (signalData != null && state.value !== signalData[signalData.length - 1].y) {
- value = signalData[signalData.length - 1].y
+ const data = props.data[icID].output.values[signal[0].index - 1];
+ if (data != null && Number(state.value) !== data[data.length - 1].y) {
+ value = data[data.length - 1].y;
}
}
// Update unit (assuming there is exactly one signal for this widget)
let unit = '';
if(signal !== undefined){
- unit = signal.unit;
+ unit = signal[0].unit;
}
return {
@@ -77,7 +74,7 @@ class WidgetValue extends Component {
{Number.isNaN(value_to_render) ? NaN : format('.3s')(value_to_render)}
{this.props.widget.customProperties.showUnit &&
[{this.state.unit}]
- }
+ }