1
0
Fork 0
mirror of https://git.rwth-aachen.de/acs/public/villas/web/ synced 2025-03-09 00:00:01 +01:00

Merge branch 'master' into feature-use-json-schema

This commit is contained in:
irismarie 2021-05-07 11:27:44 +02:00
commit 9e4859786b
32 changed files with 935 additions and 807 deletions

3
package-lock.json generated
View file

@ -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"
}

110
src/common/color-picker.js Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
******************************************************************************/
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<P>, prevState: Readonly<S>, 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 <Dialog
show={this.props.show}
title='Color Picker'
buttonTitle='Save'
onClose={(c) => this.onClose(c)}
valid={true}
>
<Form>
<SketchPicker
color={this.state.rgbColor}
disableAlpha={this.props.disableOpacity}
onChangeComplete={ this.handleChangeComplete }
width={300}
/>
</Form>
</Dialog>;
}
}
ColorPicker.propTypes = {
onClose: PropTypes.func,
disableOpacity: PropTypes.bool,
show: PropTypes.bool,
hexcolor: PropTypes.string,
opacity: PropTypes.number
}
export default ColorPicker;

View file

@ -186,17 +186,17 @@ class Dashboard extends Component {
componentDidUpdate(prevProps: Readonly<P>, prevState: Readonly<S>, 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({

View file

@ -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));

View file

@ -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);

View file

@ -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 <h1>Loading Infrastructure Component...</h1>;
}
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 <div className='section'>
<h1>{this.state.ic.name}</h1>
<Container>
<Row>
<Col>
<Table striped size="sm">
<tbody>
<tr>
<td>Name</td>
<td>{this.state.ic.name}</td>
</tr>
<tr>
<td>Description</td>
<td>{this.state.ic.description}</td>
</tr>
<tr>
<td>UUID</td>
<td>{this.state.ic.uuid}</td>
</tr>
<tr>
<td>State</td>
<td>{this.state.ic.state}</td>
</tr>
<tr>
<td>Category</td>
<td>{this.state.ic.category}</td>
</tr>
<tr>
<td>Type</td>
<td>{this.state.ic.type}</td>
</tr>
<tr>
<td>Uptime</td>
<td>{moment.duration(this.state.ic.uptime, "seconds").humanize()}</td>
</tr>
<tr>
<td>Location</td>
<td>{this.state.ic.location}</td>
</tr>
<tr>
<td>Websocket URL</td>
<td>{this.state.ic.websocketurl}</td>
</tr>
<tr>
<td>API URL</td>
<td>{this.state.ic.apiurl}</td>
</tr>
<tr>
<td>Start parameter schema</td>
<td>
{this.isJSON(this.state.ic.startparameterschema) ?
<ReactJson
src={this.state.ic.startparameterschema}
name={false}
displayDataTypes={false}
displayObjectSize={false}
enableClipboard={false}
collapsed={0}
/> : <div>No Start parameter schema JSON available.</div>}
</td>
</tr>
</tbody>
</Table>
</Col>
<Col>
{this.state.category ==="gateway" && this.state.ic.type === "villas-node" ?
<>
<div className='section-buttons-group-right'>
<IconButton
childKey={0}
tooltip='Download Graph'
onClick={() => this.downloadGraph(graphURL)}
icon='download'
buttonStyle={buttonStyle}
iconStyle={iconStyle}
/>
</div>
<hr/>
<b>Graph:</b>
<div>
<img alt={"Graph image download failed and/or incorrect image API URL"} src={graphURL} />
</div>
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" ?
<div>
<hr/>
<b>Controls:</b>
<div className='solid-button'>
<Button variant='secondary' style={{ margin: '5px' }} size='lg'
onClick={() => this.setState({ confirmCommand: true, command: 'restart' })}>Restart</Button>
<Button variant='secondary' style={{ margin: '5px' }} size='lg' onClick={() => this.setState({
confirmCommand: true,
command: 'shutdown'
})}>Shutdown</Button>
</div>
</div>
: <div />
}
<ConfirmCommand
show={this.state.confirmCommand}
command={this.state.command}
name={this.state.ic.name}
onClose={c => this.confirmCommand(c)}
/>
</>
: <div />}
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" ?
<>
<div className='section-buttons-group-right'>
<IconButton
childKey={1}
tooltip='Refresh'
onClick={() => this.refresh()}
icon='sync-alt'
buttonStyle={buttonStyle}
iconStyle={iconStyle}
/>
</div>
<hr/>
<b>Raw Status</b>
{this.state.ic.statusupdateraw !== null && this.isJSON(this.state.ic.statusupdateraw) ?
<ReactJson
src={this.state.ic.statusupdateraw}
name={false}
displayDataTypes={false}
displayObjectSize={false}
enableClipboard={false}
collapsed={1}
/> : <div>No valid JSON raw data available.</div>}
</>
:
<div />}
</Col>
</Row>
{this.state.category ==="gateway" && this.state.ic.type === "villas-node" ?
<>
<div className='section-buttons-group-right'>
<IconButton
childKey={2}
tooltip='Refresh'
onClick={() => this.refresh()}
icon='sync-alt'
buttonStyle={buttonStyle}
iconStyle={iconStyle}
/>
</div>
<Row>
render() {
if (this.state.ic === undefined) {
return <h1>Loading Infrastructure Component...</h1>;
}
let graphURL = ""
if (this.state.ic.apiurl !== "") {
graphURL = this.state.ic.apiurl + "/graph.svg"
}
return <div className='section'>
<h1>{this.state.ic.name}</h1>
<Container>
<Row>
<Col>
<Table striped size="sm">
<tbody>
<tr>
<td>Name</td>
<td>{this.state.ic.name}</td>
</tr>
<tr>
<td>Description</td>
<td>{this.state.ic.description}</td>
</tr>
<tr>
<td>UUID</td>
<td>{this.state.ic.uuid}</td>
</tr>
<tr>
<td>State</td>
<td>{this.state.ic.state}</td>
</tr>
<tr>
<td>Category</td>
<td>{this.state.ic.category}</td>
</tr>
<tr>
<td>Type</td>
<td>{this.state.ic.type}</td>
</tr>
<tr>
<td>Uptime</td>
<td>{moment.duration(this.state.ic.uptime, "seconds").humanize()}</td>
</tr>
<tr>
<td>Location</td>
<td>{this.state.ic.location}</td>
</tr>
<tr>
<td>Websocket URL</td>
<td>{this.state.ic.websocketurl}</td>
</tr>
<tr>
<td>API URL</td>
<td>{this.state.ic.apiurl}</td>
</tr>
<tr>
<td>Start parameter schema</td>
<td>
<ReactJson
src={this.state.ic.startparameterschema}
name={false}
displayDataTypes={false}
displayObjectSize={false}
enableClipboard={false}
collapsed={0}
/>
</td>
</tr>
</tbody>
</Table>
</Col>
<Col><b>Raw Status</b>
{this.isJSON(this.state.ic.statusupdateraw) ?
<ReactJson
src={this.state.ic.statusupdateraw}
name={false}
displayDataTypes={false}
displayObjectSize={false}
enableClipboard={false}
collapsed={0}
/> : <div>No valid JSON raw data available.</div>}
{this.state.ic.type === "villas-node" ?
<>
<div className='section-buttons-group-right'>
<Button style={{ margin: '5px' }} size='sm' onClick={() => this.downloadGraph(graphURL)}><Icon
icon="download" /></Button>
</div>
<hr></hr>
<b>Graph:</b>
<div>
<img alt={"Graph image download failed and/or incorrect image API URL"} src={graphURL} />
</div>
{this.state.currentUser.role === "Admin" ?
<div>
<hr></hr>
<b>Controls:</b>
<div className='solid-button'>
<Button variant='secondary' style={{ margin: '5px' }} size='lg'
onClick={() => this.setState({ confirmCommand: true, command: 'restart' })}>Restart</Button>
<Button variant='secondary' style={{ margin: '5px' }} size='lg' onClick={() => this.setState({
confirmCommand: true,
command: 'shutdown'
})}>Shutdown</Button>
</div>
</div>
: <div />
}
<ConfirmCommand show={this.state.confirmCommand} command={this.state.command} name={this.state.ic.name}
onClose={c => this.confirmCommand(c)} />
</>
: <div />}
</Col>
</Row>
</Container>
</div>;
}
<Col>
<b>Raw Status</b>
{this.state.ic.statusupdateraw !== null && this.isJSON(this.state.ic.statusupdateraw) ?
<ReactJson
src={this.state.ic.statusupdateraw}
name={false}
displayDataTypes={false}
displayObjectSize={false}
enableClipboard={false}
collapsed={1}
/> : <div>No valid JSON raw data available.</div>}
</Col>
<Col>
<b>Raw Config</b>
{this.state.ic.statusupdateraw && this.isJSON(this.state.ic.statusupdateraw["config"]) ?
<ReactJson
src={this.state.ic.statusupdateraw["config"]}
name={false}
displayDataTypes={false}
displayObjectSize={false}
enableClipboard={false}
collapsed={1}
/> : <div>No valid config JSON raw data available.</div>}
</Col>
<Col>
<b>Raw Statistics</b>
{this.state.ic.statusupdateraw && this.isJSON(this.state.ic.statusupdateraw["statistics"]) ?
<ReactJson
src={this.state.ic.statusupdateraw["statistics"]}
name={false}
displayDataTypes={false}
displayObjectSize={false}
enableClipboard={false}
collapsed={1}
/> : <div>No valid statistics JSON raw data available.</div>}
</Col>
</Row>
</>: <div />}
</Container>
</div>;
}
}
let fluxContainerConverter = require('../common/FluxContainerConverter');
export default FluxContainer.create(fluxContainerConverter.convert(InfrastructureComponent), { withProps: true });
export default FluxContainer.create(fluxContainerConverter.convert(InfrastructureComponent), { withProps: true });

View file

@ -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){

View file

@ -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 <Icon icon='check' />
} 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',

View file

@ -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() {

View file

@ -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) {

View file

@ -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);

View file

@ -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',

View file

@ -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 (
<Dialog show={this.props.show} title="Edit user" buttonTitle="Save" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
<Dialog show={this.props.show} title="Edit user" buttonTitle="Save" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={true}>
<Form>
<Form.Group as={Col} controlId="username">
<Form.Label>Username</Form.Label>
<Form.Control type="text" value={this.state.username} onChange={(e) => this.handleChange(e)} autocomplete="username" />
<Form.Control type="text" placeholder={this.props.user.username} value={this.state.username} onChange={(e) => this.handleChange(e)} autoComplete="username" />
<Form.Control.Feedback />
</Form.Group>
<Form.Group as={Col} controlId="mail">
<Form.Label>E-mail</Form.Label>
<Form.Control type="text" value={this.state.mail} onChange={(e) => this.handleChange(e)} autocomplete="email" />
<Form.Control type="text" placeholder={this.props.user.mail} value={this.state.mail} onChange={(e) => this.handleChange(e)} autoComplete="email" />
</Form.Group>
<Form.Group as={Col} controlId="oldPassword">
<Form.Label>Old Password</Form.Label>
<Form.Control type="password" placeholder="Enter current password" value={this.state.oldPassword} onChange={(e) => this.handleChange(e)} autocomplete="current-password" />
<Form.Control type="password" placeholder="Enter current password" value={this.state.oldPassword} onChange={(e) => this.handleChange(e)} autoComplete="current-password" />
</Form.Group>
<Form.Group as={Col} controlId="password">
<Form.Label>New Password</Form.Label>
<Form.Control type="password" placeholder="Enter new password" value={this.state.password} onChange={(e) => this.handleChange(e)} autocomplete="new-password" />
<Form.Control type="password" placeholder="Enter new password" value={this.state.password} onChange={(e) => this.handleChange(e)} autoComplete="new-password" />
</Form.Group>
<Form.Group as={Col} controlId="confirmPassword">
<Form.Label>Confirm New Password</Form.Label>
<Form.Control type="password" placeholder="Repeat new password" value={this.state.confirmPassword} onChange={(e) => this.handleChange(e)} autocomplete="new-password" />
<Form.Control type="password" placeholder="Repeat new password" value={this.state.confirmPassword} onChange={(e) => this.handleChange(e)} autoComplete="new-password" />
</Form.Group>
</Form>
</Dialog>

View file

@ -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 (
<Dialog show={this.props.show} title="Edit user" buttonTitle="Save" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
<Dialog show={this.props.show} title="Edit user" buttonTitle="Save" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={true}>
<Form>
<Form.Group as={Col} controlId="username">
<Form.Label>Username</Form.Label>
<Form.Control type="text" value={this.state.username} onChange={(e) => this.handleChange(e)} />
<Form.Control type="text" placeholder={this.props.user.username} value={this.state.username} onChange={(e) => this.handleChange(e)} />
<Form.Control.Feedback />
</Form.Group>
<Form.Group as={Col} controlId="mail">
<Form.Label>E-mail</Form.Label>
<Form.Control type="text" value={this.state.mail} onChange={(e) => this.handleChange(e)} />
<Form.Control type="text" placeholder={this.props.user.mail} value={this.state.mail} onChange={(e) => this.handleChange(e)} />
</Form.Group>
<Form.Group as={Col} controlId="oldPassword">
<Form.Label>Admin Password</Form.Label>
<Form.Control type="password" placeholder="Enter admin password" value={this.state.oldPassword} onChange={(e) => this.handleChange(e)} />
</Form.Group>
<Form.Group as={Col} controlId="password">
<Form.Label>Password</Form.Label>
<Form.Label>New User Password</Form.Label>
<Form.Control type="password" placeholder="Enter password" value={this.state.password} onChange={(e) => this.handleChange(e)} />
</Form.Group>
<Form.Group as={Col} controlId="confirmPassword">
<Form.Label>Confirm New Password</Form.Label>
<Form.Label>Confirm new Password</Form.Label>
<Form.Control type="password" placeholder="Enter password" value={this.state.confirmPassword} onChange={(e) => this.handleChange(e)} />
</Form.Group>
<Form.Group as={Col} controlId="role">
<Form.Label>Role</Form.Label>
<Form.Control as="select" placeholder="Select role" value={this.state.role} onChange={(e) => this.handleChange(e)}>
<Form.Control as="select" placeholder={this.props.user.role} value={this.state.role} onChange={(e) => this.handleChange(e)}>
<option key='1' value='Admin'>Administrator</option>
<option key='2' value='User'>User</option>
<option key='3' value='Guest'>Guest</option>

View file

@ -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) {

View file

@ -15,100 +15,74 @@
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
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 (
<div>
<h1>Account</h1>
<h1>Account
<span className='icon-button'>
<IconButton
childKey={0}
tooltip='Edit Account'
onClick={() => this.setState({ editModal: true })}
icon='edit'
buttonStyle={buttonStyle}
iconStyle={iconStyle}
/>
</span>
</h1>
{user ?
<>
@ -116,30 +90,28 @@ class User extends Component {
<Form.Group as={Row} controlId="username">
<Form.Label column sm={2}>Username</Form.Label>
<Col sm={10}>
<Form.Control plaintext readOnly defaultValue={user.username} />
<Form.Control plaintext readOnly value={user.username} />
</Col>
</Form.Group>
<Form.Group as={Row} controlId="mail">
<Form.Label column sm={2}>E-mail</Form.Label>
<Col sm={10}>
<Form.Control plaintext readOnly defaultValue={user.mail} type="email" />
<Form.Control plaintext readOnly value={user.mail} type="email" />
</Col>
</Form.Group>
<Form.Group as={Row} controlId="role">
<Form.Label column sm={2}>Role</Form.Label>
<Col sm={10}>
<Form.Control plaintext readOnly defaultValue={user.role} />
<Form.Control plaintext readOnly value={user.role} />
</Col>
</Form.Group>
<Form.Group as={Row} controlId="formBasicEmail">
<Form.Label column sm={2}>Created at</Form.Label>
<Col sm={10}>
<Form.Control plaintext readOnly defaultValue={user.createdAt} />
<Form.Control plaintext readOnly value={user.createdAt} />
</Col>
</Form.Group>
<Button variant="primary" onClick={() => this.setState({ editModal: true })}>
<Icon icon='edit' /> Edit
</Button>
</Form>
<EditOwnUserDialog

View file

@ -19,10 +19,13 @@ import ArrayStore from '../common/array-store';
import UsersDataManager from './users-data-manager';
import NotificationsDataManager from '../common/data-managers/notifications-data-manager';
import NotificationsFactory from "../common/data-managers/notifications-factory";
import AppDispatcher from "../common/app-dispatcher";
class UsersStore extends ArrayStore {
constructor() {
super('users', UsersDataManager);
this.currentUser = null;
}
reduce(state, action) {
@ -46,6 +49,33 @@ class UsersStore extends ArrayStore {
}
return super.reduce(state, action);
case this.type + '/start-edit':
// save current user on user edit
this.currentUser = action.currentUser;
return super.reduce(state, action)
case this.type + '/edited':
let currentUserID = this.currentUser.id;
// check if own user was updated
for (let u of [action.data]){
if (u.id === currentUserID){
console.log("Detected update of current user, updating login store")
AppDispatcher.dispatch({
type: 'users/logged-in',
token: null,
currentUser: u,
});
break;
}
}
return super.reduce(state, action)
default:
return super.reduce(state, action);
}

View file

@ -17,11 +17,10 @@
import React, { Component } from 'react';
import { Container } from 'flux/utils';
import AppDispatcher from '../common/app-dispatcher';
import UsersStore from './users-store';
import LoginStore from './login-store';
import ScenarioStore from '../scenario/scenario-store';
import Icon from '../common/icon';
import IconButton from '../common/icon-button';
import { Dropdown, DropdownButton } from 'react-bootstrap';
@ -30,14 +29,11 @@ import TableColumn from '../common/table-column';
import NewUserDialog from './new-user';
import EditUserDialog from './edit-user';
import UsersToScenarioDialog from './users-to-scenario';
import DeleteDialog from '../common/dialogs/delete-dialog';
import NotificationsDataManager from "../common/data-managers/notifications-data-manager";
import NotificationsFactory from "../common/data-managers/notifications-factory";
class Users extends Component {
static getStores() {
return [UsersStore, ScenarioStore];
return [UsersStore, ScenarioStore, LoginStore];
}
static calculateState(prevState, props) {
@ -106,15 +102,12 @@ class Users extends Component {
this.setState({ editModal: false });
if (data) {
if (data.password === data.confirmPassword) {
AppDispatcher.dispatch({
type: 'users/start-edit',
data: data,
token: this.state.token
});
} else {
NotificationsDataManager.addNotification(NotificationsFactory.UPDATE_ERROR("New password not correctly confirmed"))
}
AppDispatcher.dispatch({
type: 'users/start-edit',
data: data,
token: this.state.token,
currentUser: this.state.currentUser,
});
}
}

View file

@ -1,133 +0,0 @@
/**
* This file is part of VILLASweb.
*
* VILLASweb is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* VILLASweb is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
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 <Dialog
show={this.props.show}
title='Color Picker'
buttonTitle='Save'
onClose={(c) => this.onClose(c)}
valid={true}
>
<Form>
<SketchPicker
color={rgbColor}
disableAlpha={this.props.disableOpacity}
onChangeComplete={ this.handleChangeComplete }
width={300}
/>
</Form>
</Dialog>;
}
}
export default ColorPicker;

View file

@ -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
};
}

View file

@ -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 ( <Form.Row>
<Form.Group as={Col}>
<Form.Label>{this.props.label}</Form.Label>
@ -100,7 +94,13 @@ class EditWidgetColorControl extends Component {
</Button>
</OverlayTrigger>
</div>
<ColorPicker show={this.state.showColorPicker} onClose={(data) => this.closeEditModal(data)} widget={this.state.widget} controlId={this.props.controlId} disableOpacity={this.props.disableOpacity}/>
<ColorPicker
show={this.state.showColorPicker}
onClose={(data) => this.closeColorPickerEditModal(data)}
hexcolor={this.state.color}
opacity={this.state.opacity}
disableOpacity={this.props.disableOpacity}
/>
</Form.Group>
</Form.Row>
);

View file

@ -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 {
</span>
<div>
{
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 (<Button
style={style} key={idx} onClick={i => this.editColorZone(idx)} disabled={!this.props.widget.customProperties.colorZones}><Icon icon="pen" /></Button>
return (
<span>
<OverlayTrigger key={idx} placement={'right'} overlay={<Tooltip id={`tooltip-${"color-edit"}`}>Edit zone</Tooltip>} >
<Button
style={style}
key={idx}
onClick={i => this.editColorZone(idx)}
disabled={!this.props.widget.customProperties.colorZones}>
<Icon icon="pen" />
</Button>
</OverlayTrigger>
</span>
)
})
}
</div>
<Collapse isOpened={collapse}>
<Form.Label>Edit selected color zone:</Form.Label>
<OverlayTrigger key={0} placement={'right'} overlay={<Tooltip id={`tooltip-${"color"}`}>Change color</Tooltip>} >
<Button key={0} style={pickerStyle} onClick={this.openColorPicker.bind(this)} >
<Icon icon="paint-brush"/>
@ -258,10 +250,22 @@ class EditWidgetColorZonesControl extends React.Component {
</tbody>
</Table>
<span className='icon-button'>
<Button variant='light' onClick={this.removeZones} ><Icon style={iconStyle} classname='icon-color' icon="trash-alt" /></Button>
<OverlayTrigger key={1} placement={'right'} overlay={<Tooltip id={`tooltip-${"color-delete"}`}>Remove zone</Tooltip>} >
<Button
variant='light'
onClick={this.removeZones} >
<Icon style={iconStyle} classname='icon-color' icon="trash-alt" />
</Button>
</OverlayTrigger>
</span>
</Collapse>
<ColorPicker show={this.state.showColorPicker} onClose={(data) => this.closeEditModal(data)} widget={this.state.widget} zoneIndex={this.state.selectedIndex} controlId={'strokeStyle'} />
<ColorPicker
show={this.state.showColorPicker}
onClose={(data) => this.closeColorPickerEditModal(data)}
hexcolor={tempColor}
opacity={1}
disableOpacity={this.props.disableOpacity}
/>
</Form.Group>;
}
}

View file

@ -74,7 +74,8 @@ export default function CreateControls(widgetType = null, widget = null, session
<EditWidgetSignalsControl key={1} widget={widget} controlId={'signalIDs'} signals={signals} handleChange={(e) => handleChange(e)} direction={'out'}/>,
<EditWidgetPlotColorsControl key={2} widget={widget} controlId={'customProperties.lineColors'} signals={signals} handleChange={(e) => handleChange(e)} />,
<EditWidgetTextControl key={3} widget={widget} controlId={'customProperties.ylabel'} label={'Y-Axis name'} placeholder={'Enter a name for the y-axis'} handleChange={(e) => handleChange(e)} />,
<EditWidgetMinMaxControl key={4} widget={widget} controlId="customProperties.y" handleChange={e => handleChange(e)} />
<EditWidgetCheckboxControl key={4} widget={widget} controlId={'customProperties.showUnit'} input text="Show unit" handleChange={e => handleChange(e)} />,
<EditWidgetMinMaxControl key={5} widget={widget} controlId="customProperties.y" handleChange={e => handleChange(e)} />
);
break;
case 'Table':
@ -96,7 +97,7 @@ export default function CreateControls(widgetType = null, widget = null, session
<EditWidgetTextControl key={0} widget={widget} controlId={'name'} label={'Text'} placeholder={'Enter text'} handleChange={e => handleChange(e)} />,
<EditWidgetSignalControl key={1} widget={widget} controlId={'signalIDs'} signals={signals} handleChange={(e) => handleChange(e)} direction={'out'}/>,
<EditWidgetCheckboxControl key={2} widget={widget} controlId="customProperties.colorZones" input text="Show color zones" handleChange={e => handleChange(e)} />,
<EditWidgetColorZonesControl key={3} widget={widget} handleChange={e => handleChange(e)} />,
<EditWidgetColorZonesControl key={3} widget={widget} controlId="customProperties.zones" handleChange={e => handleChange(e)} disableOpacity={true}/>,
<EditWidgetMinMaxControl key={4} widget={widget} controlId="customProperties.value" handleChange={e => handleChange(e)} />
);
break;

View file

@ -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<P>, prevState: Readonly<S>, 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<diff; i++){
tempLineColors.push(schemeCategory10[oldNoSignals+i % 10])
lineColorsChanged = true;
}
} else if (this.state.signalIDs.length < prevState.signalIDs.length){
// less signals than before
let diff = prevState.signalIDs.length - this.state.signalIDs.length
for (let i = 0; i<diff; i++){
tempLineColors.pop()
lineColorsChanged = true;
}
}
this.setState({lineColors: tempLineColors})
if (lineColorsChanged){
this.props.handleChange({target: { id: this.props.controlId, value: tempLineColors} })
}
}
}
closeColorPickerEditModal = (data) => {
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 {
<Form.Label>Line Colors</Form.Label>
<div>
{
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 <OverlayTrigger
@ -103,16 +127,23 @@ class EditWidgetPlotColorsControl extends Component {
<Button
style={style}
key={idx}
onClick={i => this.editLineColor(signalID)}
onClick={i => this.editLineColor(idx)}
>
<Icon icon="pen" />
</Button>
</OverlayTrigger>;
})
}
</div>
<ColorPicker show={this.state.showColorPicker} onClose={(data) => this.closeEditModal(data)} widget={this.state.widget} lineIndex={this.state.selectedIndex} controlId={'lineColor'} disableOpacity={true}/>
<ColorPicker
show={this.state.showColorPicker}
onClose={(data) => this.closeColorPickerEditModal(data)}
hexcolor={this.state.lineColors[this.state.selectedIndex]}
opacity={1}
disableOpacity={true}
/>
</Form.Group>
)

View file

@ -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 = '';

View file

@ -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 (
<li key={signal.id} className="signal-legend" style={{ color: color }}>
<span className="signal-legend-name">{signal.name}</span>
<span style={{ marginLeft: '0.3em' }} className="signal-unit">{signal.unit}</span>
<span style={{ marginLeft: '0.3em' }} className="signal-scale">{signal.scalingFactor}</span>
</li>
)
} else {
return (
<li key={signal.id} className="signal-legend" style={{ color: color }}>
<span className="signal-legend-name">{signal.name}</span>
<span style={{ marginLeft: '0.3em' }} className="signal-unit">{signal.unit}</span>
</li>
)
}
return (
<li key={signal.id} className="signal-legend" style={{ color: color }}>
<span className="signal-legend-name">{signal.name}</span>
{props.showUnit ? <span style={{marginLeft: '0.3em'}} className="signal-unit">{signal.unit}</span> : <></> }
{hasScalingFactor ? <span style={{ marginLeft: '0.3em' }} className="signal-scale">{signal.scalingFactor}</span> : <></>}
</li>
)
}
class PlotLegend extends React.Component {
@ -52,11 +41,11 @@ class PlotLegend extends React.Component {
return <div className="plot-legend">
<ul>
{ this.props.lineColors !== undefined && this.props.lineColors != null ? (
this.props.signals.map( signal =>
<Legend key={signal.id} sig={signal} lineColor={this.props.lineColors[signal.id]}/>
this.props.signals.map( (signal, idx) =>
<Legend key={signal.id} sig={signal} showUnit={this.props.showUnit} index={idx} lineColor={this.props.lineColors[idx]}/>
)) : (
this.props.signals.map( signal =>
<Legend key={signal.id} sig={signal} lineColor={"undefined"}/>
this.props.signals.map( (signal, idx) =>
<Legend key={signal.id} sig={signal} showUnit={this.props.showUnit} index={idx} lineColor={"undefined"}/>
))
}
</ul>

View file

@ -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 <path d={sparkLine(values)} key={index} style={{ fill: 'none', stroke: this.props.lineColors[signalID] }} />
return <path d={sparkLine(values)} key={index} style={{ fill: 'none', stroke: this.props.lineColors[index] }} />
});
this.setState({ lines, xAxis, yAxis });

View file

@ -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 <WidgetButton
widget={widget}
editing={this.props.editing}
onInputChanged={(value, controlID, controlValue) => 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 <WidgetInput
widget={widget}
editing={this.props.editing}
onInputChanged={(value, controlID, controlValue) => 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 <WidgetSlider
widget={widget}
editing={this.props.editing}
onInputChanged={(value, controlID, controlValue) => 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') {

View file

@ -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

View file

@ -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);
}
}

View file

@ -107,7 +107,10 @@ class WidgetPlot extends React.Component {
signalIDs={this.props.widget.signalIDs}
/>
</div>
<PlotLegend signals={this.state.signals} lineColors={this.props.widget.customProperties.lineColors} />
<PlotLegend
signals={this.state.signals}
lineColors={this.props.widget.customProperties.lineColors}
showUnit={this.props.widget.customProperties.showUnit} />
</div>;
}
}

View file

@ -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: <Slider min={ this.props.widget.customProperties.rangeMin } max={ this.props.widget.customProperties.rangeMax } step={ this.props.widget.customProperties.step } value={ this.state.value } disabled={ this.props.editing } vertical={ isVertical } onChange={ (v) => this.valueIsChanging(v) } onAfterChange={ (v) => this.valueChanged(v) }/>,
control: <Slider min={ this.props.widget.customProperties.rangeMin } max={ this.props.widget.customProperties.rangeMax } step={ this.props.widget.customProperties.step } value={ this.state.value } disabled={ this.props.editing } vertical={ isVertical } onChange={ (v) => this.valueIsChanging(v) } onAfterChange={ (v) => this.valueChanged(v, true) }/>,
value: <span>{ format('.2f')(Number.parseFloat(this.state.value)) }</span>,
unit: <span className="signal-unit">{ this.state.unit }</span>
}