From e3e096248437a5afebd0e41c2dbe292a3bb1436b Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Tue, 2 Jun 2020 14:51:38 +0200 Subject: [PATCH 01/27] lamp widget able to use output signals, switching of color works #218 --- src/widget/widgets/lamp.js | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) 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; From 39790b530ef545a995d8d89e0295970711112fa2 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Tue, 2 Jun 2020 15:04:05 +0200 Subject: [PATCH 02/27] value widget displays output signal value #218 --- src/widget/widgets/value.js | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) 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}] - } + } ); } From 11d3f11e2fd0fad4286d5fdbb65f572d086fde27 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Tue, 2 Jun 2020 15:17:59 +0200 Subject: [PATCH 03/27] Table widget displays output signal values and units #218 --- src/widget/widgets/table.js | 91 +++++++++++++++---------------------- 1 file changed, 37 insertions(+), 54 deletions(-) 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){ From ce26514758be550df4096480de1efc1333096ff7 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Tue, 2 Jun 2020 17:16:13 +0200 Subject: [PATCH 04/27] Button widget toggles input signal #218 --- src/ic/ic-data-store.js | 2 +- src/widget/widget.js | 23 +++++++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/ic/ic-data-store.js b/src/ic/ic-data-store.js index 6de8669..bca9a95 100644 --- a/src/ic/ic-data-store.js +++ b/src/ic/ic-data-store.js @@ -109,7 +109,7 @@ class ICDataStore extends ReduceStore { // update message properties state[action.ic].input.timestamp = Date.now(); state[action.ic].input.sequence++; - state[action.ic].input.values[action.signal] = action.data; + state[action.ic].input.values[action.signal-1] = action.data; ICDataDataManager.send(state[action.ic].input, action.ic); diff --git a/src/widget/widget.js b/src/widget/widget.js index a2eb294..c611d52 100644 --- a/src/widget/widget.js +++ b/src/widget/widget.js @@ -72,8 +72,12 @@ class Widget extends React.Component { for (let id of props.data.signalIDs){ let signal = signals.find(s => s.id === id); - let config = configs.find(m => m.id === signal.configID); - icIDs[signal.id] = config.icID; + if (signal !== undefined) { + let config = configs.find(m => m.id === signal.configID); + if (config !== undefined){ + icIDs[signal.id] = config.icID; + } + } } return { @@ -90,10 +94,21 @@ class Widget extends React.Component { inputDataChanged(widget, data) { // The following assumes that a widget modifies/ uses exactly one signal + + // get the signal with the selected signal ID + let signalID = widget.signalIDs[0]; + let signal = this.state.signals.filter(s => s.id === signalID) + if (signal.length === 0){ + console.warn("Unable to send signal for signal ID", signalID, ". Signal not found."); + return; + } + // determine ID of infrastructure component related to signal[0] + // Remark: there is only one selected signal for an input type widget + let icID = this.state.icIDs[signal[0].id]; AppDispatcher.dispatch({ type: 'icData/inputChanged', - ic: this.state.icIDs[0], - signal: this.state.signals[0].index, + ic: icID, + signal: signal[0].index, data }); } From 0bed9b3b73c52e95083ba5cd82f5fa05b1ce1530 Mon Sep 17 00:00:00 2001 From: Laura Fuentes Grau Date: Wed, 3 Jun 2020 00:13:25 +0200 Subject: [PATCH 05/27] wip: create edit files option --- src/dashboard/dashboard-button-group.js | 7 + src/dashboard/dashboard.js | 69 ++++++++ src/file/edit-files.js | 201 ++++++++++++++++++++++++ 3 files changed, 277 insertions(+) create mode 100644 src/file/edit-files.js 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
diff --git a/src/dashboard/dashboard.js b/src/dashboard/dashboard.js index 489f366..2ced5ef 100644 --- a/src/dashboard/dashboard.js +++ b/src/dashboard/dashboard.js @@ -22,6 +22,7 @@ import classNames from 'classnames'; import Widget from '../widget/widget'; import EditWidget from '../widget/edit-widget/edit-widget'; +import EditFiles from '../file/edit-files' import WidgetContextMenu from '../widget/widget-context-menu'; import WidgetToolbox from '../widget/widget-toolbox'; import WidgetArea from '../widget/widget-area'; @@ -121,6 +122,8 @@ class Dashboard extends Component { paused: prevState.paused || false, editModal: false, + filesEditModal: false, + filesEditSaveState: prevState.filesEditSaveState || [], modalData: null, modalIndex: null, widgetChangeData: [], @@ -262,6 +265,63 @@ class Dashboard extends Component { this.setState({ editModal: true, modalData: widget, modalIndex: index }); }; + startEditFiles(){ + let tempFiles = []; + this.state.files.forEach( file => { + tempFiles.push({ + id: file.id, + name: file.name + }); + }) + this.setState({filesEditModal: true, filesEditSaveState: tempFiles}); + } + + closeEditFiles(files,deleteData,addData){ + + if(files || deleteData || addData){ + + if(addData.length > 0){ + let formData = new FormData(); + addData.forEach( file => { + delete file.id; + formData.append("file", file); + }); + AppDispatcher.dispatch({ + type: 'files/start-upload', + data: formData, + token: this.state.sessionToken, + scenarioID: this.state.dashboard.scenarioID, + }); + } + + if(deleteData.length > 0){ + deleteData.forEach( file => { + AppDispatcher.dispatch({ + type: 'files/start-remove', + data: file, + token: this.state.sessionToken + }); + }); + } + } + let formData = new FormData(); + files.forEach( file => { + if(file.type === "application/octet-stream"){ + let originalFile = this.state.filesEditSaveState.find(element => parseInt(element.id,10) === parseInt(file.id,10)); + if(originalFile.name !== file.name){ + formData.append("file", file); + AppDispatcher.dispatch({ + type: 'files/start-edit', + token: this.state.sessionToken, + data: formData + }); + } + } + }) + + this.setState({ filesEditModal: false, filesEditSaveState: [] }); + } + uploadFile(data,widget){ AppDispatcher.dispatch({ type: 'files/start-upload', @@ -416,6 +476,7 @@ class Dashboard extends Component { onFullscreen={this.props.toggleFullscreen} onPause={this.pauseData.bind(this)} onUnpause={this.unpauseData.bind(this)} + onEditFiles = {this.startEditFiles.bind(this)} />
@@ -473,6 +534,14 @@ class Dashboard extends Component { files={this.state.files} /> + + ; diff --git a/src/file/edit-files.js b/src/file/edit-files.js new file mode 100644 index 0000000..341c5cd --- /dev/null +++ b/src/file/edit-files.js @@ -0,0 +1,201 @@ +/** + * This file is part of VILLASweb. + * + * VILLASweb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * VILLASweb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with VILLASweb. If not, see . + ******************************************************************************/ + +import React from 'react'; +import {FormGroup, FormControl, FormLabel, Button} from 'react-bootstrap'; + + +import Dialog from '../common/dialogs/dialog'; + + +class EditFilesDialog extends React.Component { + valid = true; + + + constructor(props) { + super(props); + + this.state = { + files: [], + selectedFile: {}, + deleteData: [], + addData: [], + addFile: {} + }; + } + + static getDerivedStateFromProps(props, state){ + return { + files: props.files + }; + } + + + + onClose(canceled) { + if (canceled === false) { + if (this.validChanges()) { + this.props.onClose(this.state.files,this.state.deleteData,this.state.addData); + } + } else { + this.props.onClose(); + } + } +//can you add file to state array? + addFile(){ + let addFile = this.state.addFile[0]; + addFile.id = this.state.files[this.state.files.length -1 ].id +1; + let tempFiles = this.state.files; + tempFiles.push(addFile); + this.setState({files: tempFiles}); + + let tempAddFiles = this.state.addData; + tempAddFiles.push(addFile); + this.setState({addData: tempAddFiles}); + + this.setState({addFile: {}}); + + } + + deleteFile(){ + let tempFiles = this.state.files; + let changeId = false; + for (let i = 0; i < tempFiles.length; i++) { + if(changeId){ + tempFiles[i-1] = tempFiles[i]; + } + else if(tempFiles[i].id === this.state.selectedFile.id){ + changeId = true; + } + } + tempFiles.pop(); + this.setState({files: tempFiles}); + + if(this.state.selectedFile.type !== "application/octet-stream"){ + let tempAddFiles = this.state.addData; + let changePosition = false; + for (let i = 0; i < tempAddFiles.length; i++) { + if(changePosition){ + tempAddFiles[i-1] = tempAddFiles[i]; + } + else if(tempAddFiles[i].id === this.state.selectedFile.id){ + changePosition = true; + } + } + tempAddFiles.pop(); + this.setState({addData: tempAddFiles}); + } + else{ + let tempDeleteFiles = this.state.deleteData; + tempDeleteFiles.push(this.state.selectedFile); + this.setState({deleteData: tempDeleteFiles}); + } + + + } + + + handleChange(e) { + + if(e.target.id === "selectedFile"){ + let tempFile = this.state.files.find(element => element.id === parseInt(e.target.value, 10)); + + this.setState({ [e.target.id]: tempFile }); + } + else if(e.target.id === "name"){ + if(this.state.selectedFile.type === "application/octet-stream"){ + + let tempFile = this.state.selectedFile; + tempFile.name = e.target.value; + this.setState({selectedFile: tempFile}); + let tempFileList = this.state.files; + tempFileList[tempFile.id - 1] = tempFile; + this.setState({files: tempFileList}); + } + else { + const newFile = new File([this.state.selectedFile], e.target.value , {type: this.state.selectedFile.type}); + this.setState({selectedFile: newFile}); + let tempFileList = this.state.files; + newFile.id = this.state.selectedFile.id; + tempFileList[newFile.id - 1] = newFile; + this.setState({files: tempFileList}); + + let tempAddFiles = this.state.addData; + for (let i = 0; i < tempAddFiles.length; i++) { + if(tempAddFiles[i].id === newFile.id){ + tempAddFiles[i] = newFile; + } + } + this.setState({addData: tempAddFiles}); + + } + + } + + + } + + validChanges() { + return true; + } + + render() { + + let fileOptions = []; + if (this.state.files.length > 0){ + fileOptions.push( + + ) + fileOptions.push(this.state.files.map((file, index) => ( + + ))) + } else { + fileOptions = + } + + return ( + this.onClose(c)} valid={true}> +
+ + Image + this.handleChange(e)}>{fileOptions} + + + + {"File Name"} + this.handleChange(e)} /> + + + + + + + Upload + this.setState({ addFile: e.target.files })} /> + + + +
+
+ ); + } +} + +export default EditFilesDialog; From a5a7d2cf9713cd375880949f6c9d33dc34c69272 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Wed, 3 Jun 2020 14:34:26 +0200 Subject: [PATCH 06/27] Input widgets (slider, button, number input) are working with input signals; button only reacts to left click #218 --- src/widget/widget.js | 125 +++++++++++++++++++++++++++++------ src/widget/widgets/button.js | 29 +++++--- src/widget/widgets/input.js | 65 +++++++++--------- src/widget/widgets/slider.js | 57 +++++++--------- 4 files changed, 182 insertions(+), 94 deletions(-) diff --git a/src/widget/widget.js b/src/widget/widget.js index c611d52..809c65a 100644 --- a/src/widget/widget.js +++ b/src/widget/widget.js @@ -85,14 +85,24 @@ class Widget extends React.Component { signals: signals, icIDs: icIDs, files: FileStore.getState(), - - sequence: prevState != null ? prevState.sequence + 1 : 0, - sessionToken: LoginStore.getState().token }; } - inputDataChanged(widget, data) { + inputDataChanged(widget, data, controlID) { + // 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; + AppDispatcher.dispatch({ + type: 'widgets/start-edit', + token: this.state.sessionToken, + data: updatedWidget + }); + } + // The following assumes that a widget modifies/ uses exactly one signal // get the signal with the selected signal ID @@ -116,37 +126,112 @@ class Widget extends React.Component { createWidget(widget) { if (widget.type === 'CustomAction') { - return + return } else if (widget.type === 'Action') { - return + return } else if (widget.type === 'Lamp') { - return + return } else if (widget.type === 'Value') { - return + return } else if (widget.type === 'Plot') { - return + return } else if (widget.type === 'Table') { - return + return } else if (widget.type === 'Label') { - return + return } else if (widget.type === 'PlotTable') { - return this.props.onWidgetStatusChange(w, this.props.index)} paused={this.props.paused} /> + return this.props.onWidgetStatusChange(w, this.props.index)} + paused={this.props.paused} + /> } else if (widget.type === 'Image') { - return + return } else if (widget.type === 'Button') { - return this.inputDataChanged(widget, value)} signals={this.state.signals} /> + return this.inputDataChanged(widget, value, controlID)} + signals={this.state.signals} + /> } else if (widget.type === 'NumberInput') { - return this.inputDataChanged(widget, value)} signals={this.state.signals} /> + return this.inputDataChanged(widget, value, controlID)} + signals={this.state.signals} + /> } else if (widget.type === 'Slider') { - return this.props.onWidgetStatusChange(w, this.props.index) } onInputChanged={value => this.inputDataChanged(widget, value)} signals={this.state.signals}/> + return this.props.onWidgetStatusChange(w, this.props.index) } + onInputChanged={(value, controlID) => this.inputDataChanged(widget, value, controlID)} + signals={this.state.signals} + /> } else if (widget.type === 'Gauge') { - return + return } else if (widget.type === 'Box') { - return + return } else if (widget.type === 'HTML') { - return + return } else if (widget.type === 'Topology') { - return + return } return null; diff --git a/src/widget/widgets/button.js b/src/widget/widgets/button.js index 977a8ae..e3a11a0 100644 --- a/src/widget/widgets/button.js +++ b/src/widget/widgets/button.js @@ -30,32 +30,41 @@ class WidgetButton extends Component { onPress(e) { - if (!this.props.widget.customProperties.toggle) { + if (e.button === 0 && !this.props.widget.customProperties.toggle) { this.setState({ pressed: true }); this.valueChanged(this.props.widget.customProperties.on_value); } } onRelease(e) { - - let nextState = false; - if (this.props.widget.customProperties.toggle) { - nextState = !this.state.pressed; + + if (e.button === 0) { + let nextState = false; + 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.props.widget.customProperties.pressed = nextState; - this.setState({ pressed: nextState }); - this.valueChanged(nextState ? this.props.widget.customProperties.on_value : this.props.widget.customProperties.off_value); } valueChanged(newValue) { if (this.props.onInputChanged) - this.props.onInputChanged(newValue); + this.props.onInputChanged(newValue, 'pressed'); } render() { return (
- +
); } 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/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 } } From 64b8a812001ee48b25b13c14693aff7d2c548568 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Thu, 4 Jun 2020 14:25:53 +0200 Subject: [PATCH 07/27] Started revision of VILLASweb documentation --- doc/Requirements.md | 16 +++++++++ doc/Structure.md | 79 +++++++++++++++++++++++++++++++++++++++++++++ doc/development.md | 69 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 164 insertions(+) create mode 100644 doc/Requirements.md create mode 100644 doc/Structure.md create mode 100644 doc/development.md 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 + +![Datamodel](../src/img/datamodel.png) + +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. + + From 5623c9d3ff2056f5d7b5c31779f5c693c7956ff6 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Wed, 10 Jun 2020 14:54:07 +0200 Subject: [PATCH 08/27] Gauge widget uses signals (min/ max not working properly) #218 --- src/widget/widgets/gauge.js | 43 ++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/src/widget/widgets/gauge.js b/src/widget/widgets/gauge.js index cd778ad..60412b5 100644 --- a/src/widget/widgets/gauge.js +++ b/src/widget/widgets/gauge.js @@ -80,30 +80,33 @@ class WidgetGauge extends Component { static getDerivedStateFromProps(props, state){ if(props.widget.signalIDs.length === 0){ - return null; + return{ value: 0}; } + + // 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; // 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) { + returnState["value"] = 0; + return returnState; } // memorize if min or max value is updated @@ -112,14 +115,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,7 +132,7 @@ class WidgetGauge extends Component { minValue = state.minValue; maxValue = state.maxValue; - + if (minValue == null || state.useMinMaxChange) { minValue = value - 0.5; updateLabels = true; @@ -215,7 +218,7 @@ class WidgetGauge extends Component { 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 }); }); From 65680561986d28e50d776e424e3544cbc5d00b81 Mon Sep 17 00:00:00 2001 From: Laura Fuentes Grau Date: Fri, 12 Jun 2020 18:49:11 +0200 Subject: [PATCH 09/27] Gauge Widget: display issues while connected to a signal fixed --- src/dashboard/dashboard.js | 4 ++-- src/widget/widgets/gauge.js | 40 ++++++++++++++----------------------- 2 files changed, 17 insertions(+), 27 deletions(-) diff --git a/src/dashboard/dashboard.js b/src/dashboard/dashboard.js index 2ced5ef..67ded60 100644 --- a/src/dashboard/dashboard.js +++ b/src/dashboard/dashboard.js @@ -339,7 +339,7 @@ class Dashboard extends Component { AppDispatcher.dispatch({ type: 'widgets/start-load', token: this.state.sessionToken, - param: '?dashboardID=1' + param: '?dashboardID=' + this.state.dashboard.id }); this.setState({ editModal: false }); @@ -433,7 +433,7 @@ class Dashboard extends Component { AppDispatcher.dispatch({ type: 'widgets/start-load', token: this.state.sessionToken, - param: '?dashboardID=1' + param: '?dashboardID=' + this.state.dashboard.id }); this.setState({ editing: false, widgetChangeData: [], widgetAddData: []}); diff --git a/src/widget/widgets/gauge.js b/src/widget/widgets/gauge.js index 60412b5..b273701 100644 --- a/src/widget/widgets/gauge.js +++ b/src/widget/widgets/gauge.js @@ -29,9 +29,10 @@ class WidgetGauge extends Component { this.state = { value: 0, unit: '', + signalID: '', minValue: null, maxValue: null, - useColorZones: false, + colorZones: [], useMinMax: false, useMinMaxChange: false, }; @@ -48,13 +49,7 @@ class WidgetGauge extends Component { } componentDidUpdate(prevProps: Readonly

, 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); @@ -91,8 +86,11 @@ class WidgetGauge extends Component { 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) if(signal !== undefined){ returnState["unit"] = signal[0].unit; @@ -133,13 +131,13 @@ 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; @@ -147,17 +145,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) { @@ -177,10 +170,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; @@ -248,8 +238,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 }; } From 3065ef88894055fe54ef2480e6c7cd9270841d49 Mon Sep 17 00:00:00 2001 From: Laura Fuentes Grau Date: Fri, 12 Jun 2020 19:17:43 +0200 Subject: [PATCH 10/27] Fix for Gauge Widget: Widget now doesn't look broken if no signal is selected #221 --- src/widget/widgets/gauge.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/widget/widgets/gauge.js b/src/widget/widgets/gauge.js index b273701..00ffa93 100644 --- a/src/widget/widgets/gauge.js +++ b/src/widget/widgets/gauge.js @@ -75,7 +75,7 @@ class WidgetGauge extends Component { static getDerivedStateFromProps(props, state){ if(props.widget.signalIDs.length === 0){ - return{ value: 0}; + return{ value: 0, minValue: 0, maxValue: 10}; } // get the signal with the selected signal ID @@ -103,8 +103,7 @@ class WidgetGauge extends Component { || props.data[icID] == null || props.data[icID].output == null || props.data[icID].output.values == null) { - returnState["value"] = 0; - return returnState; + return{ value: 0, minValue: 0, maxValue: 10}; } // memorize if min or max value is updated @@ -202,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) { @@ -214,6 +213,7 @@ class WidgetGauge extends Component { }); } + if(this.state.signalID !== ''){ this.gauge.setOptions({ staticLabels: { font: '10px "Helvetica Neue"', @@ -224,6 +224,7 @@ class WidgetGauge extends Component { staticZones: zones }); } + } computeGaugeOptions(widget) { return { From 94f88ecfdbee4ebf1688aff93e25b5c22c283bc8 Mon Sep 17 00:00:00 2001 From: Laura Fuentes Grau Date: Sun, 14 Jun 2020 17:53:40 +0200 Subject: [PATCH 11/27] Fix for Edit Layout: pressing Cancel now deletes every newly added widget --- src/dashboard/dashboard.js | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/src/dashboard/dashboard.js b/src/dashboard/dashboard.js index 67ded60..2f558e0 100644 --- a/src/dashboard/dashboard.js +++ b/src/dashboard/dashboard.js @@ -127,7 +127,7 @@ class Dashboard extends Component { modalData: null, modalIndex: null, widgetChangeData: [], - widgetAddData:prevState.widgetAddData || [], + widgetOrigIDs: prevState.widgetOrigIDs || [], maxWidgetHeight: maxHeight || null, dropZoneHeight: maxHeight +80 || null, @@ -214,10 +214,6 @@ class Dashboard extends Component { handleDrop(widget) { widget.dashboardID = this.state.dashboard.id; - let tempChanges = this.state.widgetAddData; - tempChanges.push(widget); - - this.setState({ widgetAddData: tempChanges}) AppDispatcher.dispatch({ type: 'widgets/start-add', @@ -368,7 +364,9 @@ class Dashboard extends Component { startEditing(){ + let originalIDs = []; this.state.widgets.forEach( widget => { + originalIDs.push(widget.id); if(widget.type === 'Slider' || widget.type === 'NumberInput' || widget.type === 'Button'){ AppDispatcher.dispatch({ type: 'widgets/start-edit', @@ -377,7 +375,7 @@ class Dashboard extends Component { }); } }); - this.setState({ editing: true }); + this.setState({ editing: true, widgetOrigIDs: originalIDs }); }; saveEditing() { @@ -396,7 +394,7 @@ class Dashboard extends Component { data: widget }); }); - this.setState({ editing: false, widgetChangeData: [], widgetAddData: [] }); + this.setState({ editing: false, widgetChangeData: []}); }; saveChanges() { @@ -414,28 +412,23 @@ class Dashboard extends Component { cancelEditing() { //raw widget has no id -> cannot be deleted in its original form - let temp = []; - this.state.widgetAddData.forEach(rawWidget => { this.state.widgets.forEach(widget => { - if(widget.y === rawWidget.y && widget.x === rawWidget.x && widget.type === rawWidget.type){ - temp.push(widget); + let tempID = this.state.widgetOrigIDs.find(element => element === widget.id); + if(typeof tempID === 'undefined'){ + AppDispatcher.dispatch({ + type: 'widgets/start-remove', + data: widget, + token: this.state.sessionToken + }); } }) - }) - - temp.forEach( widget => { - AppDispatcher.dispatch({ - type: 'widgets/start-remove', - data: widget, - token: this.state.sessionToken - }); - }); + AppDispatcher.dispatch({ type: 'widgets/start-load', token: this.state.sessionToken, param: '?dashboardID=' + this.state.dashboard.id }); - this.setState({ editing: false, widgetChangeData: [], widgetAddData: []}); + this.setState({ editing: false, widgetChangeData: []}); }; From fa6652e5514125127d38b334fdabb9152195f70b Mon Sep 17 00:00:00 2001 From: Laura Fuentes Grau Date: Thu, 18 Jun 2020 12:42:08 +0200 Subject: [PATCH 12/27] (wip) styling added to the dashboard edit toolbar #227 --- src/widget/toolbox-item.js | 4 ++-- src/widget/widget-toolbox.js | 28 ++++++++++++++-------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/widget/toolbox-item.js b/src/widget/toolbox-item.js index 013c4cf..b61bdc4 100644 --- a/src/widget/toolbox-item.js +++ b/src/widget/toolbox-item.js @@ -51,7 +51,7 @@ class ToolboxItem extends React.Component { if (this.props.disabled === false) { return this.props.connectDragSource(

- + {this.props.icon && } {this.props.name} @@ -61,7 +61,7 @@ class ToolboxItem extends React.Component { else { return (
- + {this.props.icon && } {this.props.name} diff --git a/src/widget/widget-toolbox.js b/src/widget/widget-toolbox.js index 9a4309b..820b02a 100644 --- a/src/widget/widget-toolbox.js +++ b/src/widget/widget-toolbox.js @@ -39,20 +39,20 @@ class WidgetToolbox extends React.Component { const topologyItemMsg = thereIsTopologyWidget? 'Currently only one is supported' : ''; return
- - - - - - - - - - - - - - + + + + + + + + + + + + + +
From c4781b86f16e32e8b44ed0b8a5cc00044bed3757 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Thu, 18 Jun 2020 17:05:47 +0200 Subject: [PATCH 13/27] Work on edit file, use table of files instead of dropdown menu, fix upload callbacks; sizing of dialog window not working, Cancel button of dialog window obsolete #219 --- src/dashboard/dashboard.js | 59 +---- src/file/edit-files.js | 226 ++++++++---------- .../edit-widget-control-creator.js | 6 +- .../edit-widget/edit-widget-file-control.js | 37 +-- src/widget/edit-widget/edit-widget.js | 3 +- 5 files changed, 106 insertions(+), 225 deletions(-) diff --git a/src/dashboard/dashboard.js b/src/dashboard/dashboard.js index 2f558e0..cf4e656 100644 --- a/src/dashboard/dashboard.js +++ b/src/dashboard/dashboard.js @@ -272,60 +272,9 @@ class Dashboard extends Component { this.setState({filesEditModal: true, filesEditSaveState: tempFiles}); } - closeEditFiles(files,deleteData,addData){ - - if(files || deleteData || addData){ - - if(addData.length > 0){ - let formData = new FormData(); - addData.forEach( file => { - delete file.id; - formData.append("file", file); - }); - AppDispatcher.dispatch({ - type: 'files/start-upload', - data: formData, - token: this.state.sessionToken, - scenarioID: this.state.dashboard.scenarioID, - }); - } - - if(deleteData.length > 0){ - deleteData.forEach( file => { - AppDispatcher.dispatch({ - type: 'files/start-remove', - data: file, - token: this.state.sessionToken - }); - }); - } - } - let formData = new FormData(); - files.forEach( file => { - if(file.type === "application/octet-stream"){ - let originalFile = this.state.filesEditSaveState.find(element => parseInt(element.id,10) === parseInt(file.id,10)); - if(originalFile.name !== file.name){ - formData.append("file", file); - AppDispatcher.dispatch({ - type: 'files/start-edit', - token: this.state.sessionToken, - data: formData - }); - } - } - }) - - this.setState({ filesEditModal: false, filesEditSaveState: [] }); - } - - uploadFile(data,widget){ - AppDispatcher.dispatch({ - type: 'files/start-upload', - data: data, - token: this.state.sessionToken, - scenarioID: this.state.dashboard.scenarioID, - }); + closeEditFiles(){ + // TODO do we need this if the dispatches happen in the dialog? } closeEdit(data){ @@ -422,7 +371,7 @@ class Dashboard extends Component { }); } }) - + AppDispatcher.dispatch({ type: 'widgets/start-load', token: this.state.sessionToken, @@ -521,7 +470,6 @@ class Dashboard extends Component { sessionToken={this.state.sessionToken} show={this.state.editModal} onClose={this.closeEdit.bind(this)} - onUpload = {this.uploadFile.bind(this)} widget={this.state.modalData} signals={this.state.signals} files={this.state.files} @@ -533,6 +481,7 @@ class Dashboard extends Component { onClose={this.closeEditFiles.bind(this)} signals={this.state.signals} files={this.state.files} + scenarioID={this.state.dashboard.scenarioID} /> diff --git a/src/file/edit-files.js b/src/file/edit-files.js index 341c5cd..4df19d5 100644 --- a/src/file/edit-files.js +++ b/src/file/edit-files.js @@ -16,10 +16,11 @@ ******************************************************************************/ import React from 'react'; -import {FormGroup, FormControl, FormLabel, Button} from 'react-bootstrap'; - - +import {FormGroup, FormControl, Button, Col, ProgressBar} from 'react-bootstrap'; import Dialog from '../common/dialogs/dialog'; +import AppDispatcher from "../common/app-dispatcher"; +import Table from "../common/table"; +import TableColumn from "../common/table-column"; class EditFilesDialog extends React.Component { @@ -30,168 +31,133 @@ class EditFilesDialog extends React.Component { super(props); this.state = { - files: [], - selectedFile: {}, - deleteData: [], - addData: [], - addFile: {} + uploadFile: null, + uploadProgress: 0 }; } - static getDerivedStateFromProps(props, state){ - return { - files: props.files - }; - } - - - onClose(canceled) { if (canceled === false) { if (this.validChanges()) { - this.props.onClose(this.state.files,this.state.deleteData,this.state.addData); + this.props.onClose(); } } else { this.props.onClose(); } } -//can you add file to state array? - addFile(){ - let addFile = this.state.addFile[0]; - addFile.id = this.state.files[this.state.files.length -1 ].id +1; - let tempFiles = this.state.files; - tempFiles.push(addFile); - this.setState({files: tempFiles}); - let tempAddFiles = this.state.addData; - tempAddFiles.push(addFile); - this.setState({addData: tempAddFiles}); + selectUploadFile(event) { + this.setState({ uploadFile: event.target.files[0] }); + }; - this.setState({addFile: {}}); + startFileUpload(){ + // upload file + const formData = new FormData(); + formData.append("file", this.state.uploadFile); + + AppDispatcher.dispatch({ + type: 'files/start-upload', + data: formData, + token: this.props.sessionToken, + progressCallback: this.updateUploadProgress, + finishedCallback: this.clearProgress, + scenarioID: this.props.scenarioID, + }); + + // TODO make sure that dialog remains open after clicking "Upload" button + }; + + updateUploadProgress = (event) => { + this.setState({ uploadProgress: parseInt(event.percent.toFixed(), 10) }); + }; + + clearProgress = (newFileID) => { + /*if (this.props.onChange != null) { + let event = {} + event["target"] = {} + event.target["value"] = newFileID + this.props.onChange(event); + } + */ + this.setState({ uploadProgress: 0 }); + + + }; + + deleteFile(index){ + + let file = this.props.files[index] + AppDispatcher.dispatch({ + type: 'files/start-remove', + data: file, + token: this.props.sessionToken + }); + + // TODO make sure that dialog remains open after clicking delete button } - deleteFile(){ - let tempFiles = this.state.files; - let changeId = false; - for (let i = 0; i < tempFiles.length; i++) { - if(changeId){ - tempFiles[i-1] = tempFiles[i]; - } - else if(tempFiles[i].id === this.state.selectedFile.id){ - changeId = true; - } - } - tempFiles.pop(); - this.setState({files: tempFiles}); - - if(this.state.selectedFile.type !== "application/octet-stream"){ - let tempAddFiles = this.state.addData; - let changePosition = false; - for (let i = 0; i < tempAddFiles.length; i++) { - if(changePosition){ - tempAddFiles[i-1] = tempAddFiles[i]; - } - else if(tempAddFiles[i].id === this.state.selectedFile.id){ - changePosition = true; - } - } - tempAddFiles.pop(); - this.setState({addData: tempAddFiles}); - } - else{ - let tempDeleteFiles = this.state.deleteData; - tempDeleteFiles.push(this.state.selectedFile); - this.setState({deleteData: tempDeleteFiles}); - } - - - } - - - handleChange(e) { - - if(e.target.id === "selectedFile"){ - let tempFile = this.state.files.find(element => element.id === parseInt(e.target.value, 10)); - - this.setState({ [e.target.id]: tempFile }); - } - else if(e.target.id === "name"){ - if(this.state.selectedFile.type === "application/octet-stream"){ - - let tempFile = this.state.selectedFile; - tempFile.name = e.target.value; - this.setState({selectedFile: tempFile}); - let tempFileList = this.state.files; - tempFileList[tempFile.id - 1] = tempFile; - this.setState({files: tempFileList}); - } - else { - const newFile = new File([this.state.selectedFile], e.target.value , {type: this.state.selectedFile.type}); - this.setState({selectedFile: newFile}); - let tempFileList = this.state.files; - newFile.id = this.state.selectedFile.id; - tempFileList[newFile.id - 1] = newFile; - this.setState({files: tempFileList}); - - let tempAddFiles = this.state.addData; - for (let i = 0; i < tempAddFiles.length; i++) { - if(tempAddFiles[i].id === newFile.id){ - tempAddFiles[i] = newFile; - } - } - this.setState({addData: tempAddFiles}); - - } - - } - - - } - - validChanges() { - return true; - } render() { let fileOptions = []; - if (this.state.files.length > 0){ + if (this.props.files.length > 0){ fileOptions.push( ) - fileOptions.push(this.state.files.map((file, index) => ( + fileOptions.push(this.props.files.map((file, index) => ( ))) } else { fileOptions = } - + + const progressBarStyle = { + marginLeft: '100px', + marginTop: '-40px' + }; + return ( - this.onClose(c)} valid={true}> +
- - Image + + + + + + this.deleteFile(index)} + /> +
+ + + this.handleChange(e)}>{fileOptions} - - - - {"File Name"} - this.handleChange(e)} /> - + disabled={this.props.disabled} + type='file' + onChange={(event) => this.selectUploadFile(event)} /> - - - - Upload - this.setState({ addFile: e.target.files })} /> + + - + + +
); diff --git a/src/widget/edit-widget/edit-widget-control-creator.js b/src/widget/edit-widget/edit-widget-control-creator.js index 78bff0c..5c12734 100644 --- a/src/widget/edit-widget/edit-widget-control-creator.js +++ b/src/widget/edit-widget/edit-widget-control-creator.js @@ -33,7 +33,7 @@ import EditWidgetMinMaxControl from './edit-widget-min-max-control'; import EditWidgetHTMLContent from './edit-widget-html-content'; import EditWidgetParametersControl from './edit-widget-parameters-control'; -export default function CreateControls(widgetType = null, widget = null, sessionToken = null, files = null, signals, handleChange, onUpload) { +export default function CreateControls(widgetType = null, widget = null, sessionToken = null, files = null, signals, handleChange) { // Use a list to concatenate the controls according to the widget type var DialogControls = []; @@ -84,7 +84,7 @@ export default function CreateControls(widgetType = null, widget = null, session // Restrict to only image file types (MIME) //let imageControlFiles = files == null? [] : files.filter(file => file.type.includes('image')); DialogControls.push( - handleChange(e)} onUpload={(f,i) => onUpload(f,i)} />, + handleChange(e)} />, handleChange(e)} /> ); break; @@ -149,7 +149,7 @@ export default function CreateControls(widgetType = null, widget = null, session // Restrict to only xml files (MIME) //let topologyControlFiles = files == null? [] : files.filter( file => file.type.includes('xml')); DialogControls.push( - handleChange(e) } onUpload={(f,i) => onUpload(f,i)} /> + handleChange(e) } /> ); break; diff --git a/src/widget/edit-widget/edit-widget-file-control.js b/src/widget/edit-widget/edit-widget-file-control.js index 298ba01..a55aa23 100644 --- a/src/widget/edit-widget/edit-widget-file-control.js +++ b/src/widget/edit-widget/edit-widget-file-control.js @@ -16,7 +16,7 @@ ******************************************************************************/ import React from 'react'; -import {FormGroup, FormControl, FormLabel, Button, ProgressBar} from 'react-bootstrap'; +import {FormGroup, FormControl, FormLabel} from 'react-bootstrap'; class EditFileWidgetControl extends React.Component { @@ -24,41 +24,16 @@ class EditFileWidgetControl extends React.Component { super(props); this.state = { - widget: { }, files: [], - fileList: null, - progress: 0 }; } static getDerivedStateFromProps(props, state){ return { - widget: props.widget, files: props.files.filter(file => file.type.includes(props.type)) }; } - startFileUpload = () => { - // get selected file - let formData = new FormData(); - - for (let key in this.state.fileList) { - if (this.state.fileList.hasOwnProperty(key) && this.state.fileList[key] instanceof File) { - formData.append("file", this.state.fileList[key]); - } - } - - this.props.onUpload(formData,this.props.widget); - } - - uploadProgress = (e) => { - this.setState({ progress: Math.round(e.percent) }); - } - - clearProgress = () => { - this.setState({ progress: 0 }); - } - handleFileChange(e){ this.props.handleChange({ target: { id: this.props.controlId, value: e.target.value } }); } @@ -88,17 +63,9 @@ class EditFileWidgetControl extends React.Component { Image this.handleFileChange(e)}>{fileOptions} - - - Upload - this.setState({ fileList: e.target.files }) } /> - - - -
; } } diff --git a/src/widget/edit-widget/edit-widget.js b/src/widget/edit-widget/edit-widget.js index 594acf0..01cffdb 100644 --- a/src/widget/edit-widget/edit-widget.js +++ b/src/widget/edit-widget/edit-widget.js @@ -177,8 +177,7 @@ class EditWidgetDialog extends React.Component { this.props.sessionToken, this.props.files, this.props.signals, - (e) => this.handleChange(e), - (f,i) => this.props.onUpload(f,i)); + (e) => this.handleChange(e)); } return ( From 5844efdbbb6bfd6f9cea9a024ee84934bc398038 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Fri, 19 Jun 2020 09:34:46 +0200 Subject: [PATCH 14/27] Removed plot-table widget as it is redundant to plot widget #218 --- .../widget/edit-widget-control-creator.js | 1 - src/ic/ic-data-data-manager.js | 2 +- src/styles/widgets.css | 64 --------- .../edit-widget-control-creator.js | 8 -- src/widget/widget-factory.js | 11 -- src/widget/widget-toolbox.js | 1 - src/widget/widget.js | 11 -- src/widget/widgets/plot-table.js | 130 ------------------ 8 files changed, 1 insertion(+), 227 deletions(-) delete mode 100644 src/widget/widgets/plot-table.js 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/ic/ic-data-data-manager.js b/src/ic/ic-data-data-manager.js index 5aebae9..2e09a7c 100644 --- a/src/ic/ic-data-data-manager.js +++ b/src/ic/ic-data-data-manager.js @@ -58,7 +58,7 @@ class IcDataDataManager { if (socket == null) { return false; } - + console.log("Sending to IC", identifier, "message: ", message); const data = this.messageToBuffer(message); socket.send(data); diff --git a/src/styles/widgets.css b/src/styles/widgets.css index 4e367a3..192acdd 100644 --- a/src/styles/widgets.css +++ b/src/styles/widgets.css @@ -115,65 +115,6 @@ div[class*="-widget"] .btn[disabled], .btn.disabled, div[class*="-widget"] input /* End edit menu: Colors */ -/* PlotTable widget */ -.plot-table-widget, .plot-widget, .value-widget, .image-widget, .label-widget { - width: 100%; - height: 100%; - padding: 3px 6px; -} - -.plot-table-widget { - display: -webkit-flex; - display: flex; - flex-direction: column; -} - -.plot-table-widget .content { - -webkit-flex: 1 0 auto; - flex: 1 0 auto; - display: -webkit-flex; - display: flex; - flex-direction: column; -} - -.table-plot-row { - -webkit-flex: 1 0 auto; - flex: 1 0 auto; - display: -webkit-flex; - display: flex; -} - -.plot-table-widget .widget-table { - -webkit-flex: 1 0 auto; - flex: 1 0 auto; - flex-basis: 90px; - max-width: 50%; - display: flex; - flex-direction: column; - justify-content: center; - margin-right: 10px; -} - -.plot-table-widget small { - text-align: center; -} - -.plot-table-widget .checkbox label { - height: 100%; - width: 100%; - padding: 6px 12px; - overflow-x: hidden; -} - -.plot-table-widget .btn { - padding: 0px; -} - -.plot-table-widget input[type="checkbox"] { - display: none; -} -/* End PlotTable Widget */ - /* Plot Widget */ .plot-widget { display: -webkit-flex; @@ -438,8 +379,3 @@ div[class*="-widget"] label { border: 2px solid; } /* End box widget */ - -.plot-table-widget .widget-plot { - -webkit-flex: 1 0 auto; - flex: 1 0 auto; -} diff --git a/src/widget/edit-widget/edit-widget-control-creator.js b/src/widget/edit-widget/edit-widget-control-creator.js index 5c12734..90ded85 100644 --- a/src/widget/edit-widget/edit-widget-control-creator.js +++ b/src/widget/edit-widget/edit-widget-control-creator.js @@ -97,14 +97,6 @@ export default function CreateControls(widgetType = null, widget = null, session handleChange(e)} /> ); break; - case 'PlotTable': - DialogControls.push( - handleChange(e)} />, - handleChange(e)} />, - handleChange(e)} />, - handleChange(e)} /> - ); - break; case 'Slider': DialogControls.push( handleChange(e)} />, diff --git a/src/widget/widget-factory.js b/src/widget/widget-factory.js index 4586464..1360988 100644 --- a/src/widget/widget-factory.js +++ b/src/widget/widget-factory.js @@ -105,17 +105,6 @@ class WidgetFactory { widget.customProperties.fontColor = 0; widget.customProperties.resizeTopBottomLock = true; break; - case 'PlotTable': - widget.customProperties.ylabel = ''; - widget.minWidth = 200; - widget.minHeight = 100; - widget.width = 600; - widget.height = 300; - widget.customProperties.time = 60; - widget.customProperties.yMin = 0; - widget.customProperties.yMax = 10; - widget.customProperties.yUseMinMax = false; - break; case 'Image': widget.minWidth = 20; widget.minHeight = 20; diff --git a/src/widget/widget-toolbox.js b/src/widget/widget-toolbox.js index 820b02a..60be4af 100644 --- a/src/widget/widget-toolbox.js +++ b/src/widget/widget-toolbox.js @@ -45,7 +45,6 @@ class WidgetToolbox extends React.Component { - diff --git a/src/widget/widget.js b/src/widget/widget.js index 809c65a..11fe5c2 100644 --- a/src/widget/widget.js +++ b/src/widget/widget.js @@ -35,7 +35,6 @@ import WidgetValue from './widgets/value'; import WidgetPlot from './widgets/plot'; import WidgetTable from './widgets/table'; import WidgetLabel from './widgets/label'; -import WidgetPlotTable from './widgets/plot-table'; import WidgetImage from './widgets/image'; import WidgetButton from './widgets/button'; import WidgetInput from './widgets/input'; @@ -170,16 +169,6 @@ class Widget extends React.Component { return - } else if (widget.type === 'PlotTable') { - return this.props.onWidgetStatusChange(w, this.props.index)} - paused={this.props.paused} - /> } else if (widget.type === 'Image') { return . - ******************************************************************************/ - -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 ( -
-
-
-
- {checkBoxes.length > 0 ? ( - - {checkBoxes} - - ) : (Use edit menu to change selected signals.) - } -
- -
- -
-
- {showLegend ? ( - ) : (
) - } -
-
- ); - } -} -export default WidgetPlotTable; From b07a804bf3a15d6dcd047a8ab9d86b4dbb6af983 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Fri, 19 Jun 2020 09:51:25 +0200 Subject: [PATCH 15/27] Consider signal direction when connecting signals with widgets (out signals only for display widgets and in signals only for manipulation widgets) #218 --- .../edit-widget/edit-widget-control-creator.js | 18 +++++++++--------- .../edit-widget/edit-widget-signal-control.js | 10 ++++++---- .../edit-widget/edit-widget-signals-control.js | 16 +++++++++++++--- 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/src/widget/edit-widget/edit-widget-control-creator.js b/src/widget/edit-widget/edit-widget-control-creator.js index 90ded85..1b2f996 100644 --- a/src/widget/edit-widget/edit-widget-control-creator.js +++ b/src/widget/edit-widget/edit-widget-control-creator.js @@ -47,20 +47,20 @@ export default function CreateControls(widgetType = null, widget = null, session break; case 'Action': DialogControls.push( - handleChange(e)} />, + handleChange(e)} direction={'in'} />, ); break; case 'Value': DialogControls.push( handleChange(e)} />, - handleChange(e)} />, + handleChange(e)} direction={'out'}/>, handleChange(e)} />, handleChange(e)} /> ); break; case 'Lamp': DialogControls.push( - handleChange(e)} />, + handleChange(e)} direction={'out'}/>, handleChange(e)} />, handleChange(e)} />, handleChange(e)} />, @@ -69,14 +69,14 @@ export default function CreateControls(widgetType = null, widget = null, session case 'Plot': DialogControls.push( handleChange(e)} />, - handleChange(e)} />, + handleChange(e)} direction={'out'}/>, handleChange(e)} />, handleChange(e)} /> ); break; case 'Table': DialogControls.push( - handleChange(e)} />, + handleChange(e)} direction={'out'}/>, handleChange(e)} /> ); break; @@ -91,7 +91,7 @@ export default function CreateControls(widgetType = null, widget = null, session case 'Gauge': DialogControls.push( handleChange(e)} />, - handleChange(e)} />, + handleChange(e)} direction={'out'}/>, handleChange(e)} />, handleChange(e)} />, handleChange(e)} /> @@ -101,7 +101,7 @@ export default function CreateControls(widgetType = null, widget = null, session DialogControls.push( handleChange(e)} />, handleChange(e)} />, - handleChange(e)} />, + handleChange(e)} direction={'in'}/>, handleChange(e)} />, handleChange(e)} />, handleChange(e)} />, @@ -112,7 +112,7 @@ export default function CreateControls(widgetType = null, widget = null, session case 'Button': DialogControls.push( handleChange(e)} />, - handleChange(e)} />, + handleChange(e)} direction={'in'}/>, handleChange(e)} />, handleChange(e)} />, handleChange(e)} /> @@ -148,7 +148,7 @@ export default function CreateControls(widgetType = null, widget = null, session case 'NumberInput': DialogControls.push( handleChange(e)} />, - handleChange(e)} />, + handleChange(e)} direction={'in'}/>, handleChange(e)} /> ); break; diff --git a/src/widget/edit-widget/edit-widget-signal-control.js b/src/widget/edit-widget/edit-widget-signal-control.js index aaa9c24..cc9135d 100644 --- a/src/widget/edit-widget/edit-widget-signal-control.js +++ b/src/widget/edit-widget/edit-widget-signal-control.js @@ -23,13 +23,15 @@ class EditWidgetSignalControl extends Component { super(props); this.state = { - widget: {} + widget: {}, + signals: [] }; } static getDerivedStateFromProps(props, state){ return { - widget: props.widget + widget: props.widget, + signals: props.signals.filter(s => s.direction === props.direction) }; } @@ -51,10 +53,10 @@ class EditWidgetSignalControl extends Component { this.handleSignalChange(e)}> { - this.props.signals.length === 0 ? ( + this.state.signals.length === 0 ? ( ) : ( - this.props.signals.map((signal, index) => ( + this.state.signals.map((signal, index) => ( )) ) diff --git a/src/widget/edit-widget/edit-widget-signals-control.js b/src/widget/edit-widget/edit-widget-signals-control.js index 109df2e..ad4658b 100644 --- a/src/widget/edit-widget/edit-widget-signals-control.js +++ b/src/widget/edit-widget/edit-widget-signals-control.js @@ -23,7 +23,17 @@ class EditWidgetSignalsControl extends Component { super(props); this.state = { + widget: {}, + signals: [], + checkedSignals: props.widget[props.controlId] + }; + } + + + static getDerivedStateFromProps(props, state){ + return { widget: props.widget, + signals: props.signals.filter(s => s.direction === props.direction), checkedSignals: props.widget[props.controlId] }; } @@ -48,10 +58,10 @@ class EditWidgetSignalsControl extends Component { Signals { - this.props.signals === 0 || !this.state.widget.hasOwnProperty(this.props.controlId)? ( + this.state.signals === 0 || !this.state.widget.hasOwnProperty(this.props.controlId)? ( No signals available ) : ( - this.props.signals.map((signal, index) => ( + this.state.signals.map((signal, index) => ( this.handleSignalChange(e.target.checked, signal.id)}> )) - ) + ) } ); From 622a5d3f22434924cd6217c9f580828f064bc829 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Fri, 19 Jun 2020 15:13:33 +0200 Subject: [PATCH 16/27] fixes for color control, selected color is now checked --- .../edit-widget/edit-widget-color-control.js | 11 ++++++++- .../edit-widget-control-creator.js | 2 +- .../edit-widget/edit-widget-number-control.js | 23 ++++++++++++++----- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/widget/edit-widget/edit-widget-color-control.js b/src/widget/edit-widget/edit-widget-color-control.js index c031597..2158a55 100644 --- a/src/widget/edit-widget/edit-widget-color-control.js +++ b/src/widget/edit-widget/edit-widget-color-control.js @@ -75,7 +75,16 @@ class EditWidgetColorControl extends Component { 'checked': idx === (isCustomProperty ? this.state.widget[parts[0]][parts[1]] : this.state.widget[this.props.controlId]) }); - return ( this.props.handleChange({target: { id: this.props.controlId, value: idx}})} />) + return ( this.props.handleChange({target: { id: this.props.controlId, value: idx}})} />) } ) } diff --git a/src/widget/edit-widget/edit-widget-control-creator.js b/src/widget/edit-widget/edit-widget-control-creator.js index 1b2f996..2b656dc 100644 --- a/src/widget/edit-widget/edit-widget-control-creator.js +++ b/src/widget/edit-widget/edit-widget-control-creator.js @@ -122,7 +122,7 @@ export default function CreateControls(widgetType = null, widget = null, session DialogControls.push( handleChange(e)} />, handleChange(e)} />, - handleChange(e)} /> + handleChange(e)} /> ); break; case 'Label': diff --git a/src/widget/edit-widget/edit-widget-number-control.js b/src/widget/edit-widget/edit-widget-number-control.js index 5354863..3b3101f 100644 --- a/src/widget/edit-widget/edit-widget-number-control.js +++ b/src/widget/edit-widget/edit-widget-number-control.js @@ -25,25 +25,36 @@ class EditWidgetNumberControl extends Component { this.state = { widget: { customProperties:{} - } + } }; } - + static getDerivedStateFromProps(props, state){ return{ - widget: props.widget + widget: props.widget }; - } + } render() { let step = 1; if(this.props.controlId ==='customProperties.background_color_opacity'){ step = 0.1; - } + } + + let parts = this.props.controlId.split('.'); + let isCustomProperty = true; + if (parts.length === 1){ + isCustomProperty = false; + } + return ( {this.props.label} - this.props.handleChange(e)} /> + this.props.handleChange(e)} /> ); } From d8fdf33dbcecb204249d6f2e8856beb0187be293 Mon Sep 17 00:00:00 2001 From: Laura Fuentes Grau Date: Sun, 21 Jun 2020 16:43:21 +0200 Subject: [PATCH 17/27] Fix for edit files: table now has a fixed layout/ doesn't protrude out from dialog and close/cancel button closes dialog #219 --- src/dashboard/dashboard.js | 2 +- src/file/edit-files.js | 21 ++++++++++++--------- src/styles/app.css | 21 +++++++++++++++++++++ 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/dashboard/dashboard.js b/src/dashboard/dashboard.js index cf4e656..5000ede 100644 --- a/src/dashboard/dashboard.js +++ b/src/dashboard/dashboard.js @@ -273,7 +273,7 @@ class Dashboard extends Component { } closeEditFiles(){ - + this.setState({ filesEditModal: false }); // TODO do we need this if the dispatches happen in the dialog? } diff --git a/src/file/edit-files.js b/src/file/edit-files.js index 4df19d5..047c2da 100644 --- a/src/file/edit-files.js +++ b/src/file/edit-files.js @@ -38,7 +38,7 @@ class EditFilesDialog extends React.Component { onClose(canceled) { if (canceled === false) { - if (this.validChanges()) { + if (true) { this.props.onClose(); } } else { @@ -64,6 +64,7 @@ class EditFilesDialog extends React.Component { scenarioID: this.props.scenarioID, }); + this.setState({ uploadFile: null }); // TODO make sure that dialog remains open after clicking "Upload" button }; @@ -118,21 +119,23 @@ class EditFilesDialog extends React.Component { }; return ( - + this.onClose(c)} valid={true}>
- - - - - + +
+
+ + + + this.deleteFile(index)} />
- +
Date: Sun, 21 Jun 2020 17:57:35 +0200 Subject: [PATCH 18/27] Fix for edit files: dialog now stays open after adding/deleting file, cancel button deleted #219 --- src/common/dialogs/dialog.js | 2 +- src/dashboard/dashboard.js | 2 +- src/file/edit-files.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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/dashboard/dashboard.js b/src/dashboard/dashboard.js index 5000ede..874eb10 100644 --- a/src/dashboard/dashboard.js +++ b/src/dashboard/dashboard.js @@ -122,7 +122,7 @@ class Dashboard extends Component { paused: prevState.paused || false, editModal: false, - filesEditModal: false, + filesEditModal: prevState.filesEditModal || false, filesEditSaveState: prevState.filesEditSaveState || [], modalData: null, modalIndex: null, diff --git a/src/file/edit-files.js b/src/file/edit-files.js index 047c2da..c47c733 100644 --- a/src/file/edit-files.js +++ b/src/file/edit-files.js @@ -119,7 +119,7 @@ class EditFilesDialog extends React.Component { }; return ( - this.onClose(c)} valid={true}> + this.onClose(c)} blendOutCancel = {true} valid={true}>
From eb1756852b77b90a27cc8052ae9537ed9c66726b Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Tue, 23 Jun 2020 09:34:33 +0200 Subject: [PATCH 19/27] Modify send command to IC buttons (point 1 of #225) --- src/ic/ic-action.js | 3 ++- src/scenario/scenario.js | 21 ++++++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/ic/ic-action.js b/src/ic/ic-action.js index 4e787af..6a9e591 100644 --- a/src/ic/ic-action.js +++ b/src/ic/ic-action.js @@ -55,11 +55,12 @@ class ICAction extends React.Component { )); return
+ Send command to infrastructure component {actionList} - +
; diff --git a/src/scenario/scenario.js b/src/scenario/scenario.js index 0e4b9ed..e7cd701 100644 --- a/src/scenario/scenario.js +++ b/src/scenario/scenario.js @@ -234,12 +234,18 @@ class Scenario extends React.Component { this.setState({ selectedConfigs: selectedConfigs }); } - runAction = action => { + runAction(action) { + + if(action.data.action === 'none'){ + console.warn("No command selected. Nothing was sent."); + return; + } + for (let index of this.state.selectedConfigs) { // get IC for component config let ic = null; for (let component of this.state.ics) { - if (component._id === this.state.configs[index].icID) { + if (component.id === this.state.configs[index].icID) { ic = component; } } @@ -410,11 +416,15 @@ class Scenario extends React.Component { marginLeft: '10px' }; + const tableHeadingStyle = { + paddingTop: '30px' + } + return

{this.state.scenario.name}

{/*Component Configurations table*/} -

Component Configurations

+

Component Configurations

this.onConfigChecked(index, event)} width='30' /> @@ -447,8 +457,9 @@ class Scenario extends React.Component {
this.runAction(action)} actions={[ + { id: '-1', title: 'Select command', data: { action: 'none' } }, { id: '0', title: 'Start', data: { action: 'start' } }, { id: '1', title: 'Stop', data: { action: 'stop' } }, { id: '2', title: 'Pause', data: { action: 'pause' } }, @@ -493,7 +504,7 @@ class Scenario extends React.Component { configID={this.state.modalConfigData.id}/> {/*Dashboard table*/} -

Dashboards

+

Dashboards

From 24650a0b08c8f49df62cccfddfe5e5e63287f755 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Tue, 23 Jun 2020 09:37:08 +0200 Subject: [PATCH 20/27] fix endpoint for sending commands to ICs --- src/ic/ics-data-manager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ic/ics-data-manager.js b/src/ic/ics-data-manager.js index 73d3e91..2925b1b 100644 --- a/src/ic/ics-data-manager.js +++ b/src/ic/ics-data-manager.js @@ -26,7 +26,7 @@ class IcsDataManager extends RestDataManager { doActions(ic, action, token = null) { // TODO: Make only infrastructure component id dependent - RestAPI.post(this.makeURL(this.url + '/' + ic.id), action, token).then(response => { + RestAPI.post(this.makeURL(this.url + '/' + ic.id + '/action'), action, token).then(response => { AppDispatcher.dispatch({ type: 'ics/action-started', data: response From b00912488252d6b324d81367e8358ddc9f334dc0 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Tue, 23 Jun 2020 09:39:08 +0200 Subject: [PATCH 21/27] remove todo --- src/ic/ics-data-manager.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ic/ics-data-manager.js b/src/ic/ics-data-manager.js index 2925b1b..89455c3 100644 --- a/src/ic/ics-data-manager.js +++ b/src/ic/ics-data-manager.js @@ -25,7 +25,6 @@ class IcsDataManager extends RestDataManager { } doActions(ic, action, token = null) { - // TODO: Make only infrastructure component id dependent RestAPI.post(this.makeURL(this.url + '/' + ic.id + '/action'), action, token).then(response => { AppDispatcher.dispatch({ type: 'ics/action-started', From ea5311a43b1fcd5ee571907a01f349d519c97028 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Tue, 23 Jun 2020 11:28:08 +0200 Subject: [PATCH 22/27] remove todos --- src/file/edit-files.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/file/edit-files.js b/src/file/edit-files.js index c47c733..4f3c8a7 100644 --- a/src/file/edit-files.js +++ b/src/file/edit-files.js @@ -65,7 +65,6 @@ class EditFilesDialog extends React.Component { }); this.setState({ uploadFile: null }); - // TODO make sure that dialog remains open after clicking "Upload" button }; updateUploadProgress = (event) => { @@ -93,9 +92,6 @@ class EditFilesDialog extends React.Component { data: file, token: this.props.sessionToken }); - - // TODO make sure that dialog remains open after clicking delete button - } From ef1d960cd48abbb9f39263ae1a92a445f278ea73 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Tue, 23 Jun 2020 11:48:37 +0200 Subject: [PATCH 23/27] improve signal mapping dialog (point 2 of #225) --- src/scenario/scenario.js | 68 +++++---------------- src/signal/edit-signal-mapping.js | 99 ++++++++++++++++--------------- 2 files changed, 66 insertions(+), 101 deletions(-) diff --git a/src/scenario/scenario.js b/src/scenario/scenario.js index e7cd701..3e2d7cd 100644 --- a/src/scenario/scenario.js +++ b/src/scenario/scenario.js @@ -49,6 +49,10 @@ class Scenario extends React.Component { } static calculateState(prevState, props) { + if (prevState == null) { + prevState = {}; + } + // get selected scenario const sessionToken = LoginStore.getState().token; @@ -86,12 +90,12 @@ class Scenario extends React.Component { deleteConfigModal: false, importConfigModal: false, editConfigModal: false, - modalConfigData: {}, + modalConfigData: (prevState.modalConfigData !== {} && prevState.modalConfigData !== undefined )? prevState.modalConfigData : {}, selectedConfigs: [], modalConfigIndex: 0, - editOutputSignalsModal: false, - editInputSignalsModal: false, + editOutputSignalsModal: prevState.editOutputSignalsModal || false, + editInputSignalsModal: prevState.editInputSignalsModal || false, newDashboardModal: false, deleteDashboardModal: false, @@ -346,52 +350,12 @@ class Scenario extends React.Component { * Signal modification methods ############################################## */ - closeDeleteSignalModal(data){ - // data contains the signal to be deleted - if (data){ - - AppDispatcher.dispatch({ - type: 'signals/start-remove', - data: data, - token: this.state.sessionToken - }); - - } - } - - closeNewSignalModal(data){ - //data contains the new signal incl. configID and direction - if (data) { - AppDispatcher.dispatch({ - type: 'signals/start-add', - data: data, - token: this.state.sessionToken - }); - } - } - - closeEditSignalsModal(data, direction){ - + closeEditSignalsModal(direction){ if( direction === "in") { this.setState({editInputSignalsModal: false}); } else if( direction === "out"){ this.setState({editOutputSignalsModal: false}); - } else { - return; // no valid direction } - - if (data){ - //data is an array of signals - for (let sig of data) { - //dispatch changes to signals - AppDispatcher.dispatch({ - type: 'signals/start-edit', - data: sig, - token: this.state.sessionToken, - }); - } - } - } /* ############################################## @@ -488,20 +452,20 @@ class Scenario extends React.Component { this.closeEditSignalsModal(data, direction)} - onAdd={(data) => this.closeNewSignalModal(data)} - onDelete={(data) => this.closeDeleteSignalModal(data)} + onCloseEdit={(direction) => this.closeEditSignalsModal(direction)} direction="Output" signals={this.state.signals} - configID={this.state.modalConfigData.id} /> + configID={this.state.modalConfigData.id} + sessionToken={this.state.sessionToken} + /> this.closeEditSignalsModal(data, direction)} - onAdd={(data) => this.closeNewSignalModal(data)} - onDelete={(data) => this.closeDeleteSignalModal(data)} + onCloseEdit={(direction) => this.closeEditSignalsModal(direction)} direction="Input" signals={this.state.signals} - configID={this.state.modalConfigData.id}/> + configID={this.state.modalConfigData.id} + sessionToken={this.state.sessionToken} + /> {/*Dashboard table*/}

Dashboards

diff --git a/src/signal/edit-signal-mapping.js b/src/signal/edit-signal-mapping.js index 0bfac11..178194f 100644 --- a/src/signal/edit-signal-mapping.js +++ b/src/signal/edit-signal-mapping.js @@ -18,35 +18,29 @@ import React from 'react'; import PropTypes from 'prop-types'; import {Button, FormGroup, FormLabel, FormText} from 'react-bootstrap'; - import Table from '../common/table'; import TableColumn from '../common/table-column'; import Dialog from "../common/dialogs/dialog"; - -import SignalStore from "./signal-store" import Icon from "../common/icon"; +import AppDispatcher from "../common/app-dispatcher"; class EditSignalMapping extends React.Component { - valid = false; - - static getStores() { - return [ SignalStore]; - } constructor(props) { - super(props); + super(props); - let dir = ""; - if ( this.props.direction === "Output"){ - dir = "out"; - } else if ( this.props.direction === "Input" ){ - dir = "in"; - } + let dir = ""; + if ( this.props.direction === "Output"){ + dir = "out"; + } else if ( this.props.direction === "Input" ){ + dir = "in"; + } - this.state = { - dir, - signals: [], - }; + + this.state = { + dir, + signals: [] + }; } static getDerivedStateFromProps(props, state){ @@ -63,55 +57,52 @@ class EditSignalMapping extends React.Component { onClose(canceled) { - if (canceled === false) { - if (this.valid) { - let data = this.state.signals; - - //forward modified signals back to callback function - this.props.onCloseEdit(data, this.state.dir) - } - } else { - this.props.onCloseEdit(null, this.state.dir); - } - } - - onDelete(e){ - console.log("On signal delete"); + this.props.onCloseEdit(this.state.dir) } - handleMappingChange = (event, row, column) => { const signals = this.state.signals; + let sig = {} if (column === 1) { // Name change if (event.target.value !== '') { - signals[row].name = event.target.value; - this.setState({signals: signals}); - this.valid = true; + sig = this.state.signals[row]; + sig.name = event.target.value; } } else if (column === 2) { // unit change if (event.target.value !== '') { - signals[row].unit = event.target.value; - this.setState({signals: signals}); - this.valid = true; + sig = this.state.signals[row]; + sig.unit = event.target.value; } } else if (column === 0) { //index change - - signals[row].index = parseInt(event.target.value, 10); - this.setState({signals: signals}); - this.valid = true; + sig = this.state.signals[row]; + sig.index = parseInt(event.target.value, 10); } + + if (sig !== {}){ + //dispatch changes to signal + AppDispatcher.dispatch({ + type: 'signals/start-edit', + data: sig, + token: this.props.sessionToken, + }); + } + }; handleDelete = (index) => { let data = this.state.signals[index] - this.props.onDelete(data); + + AppDispatcher.dispatch({ + type: 'signals/start-remove', + data: data, + token: this.props.sessionToken + }); }; handleAdd = () => { - console.log("add signal"); let newSignal = { configID: this.props.configID, @@ -121,12 +112,15 @@ class EditSignalMapping extends React.Component { index: 999 }; - this.props.onAdd(newSignal) + AppDispatcher.dispatch({ + type: 'signals/start-add', + data: newSignal, + token: this.props.sessionToken + }); }; resetState() { - this.valid=false; let signals = this.props.signals.filter((sig) => { return (sig.configID === this.props.configID) && (sig.direction === this.state.dir); @@ -143,7 +137,14 @@ class EditSignalMapping extends React.Component { return( - this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}> + this.onClose(c)} + onReset={() => this.resetState()} + valid={true}> {this.props.direction} Mapping From 5ea80ae44d32a8965be1b3b9ae2c4b680638edfb Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Tue, 23 Jun 2020 11:51:18 +0200 Subject: [PATCH 24/27] remove unused variable --- src/signal/edit-signal-mapping.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/signal/edit-signal-mapping.js b/src/signal/edit-signal-mapping.js index 178194f..43235bc 100644 --- a/src/signal/edit-signal-mapping.js +++ b/src/signal/edit-signal-mapping.js @@ -61,7 +61,6 @@ class EditSignalMapping extends React.Component { } handleMappingChange = (event, row, column) => { - const signals = this.state.signals; let sig = {} if (column === 1) { // Name change From 85390df0b8c0ec64d09e02c98b6154fa8203782f Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Tue, 23 Jun 2020 15:04:36 +0200 Subject: [PATCH 25/27] fix labelManipulation function of ICs (naming of labels changed with transition to bootstrap 4.5), state now showing colored. --- src/common/table.js | 5 ++++- src/ic/ics.js | 23 ++++++++++++----------- src/styles/app.css | 2 +- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/common/table.js b/src/common/table.js index 020f9bd..6ffd303 100644 --- a/src/common/table.js +++ b/src/common/table.js @@ -85,15 +85,18 @@ class CustomTable extends Component { labelContent = child.props.labelModifier(labelContent, data); } + let labelStyle = child.props.labelStyle(data[labelKey], data) + cell.push(   - + {labelContent.toString()} ); } + if (child.props.dataIndex) { cell.push(index); } diff --git a/src/ic/ics.js b/src/ic/ics.js index 603e474..e24cd93 100644 --- a/src/ic/ics.js +++ b/src/ic/ics.js @@ -220,39 +220,40 @@ class InfrastructureComponents extends Component { return Date.now() - new Date(component.stateUpdatedAt) > fiveMinutes; } - static stateLabelStyle(state, component){ - var style = [ 'label' ]; + stateLabelStyle(state, component){ + + var style = [ 'badge' ]; if (InfrastructureComponents.isICOutdated(component) && state !== 'shutdown') { - style.push('label-outdated'); + style.push('badge-outdated'); } switch (state) { case 'running': - style.push('label-success'); + style.push('badge-success'); break; case 'paused': - style.push('label-info'); + style.push('badge-info'); break; case 'idle': - style.push('label-primary'); + style.push('badge-primary'); break; case 'error': - style.push('label-danger'); + style.push('badge-danger'); break; case 'shutdown': - style.push('label-warning'); + style.push('badge-warning'); break; default: - style.push('label-default'); + style.push('badge-default'); } - return style.join(' '); + return style.join(' ') } static stateUpdateModifier(updatedAt) { @@ -273,7 +274,7 @@ class InfrastructureComponents extends Component {
this.onICChecked(index, event)} width='30' /> - + this.stateLabelStyle(state, component)} /> diff --git a/src/styles/app.css b/src/styles/app.css index e51cf0c..97c6548 100644 --- a/src/styles/app.css +++ b/src/styles/app.css @@ -400,6 +400,6 @@ body { margin-right: 0 !important; } -.label-outdated { +.badge-outdated { opacity: 0.4; } From 78942826182bdfc476b5c72154c7f06fac38e6ad Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Tue, 23 Jun 2020 15:22:25 +0200 Subject: [PATCH 26/27] show running state of scenario with icon (closes #223) --- src/common/table.js | 6 +++--- src/scenario/scenarios.js | 11 ++++++++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/common/table.js b/src/common/table.js index 6ffd303..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,7 +79,7 @@ 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); @@ -90,7 +90,7 @@ class CustomTable extends Component { cell.push(   - {labelContent.toString()} + {labelContent} ); diff --git a/src/scenario/scenarios.js b/src/scenario/scenarios.js index 22711a7..aceca0f 100644 --- a/src/scenario/scenarios.js +++ b/src/scenario/scenarios.js @@ -214,6 +214,15 @@ class Scenarios extends Component { FileSaver.saveAs(blob, 'scenario - ' + scenario.name + '.json'); } + modifyRunningColumn(running){ + + if(running){ + return + } else { + return + } + + } render() { const buttonStyle = { @@ -227,7 +236,7 @@ class Scenarios extends Component {
- + this.modifyRunningColumn(running)}/> Date: Tue, 23 Jun 2020 15:26:30 +0200 Subject: [PATCH 27/27] fix for running state of scenarios, show active state of users with icon instead of text. --- src/scenario/scenarios.js | 4 ++-- src/user/users.js | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/scenario/scenarios.js b/src/scenario/scenarios.js index aceca0f..87aa7e5 100644 --- a/src/scenario/scenarios.js +++ b/src/scenario/scenarios.js @@ -217,9 +217,9 @@ class Scenarios extends Component { modifyRunningColumn(running){ if(running){ - return - } else { return + } else { + return } } diff --git a/src/user/users.js b/src/user/users.js index 7deb4b3..d97c539 100644 --- a/src/user/users.js +++ b/src/user/users.js @@ -122,6 +122,16 @@ class Users extends Component { } }; + modifyActiveColumn(active){ + + if(active){ + return + } else { + return + } + + } + render() { return ( @@ -132,8 +142,8 @@ class Users extends Component { - this.getHumanRoleName(role)} /> - + this.getHumanRoleName(role)} /> + this.modifyActiveColumn(active)} /> this.setState({ editModal: true, modalData: this.state.users[index] })} onDelete={index => this.setState({ deleteModal: true, modalData: this.state.users[index] })} />