diff --git a/package-lock.json b/package-lock.json index 7d5f657..b1fb78b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17872,7 +17872,8 @@ }, "ssri": { "version": "6.0.1", - "resolved": "", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", "requires": { "figgy-pudding": "^3.5.1" } diff --git a/src/common/color-picker.js b/src/common/color-picker.js new file mode 100644 index 0000000..775e9c7 --- /dev/null +++ b/src/common/color-picker.js @@ -0,0 +1,110 @@ +/** + * 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 { Form } from 'react-bootstrap'; +import { SketchPicker } from 'react-color'; +import Dialog from './dialogs/dialog'; +import PropTypes from 'prop-types' + +class ColorPicker extends React.Component { + + constructor(props) { + super(props); + + this.state = { + rgbColor: {}, + }; + } + + componentDidUpdate(prevProps: Readonly

, prevState: Readonly, snapshot: SS) { + + if(this.props.show !== prevProps.show){ + // update color if show status of color picker has changed + this.setState({rgbColor: ColorPicker.hexToRgb(this.props.hexcolor,this.props.opacity)}) + } + } + + static hexToRgb(hex,opacity) { + let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return result ? { + r: parseInt(result[1], 16), + g: parseInt(result[2], 16), + b: parseInt(result[3], 16), + a: opacity + } : null; + } + + handleChangeComplete = (color) => { + this.setState({rgbColor: color.rgb}) + }; + + onClose = canceled => { + if (canceled) { + if (this.props.onClose != null) { + this.props.onClose(); + } + + return; + } + + if (this.props.onClose != null) { + + const rgbToHex = (r, g, b) => '#' + [r, g, b].map(x => { + const hex = x.toString(16) + return hex.length === 1 ? '0' + hex : hex + }).join('') + + let data = { + hexcolor: rgbToHex(this.state.rgbColor.r, this.state.rgbColor.g,this.state.rgbColor.b), + opacity: this.state.rgbColor.a, + } + + this.props.onClose(data); + } + }; + + render() { + + return

this.onClose(c)} + valid={true} + > +
+ + +
; + } +} + +ColorPicker.propTypes = { + onClose: PropTypes.func, + disableOpacity: PropTypes.bool, + show: PropTypes.bool, + hexcolor: PropTypes.string, + opacity: PropTypes.number +} + +export default ColorPicker; diff --git a/src/dashboard/dashboard.js b/src/dashboard/dashboard.js index 077de3f..d15a671 100644 --- a/src/dashboard/dashboard.js +++ b/src/dashboard/dashboard.js @@ -186,17 +186,17 @@ class Dashboard extends Component { componentDidUpdate(prevProps: Readonly

, prevState: Readonly, snapshot: SS) { // open web sockets if ICs are already known and sockets are not opened yet if (this.state.ics !== undefined && !Dashboard.webSocketsOpened) { - // only open sockets of ICs with configured input or output signals - let relevantICs = this.state.ics.filter(ic => { - let result = false; - this.state.configs.forEach(config => { - if(ic.id === config.icID && (config.inputLength !== 0 || config.outputLength !== 0)){ - result = true; - } + // only open sockets of ICs with configured input or output signals + let relevantICs = this.state.ics.filter(ic => { + let result = false; + this.state.configs.forEach(config => { + if(ic.id === config.icID && (config.inputLength !== 0 || config.outputLength !== 0)){ + result = true; + } + }) + return result; }) - return result; - }) - + if (relevantICs.length > 0) { console.log("Starting to open IC websockets:", relevantICs); AppDispatcher.dispatch({ diff --git a/src/ic/ic-data-store.js b/src/ic/ic-data-store.js index 7a81698..a6c53e0 100644 --- a/src/ic/ic-data-store.js +++ b/src/ic/ic-data-store.js @@ -110,6 +110,7 @@ class ICDataStore extends ReduceStore { state[action.ic].input.timestamp = Date.now(); state[action.ic].input.sequence++; state[action.ic].input.values[action.signal-1] = action.data; + state[action.ic].input.length = state[action.ic].input.values.length // copy of state needed because changes are not yet propagated let input = JSON.parse(JSON.stringify(state[action.ic].input)); diff --git a/src/ic/ic-store.js b/src/ic/ic-store.js index bc9aa18..eb6acf9 100644 --- a/src/ic/ic-store.js +++ b/src/ic/ic-store.js @@ -114,15 +114,21 @@ class InfrastructureComponentStore extends ArrayStore { case 'ics/status-received': let tempIC = action.ic; - if(!tempIC.managedexternally){ + if (!tempIC.managedexternally) { tempIC.state = action.data.state; tempIC.uptime = action.data.time_now - action.data.time_started; tempIC.statusupdateraw = action.data + tempIC.uuid = action.data.uuid AppDispatcher.dispatch({ type: 'ics/start-edit', data: tempIC, token: action.token, }); + + if(tempIC.type === "villas-node"){ + ICsDataManager.getConfig(action.url, action.token, tempIC); + } + } return super.reduce(state, action); @@ -130,9 +136,48 @@ class InfrastructureComponentStore extends ArrayStore { console.log("status error:", action.error); return super.reduce(state, action); - case 'ics/nodestats-received': + case 'ics/config-error': + console.log("config error:", action.error); + return super.reduce(state, action); + + case 'ics/config-received': + let temp = action.ic; + if (!temp.managedexternally) { + if (temp.statusupdateraw === null || temp.statusupdateraw === undefined) { + temp.statusupdateraw = {}; + } + temp.statusupdateraw["config"] = action.data; + AppDispatcher.dispatch({ + type: 'ics/start-edit', + data: temp, + token: action.token, + }); + + if(temp.type === "villas-node") { + ICsDataManager.getStatistics(action.url, action.token, temp); + } + + } + return super.reduce(state, action); + + case 'ics/statistics-error': + if (action.error.status === 400){ + // in case of bad request add the error message to the raw status + // most likely the statistics collection is disabled for this node + AppDispatcher.dispatch({ + type: 'ics/statistics-received', + data: action.error.response.text, + token: action.token, + ic: action.ic + }); + } else { + console.log("statistics error:", action.error); + } + return super.reduce(state, action); + + case 'ics/statistics-received': let tempIC2 = action.ic; - if(!tempIC2.managedexternally){ + if (!tempIC2.managedexternally) { if (tempIC2.statusupdateraw === null || tempIC2.statusupdateraw === undefined) { tempIC2.statusupdateraw = {}; } @@ -145,10 +190,6 @@ class InfrastructureComponentStore extends ArrayStore { } return super.reduce(state, action); - case 'ics/nodestats-error': - console.log("nodestats error:", action.error); - return super.reduce(state, action); - case 'ics/restart': ICsDataManager.restart(action.url, action.token); return super.reduce(state, action); diff --git a/src/ic/ic.js b/src/ic/ic.js index 9bb730f..40e300b 100644 --- a/src/ic/ic.js +++ b/src/ic/ic.js @@ -16,225 +16,320 @@ ******************************************************************************/ import React from 'react'; -import InfrastructureComponentStore from './ic-store'; +import ICstore from './ic-store'; +import ICdataStore from './ic-data-store' import { Container as FluxContainer } from 'flux/utils'; import AppDispatcher from '../common/app-dispatcher'; import { Container, Col, Row, Table, Button } from 'react-bootstrap'; import moment from 'moment'; import ReactJson from 'react-json-view'; import ConfirmCommand from './confirm-command'; -import Icon from "../common/icon"; +import IconButton from '../common/icon-button'; +import FileSaver from 'file-saver'; class InfrastructureComponent extends React.Component { - constructor(props) { - super(props); + constructor(props) { + super(props); - this.state = { - confirmCommand: false, - command: '', - }; + this.state = { + confirmCommand: false, + command: '', + sessionToken: localStorage.getItem("token"), + currentUser: JSON.parse(localStorage.getItem("currentUser")), + }; + } + + static getStores() { + return [ICstore, ICdataStore]; + } + + static calculateState(prevState, props) { + return { + ic: ICstore.getState().find(ic => ic.id === parseInt(props.match.params.ic, 10)) + } + } + + componentDidMount() { + let icID = parseInt(this.props.match.params.ic, 10); + + AppDispatcher.dispatch({ + type: 'ics/start-load', + data: icID, + token: this.state.sessionToken, + }); + } + + + refresh() { + // get status of VILLASnode and VILLASrelay ICs + if (this.state.ic.category === "gateway" && (this.state.ic.type === "villas-node" || this.state.ic.type === "villas-relay") + && this.state.ic.apiurl !== '' && this.state.ic.apiurl !== undefined && this.state.ic.apiurl !== null && !this.state.ic.managedexternally) { + AppDispatcher.dispatch({ + type: 'ics/get-status', + url: this.state.ic.apiurl, + token: this.state.sessionToken, + ic: this.state.ic + }); + } + } + + isJSON(data) { + if (data === undefined || data === null) { + return false; + } + let str = JSON.stringify(data); + try { + JSON.parse(str) + } + catch (ex) { + return false + } + return true + } + + async downloadGraph(url) { + let blob = await fetch(url).then(r => r.blob()) + FileSaver.saveAs(blob, this.state.ic.name + ".svg"); + } + + sendControlCommand() { + if (this.state.command === "restart") { + AppDispatcher.dispatch({ + type: 'ics/restart', + url: this.state.ic.apiurl + "/restart", + token: this.state.sessionToken, + }); + } else if (this.state.command === "shutdown") { + AppDispatcher.dispatch({ + type: 'ics/shutdown', + url: this.state.ic.apiurl + "/shutdown", + token: this.state.sessionToken, + }); + } + } + + confirmCommand(canceled){ + if(!canceled){ + this.sendControlCommand(); } - static getStores() { - return [InfrastructureComponentStore]; + this.setState({confirmCommand: false, command: ''}); + } + + + render() { + if (this.state.ic === undefined) { + return

Loading Infrastructure Component...

; } - static calculateState(prevState, props) { - if (prevState == null) { - prevState = {}; - } - - return { - sessionToken: localStorage.getItem("token"), - currentUser: JSON.parse(localStorage.getItem("currentUser")), - ic: InfrastructureComponentStore.getState().find(ic => ic.id === parseInt(props.match.params.ic, 10)) - } + let graphURL = "" + if (this.state.ic.apiurl !== "") { + graphURL = this.state.ic.apiurl + "/graph.svg" } - componentDidMount() { - let icID = parseInt(this.props.match.params.ic, 10); - - AppDispatcher.dispatch({ - type: 'ics/start-load', - data: icID, - token: this.state.sessionToken, - }); + const buttonStyle = { + marginLeft: '5px', } - isJSON(data) { - if (data === undefined || data === null) { - return false; - } - let str = JSON.stringify(data); - try { - JSON.parse(str) - } - catch (ex) { - return false - } - return true + const iconStyle = { + height: '25px', + width: '25px' } - async downloadGraph(url) { - let blob = await fetch(url).then(r => r.blob()) - FileSaver.saveAs(blob, this.state.ic.name + ".svg"); - } + return
+

{this.state.ic.name}

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Name{this.state.ic.name}
Description{this.state.ic.description}
UUID{this.state.ic.uuid}
State{this.state.ic.state}
Category{this.state.ic.category}
Type{this.state.ic.type}
Uptime{moment.duration(this.state.ic.uptime, "seconds").humanize()}
Location{this.state.ic.location}
Websocket URL{this.state.ic.websocketurl}
API URL{this.state.ic.apiurl}
Start parameter schema + {this.isJSON(this.state.ic.startparameterschema) ? + :
No Start parameter schema JSON available.
} +
+ + + {this.state.category ==="gateway" && this.state.ic.type === "villas-node" ? + <> +
+ this.downloadGraph(graphURL)} + icon='download' + buttonStyle={buttonStyle} + iconStyle={iconStyle} + /> +
+
+ Graph: +
+ {"Graph +
- sendControlCommand() { - if (this.state.command === "restart") { - AppDispatcher.dispatch({ - type: 'ics/restart', - url: this.state.ic.apiurl + "/restart", - token: this.state.sessionToken, - }); - } else if (this.state.command === "shutdown") { - AppDispatcher.dispatch({ - type: 'ics/shutdown', - url: this.state.ic.apiurl + "/shutdown", - token: this.state.sessionToken, - }); - } - } + {this.state.currentUser.role === "Admin" ? +
+
+ Controls: +
+ + +
+
+ :
+ } + this.confirmCommand(c)} + /> + + :
} - confirmCommand(canceled){ - if(!canceled){ - this.sendControlCommand(); - } - - this.setState({confirmCommand: false, command: ''}); - } - - async downloadGraph(url) { - - let blob = await fetch(url).then(r => r.blob()) - FileSaver.saveAs(blob, this.props.ic.name + ".svg"); - } + {this.state.category ==="gateway" && this.state.ic.type === "villas-relay" ? + <> +
+ this.refresh()} + icon='sync-alt' + buttonStyle={buttonStyle} + iconStyle={iconStyle} + /> +
+
+ Raw Status + {this.state.ic.statusupdateraw !== null && this.isJSON(this.state.ic.statusupdateraw) ? + :
No valid JSON raw data available.
} + + : +
} + + + {this.state.category ==="gateway" && this.state.ic.type === "villas-node" ? + <> +
+ this.refresh()} + icon='sync-alt' + buttonStyle={buttonStyle} + iconStyle={iconStyle} + /> +
+ - render() { - if (this.state.ic === undefined) { - return

Loading Infrastructure Component...

; - } - - let graphURL = "" - if (this.state.ic.apiurl !== "") { - graphURL = this.state.ic.apiurl + "/graph.svg" - } - - return
-

{this.state.ic.name}

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Name{this.state.ic.name}
Description{this.state.ic.description}
UUID{this.state.ic.uuid}
State{this.state.ic.state}
Category{this.state.ic.category}
Type{this.state.ic.type}
Uptime{moment.duration(this.state.ic.uptime, "seconds").humanize()}
Location{this.state.ic.location}
Websocket URL{this.state.ic.websocketurl}
API URL{this.state.ic.apiurl}
Start parameter schema - -
- - Raw Status - {this.isJSON(this.state.ic.statusupdateraw) ? - :
No valid JSON raw data available.
} - - {this.state.ic.type === "villas-node" ? - <> -
- -
-
- Graph: -
- {"Graph -
- - {this.state.currentUser.role === "Admin" ? -
-
- Controls: -
- - -
-
- :
- } - this.confirmCommand(c)} /> - - :
} - - - - -
; - } + + Raw Status + {this.state.ic.statusupdateraw !== null && this.isJSON(this.state.ic.statusupdateraw) ? + :
No valid JSON raw data available.
} + + + Raw Config + {this.state.ic.statusupdateraw && this.isJSON(this.state.ic.statusupdateraw["config"]) ? + :
No valid config JSON raw data available.
} + + + Raw Statistics + {this.state.ic.statusupdateraw && this.isJSON(this.state.ic.statusupdateraw["statistics"]) ? + :
No valid statistics JSON raw data available.
} + + + :
} + +
; + } } let fluxContainerConverter = require('../common/FluxContainerConverter'); -export default FluxContainer.create(fluxContainerConverter.convert(InfrastructureComponent), { withProps: true }); \ No newline at end of file +export default FluxContainer.create(fluxContainerConverter.convert(InfrastructureComponent), { withProps: true }); diff --git a/src/ic/ics-data-manager.js b/src/ic/ics-data-manager.js index 74b4080..fcfd5db 100644 --- a/src/ic/ics-data-manager.js +++ b/src/ic/ics-data-manager.js @@ -102,12 +102,17 @@ class IcsDataManager extends RestDataManager { } getStatus(url,token,ic){ - RestAPI.get(url + "/status", null).then(response => { + let requestURL = url; + if(ic.type === "villas-node"){ + requestURL += "/status"; + } + RestAPI.get(requestURL, null).then(response => { AppDispatcher.dispatch({ type: 'ics/status-received', data: response, token: token, - ic: ic + ic: ic, + url: url }); }).catch(error => { AppDispatcher.dispatch({ @@ -116,24 +121,51 @@ class IcsDataManager extends RestDataManager { }) }) - // get name of websocket - /*let ws_api = ic.websocketurl.split("/") - let ws_name = ws_api[ws_api.length-1] // websocket name is the last element in the websocket url + } + + getConfig(url,token,ic){ + + // get the name of the node + let ws_api = ic.websocketurl.split("/") + let ws_name = ws_api[ws_api.length-1] // name is the last element in the websocket url + + RestAPI.get(url + "/node/" + ws_name, null).then(response => { + AppDispatcher.dispatch({ + type: 'ics/config-received', + data: response, + token: token, + ic: ic, + url: url + }); + }).catch(error => { + AppDispatcher.dispatch({ + type: 'ics/config-error', + error: error + }) + }) + } + + getStatistics(url,token,ic){ + + // get the name of the node + let ws_api = ic.websocketurl.split("/") + let ws_name = ws_api[ws_api.length-1] // name is the last element in the websocket url RestAPI.get(url + "/node/" + ws_name + "/stats", null).then(response => { AppDispatcher.dispatch({ - type: 'ics/nodestats-received', + type: 'ics/statistics-received', data: response, token: token, ic: ic }); }).catch(error => { AppDispatcher.dispatch({ - type: 'ics/nodestats-error', - error: error + type: 'ics/statistics-error', + error: error, + token: token, + ic: ic }) - })*/ - + }) } restart(url,token){ diff --git a/src/ic/ics.js b/src/ic/ics.js index 1b3358f..7452fa8 100644 --- a/src/ic/ics.js +++ b/src/ic/ics.js @@ -133,20 +133,21 @@ class InfrastructureComponents extends Component { token: this.state.sessionToken, }); - // get status of VILLASnode and VILLASrelay ICs - this.state.ics.forEach(ic => { - if ((ic.type === "villas-node" || ic.type === "villas-relay") - && ic.apiurl !== '' && ic.apiurl !== undefined && ic.apiurl !== null && !ic.managedexternally) { - AppDispatcher.dispatch({ - type: 'ics/get-status', - url: ic.apiurl, - token: this.state.sessionToken, - ic: ic - }); - } - }) - } + + // get status of VILLASnode and VILLASrelay ICs + this.state.ics.forEach(ic => { + if (ic.category === "gateway" && (ic.type === "villas-node" || ic.type === "villas-relay") + && ic.apiurl !== '' && ic.apiurl !== undefined && ic.apiurl !== null && !ic.managedexternally) { + AppDispatcher.dispatch({ + type: 'ics/get-status', + url: ic.apiurl, + token: this.state.sessionToken, + ic: ic + }); + } + }) + } closeNewModal(data) { @@ -201,9 +202,6 @@ class InfrastructureComponents extends Component { } } - closeICModal(data){ - this.setState({ icModal : false }); - } closeDeleteModal(confirmDelete){ this.setState({ deleteModal: false }); @@ -345,14 +343,6 @@ class InfrastructureComponents extends Component { return dateTime.fromNow() } - modifyManagedExternallyColumn(managedExternally, component){ - if(managedExternally){ - return - } else { - return "" - } - } - modifyUptimeColumn(uptime, component){ if(uptime >= 0){ let momentDurationFormatSetup = require("moment-duration-format"); @@ -366,11 +356,6 @@ class InfrastructureComponents extends Component { } } - openICStatus(ic){ - let index = this.state.ics.indexOf(ic); - this.setState({ icModal: true, modalIC: ic, modalIndex: index }) - } - isLocalIC(index, ics){ let ic = ics[index] return !ic.managedexternally @@ -450,10 +435,9 @@ class InfrastructureComponents extends Component { } render() { - const buttonStyle = { - marginLeft: '10px' - }; + marginLeft: '10px', + } const iconStyle = { height: '30px', diff --git a/src/scenario/scenario.js b/src/scenario/scenario.js index d9e9d4c..57e7a15 100644 --- a/src/scenario/scenario.js +++ b/src/scenario/scenario.js @@ -30,6 +30,7 @@ import SignalStore from '../signal/signal-store' import FileStore from "../file/file-store" import WidgetStore from "../widget/widget-store"; import ResultStore from "../result/result-store" +import UsersStore from "../user/users-store" import DashboardTable from '../dashboard/dashboard-table' import ResultTable from "../result/result-table"; @@ -67,7 +68,7 @@ class Scenario extends React.Component { } static getStores() { - return [ScenarioStore, ConfigStore, DashboardStore, ICStore, SignalStore, FileStore, WidgetStore, ResultStore]; + return [ScenarioStore, ConfigStore, DashboardStore, ICStore, SignalStore, FileStore, WidgetStore, ResultStore, UsersStore]; } componentDidMount() { diff --git a/src/scenario/scenarios.js b/src/scenario/scenarios.js index 51f8d3e..4a3ecbb 100644 --- a/src/scenario/scenarios.js +++ b/src/scenario/scenarios.js @@ -18,21 +18,19 @@ import React, { Component } from 'react'; import { Container } from 'flux/utils'; import FileSaver from 'file-saver'; - import AppDispatcher from '../common/app-dispatcher'; import ScenarioStore from './scenario-store'; import DashboardStore from '../dashboard/dashboard-store'; import WidgetStore from "../widget/widget-store"; import ConfigStore from '../componentconfig/config-store'; import SignalStore from '../signal/signal-store' - -import Icon from '../common/icon'; +import ResultStore from '../result/result-store' +import FileStore from '../file/file-store' import Table from '../common/table'; import TableColumn from '../common/table-column'; import NewScenarioDialog from './new-scenario'; import EditScenarioDialog from './edit-scenario'; import ImportScenarioDialog from './import-scenario'; - import DeleteDialog from '../common/dialogs/delete-dialog'; import IconButton from '../common/icon-button'; @@ -40,7 +38,7 @@ import IconButton from '../common/icon-button'; class Scenarios extends Component { static getStores() { - return [ScenarioStore, DashboardStore, WidgetStore, ConfigStore, SignalStore]; + return [ScenarioStore, DashboardStore, WidgetStore, ConfigStore, SignalStore, ResultStore, FileStore]; } static calculateState(prevState, props) { diff --git a/src/signal/edit-signal-mapping.js b/src/signal/edit-signal-mapping.js index e5bba88..cc235d2 100644 --- a/src/signal/edit-signal-mapping.js +++ b/src/signal/edit-signal-mapping.js @@ -95,7 +95,6 @@ class EditSignalMappingDialog extends React.Component { let signals = this.state.signals; let modifiedSignals = this.state.modifiedSignalIDs; - console.log("HandleMappingChange", row, column) if (column === 2) { // Name change signals[row].name = event.target.value; if (modifiedSignals.find(id => id === signals[row].id) === undefined) { @@ -112,7 +111,6 @@ class EditSignalMappingDialog extends React.Component { modifiedSignals.push(signals[row].id); } } else if (column === 1) { //index change - console.log("Index change") signals[row].index =parseInt(event.target.value, 10); if (modifiedSignals.find(id => id === signals[row].id) === undefined) { modifiedSignals.push(signals[row].id); diff --git a/src/signal/signals-data-manager.js b/src/signal/signals-data-manager.js index 1a5ee0c..b833534 100644 --- a/src/signal/signals-data-manager.js +++ b/src/signal/signals-data-manager.js @@ -71,7 +71,7 @@ class SignalsDataManager extends RestDataManager{ let configured = false; let error = false; for(let nodeConfig of nodes){ - console.log("parsing node config: ", nodeConfig) + //console.log("parsing node config: ", nodeConfig) if(!nodeConfig.hasOwnProperty("name")){ console.warn("Could not parse the following node config because it lacks a name parameter:", nodeConfig); } else if(nodeConfig.name === socketname){ @@ -128,7 +128,6 @@ class SignalsDataManager extends RestDataManager{ for (let outSig of nodeConfig.out.signals) { if (outSig.enabled) { - console.log("adding output signal:", outSig); let newSignal = { configID: configID, direction: 'out', diff --git a/src/user/edit-own-user.js b/src/user/edit-own-user.js index a5b7756..8b49221 100644 --- a/src/user/edit-own-user.js +++ b/src/user/edit-own-user.js @@ -19,17 +19,16 @@ import React from 'react'; import { Form, Col } from 'react-bootstrap'; import Dialog from '../common/dialogs/dialog'; +import NotificationsDataManager from "../common/data-managers/notifications-data-manager"; +import NotificationsFactory from "../common/data-managers/notifications-factory"; class EditOwnUserDialog extends React.Component { - valid: true; - constructor(props) { super(props); this.state = { - username: this.props.user.username, - id: this.props.user.id, + username: "", mail: this.props.user.mail, password: '', oldPassword: '', @@ -39,9 +38,29 @@ class EditOwnUserDialog extends React.Component { onClose(canceled) { if (canceled === false) { - if (this.valid) { - this.props.onClose(this.state); + + let user = {}; + user.id = this.props.user.id; + user.password = this.state.password; + user.oldPassword = this.state.oldPassword; + user.confirmPassword = this.state.confirmPassword + + if (this.state.username != null && this.state.username !== this.props.user.username){ + user.username = this.state.username; } + + if (this.state.mail != null && this.state.mail !== this.props.user.mail){ + user.mail = this.state.mail; + } + + if (this.state.password !== '' && this.state.oldPassword !== '' && this.state.password === this.state.confirmPassword ) { + user.password = this.state.password; + user.oldPassword = this.state.oldPassword; + } else if (this.state.password !== '' && this.state.password !== this.state.confirmPassword) { + NotificationsDataManager.addNotification(NotificationsFactory.UPDATE_ERROR('New password not correctly confirmed')); + } + + this.props.onClose(user); } else { this.props.onClose(); } @@ -49,42 +68,11 @@ class EditOwnUserDialog extends React.Component { handleChange(e) { this.setState({ [e.target.id]: e.target.value }); - - // check all controls - let username = true; - let mail = true; - let pw = true; - let oldPassword = true; - let confirmPassword = true; - - if (this.state.username === '') { - username = false; - } - - if (this.state.mail === '') { - mail = false; - } - - if (this.state.password === '') { - pw = false; - } - - if (this.state.oldPassword === '') { - oldPassword = false; - } - - if (this.state.confirmPassword === '') { - confirmPassword = false; - } - - // form is valid if the following condition is met - this.valid = username || mail || (oldPassword && pw && confirmPassword); } resetState() { this.setState({ username: this.props.user.username, - id: this.props.user.id, mail: this.props.user.mail, oldPassword: '', confirmPassword: '', @@ -94,28 +82,28 @@ class EditOwnUserDialog extends React.Component { render() { return ( - this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}> + this.onClose(c)} onReset={() => this.resetState()} valid={true}>
Username - this.handleChange(e)} autocomplete="username" /> + this.handleChange(e)} autoComplete="username" /> E-mail - this.handleChange(e)} autocomplete="email" /> + this.handleChange(e)} autoComplete="email" /> Old Password - this.handleChange(e)} autocomplete="current-password" /> + this.handleChange(e)} autoComplete="current-password" /> New Password - this.handleChange(e)} autocomplete="new-password" /> + this.handleChange(e)} autoComplete="new-password" /> Confirm New Password - this.handleChange(e)} autocomplete="new-password" /> + this.handleChange(e)} autoComplete="new-password" />
diff --git a/src/user/edit-user.js b/src/user/edit-user.js index 5d20bf4..26a9fcb 100644 --- a/src/user/edit-user.js +++ b/src/user/edit-user.js @@ -19,30 +19,55 @@ import React from 'react'; import { Form, Col } from 'react-bootstrap'; import Dialog from '../common/dialogs/dialog'; +import NotificationsDataManager from "../common/data-managers/notifications-data-manager"; +import NotificationsFactory from "../common/data-managers/notifications-factory"; class EditUserDialog extends React.Component { - valid: true; - constructor(props) { super(props); this.state = { - username: props.user.username, - mail: props.user.mail, - role: props.user.role, - id: props.user.id, - active: props.user.active, - password: '', - confirmPassword: '', - oldPassword: '' + username: '', + mail: '', + role: '', + active: '', + password: "", + confirmPassword: "", + oldPassword: "", } } onClose(canceled) { if (canceled === false) { - if (this.valid) { - this.props.onClose(this.state); + + let user = {} + user.id = this.props.user.id; + + if (this.state.username != null && this.state.username !== this.props.user.username){ + user.username = this.state.username } + + if (this.state.mail != null && this.state.mail !== this.props.user.mail){ + user.mail = this.state.mail + } + + if (this.state.role != null && this.state.role !== this.props.user.role){ + user.role = this.state.role + } + + if (this.state.active != null && this.state.active !== this.props.user.active){ + user.active = this.state.active + } + + if (this.state.password !== '' && this.state.oldPassword !== '' && this.state.password === this.state.confirmPassword) { + user.password = this.state.password; + user.oldpassword = this.state.oldPassword + } else if (this.state.password !== '' && this.state.password !== this.state.confirmPassword){ + NotificationsDataManager.addNotification(NotificationsFactory.UPDATE_ERROR("New password not correctly confirmed")) + } + + this.props.onClose(user); + } else { this.props.onClose(); } @@ -50,46 +75,6 @@ class EditUserDialog extends React.Component { handleChange(e) { this.setState({ [e.target.id]: e.target.value }); - - // check all controls - var username = true; - var role = true; - var mail = true; - var pw = true; - var active = true; - var confirmPassword = true; - var oldPassword = true; - - if (this.state.username === '') { - username = false; - } - - if (this.state.role === '') { - role = false; - } - - if (this.state.mail === '') { - mail = false; - } - - if (this.state.password === '') { - pw = false; - } - - if (this.state.active === '') { - active = false; - } - - if (this.state.confirmPassword === '') { - confirmPassword = false; - } - - if (this.state.oldPassword === '') { - oldPassword = false; - } - - // form is valid if any of the fields contain somethig - this.valid = username || role || mail || pw || active || confirmPassword || oldPassword; } resetState() { @@ -97,38 +82,40 @@ class EditUserDialog extends React.Component { username: this.props.user.username, mail: this.props.user.mail, role: this.props.user.role, - id: this.props.user.id + password: "", + confirmPassword: "", + oldPassword: "", }); } render() { return ( - this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}> + this.onClose(c)} onReset={() => this.resetState()} valid={true}>
Username - this.handleChange(e)} /> + this.handleChange(e)} /> E-mail - this.handleChange(e)} /> + this.handleChange(e)} /> Admin Password this.handleChange(e)} /> - Password + New User Password this.handleChange(e)} /> - Confirm New Password + Confirm new Password this.handleChange(e)} /> Role - this.handleChange(e)}> + this.handleChange(e)}> diff --git a/src/user/login-store.js b/src/user/login-store.js index 3e0eba6..f5cea52 100644 --- a/src/user/login-store.js +++ b/src/user/login-store.js @@ -66,11 +66,14 @@ class LoginStore extends ReduceStore { return Object.assign({}, state, { token: null, currentUser: null, loginMessage: null}); case 'users/logged-in': - // save login in local storage - localStorage.setItem('token', action.token); + // save login data in local storage and loginStore + let newState = state + if (action.token != null){ + localStorage.setItem('token', action.token); + newState = Object.assign({}, state, {token: action.token}) + } localStorage.setItem('currentUser', JSON.stringify(action.currentUser)); - - return Object.assign({}, state, { token: action.token, currentUser: action.currentUser}); + return Object.assign({}, newState, { currentUser: action.currentUser}); case 'users/login-error': if (action.error && !action.error.handled) { diff --git a/src/user/user.js b/src/user/user.js index 26bd161..a4cdfd6 100644 --- a/src/user/user.js +++ b/src/user/user.js @@ -15,100 +15,74 @@ * along with VILLASweb. If not, see . ******************************************************************************/ -import React, { Component } from 'react'; +import React from 'react'; import { Container } from 'flux/utils'; -import { Button, Form, Row, Col } from 'react-bootstrap'; - +import { Form, Row, Col } from 'react-bootstrap'; import AppDispatcher from '../common/app-dispatcher'; -import UsersStore from './users-store'; - -import Icon from '../common/icon'; import EditOwnUserDialog from './edit-own-user' -import NotificationsDataManager from "../common/data-managers/notifications-data-manager" -import NotificationsFactory from "../common/data-managers/notifications-factory"; +import IconButton from "../common/icon-button"; +import LoginStore from './login-store' + +class User extends React.Component { + constructor() { + super(); + + this.state = { + token: LoginStore.getState().token, + editModal: false, + } + } -class User extends Component { static getStores() { - return [ UsersStore ]; + return [ LoginStore ]; } static calculateState(prevState, props) { - prevState = prevState || {}; - - let currentUserID = JSON.parse(localStorage.getItem("currentUser")).id; - let currentUser = UsersStore.getState().find(user => user.id === parseInt(currentUserID, 10)); - return { - currentUser, - token: localStorage.getItem("token"), - editModal: false, - }; - } - - componentDidMount() { - let currentUserID = JSON.parse(localStorage.getItem("currentUser")).id; - - AppDispatcher.dispatch({ - type: 'users/start-load', - data: parseInt(currentUserID, 10), - token: this.state.token - }); + currentUser: LoginStore.getState().currentUser + } } closeEditModal(data) { this.setState({ editModal: false }); - let updatedData = {} - let updatedUser = this.state.currentUser; - let hasChanged = false; - let pwChanged = false; - - updatedData.id = this.state.currentUser.id; - if (data) { - if (data.username !== this.state.currentUser.username) { - hasChanged = true; - updatedData.username = data.username; - updatedUser.username = data.username - } - - if (data.mail !== this.state.currentUser.mail) { - hasChanged = true; - updatedData.mail = data.mail; - updatedUser.mail = data.mail; - } - - if (data.password !== '' && data.oldPassword !== '' && data.password === data.confirmPassword ) { - pwChanged = true; - updatedData.password = data.password; - updatedData.oldPassword = data.oldPassword; - } else if (data.password !== '' && data.password !== data.confirmPassword) { - NotificationsDataManager.addNotification(NotificationsFactory.UPDATE_ERROR('New password not correctly confirmed')); - return - } - - if (hasChanged || pwChanged) { - if (hasChanged){ - this.setState({ currentUser: updatedUser }) - } - - AppDispatcher.dispatch({ - type: 'users/start-edit', - data: updatedData, - token: this.state.token - }); - } else { - NotificationsDataManager.addNotification(NotificationsFactory.UPDATE_WARNING('No update requested, no input data')); - } + AppDispatcher.dispatch({ + type: 'users/start-edit', + data: data, + token: this.state.token, + currentUser: this.state.currentUser, + }); } } render() { let user = this.state.currentUser; + const buttonStyle = { + marginLeft: '10px', + } + + const iconStyle = { + height: '30px', + width: '30px' + } + return (
-

Account

+

Account + + + this.setState({ editModal: true })} + icon='edit' + buttonStyle={buttonStyle} + iconStyle={iconStyle} + /> + +

{user ? <> @@ -116,30 +90,28 @@ class User extends Component { Username - + E-mail - + Role - + Created at - + - + . - ******************************************************************************/ - -import React from 'react'; -import { Form } from 'react-bootstrap'; -import { SketchPicker } from 'react-color'; -import Dialog from '../../common/dialogs/dialog'; - - -class ColorPicker extends React.Component { - valid = true; - - constructor(props) { - super(props); - - this.state = { - widget: {} - }; - } - - static getDerivedStateFromProps(props, state) { - return { - widget: props.widget - }; - } - - hexToRgb = (hex,opacity) => { - var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); - return result ? { - r: parseInt(result[1], 16), - g: parseInt(result[2], 16), - b: parseInt(result[3], 16), - a: opacity - } : null; - } - - handleChangeComplete = (color) => { - let temp = this.state.widget; - - if (this.props.controlId === 'strokeStyle'){ - temp.customProperties.zones[this.props.zoneIndex]['strokeStyle'] = color.hex; - } - else if (this.props.controlId === 'lineColor'){ - temp.customProperties.lineColors[this.props.lineIndex] = color.hex; - } - else { - let parts = this.props.controlId.split('.'); - let isCustomProperty = true; - - if (parts.length === 1){ - isCustomProperty = false; - } - - isCustomProperty ? temp[parts[0]][parts[1]] = color.hex : temp[this.props.controlId] = color.hex; - isCustomProperty ? temp[parts[0]][parts[1] + "_opacity"] = color.rgb.a : temp[this.props.controlId +"_opacity"] = color.rgb.a; - } - - this.setState({ widget: temp }); - }; - - onClose = canceled => { - if (canceled) { - if (this.props.onClose != null) { - this.props.onClose(); - } - - return; - } - - if (this.valid && this.props.onClose != null) { - this.props.onClose(this.state.widget); - } - }; - - render() { - let hexColor; - let opacity = 1; - let parts = this.props.controlId.split('.'); - let isCustomProperty = true; - if (parts.length === 1) { - isCustomProperty = false; - } - - if (this.props.controlId === 'strokeStyle') { - if (typeof this.state.widget.customProperties.zones[this.props.zoneIndex] !== 'undefined') { - hexColor = this.state.widget.customProperties.zones[this.props.zoneIndex]['strokeStyle']; - } - } - else if (this.props.controlId === 'lineColor') { - if (typeof this.state.widget.customProperties.lineColors[this.props.lineIndex] !== 'undefined') { - hexColor = this.state.widget.customProperties.lineColors[this.props.lineIndex]; - } - } - else{ - hexColor = isCustomProperty ? this.state.widget[parts[0]][parts[1]]: this.state.widget[this.props.controlId]; - opacity = isCustomProperty ? this.state.widget[parts[0]][parts[1] + "_opacity"]: this.state.widget[this.props.controlId + "_opacity"]; - } - - let rgbColor = this.hexToRgb(hexColor, opacity); - return this.onClose(c)} - valid={true} - > -
- - -
; - } -} - -export default ColorPicker; diff --git a/src/widget/edit-widget/edit-widget-checkbox-control.js b/src/widget/edit-widget/edit-widget-checkbox-control.js index 6dd8ce0..9ea51c4 100644 --- a/src/widget/edit-widget/edit-widget-checkbox-control.js +++ b/src/widget/edit-widget/edit-widget-checkbox-control.js @@ -21,21 +21,19 @@ import { Form } from 'react-bootstrap'; class EditWidgetCheckboxControl extends React.Component { constructor(props) { super(props); + } - let parts = this.props.controlId.split('.'); - let isCustomProperty = true; - if (parts.length ===1){ - isCustomProperty = false; - } - + static getDerivedStateFromProps(props, state) { + let parts = props.controlId.split('.'); let isChecked; - if (isCustomProperty){ - isChecked = this.props.widget[parts[0]][parts[1]] - } else{ - isChecked = this.props.widget[this.props.controlId] + + if (parts.length ===1){ + isChecked = props.widget[props.controlId] + } else { + isChecked = props.widget[parts[0]][parts[1]] } - this.state = { + return { isChecked }; } diff --git a/src/widget/edit-widget/edit-widget-color-control.js b/src/widget/edit-widget/edit-widget-color-control.js index 6fd82f7..0995453 100644 --- a/src/widget/edit-widget/edit-widget-color-control.js +++ b/src/widget/edit-widget/edit-widget-color-control.js @@ -17,7 +17,7 @@ import React, { Component } from 'react'; import { Form, OverlayTrigger, Tooltip, Button, Col } from 'react-bootstrap'; -import ColorPicker from './color-picker' +import ColorPicker from '../../common/color-picker' import Icon from "../../common/icon"; // schemeCategory20 no longer available in d3 @@ -28,55 +28,51 @@ class EditWidgetColorControl extends Component { super(props); this.state = { - widget: {}, + color: null, + opacity: null, showColorPicker: false, originalColor: null }; } static getDerivedStateFromProps(props, state){ + let parts = props.controlId.split('.'); + let isCustomProperty = true; + if (parts.length === 1){ + isCustomProperty = false; + } + + let color = (isCustomProperty ? props.widget[parts[0]][parts[1]] : props.widget[props.controlId]); + let opacity = (isCustomProperty ? props.widget[parts[0]][parts[1] + "_opacity"] : props.widget[props.controlId + "_opacity"]); + return { - widget: props.widget + color: color, + opacity: opacity, }; } openColorPicker = () =>{ - let parts = this.props.controlId.split('.'); - let isCustomProperty = true; - if (parts.length === 1){ - isCustomProperty = false; - } - let color = (isCustomProperty ? this.props.widget[parts[0]][parts[1]] : this.props.widget[this.props.controlId]); - - this.setState({showColorPicker: true, originalColor: color}); + this.setState({showColorPicker: true, originalColor: this.state.color}); } - closeEditModal = (data) => { + closeColorPickerEditModal = (data) => { this.setState({showColorPicker: false}) - if(typeof data === 'undefined'){ - let parts = this.props.controlId.split('.'); - let isCustomProperty = true; - if (parts.length === 1) { - isCustomProperty = false; - } - let temp = this.state.widget; - isCustomProperty ? temp[parts[0]][parts[1]] = this.state.originalColor : temp[this.props.controlId] = this.state.originalColor; - this.setState({ widget: temp }); + if(typeof data === 'undefined'){ + + this.setState({ color: this.state.originalColor }); + } else { + // color picker with result data {hexcolor, opacity} + this.setState({color: data.hexcolor, opacity: data.opacity}) + this.props.handleChange({target: { id: this.props.controlId, value: data.hexcolor} }) + this.props.handleChange({target: { id: this.props.controlId + "_opacity", value: data.opacity} }) } } render() { - let parts = this.props.controlId.split('.'); - let isCustomProperty = true; - if (parts.length === 1){ - isCustomProperty = false; - } - let color = (isCustomProperty ? this.props.widget[parts[0]][parts[1]] : this.props.widget[this.props.controlId]); - let opacity = (isCustomProperty ? this.props.widget[parts[0]][parts[1] + "_opacity"] : this.props.widget[this.props.controlId + "_opacity"]); let style = { - backgroundColor: color, - opacity: opacity, + backgroundColor: this.state.color, + opacity: this.state.opacity, width: '80px', height: '40px', } @@ -85,8 +81,6 @@ class EditWidgetColorControl extends Component { if(this.props.disableOpacity){ tooltipText = "Change border color"; } - - return ( {this.props.label} @@ -100,7 +94,13 @@ class EditWidgetColorControl extends Component {
- this.closeEditModal(data)} widget={this.state.widget} controlId={this.props.controlId} disableOpacity={this.props.disableOpacity}/> + this.closeColorPickerEditModal(data)} + hexcolor={this.state.color} + opacity={this.state.opacity} + disableOpacity={this.props.disableOpacity} + />
); diff --git a/src/widget/edit-widget/edit-widget-color-zones-control.js b/src/widget/edit-widget/edit-widget-color-zones-control.js index dd990bb..46b4ed7 100644 --- a/src/widget/edit-widget/edit-widget-color-zones-control.js +++ b/src/widget/edit-widget/edit-widget-color-zones-control.js @@ -17,8 +17,7 @@ import React from 'react'; import { Form, Table, Button, Tooltip, OverlayTrigger } from 'react-bootstrap'; -import ColorPicker from './color-picker' - +import ColorPicker from '../../common/color-picker' import Icon from '../../common/icon'; import { Collapse } from 'react-collapse'; @@ -27,12 +26,7 @@ class EditWidgetColorZonesControl extends React.Component { super(props); this.state = { - widget: { - customProperties:{ - zones: [] - } - }, - selectedZone: null, + colorZones: [], selectedIndex: null, showColorPicker: false, originalColor: null, @@ -43,89 +37,88 @@ class EditWidgetColorZonesControl extends React.Component { static getDerivedStateFromProps(props, state){ return { - widget: props.widget + colorZones: props.widget.customProperties.zones }; } addZone = () => { // add row - const widget = this.state.widget; - widget.customProperties.zones.push({ strokeStyle: '#d3cbcb', min: 0, max: 100 }); + const zones = JSON.parse(JSON.stringify(this.state.colorZones)); + zones.push({ strokeStyle: '#d3cbcb', min: 0, max: 100 }); - if(widget.customProperties.zones.length > 0){ - let length = widget.customProperties.zones.length + if(zones.length > 0){ + let length = zones.length - for(let i= 0 ; i < length; i++){ - widget.customProperties.zones[i].min = i* 100/length; - widget.customProperties.zones[i].max = (i+1)* 100/length; - } + for(let i= 0 ; i < length; i++){ + zones[i].min = i* 100/length; + zones[i].max = (i+1)* 100/length; + } } - this.setState({ widget, selectedZone: null, selectedIndex: null }); - - this.sendEvent(widget); + this.setState({ colorZones: zones, selectedIndex: null }); + this.props.handleChange({target: { id: this.props.controlId, value: zones}}) } removeZones = () => { - - let temp = this.state.widget; - - temp.customProperties.zones.splice(this.state.selectedIndex, 1); - - if(temp.customProperties.zones.length > 0){ - let length = temp.customProperties.zones.length - + let zones = JSON.parse(JSON.stringify(this.state.colorZones)); + zones.splice(this.state.selectedIndex, 1); + if(zones.length > 0){ + let length = zones.length for(let i= 0 ; i < length; i++){ - temp.customProperties.zones[i].min = i* 100/length; - temp.customProperties.zones[i].max = (i+1)* 100/length; + zones[i].min = i* 100/length; + zones[i].max = (i+1)* 100/length; } - } - - this.setState({widget: temp,selectedZone: null, selectedIndex: null}); - + } + this.setState({colorZones: zones, selectedIndex: null}); + this.props.handleChange({target: { id: this.props.controlId, value: zones}}) } changeCell = (event, row, column) => { // change row - const widget = this.state.widget; + const zones = JSON.parse(JSON.stringify(this.state.colorZones)) if (column === 1) { - widget.customProperties.zones[row].strokeStyle = event.target.value; + zones[row].strokeStyle = event.target.value; } else if (column === 2) { - widget.customProperties.zones[row].min = event.target.value; + zones[row].min = event.target.value; } else if (column === 3) { - widget.customProperties.zones[row].max = event.target.value; + zones[row].max = event.target.value; } - this.setState({ widget }); - - this.sendEvent(widget); + this.setState({ colorZones: zones }); + this.props.handleChange({target: { id: this.props.controlId, value: zones}}) } editColorZone = (index) => { if(this.state.selectedIndex !== index){ - this.setState({selectedZone: this.state.widget.customProperties.zones[index], selectedIndex: index , minValue: this.state.widget.customProperties.zones[index].min, maxValue: this.state.widget.customProperties.zones[index].max}); + this.setState({ + selectedIndex: index , + minValue: this.state.colorZones[index].min, + maxValue: this.state.colorZones[index].max} + ); } else{ - this.setState({selectedZone: null, selectedIndex: null}); + this.setState({selectedIndex: null}); } } openColorPicker = () => { - - let color = this.state.selectedZone.strokeStyle; - + let color = this.state.colorZones[this.state.selectedIndex].strokeStyle; this.setState({showColorPicker: true, originalColor: color}); } - closeEditModal = (data) => { + closeColorPickerEditModal = (data) => { this.setState({showColorPicker: false}) + let zones = JSON.parse(JSON.stringify(this.state.colorZones)) if(typeof data === 'undefined'){ - let temp = this.state.selectedZone; - temp.strokeStyle = this.state.originalColor; - - this.setState({ selectedZone : temp }); + zones[this.state.selectedIndex].strokeStyle = this.state.originalColor + this.setState({ colorZones : zones }); + } else { + // color picker with result data {hexcolor, opacity} + zones[this.state.selectedIndex].strokeStyle = data.hexcolor + this.setState({ colorZones : zones }); + this.props.handleChange({target: { id: this.props.controlId, value: zones}}) } } @@ -134,14 +127,15 @@ class EditWidgetColorZonesControl extends React.Component { if(e.target.value < 0) return; this.setState({minValue: e.target.value}); - let temp = this.state.widget; - temp.customProperties.zones[this.state.selectedIndex]['min'] = e.target.value; + let zones = JSON.parse(JSON.stringify(this.state.colorZones)); + zones[this.state.selectedIndex]['min'] = e.target.value; if(this.state.selectedIndex !== 0){ - temp.customProperties.zones[this.state.selectedIndex - 1]['max'] = e.target.value + zones[this.state.selectedIndex - 1]['max'] = e.target.value } - this.setState({ widget: temp }); + this.setState({ colorZones: zones }); + this.props.handleChange({target: { id: this.props.controlId, value: zones}}) } handleMaxChange = (e) => { @@ -149,32 +143,19 @@ class EditWidgetColorZonesControl extends React.Component { if(e.target.value > 100) return; this.setState({maxValue: e.target.value}); - let temp = this.state.widget; - temp.customProperties.zones[this.state.selectedIndex]['max'] = e.target.value; + let zones = JSON.parse(JSON.stringify(this.state.colorZones)); + zones[this.state.selectedIndex]['max'] = e.target.value; - if(this.state.selectedIndex !== this.state.widget.customProperties.zones.length -1){ - temp.customProperties.zones[this.state.selectedIndex + 1]['min'] = e.target.value + if(this.state.selectedIndex !== zones.length -1){ + zones[this.state.selectedIndex + 1]['min'] = e.target.value } - this.setState({ widget: temp }); - } - - sendEvent(widget) { - // create event - const event = { - target: { - id: 'zones', - value: widget.customProperties.zones - } - }; - - this.props.handleChange(event); + this.setState({ colorZones: zones }); + this.props.handleChange({target: { id: this.props.controlId, value: zones}}) } - render() { - const buttonStyle = { marginBottom: '10px', marginLeft: '120px', @@ -187,9 +168,9 @@ class EditWidgetColorZonesControl extends React.Component { let tempColor = 'FFFFFF'; let collapse = false; - if(this.state.selectedZone !== null){ + if(this.state.selectedIndex !== null){ collapse = true; - tempColor = this.state.selectedZone.strokeStyle; + tempColor = this.state.colorZones[this.state.selectedIndex].strokeStyle; } let pickerStyle = { @@ -213,7 +194,7 @@ class EditWidgetColorZonesControl extends React.Component {
{ - this.state.widget.customProperties.zones.map((zone, idx) => { + this.state.colorZones.map((zone, idx) => { let color = zone.strokeStyle; let width = (zone.max - zone.min)*(260/100); let style = { @@ -221,13 +202,24 @@ class EditWidgetColorZonesControl extends React.Component { width: width, height: '40px' } - return ( + return ( + + Edit zone} > + + + ) }) }
+ Edit selected color zone: Change color} > + Remove zone} > + + - this.closeEditModal(data)} widget={this.state.widget} zoneIndex={this.state.selectedIndex} controlId={'strokeStyle'} /> + this.closeColorPickerEditModal(data)} + hexcolor={tempColor} + opacity={1} + disableOpacity={this.props.disableOpacity} + /> ; } } diff --git a/src/widget/edit-widget/edit-widget-control-creator.js b/src/widget/edit-widget/edit-widget-control-creator.js index d2ee3fc..8eb6583 100644 --- a/src/widget/edit-widget/edit-widget-control-creator.js +++ b/src/widget/edit-widget/edit-widget-control-creator.js @@ -74,7 +74,8 @@ export default function CreateControls(widgetType = null, widget = null, session handleChange(e)} direction={'out'}/>, handleChange(e)} />, handleChange(e)} />, - handleChange(e)} /> + handleChange(e)} />, + handleChange(e)} /> ); break; case 'Table': @@ -96,7 +97,7 @@ export default function CreateControls(widgetType = null, widget = null, session handleChange(e)} />, handleChange(e)} direction={'out'}/>, handleChange(e)} />, - handleChange(e)} />, + handleChange(e)} disableOpacity={true}/>, handleChange(e)} /> ); break; diff --git a/src/widget/edit-widget/edit-widget-plot-colors-control.js b/src/widget/edit-widget/edit-widget-plot-colors-control.js index fbef75c..8dc6f9e 100644 --- a/src/widget/edit-widget/edit-widget-plot-colors-control.js +++ b/src/widget/edit-widget/edit-widget-plot-colors-control.js @@ -17,12 +17,9 @@ import React, { Component } from 'react'; import { OverlayTrigger, Tooltip , Button, Form } from 'react-bootstrap'; -import ColorPicker from './color-picker' +import ColorPicker from '../../common/color-picker' import Icon from "../../common/icon"; -import { scaleOrdinal } from "d3-scale"; -import { schemeCategory10 } from "d3-scale-chromatic"; - -// schemeCategory20 no longer available in d3 +import {schemeCategory10} from "d3-scale-chromatic"; class EditWidgetPlotColorsControl extends Component { @@ -30,51 +27,78 @@ class EditWidgetPlotColorsControl extends Component { super(props); this.state = { - widget: {}, showColorPicker: false, originalColor: null, - selectedIndex: null + selectedIndex: null, + lineColors: [], + signalIDs: [] }; } - static getDerivedStateFromProps(props, state){ - - let widget = props.widget; - if(widget.customProperties.lineColors === undefined || widget.customProperties.lineColors === null){ - // for backwards compatibility with old plots - widget.customProperties.lineColors = [] - - const newLineColor = scaleOrdinal(schemeCategory10); - for (let signalID of widget.signalIDs){ - widget.customProperties.lineColors.push(newLineColor(signalID)) - } - } - + static getDerivedStateFromProps(props, state) { return { - widget: widget + lineColors: props.widget.customProperties.lineColors, + signalIDs: props.widget.signalIDs, }; } -//same here + componentDidUpdate(prevProps: Readonly

, prevState: Readonly, snapshot: SS) { - closeEditModal = (data) => { + let lineColorsChanged = false; + + if (JSON.stringify(this.state.signalIDs) !== JSON.stringify(prevState.signalIDs)){ + // if there was a change to the signal IDs + let tempLineColors = JSON.parse(JSON.stringify(this.state.lineColors)); + let oldNoSignals = tempLineColors.length + + if (this.state.signalIDs.length > prevState.signalIDs.length){ + // more signals than before + let diff = this.state.signalIDs.length - prevState.signalIDs.length + for (let i = 0; i { this.setState({showColorPicker: false}) + let tempLineColors = JSON.parse(JSON.stringify(this.state.lineColors)); if(typeof data === 'undefined'){ - - let temp = this.state.widget; - temp.customProperties.lineColors[this.state.selectedIndex] = this.state.originalColor; - this.setState({ widget: temp }); + // Color picker canceled + tempLineColors[this.state.selectedIndex] = this.state.originalColor; + this.setState({lineColors: tempLineColors}) + } else { + // color picker with result data {hexcolor, opacity} + tempLineColors[this.state.selectedIndex] = data.hexcolor + this.setState({lineColors: tempLineColors}) + this.props.handleChange({target: { id: this.props.controlId, value: tempLineColors} }) } } editLineColor = (index) => { if(this.state.selectedIndex !== index){ - let color = this.state.widget.customProperties.lineColors[index]; - this.setState({selectedIndex: index, showColorPicker: true, originalColor: color}); - } - else{ - this.setState({selectedIndex: null}); - } + let color = typeof this.state.lineColors[index] === "undefined" ? schemeCategory10[index % 10] : this.state.lineColors[index]; + this.setState({selectedIndex: index, showColorPicker: true, originalColor: color}); + } + else{ + this.setState({selectedIndex: null}); + } } render() { @@ -84,15 +108,15 @@ class EditWidgetPlotColorsControl extends Component { Line Colors

{ - this.state.widget.signalIDs.map((signalID, idx) => { - let color = this.state.widget.customProperties.lineColors[signalID]; - let width = 260 / this.state.widget.signalIDs.length; + this.props.widget.signalIDs.map((signalID, idx) => { + + let color = typeof this.state.lineColors[idx] === "undefined" ? schemeCategory10[idx % 10] : this.state.lineColors[idx]; + let width = 260 / this.props.widget.signalIDs.length; let style = { backgroundColor: color, width: width, height: '40px' } - let signal = this.props.signals.find(signal => signal.id === signalID); return this.editLineColor(signalID)} + onClick={i => this.editLineColor(idx)} > + ; }) }
- this.closeEditModal(data)} widget={this.state.widget} lineIndex={this.state.selectedIndex} controlId={'lineColor'} disableOpacity={true}/> + this.closeColorPickerEditModal(data)} + hexcolor={this.state.lineColors[this.state.selectedIndex]} + opacity={1} + disableOpacity={true} + /> ) diff --git a/src/widget/widget-factory.js b/src/widget/widget-factory.js index e3f4c2c..8a9baa8 100644 --- a/src/widget/widget-factory.js +++ b/src/widget/widget-factory.js @@ -90,6 +90,7 @@ class WidgetFactory { widget.customProperties.yMax = 10; widget.customProperties.yUseMinMax = false; widget.customProperties.lineColors = []; + widget.customProperties.showUnit = false; break; case 'Table': widget.minWidth = 200; @@ -150,7 +151,7 @@ class WidgetFactory { widget.customProperties.rangeMin = 0; widget.customProperties.rangeMax = 200; widget.customProperties.rangeUseMinMax = true; - widget.customProperties.showUnit = true; + widget.customProperties.showUnit = false; widget.customProperties.continous_update = false; widget.customProperties.default_value = '0'; widget.customProperties.value = ''; diff --git a/src/widget/widget-plot/plot-legend.js b/src/widget/widget-plot/plot-legend.js index 00d1b77..096c67b 100644 --- a/src/widget/widget-plot/plot-legend.js +++ b/src/widget/widget-plot/plot-legend.js @@ -24,26 +24,15 @@ function Legend(props){ const signal = props.sig; const hasScalingFactor = (signal.scalingFactor !== 1); - const newLineColor = scaleOrdinal(schemeCategory10); + let color = typeof props.lineColor === "undefined" ? schemeCategory10[props.index % 10] : props.lineColor; - let color = typeof props.lineColor === "undefined" ? newLineColor(signal.id) : props.lineColor; - - if(hasScalingFactor){ - return ( -
  • - {signal.name} - {signal.unit} - {signal.scalingFactor} -
  • - ) - } else { - return ( -
  • - {signal.name} - {signal.unit} -
  • - ) - } + return ( +
  • + {signal.name} + {props.showUnit ? {signal.unit} : <> } + {hasScalingFactor ? {signal.scalingFactor} : <>} +
  • + ) } class PlotLegend extends React.Component { @@ -52,11 +41,11 @@ class PlotLegend extends React.Component { return
      { this.props.lineColors !== undefined && this.props.lineColors != null ? ( - this.props.signals.map( signal => - + this.props.signals.map( (signal, idx) => + )) : ( - this.props.signals.map( signal => - + this.props.signals.map( (signal, idx) => + )) }
    diff --git a/src/widget/widget-plot/plot.js b/src/widget/widget-plot/plot.js index 17e61fc..0c92bc1 100644 --- a/src/widget/widget-plot/plot.js +++ b/src/widget/widget-plot/plot.js @@ -199,7 +199,6 @@ class Plot extends React.Component { // generate paths from data const sparkLine = line().x(p => xScale(p.x)).y(p => yScale(p.y)); - const newLineColor = scaleOrdinal(schemeCategory10); const lines = this.state.data.map((values, index) => { let signalID = this.props.signalIDs[index]; @@ -208,10 +207,10 @@ class Plot extends React.Component { this.props.lineColors = [] // for backwards compatibility } - if (typeof this.props.lineColors[signalID] === "undefined") { - this.props.lineColors[signalID] = newLineColor(signalID); + if (typeof this.props.lineColors[index] === "undefined") { + this.props.lineColors[index] = schemeCategory10[index % 10]; } - return + return }); this.setState({ lines, xAxis, yAxis }); diff --git a/src/widget/widget.js b/src/widget/widget.js index 1523545..8e093c9 100644 --- a/src/widget/widget.js +++ b/src/widget/widget.js @@ -94,18 +94,20 @@ class Widget extends React.Component { }; } - inputDataChanged(widget, data, controlID, controlValue) { + inputDataChanged(widget, data, controlID, controlValue, isFinalChange) { // 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] = controlValue; - AppDispatcher.dispatch({ - type: 'widgets/start-edit', - token: this.state.sessionToken, - data: updatedWidget - }); + if(isFinalChange) { + AppDispatcher.dispatch({ + type: 'widgets/start-edit', + token: this.state.sessionToken, + data: updatedWidget + }); + } } // The following assumes that a widget modifies/ uses exactly one signal @@ -185,21 +187,21 @@ class Widget extends React.Component { return this.inputDataChanged(widget, value, controlID, controlValue)} + onInputChanged={(value, controlID, controlValue, isFinalChange) => this.inputDataChanged(widget, value, controlID, controlValue, isFinalChange)} signals={this.state.signals} /> } else if (widget.type === 'NumberInput') { return this.inputDataChanged(widget, value, controlID, controlValue)} + onInputChanged={(value, controlID, controlValue, isFinalChange) => this.inputDataChanged(widget, value, controlID, controlValue, isFinalChange)} signals={this.state.signals} /> } else if (widget.type === 'Slider') { return this.inputDataChanged(widget, value, controlID, controlValue)} + onInputChanged={(value, controlID, controlValue, isFinalChange) => this.inputDataChanged(widget, value, controlID, controlValue, isFinalChange)} signals={this.state.signals} /> } else if (widget.type === 'Gauge') { diff --git a/src/widget/widgets/button.js b/src/widget/widgets/button.js index 897e1d9..f68144f 100644 --- a/src/widget/widgets/button.js +++ b/src/widget/widgets/button.js @@ -54,14 +54,14 @@ class WidgetButton extends Component { valueChanged(newValue, pressed) { if (this.props.onInputChanged) { - this.props.onInputChanged(newValue, 'pressed', pressed); + this.props.onInputChanged(newValue, 'pressed', pressed, true); } } render() { const buttonStyle = { - backgroundColor: this.props.widget.customProperties.background_color, + backgroundColor: this.props.widget.customProperties.background_color, borderColor: this.props.widget.customProperties.border_color, color: this.props.widget.customProperties.font_color, opacity: this.props.widget.customProperties.background_color_opacity diff --git a/src/widget/widgets/input.js b/src/widget/widgets/input.js index 21c0421..8d99564 100644 --- a/src/widget/widgets/input.js +++ b/src/widget/widgets/input.js @@ -73,7 +73,7 @@ class WidgetInput extends Component { valueChanged(newValue) { if (this.props.onInputChanged) { - this.props.onInputChanged(Number(newValue), 'value', Number(newValue)); + this.props.onInputChanged(Number(newValue), 'value', Number(newValue), true); } } diff --git a/src/widget/widgets/plot.js b/src/widget/widgets/plot.js index 1479081..a9303ab 100644 --- a/src/widget/widgets/plot.js +++ b/src/widget/widgets/plot.js @@ -107,7 +107,10 @@ class WidgetPlot extends React.Component { signalIDs={this.props.widget.signalIDs} />
    - +
    ; } } diff --git a/src/widget/widgets/slider.js b/src/widget/widgets/slider.js index 23360d4..82b6926 100644 --- a/src/widget/widgets/slider.js +++ b/src/widget/widgets/slider.js @@ -79,14 +79,14 @@ class WidgetSlider extends Component { valueIsChanging(newValue) { this.props.widget.customProperties.value = newValue; if (this.props.widget.customProperties.continous_update) - this.valueChanged(newValue); + this.valueChanged(newValue, false); this.setState({ value: newValue }); } - valueChanged(newValue) { + valueChanged(newValue, isFinalChange) { if (this.props.onInputChanged) { - this.props.onInputChanged(newValue, 'value', newValue); + this.props.onInputChanged(newValue, 'value', newValue, isFinalChange); } } @@ -95,7 +95,7 @@ class WidgetSlider extends Component { let isVertical = this.props.widget.customProperties.orientation === WidgetSlider.OrientationTypes.VERTICAL.value; let fields = { name: this.props.widget.name, - control: this.valueIsChanging(v) } onAfterChange={ (v) => this.valueChanged(v) }/>, + control: this.valueIsChanging(v) } onAfterChange={ (v) => this.valueChanged(v, true) }/>, value: { format('.2f')(Number.parseFloat(this.state.value)) }, unit: { this.state.unit } }