From 8523b9e3542e3df6cd2ca3698cb32771092b0c16 Mon Sep 17 00:00:00 2001 From: irismarie Date: Thu, 25 Jun 2020 13:05:40 +0200 Subject: [PATCH 01/15] show border of box widget, closes #228 --- src/styles/widgets.css | 4 ++-- src/widget/widget-factory.js | 1 + src/widget/widgets/box.js | 4 +--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/styles/widgets.css b/src/styles/widgets.css index 192acdd..4a8c976 100644 --- a/src/styles/widgets.css +++ b/src/styles/widgets.css @@ -373,9 +373,9 @@ div[class*="-widget"] label { /* End table widget*/ /* Begin box widget */ -.box-widget .border { +.box-widget { width: 100%; height: 100%; - border: 2px solid; + border: 2px solid lightgray; } /* End box widget */ diff --git a/src/widget/widget-factory.js b/src/widget/widget-factory.js index 1360988..8b08117 100644 --- a/src/widget/widget-factory.js +++ b/src/widget/widget-factory.js @@ -168,6 +168,7 @@ class WidgetFactory { widget.width = 100; widget.height = 100; widget.customProperties.border_color = 0; + widget.customProperties.background_color = 9; widget.customProperties.background_color_opacity = 0.5; widget.z = 0; break; diff --git a/src/widget/widgets/box.js b/src/widget/widgets/box.js index ff470f3..88e7c67 100644 --- a/src/widget/widgets/box.js +++ b/src/widget/widgets/box.js @@ -31,10 +31,8 @@ class WidgetBox extends Component { } return ( -
-
+
{ } -
); } From b317cc095a05038106035bc2a692db4d05b34b26 Mon Sep 17 00:00:00 2001 From: Laura Fuentes Grau Date: Thu, 25 Jun 2020 18:01:26 +0200 Subject: [PATCH 02/15] wip: make dashboard manually resizable #230 --- src/dashboard/dashboard.js | 30 ++++++++++++++++++++++++++---- src/styles/app.css | 2 +- src/widget/widget-area.js | 7 +------ src/widget/widget-toolbox.js | 16 +++++++++++++++- 4 files changed, 43 insertions(+), 12 deletions(-) diff --git a/src/dashboard/dashboard.js b/src/dashboard/dashboard.js index 874eb10..86763a2 100644 --- a/src/dashboard/dashboard.js +++ b/src/dashboard/dashboard.js @@ -120,6 +120,7 @@ class Dashboard extends Component { editing: prevState.editing || false, paused: prevState.paused || false, + dropZoneHeight: prevState.dropZoneHeight || maxHeight +80, editModal: false, filesEditModal: prevState.filesEditModal || false, @@ -130,7 +131,6 @@ class Dashboard extends Component { widgetOrigIDs: prevState.widgetOrigIDs || [], maxWidgetHeight: maxHeight || null, - dropZoneHeight: maxHeight +80 || null, }; } @@ -389,6 +389,27 @@ class Dashboard extends Component { this.forceUpdate(); }; + setDashboardSize(value) { + const maxHeight = Object.values(this.state.widgets).reduce((currentHeight, widget) => { + const absolutHeight = widget.y + widget.height; + + return absolutHeight > currentHeight ? absolutHeight : currentHeight; + }, 0); + let tempSize = null; + if(value === -1){ + tempSize = this.state.dropZoneHeight - 50; + if(tempSize > maxHeight +80){ + this.setState({dropZoneHeight: tempSize}); + } + } + else{ + tempSize = this.state.dropZoneHeight + 50; + this.setState( {dropZoneHeight: tempSize}); + return tempSize; + } + + } + pauseData(){ this.setState({ paused: true }); }; @@ -402,6 +423,7 @@ class Dashboard extends Component { const grid = this.state.dashboard.grid; const boxClasses = classNames('section', 'box', { 'fullscreen-padding': this.props.isFullscreen }); let draggable = this.state.editing; + let dropZoneHeight = this.state.dropZoneHeight; return
@@ -424,10 +446,10 @@ class Dashboard extends Component {
e.preventDefault() }> {this.state.editing && - + } {!draggable?( - + {this.state.widgets != null && Object.keys(this.state.widgets).map(widgetKey => ( ) : ( - + {this.state.widgets != null && Object.keys(this.state.widgets).map(widgetKey => ( { - const absolutHeight = widget.y + widget.height; - return absolutHeight > currentHeight ? absolutHeight : currentHeight; - }, 0); - - return + return {this.props.children} diff --git a/src/widget/widget-toolbox.js b/src/widget/widget-toolbox.js index 60be4af..06bf481 100644 --- a/src/widget/widget-toolbox.js +++ b/src/widget/widget-toolbox.js @@ -18,6 +18,8 @@ import React from 'react'; import PropTypes from 'prop-types'; import Slider from 'rc-slider'; +import { Button } from 'react-bootstrap'; +import Icon from "../common/icon"; import ToolboxItem from './toolbox-item'; @@ -57,6 +59,17 @@ class WidgetToolbox extends React.Component {
Grid: { this.props.grid > 1 ? this.props.grid : 'Disabled' } + +
+
+
+
+ +
; @@ -66,7 +79,8 @@ class WidgetToolbox extends React.Component { WidgetToolbox.propTypes = { widgets: PropTypes.array, grid: PropTypes.number, - onGridChange: PropTypes.func + onGridChange: PropTypes.func, + onDashboardSizeChange: PropTypes.func }; export default WidgetToolbox; From 453ed4b154a769fe31e4d1f0e0aa939e99a1330a Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Fri, 26 Jun 2020 10:49:54 +0200 Subject: [PATCH 03/15] new scalingFactor signal parameter is editable in signal mapping dialog window #130 --- src/signal/edit-signal-mapping.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/signal/edit-signal-mapping.js b/src/signal/edit-signal-mapping.js index 43235bc..cf5ac2e 100644 --- a/src/signal/edit-signal-mapping.js +++ b/src/signal/edit-signal-mapping.js @@ -73,6 +73,11 @@ class EditSignalMapping extends React.Component { sig = this.state.signals[row]; sig.unit = event.target.value; } + } else if (column === 3) { // scaling factor change + if (parseFloat(event.target.value) !== 0.0) { + sig = this.state.signals[row]; + sig.scalingFactor = parseFloat(event.target.value); + } } else if (column === 0) { //index change sig = this.state.signals[row]; sig.index = parseInt(event.target.value, 10); @@ -108,7 +113,8 @@ class EditSignalMapping extends React.Component { direction: this.state.dir, name: "PlaceholderName", unit: "PlaceholderUnit", - index: 999 + index: 999, + scalingFactor: 1.0 }; AppDispatcher.dispatch({ @@ -152,6 +158,7 @@ class EditSignalMapping extends React.Component { this.handleMappingChange(e, row, column)} /> this.handleMappingChange(e, row, column)} /> this.handleMappingChange(e, row, column)} /> + this.handleMappingChange(e, row, column)} /> this.handleDelete(index)} /> From 24ccc9eca95c8e51cb2f0e28fa0bcc46a2d47020 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Fri, 26 Jun 2020 12:02:35 +0200 Subject: [PATCH 04/15] improve signal mapping dialog, table widget applies scaling Factor for signals and changes legend accordingly #130 --- src/common/table-column.js | 1 + src/common/table.js | 2 +- src/signal/edit-signal-mapping.js | 73 +++++++++++++++++++------------ src/widget/widgets/table.js | 15 ++++--- 4 files changed, 56 insertions(+), 35 deletions(-) diff --git a/src/common/table-column.js b/src/common/table-column.js index 748035a..53b99e5 100644 --- a/src/common/table-column.js +++ b/src/common/table-column.js @@ -29,6 +29,7 @@ class TableColumn extends Component { linkKey: '', dataIndex: false, inlineEditable: false, + inputType: 'text', clickable: false, labelKey: null, checkbox: false, diff --git a/src/common/table.js b/src/common/table.js index b4edea3..d68191a 100644 --- a/src/common/table.js +++ b/src/common/table.js @@ -205,7 +205,7 @@ class CustomTable extends Component { return ( {(this.state.editCell[0] === cellIndex && this.state.editCell[1] === rowIndex ) ? ( - children[cellIndex].props.onInlineChange(event, rowIndex, cellIndex)} ref={ref => { this.activeInput = ref; }} /> + children[cellIndex].props.onInlineChange(event, rowIndex, cellIndex)} ref={ref => { this.activeInput = ref; }} /> ) : ( {cell.map((element, elementIndex) => ( diff --git a/src/signal/edit-signal-mapping.js b/src/signal/edit-signal-mapping.js index cf5ac2e..0c64108 100644 --- a/src/signal/edit-signal-mapping.js +++ b/src/signal/edit-signal-mapping.js @@ -39,7 +39,8 @@ class EditSignalMapping extends React.Component { this.state = { dir, - signals: [] + signals: [], + modifiedSignalIDs : [] }; } @@ -51,46 +52,60 @@ class EditSignalMapping extends React.Component { }); return { - signals: signals + signals: signals, }; } onClose(canceled) { + + for (let signalID of this.state.modifiedSignalIDs){ + + let sig = this.state.signals.find(s => s.id === signalID); + + //dispatch changes to signal + AppDispatcher.dispatch({ + type: 'signals/start-edit', + data: sig, + token: this.props.sessionToken, + }); + } + this.props.onCloseEdit(this.state.dir) } handleMappingChange = (event, row, column) => { - let sig = {} + + let signals = this.state.signals; + let modifiedSignals = this.state.modifiedSignalIDs; + if (column === 1) { // Name change - if (event.target.value !== '') { - sig = this.state.signals[row]; - sig.name = event.target.value; - } + signals[row].name = event.target.value; + if (modifiedSignals.find(id => id === signals[row].id) === undefined){ + modifiedSignals.push(signals[row].id); + } } else if (column === 2) { // unit change - if (event.target.value !== '') { - sig = this.state.signals[row]; - sig.unit = event.target.value; - } + signals[row].unit = event.target.value; + if (modifiedSignals.find(id => id === signals[row].id) === undefined){ + modifiedSignals.push(signals[row].id); + } } else if (column === 3) { // scaling factor change - if (parseFloat(event.target.value) !== 0.0) { - sig = this.state.signals[row]; - sig.scalingFactor = parseFloat(event.target.value); + signals[row].scalingFactor = parseFloat(event.target.value); + if (modifiedSignals.find(id => id === signals[row].id) === undefined){ + modifiedSignals.push(signals[row].id); } } else if (column === 0) { //index change - sig = this.state.signals[row]; - sig.index = parseInt(event.target.value, 10); + signals[row].index =parseInt(event.target.value, 10); + if (modifiedSignals.find(id => id === signals[row].id) === undefined){ + modifiedSignals.push(signals[row].id); + } } - if (sig !== {}){ - //dispatch changes to signal - AppDispatcher.dispatch({ - type: 'signals/start-edit', - data: sig, - token: this.props.sessionToken, - }); - } + this.setState({ + signals: signals, + modifiedSignalIDs: modifiedSignals + }) }; @@ -145,7 +160,7 @@ class EditSignalMapping extends React.Component { this.onClose(c)} onReset={() => this.resetState()} @@ -155,10 +170,10 @@ class EditSignalMapping extends React.Component { {this.props.direction} Mapping Click Index, Name or Unit cell to edit - this.handleMappingChange(e, row, column)} /> - this.handleMappingChange(e, row, column)} /> - this.handleMappingChange(e, row, column)} /> - this.handleMappingChange(e, row, column)} /> + this.handleMappingChange(e, row, column)} /> + this.handleMappingChange(e, row, column)} /> + this.handleMappingChange(e, row, column)} /> + this.handleMappingChange(e, row, column)} /> this.handleDelete(index)} />
diff --git a/src/widget/widgets/table.js b/src/widget/widgets/table.js index 089eec0..3ecc59a 100644 --- a/src/widget/widgets/table.js +++ b/src/widget/widgets/table.js @@ -41,15 +41,20 @@ class WidgetTable extends Component { // determine ID of infrastructure component related to signal (via config) let icID = props.icIDs[sig.id] + let signalName = sig.name; + if(sig.scalingFactor !== 1.0){ + signalName = signalName + "(x" + String(sig.scalingFactor) + ")"; + } + // 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, + name: signalName, unit: sig.unit, - value: data[data.length - 1].y + value: data[data.length - 1].y * sig.scalingFactor }); } @@ -59,9 +64,9 @@ class WidgetTable extends Component { 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, + name: signalName, unit: sig.unit, - value: data[data.length - 1].y + value: data[data.length - 1].y * sig.scalingFactor }); } } @@ -86,7 +91,7 @@ class WidgetTable extends Component { var columns = [ , - + ]; if (this.props.widget.customProperties.showUnit) From fd5e6e476f875dad37397e7fb933d1ac29e10619 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Fri, 26 Jun 2020 13:26:23 +0200 Subject: [PATCH 05/15] gauge widget considers signal scaling factor #130 --- src/widget/widgets/gauge.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/widget/widgets/gauge.js b/src/widget/widgets/gauge.js index 00ffa93..3aa3e3b 100644 --- a/src/widget/widgets/gauge.js +++ b/src/widget/widgets/gauge.js @@ -49,7 +49,7 @@ class WidgetGauge extends Component { } componentDidUpdate(prevProps: Readonly

, prevState: Readonly, snapshot: SS): void { - + // update gauge's value if(prevState.value !== this.state.value){ this.gauge.set(this.state.value) @@ -116,7 +116,7 @@ class WidgetGauge extends Component { // 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 (data != null) { - const value = Math.round(data[data.length - 1].y * 1e3) / 1e3; + const value = signal[0].scalingFactor * Math.round(data[data.length - 1].y * 1e3) / 1e3; let minValue = null; let maxValue = null; @@ -149,7 +149,7 @@ class WidgetGauge extends Component { maxValue = props.widget.customProperties.valueMax; updateMaxValue = true; updateLabels = true; - + } if (updateLabels === false && state.gauge) { @@ -169,7 +169,7 @@ class WidgetGauge extends Component { if(props.widget.customProperties.valueUseMinMax !== state.useMinMax){ returnState["useMinMax"] = props.widget.customProperties.valueUseMinMax; } - + // prepare returned state if(updateValue === true){ returnState["value"] = value; @@ -201,7 +201,7 @@ 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) { From e8893f165ed1fba7c7ea0d56770dfbefac8ae704 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Fri, 26 Jun 2020 15:57:47 +0200 Subject: [PATCH 06/15] Lamp and plot widgets consider signal scaling #130 --- src/widget/widget-plot/plot-legend.js | 2 +- src/widget/widgets/lamp.js | 4 ++-- src/widget/widgets/plot.js | 27 +++++++++++++++++++++++++-- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/widget/widget-plot/plot-legend.js b/src/widget/widget-plot/plot-legend.js index f3f4cd6..5ac618c 100644 --- a/src/widget/widget-plot/plot-legend.js +++ b/src/widget/widget-plot/plot-legend.js @@ -27,7 +27,7 @@ class PlotLegend extends React.Component {

    {this.props.signals.map(signal =>
  • - {signal.name} + {signal.name + "(x" + signal.scalingFactor + ")"} {signal.unit}
  • )} diff --git a/src/widget/widgets/lamp.js b/src/widget/widgets/lamp.js index 939caed..c0ca9a0 100644 --- a/src/widget/widgets/lamp.js +++ b/src/widget/widgets/lamp.js @@ -49,8 +49,8 @@ class WidgetLamp extends Component { // check if value has changed 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 }; + if (data != null && Number(state.value) !== signal[0].scalingFactor * data[data.length - 1].y) { + return { value: signal[0].scalingFactor * data[data.length - 1].y }; } return null; diff --git a/src/widget/widgets/plot.js b/src/widget/widgets/plot.js index 20245e2..e87e7d1 100644 --- a/src/widget/widgets/plot.js +++ b/src/widget/widgets/plot.js @@ -49,13 +49,31 @@ class WidgetPlot extends React.Component { 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]); + let values = props.data[icID].output.values[sig.index-1]; + if(sig.scalingFactor !== 1) { + let scaledValues = JSON.parse(JSON.stringify(values)); + for (let i=0; i< scaledValues.length; i++){ + scaledValues[i].y = scaledValues[i].y * sig.scalingFactor; + } + data.push(scaledValues); + } else { + data.push(values); + } } } } 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]); + let values = props.data[icID].output.values[sig.index-1]; + if(sig.scalingFactor !== 1) { + let scaledValues = JSON.parse(JSON.stringify(values)); + for (let i=0; i< scaledValues.length; i++){ + scaledValues[i].y = scaledValues[i].y * sig.scalingFactor; + } + data.push(scaledValues); + } else { + data.push(values); + } } } } @@ -67,6 +85,11 @@ class WidgetPlot extends React.Component { } + + scaleData(data, scaleFactor){ + // data is an array of value pairs x,y + } + render() { return
    From 209649ead864987e0b5c25dc67ec2c12a626fe25 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Mon, 29 Jun 2020 11:20:56 +0200 Subject: [PATCH 07/15] Value widget considers signal scaling factor #130 --- src/widget/widgets/value.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/widget/widgets/value.js b/src/widget/widgets/value.js index 0468b3e..8c4c5a9 100644 --- a/src/widget/widgets/value.js +++ b/src/widget/widgets/value.js @@ -47,7 +47,7 @@ class WidgetValue extends Component { // check if value has changed 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; + value = signal[0].scalingFactor * data[data.length - 1].y; } } @@ -66,12 +66,12 @@ class WidgetValue extends Component { render() { let value_to_render = Number(this.state.value); - let value_width = this.props.widget.customProperties.textSize*(value_to_render < 1000 ? (2):(3)); + let value_width = this.props.widget.customProperties.textSize*(Math.abs(value_to_render) < 1000 ? (5):(8)); let unit_width = this.props.widget.customProperties.textSize*(this.state.unit.length + 0.7); return (
    {this.props.widget.name} - {Number.isNaN(value_to_render) ? NaN : format('.3s')(value_to_render)} + {Number.isNaN(value_to_render) ? NaN : format('.3f')(value_to_render)} {this.props.widget.customProperties.showUnit && [{this.state.unit}] } From ec99815eb1a4e557af314c1d868e47fc8bd39544 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Mon, 29 Jun 2020 12:10:37 +0200 Subject: [PATCH 08/15] fix for sending of data to IC --- src/ic/ic-data-store.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ic/ic-data-store.js b/src/ic/ic-data-store.js index bca9a95..7a81698 100644 --- a/src/ic/ic-data-store.js +++ b/src/ic/ic-data-store.js @@ -111,8 +111,11 @@ class ICDataStore extends ReduceStore { state[action.ic].input.sequence++; state[action.ic].input.values[action.signal-1] = action.data; - ICDataDataManager.send(state[action.ic].input, action.ic); + // copy of state needed because changes are not yet propagated + let input = JSON.parse(JSON.stringify(state[action.ic].input)); + ICDataDataManager.send(input, action.ic); + this.__emitChange(); return state; default: From 547d6c387735820d4943974527dac3a32e68b043 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Mon, 29 Jun 2020 12:13:02 +0200 Subject: [PATCH 09/15] Input widgets (button, number input, slider) consider scaling factor of signal #130 , fix for widget parameter update upon signal value change --- src/widget/widget.js | 12 ++++++------ src/widget/widgets/button.js | 20 ++++++++++++-------- src/widget/widgets/input.js | 2 +- src/widget/widgets/slider.js | 2 +- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/widget/widget.js b/src/widget/widget.js index 11fe5c2..e2faeb1 100644 --- a/src/widget/widget.js +++ b/src/widget/widget.js @@ -88,13 +88,13 @@ class Widget extends React.Component { }; } - inputDataChanged(widget, data, controlID) { + inputDataChanged(widget, data, controlID, controlValue) { // controlID is the path to the widget customProperty that is changed (for example 'value') // modify the widget customProperty if (controlID !== '') { let updatedWidget = JSON.parse(JSON.stringify(widget)); - updatedWidget.customProperties[controlID] = data; + updatedWidget.customProperties[controlID] = controlValue; AppDispatcher.dispatch({ type: 'widgets/start-edit', token: this.state.sessionToken, @@ -118,7 +118,7 @@ class Widget extends React.Component { type: 'icData/inputChanged', ic: icID, signal: signal[0].index, - data + data: signal[0].scalingFactor * data }); } @@ -179,14 +179,14 @@ class Widget extends React.Component { return this.inputDataChanged(widget, value, controlID)} + onInputChanged={(value, controlID, controlValue) => this.inputDataChanged(widget, value, controlID, controlValue)} signals={this.state.signals} /> } else if (widget.type === 'NumberInput') { return this.inputDataChanged(widget, value, controlID)} + onInputChanged={(value, controlID, controlValue) => this.inputDataChanged(widget, value, controlID, controlValue)} signals={this.state.signals} /> } else if (widget.type === 'Slider') { @@ -194,7 +194,7 @@ class Widget extends React.Component { widget={widget} editing={this.props.editing} onWidgetChange={(w) => this.props.onWidgetStatusChange(w, this.props.index) } - onInputChanged={(value, controlID) => this.inputDataChanged(widget, value, controlID)} + onInputChanged={(value, controlID, controlValue) => this.inputDataChanged(widget, value, controlID, controlValue)} signals={this.state.signals} /> } else if (widget.type === 'Gauge') { diff --git a/src/widget/widgets/button.js b/src/widget/widgets/button.js index e3a11a0..e95e740 100644 --- a/src/widget/widgets/button.js +++ b/src/widget/widgets/button.js @@ -28,11 +28,16 @@ class WidgetButton extends Component { } } + static getDerivedStateFromProps(props, state){ + return { + pressed: props.widget.customProperties.pressed + } + } + onPress(e) { if (e.button === 0 && !this.props.widget.customProperties.toggle) { - this.setState({ pressed: true }); - this.valueChanged(this.props.widget.customProperties.on_value); + this.valueChanged(this.props.widget.customProperties.on_value, true); } } @@ -43,15 +48,14 @@ class WidgetButton extends Component { if (this.props.widget.customProperties.toggle) { nextState = !this.state.pressed; } - this.props.widget.customProperties.pressed = nextState; - this.setState({pressed: nextState}); - this.valueChanged(nextState ? this.props.widget.customProperties.on_value : this.props.widget.customProperties.off_value); + this.valueChanged(nextState ? this.props.widget.customProperties.on_value : this.props.widget.customProperties.off_value, nextState); } } - valueChanged(newValue) { - if (this.props.onInputChanged) - this.props.onInputChanged(newValue, 'pressed'); + valueChanged(newValue, pressed) { + if (this.props.onInputChanged) { + this.props.onInputChanged(newValue, 'pressed', pressed); + } } render() { diff --git a/src/widget/widgets/input.js b/src/widget/widgets/input.js index 7fe7c38..0b87617 100644 --- a/src/widget/widgets/input.js +++ b/src/widget/widgets/input.js @@ -71,7 +71,7 @@ class WidgetInput extends Component { valueChanged(newValue) { if (this.props.onInputChanged) { - this.props.onInputChanged(Number(newValue), 'value'); + this.props.onInputChanged(Number(newValue), 'value', Number(newValue)); } } diff --git a/src/widget/widgets/slider.js b/src/widget/widgets/slider.js index 89f4da7..10bb055 100644 --- a/src/widget/widgets/slider.js +++ b/src/widget/widgets/slider.js @@ -105,7 +105,7 @@ class WidgetSlider extends Component { valueChanged(newValue) { if (this.props.onInputChanged) { - this.props.onInputChanged(newValue, 'value'); + this.props.onInputChanged(newValue, 'value', newValue); } } From 02ae930f7c431f7986726c8fcb96bd6ff2524284 Mon Sep 17 00:00:00 2001 From: Laura Fuentes Grau Date: Wed, 1 Jul 2020 19:59:25 +0200 Subject: [PATCH 10/15] Dropzoneheight now saved as dashboard property #230 , deleted unnecessary code, (wip) added tooltips to dashboard resizing buttons #235 --- src/dashboard/dashboard.js | 71 ++++++++++++------------------------ src/widget/widget-toolbox.js | 6 ++- 2 files changed, 28 insertions(+), 49 deletions(-) diff --git a/src/dashboard/dashboard.js b/src/dashboard/dashboard.js index 86763a2..05f69e1 100644 --- a/src/dashboard/dashboard.js +++ b/src/dashboard/dashboard.js @@ -73,6 +73,10 @@ class Dashboard extends Component { return thisWidgetHeight > maxHeightSoFar? thisWidgetHeight : maxHeightSoFar; }, 0); + if(dashboard.height === 0 || maxHeight + 80 > dashboard.height) + { + dashboard.height = maxHeight + 80; + } // filter component configurations to the ones that belong to this scenario let configs = [] @@ -108,7 +112,7 @@ class Dashboard extends Component { return ICused; }); } - + return { dashboard, widgets, @@ -120,7 +124,6 @@ class Dashboard extends Component { editing: prevState.editing || false, paused: prevState.paused || false, - dropZoneHeight: prevState.dropZoneHeight || maxHeight +80, editModal: false, filesEditModal: prevState.filesEditModal || false, @@ -188,25 +191,6 @@ class Dashboard extends Component { } } - /* - * Adapt the area's height with the position of the new widget. - * Return true if the height increased, otherwise false. - */ - increaseHeightWithWidget(widget) { - let increased = false; - let thisWidgetHeight = widget.y + widget.height; - - if (thisWidgetHeight > this.state.maxWidgetHeight) { - increased = true; - - this.setState({ - maxWidgetHeight: thisWidgetHeight, - dropZoneHeight: thisWidgetHeight + 40 - }); - } - - return increased; - } transformToWidgetsList(widgets) { return Object.keys(widgets).map( (key) => widgets[key]); @@ -238,24 +222,6 @@ class Dashboard extends Component { } - /* - * Set the initial height state based on the existing widgets - */ - computeHeightWithWidgets(widgets) { - // Compute max height from widgets - let maxHeight = Object.keys(widgets).reduce( (maxHeightSoFar, widgetKey) => { - let thisWidget = widgets[widgetKey]; - let thisWidgetHeight = thisWidget.y + thisWidget.height; - - return thisWidgetHeight > maxHeightSoFar? thisWidgetHeight : maxHeightSoFar; - }, 0); - - this.setState({ - maxWidgetHeight: maxHeight, - dropZoneHeight: maxHeight + 80 - }); - } - editWidget(widget, index){ this.setState({ editModal: true, modalData: widget, modalIndex: index }); @@ -377,6 +343,12 @@ class Dashboard extends Component { token: this.state.sessionToken, param: '?dashboardID=' + this.state.dashboard.id }); + + AppDispatcher.dispatch({ + type: 'dashboards/start-load', + data: this.props.match.params.dashboard, + token: this.state.sessionToken + }); this.setState({ editing: false, widgetChangeData: []}); }; @@ -395,19 +367,22 @@ class Dashboard extends Component { return absolutHeight > currentHeight ? absolutHeight : currentHeight; }, 0); - let tempSize = null; + let dashboard = this.state.dashboard; if(value === -1){ - tempSize = this.state.dropZoneHeight - 50; - if(tempSize > maxHeight +80){ - this.setState({dropZoneHeight: tempSize}); + + let tempHeight = this.state.dashboard.height - 50; + + if(tempHeight > (maxHeight + 80)){ + dashboard.height = tempHeight; + this.setState({dashboard}); } } else{ - tempSize = this.state.dropZoneHeight + 50; - this.setState( {dropZoneHeight: tempSize}); - return tempSize; + dashboard.height = this.state.dashboard.height +50; + this.setState( {dashboard}); } - + + this.forceUpdate(); } pauseData(){ @@ -423,7 +398,7 @@ class Dashboard extends Component { const grid = this.state.dashboard.grid; const boxClasses = classNames('section', 'box', { 'fullscreen-padding': this.props.isFullscreen }); let draggable = this.state.editing; - let dropZoneHeight = this.state.dropZoneHeight; + let dropZoneHeight = this.state.dashboard.height; return
    diff --git a/src/widget/widget-toolbox.js b/src/widget/widget-toolbox.js index 06bf481..18912f1 100644 --- a/src/widget/widget-toolbox.js +++ b/src/widget/widget-toolbox.js @@ -18,7 +18,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import Slider from 'rc-slider'; -import { Button } from 'react-bootstrap'; +import { Button, OverlayTrigger, Tooltip } from 'react-bootstrap'; import Icon from "../common/icon"; import ToolboxItem from './toolbox-item'; @@ -64,12 +64,16 @@ class WidgetToolbox extends React.Component {
    + Increase dashboard height } > + + Decrease dashboard height } > +
    ; From 8ec62483472a4402a6e111e043869ebceaffd8dd Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Thu, 2 Jul 2020 09:11:05 +0200 Subject: [PATCH 11/15] revise development and requirements documentation --- doc/Requirements.md | 18 +++++++----------- doc/development.md | 13 +++++++------ 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/doc/Requirements.md b/doc/Requirements.md index c53871b..2ec0efe 100644 --- a/doc/Requirements.md +++ b/doc/Requirements.md @@ -1,16 +1,12 @@ # Requirements {#web-requirements} -## Services - - NodeJS: Runs VILLASweb frontend - - Go: Runs VILLASweb backend - - PostgreSQL database (min version 11): Backend database +## Services and tools required for development + - [NodeJS with npm](https://nodejs.org/en/): Runs VILLASweb frontend + - [Go](https://golang.org/): Runs VILLASweb backend + - [PostgreSQL database](https://www.postgresql.org/) (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 + - [Docker](https://www.docker.com/): Container management system -## Installed on your local computer - - NodeJS with npm - - Go (at least version 1.11) - - [swag](https://github.com/swaggo/swag) - - Docker +## Additional requirements for productive use + - [NGinX](https://www.nginx.com/): Webserver and reverse proxy for backends diff --git a/doc/development.md b/doc/development.md index 259fb19..5b29595 100644 --- a/doc/development.md +++ b/doc/development.md @@ -8,11 +8,11 @@ In order to get started with VILLASweb, you might also want to check our our [de ### Description -The website itself based on the React JavaScript framework. +The website itself based on the [React JavaScript framework](https://reactjs.org/) and the [Flux library](https://facebook.github.io/flux/). ### Required - - NodeJS with npm + - [NodeJS with npm](https://nodejs.org/en/) ### Setup @@ -25,18 +25,19 @@ The website itself based on the React JavaScript framework. - `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. +The backend must be running to make the website work. +Type `http://localhost:3000/` in the address field of your browser to open the website. ## Backend ### Description -The backend of VILLASweb uses the programming language Go and a PostgreSQL data base. +The backend of VILLASweb uses the programming language Go and a PostgreSQL database. ### Required - - Go (min version 1.11) - - Running PostgreSQL data base (min version 11) + - [Go](https://golang.org/) (min version 1.11) + - [PostgreSQL database](https://www.postgresql.org/) (min version 11) - [swag](https://github.com/swaggo/swag) ### Setup and Running From b31f0a571d08cfc0b5a63e48fd2b0e49b0246f86 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Thu, 2 Jul 2020 09:29:51 +0200 Subject: [PATCH 12/15] start documentation of production use --- doc/Production.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 doc/Production.md diff --git a/doc/Production.md b/doc/Production.md new file mode 100644 index 0000000..fe38b4c --- /dev/null +++ b/doc/Production.md @@ -0,0 +1,18 @@ +# Production Setup {#web-production} + +For development setup instructions see @ref web-development. +The production setup is based on docker. +Clone the [frontend](https://git.rwth-aachen.de/acs/public/villas/web) and [backend](https://git.rwth-aachen.de/acs/public/villas/web-backend-go) repositories on your computer and build the Docker images for both: + +#### Frontend + - `cd VILLASweb` + - `docker build -t villasweb-frontend .` + +#### Backend + - `cd ..\VILLASweb-backend-go` + - `docker build -t villasweb-backend .` + +#### TODO Docker compose and/or Kubernetes +Run the production docker-compose file: + - `docker-compose -f docker-compose-production.yml up -d` + From 284afb27e79acfbccc830e81a24cd491d4ecab6d Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Thu, 2 Jul 2020 10:03:11 +0200 Subject: [PATCH 13/15] Work on production documentation; add draft of example VILLASnode configuration --- doc/Production.md | 70 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 3 deletions(-) diff --git a/doc/Production.md b/doc/Production.md index fe38b4c..b53ca7e 100644 --- a/doc/Production.md +++ b/doc/Production.md @@ -1,18 +1,82 @@ # Production Setup {#web-production} +## Setting up VILLASweb for production + For development setup instructions see @ref web-development. The production setup is based on docker. Clone the [frontend](https://git.rwth-aachen.de/acs/public/villas/web) and [backend](https://git.rwth-aachen.de/acs/public/villas/web-backend-go) repositories on your computer and build the Docker images for both: -#### Frontend +### Frontend - `cd VILLASweb` - `docker build -t villasweb-frontend .` -#### Backend +### Backend - `cd ..\VILLASweb-backend-go` - `docker build -t villasweb-backend .` -#### TODO Docker compose and/or Kubernetes +### WIP Docker compose and/or Kubernetes Run the production docker-compose file: - `docker-compose -f docker-compose-production.yml up -d` + +## Configure VILLASnode to get data into VILLASweb + +### Install VILLASnode + +See: @ref node-installation + +### Create a VILLASnode demo data source + +1. Create a new empty configuration file with the following contents and save it as `webdemo.conf`: + +> WIP this example configuration requires revision! +``` +nodes = { + sine = { + type = "signal" + + signal = "mixed" + values = 5 + rate = 25 + frequency = 5 + } + + web = { + type = "websocket" + + destinations = [ + "TODO" + ] + } + } + + paths = ( + { + in = "sine" + out = "web" + } + ) +``` + +The node `sine` is a software signal generator for 5 signals. +The node `web` is the websocket interface to stream the data generated by the `sine` node to the browser. + +> Note: If you do not want to use your local system as the destination for the websocket node, +>change the option `destinations` of the `web` node to the destination of your production environment, for example `https://my.production.environment/ws/webdemo`. + +### Start the VILLASnode gateway + +Run the following command on your system: + +```bash +villas node webdemo.conf +``` +> Note: Change the path to the configuration file accordingly. The `villas` command will only work if VILLASnode is installed on your system. + +### Visualize real-time data in VILLASweb Dashboards +1. Use the VILLASweb frontend to create a new infrastructure component for the VILLASnode gateway from above (Admin user required). +2. Set the `host` parameter of the component to the target you used as the `web.destinations` parameter in the configuration from above. +3. Create a new scenario in VILLASweb and within that scenario create a new component configuration that uses the infrastructure component you created under 2. +4. WIP: Use the signal auto-configure function to retrieve the signal configuration of the VILLASnode automatically. +5. Create a new dashboard with widgets of your choice and link these widgets to the signals received from the infrastructure component. +6. Enjoy what you see. From 347fa12b7a585b7d607e71b95f171d16088fa756 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Thu, 2 Jul 2020 10:06:20 +0200 Subject: [PATCH 14/15] fix indentation --- doc/Production.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/doc/Production.md b/doc/Production.md index b53ca7e..e5fd20c 100644 --- a/doc/Production.md +++ b/doc/Production.md @@ -33,27 +33,27 @@ See: @ref node-installation ``` nodes = { sine = { - type = "signal" - - signal = "mixed" - values = 5 - rate = 25 - frequency = 5 + type = "signal" + + signal = "mixed" + values = 5 + rate = 25 + frequency = 5 } web = { - type = "websocket" - - destinations = [ - "TODO" - ] + type = "websocket" + + destinations = [ + "TODO" + ] } } paths = ( { - in = "sine" - out = "web" + in = "sine" + out = "web" } ) ``` From fe731815cbedd8cd4551bb2e82d1d4cf4be32515 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Thu, 2 Jul 2020 10:20:16 +0200 Subject: [PATCH 15/15] revision of README --- README.md | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 41ba215..76c1608 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,39 @@ # VILLASweb -## Description - -This is VILLASweb, the website displaying and processing simulation data in the web browser. The term __frontend__ refers to this project, the actual website. - -The frontend connects to __two__ backends: _VILLASweb-backend-go_ and _VILLASnode_. - -VILLASnode provides actual simulation data via websockets. VILLASweb-backend-go provides any other data like user accounts, infrastructure components and configurations, dashboards etc. - +This is VILLASweb, the website to configure real-time co-simulations and display simulation real-time data in the web browser. +The term **frontend** refers to this project, the actual website. +The frontend connects to **two** backends: [VILLASweb-backend-go](https://git.rwth-aachen.de/acs/public/villas/web-backend-go) and [VILLASnode](https://git.rwth-aachen.de/acs/public/villas/node). +VILLASnode provides actual simulation data via websockets. VILLASweb-backend-go provides any other data such as user accounts, infrastructure components and configurations, dashboards etc. For more information on the backends see their repositories. ## Frameworks - The frontend is build upon [ReactJS](https://facebook.github.io/react/) and [Flux](https://facebook.github.io/flux/). - React is responsible for rendering the UI and Flux for handling the data and communication with the backends. For more information also have a look at REACT.md - -Additional libraries are used, for a complete list see package.json. +Additional libraries are used, for a complete list see the file `package.json`. ## Data model - ![Datamodel](src/img/datamodel.png) ## Quick start - -We recommend Docker to get started quickly: - ```bash -$ git clone --recursive git@git.rwth-aachen.de:VILLASframework/VILLASweb.git -$ cd VILLASweb +$ git clone --recursive https://git.rwth-aachen.de/acs/public/villas/web.git +$ cd web $ npm install $ npm start ``` - We recommend to start the VILLASweb-backend-go before the frontend. If you want to use test data (including some test users), you can start the backend with the parameter `-mode=test`. Please check the repository of the VILLASweb-backend-go to find information on the test user login names and passwords. The testing mode is NOT intended for production deployments. +## Documentation + +More details on the setup and usage of VILLASweb is available here: +- [Requirements](doc/Requirements.md) +- [Structure and datamodel](doc/Structure.md) +- [Development setup](doc/development.md) +- [Production setup](doc/Production.md) + ## Copyright 2020, Institute for Automation of Complex Power Systems, EONERC