mirror of
https://git.rwth-aachen.de/acs/public/villas/web/
synced 2025-03-09 00:00:01 +01:00
merge develop into feature_addUser (conflicts in scenario.js)
This commit is contained in:
commit
3bd2a9eaa3
39 changed files with 906 additions and 689 deletions
16
doc/Requirements.md
Normal file
16
doc/Requirements.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
# Requirements {#web-requirements}
|
||||
|
||||
## Services
|
||||
- NodeJS: Runs VILLASweb frontend
|
||||
- Go: Runs VILLASweb backend
|
||||
- PostgreSQL database (min version 11): Backend database
|
||||
- [swag](https://github.com/swaggo/swag): For automated API documentation creation
|
||||
- NGinX: Webserver and reverse proxy for backends (only for production)
|
||||
- Docker: Container management system
|
||||
|
||||
## Installed on your local computer
|
||||
- NodeJS with npm
|
||||
- Go (at least version 1.11)
|
||||
- [swag](https://github.com/swaggo/swag)
|
||||
- Docker
|
||||
|
79
doc/Structure.md
Normal file
79
doc/Structure.md
Normal file
|
@ -0,0 +1,79 @@
|
|||
# VILLASweb data structure {#web-datastructure}
|
||||
|
||||
This document describes how data (scenarios, infrastructure components, users etc., not only live data) is structured in VILLASweb.
|
||||
|
||||
## Data model
|
||||
|
||||

|
||||
|
||||
VILLASweb features the following data classes:
|
||||
|
||||
- Users
|
||||
- Infrastructure Components
|
||||
- Scenarios
|
||||
* Component Configurations and Signals
|
||||
* Dashboards and Widgets
|
||||
* Files
|
||||
|
||||
### Users
|
||||
- You need a username and a password to authenticate in VILLASweb
|
||||
- There exist three categories of users: Guest, User, and Admin
|
||||
- Guests have only read access and cannot modify anything
|
||||
- Users are normal users, they have access to their scenarios, can see available infrastructure components, and modify their accounts (except for their role)
|
||||
- Admin users have full access to everything, they are the only users that can create new users or change the role of existing users. Only admin users can add or modify infrastructure components.
|
||||
|
||||
### Infrastructure Components
|
||||
- Components of research infrastructure
|
||||
- Category: for example simulator, gateway, amplifier, database, etc.
|
||||
- Type: for example RTDS, OpalRT, VILLASnode, Cassandra
|
||||
- Can only be added/ modified by admin users
|
||||
|
||||
### Scenarios
|
||||
- A collection of component configurations, dashboards, and files for a specific experiment
|
||||
- Users can have access to multiple scenarios
|
||||
- Users can be added to and removed from scenarios
|
||||
|
||||
### Component Configurations and Signals
|
||||
- Configure an infrastructure component for the use in a specific scenario
|
||||
- Input signals: Signals that can be modified in VILLASweb
|
||||
- Output signals: Signals that can be visualized on dashboards of VILLASweb
|
||||
- Parameters: Additional configuration parameters of the infrastructure component
|
||||
- Signals are the actual live data that is displayed or modified through VILLASweb dashboards
|
||||
|
||||
### Dashboards and Widgets
|
||||
- Visualize ongoing experiments in real-time
|
||||
- Interact with ongoing experiments in real-time
|
||||
- Use widgets to design the dashboard according to the needs
|
||||
|
||||
### Files
|
||||
- Files can be added to scenarios optionally
|
||||
- Can be images, model files, CIM xml files
|
||||
- Can be used in widgets or component configurations
|
||||
|
||||
## Setup strategy
|
||||
|
||||
The easiest way to start from scratch is the following (assuming the infrastructure components are already configured by an admin user, see below):
|
||||
|
||||
1. Create a new scenario.
|
||||
2. Create and configure a new component configuration and link it with an available infrastructure component.
|
||||
3. Configure the input and output signals of the component configuration according to the signals provided by the selected infrastructure component. The number of signals and their order (index starting at 1) must match.
|
||||
4. Create a new dashboard and add widgets as desired. Configure the widgets by right-clicking to open the edit menu
|
||||
5. If needed, files can be added to the scenario and used by component configurations or widgets (models, images, CIM-files, etc.)
|
||||
6. For collaboration with other users, users can be added to a scenario
|
||||
|
||||
### Setup of infrastructure components
|
||||
|
||||
In the "Infrastructure Components" menu point admin users can create and edit components to be used in experiments. Normal uses can view the available components, but not edit them.
|
||||
The components are global at any time and are shared among all users of VILLASweb.
|
||||
|
||||
To create a new infrastructure component, you need to provide:
|
||||
- Name
|
||||
- Category (see above for examples)
|
||||
- Type (see above for examples)
|
||||
- Location
|
||||
- Host (network address of the component)
|
||||
|
||||
At the moment, you need to know the input and output signals of the infrastructure component a priori to be able to create compatible component configurations by hand.
|
||||
An auto-detection mechanism for signals is planned for future releases.
|
||||
|
||||
> Hint: At least one infrastructure component is required to receive data in VILLASweb.
|
69
doc/development.md
Normal file
69
doc/development.md
Normal file
|
@ -0,0 +1,69 @@
|
|||
# Development {#web-development}
|
||||
|
||||
- @subpage web-datastructure
|
||||
|
||||
In order to get started with VILLASweb, you might also want to check our our [demo project](https://git.rwth-aachen.de/acs/public/villas/Demo) which is simple to setup using Docker Compose.
|
||||
|
||||
## Frontend
|
||||
|
||||
### Description
|
||||
|
||||
The website itself based on the React JavaScript framework.
|
||||
|
||||
### Required
|
||||
|
||||
- NodeJS with npm
|
||||
|
||||
### Setup
|
||||
|
||||
- `git clone git@git.rwth-aachen.de/acs/public/villas/web.git` to copy the project on your computer
|
||||
- `cd VILLASweb`
|
||||
- `npm install`
|
||||
|
||||
### Running
|
||||
|
||||
- `npm start`
|
||||
|
||||
This runs the development server for the website on your local computer at port 3000.
|
||||
The backend must be running to make the website work.
|
||||
|
||||
## Backend
|
||||
|
||||
### Description
|
||||
|
||||
The backend of VILLASweb uses the programming language Go and a PostgreSQL data base.
|
||||
|
||||
### Required
|
||||
|
||||
- Go (min version 1.11)
|
||||
- Running PostgreSQL data base (min version 11)
|
||||
- [swag](https://github.com/swaggo/swag)
|
||||
|
||||
### Setup and Running
|
||||
|
||||
- `git clone git@git.rwth-aachen.de/acs/public/villas/web-backend-go.git` to copy the project on your computer
|
||||
- `cd VILLASweb-backend-go`
|
||||
- `go mod tidy`
|
||||
- `go run start.go [params]`
|
||||
|
||||
To obtain a list of available parameters use `go run start.go --help`.
|
||||
To run the tests use `go test $(go list ./... ) -p 1` in the top-level folder of the repo.
|
||||
|
||||
Running the backend will only work if the PostgreSQL database is setup properly. Otherwise, you will get error messages.
|
||||
|
||||
### Auto-generate the API documentation
|
||||
|
||||
The documentation of the VILLASweb API in the OpenAPI format can be auto-generated from the source code documentation using the tool swag.
|
||||
To do this run the following in the top-level folder of the repo:
|
||||
|
||||
- `go mod tidy`
|
||||
- `go install github.com/swaggo/swag/cmd/swag`
|
||||
- `swag init -p pascalcase -g "start.go" -o "./doc/api/"`
|
||||
|
||||
The `.yaml` and `.json` files in OpenAPI swagger format are created in the output folder `doc/api`.
|
||||
|
||||
### PostgreSQL database setup
|
||||
|
||||
Please check the [Readme file in the backend repository](https://git.rwth-aachen.de/acs/public/villas/web-backend-go) for some useful hints on the local setup of the PostreSQL database.
|
||||
|
||||
|
|
@ -30,7 +30,6 @@ describe('edit widget control creator', () => {
|
|||
{ args: { widgetType: 'Table' }, result: { controlNumber: 2, controlTypes: [EditWidgetCheckboxControl] } },
|
||||
{ args: { widgetType: 'Image' }, result: { controlNumber: 2, controlTypes: [EditFileWidgetControl, EditWidgetAspectControl] } },
|
||||
{ args: { widgetType: 'Gauge' }, result: { controlNumber: 6, controlTypes: [EditWidgetTextControl, EditWidgetSignalControl, EditWidgetCheckboxControl, EditWidgetColorZonesControl, EditWidgetMinMaxControl] } },
|
||||
{ args: { widgetType: 'PlotTable' }, result: { controlNumber: 5, controlTypes: [EditWidgetSignalsControl, EditWidgetTextControl, EditWidgetTimeControl, EditWidgetMinMaxControl] } },
|
||||
{ args: { widgetType: 'Slider' }, result: { controlNumber: 9, controlTypes: [EditWidgetTextControl, EditWidgetOrientation, EditWidgetSignalControl, EditWidgetCheckboxControl, EditWidgetCheckboxControl, EditWidgetMinMaxControl, EditWidgetNumberControl, EditWidgetNumberControl] } },
|
||||
{ args: { widgetType: 'Button' }, result: { controlNumber: 6, controlTypes: [EditWidgetTextControl, EditWidgetSignalControl, EditWidgetCheckboxControl, EditWidgetNumberControl, EditWidgetNumberControl] } },
|
||||
{ args: { widgetType: 'Box' }, result: { controlNumber: 2, controlTypes: [EditWidgetColorControl, EditWidgetColorControl] } },
|
||||
|
|
|
@ -56,7 +56,7 @@ class Dialog extends React.Component {
|
|||
</Modal.Body>
|
||||
|
||||
<Modal.Footer>
|
||||
<Button onClick={this.cancelModal}>Cancel</Button>
|
||||
{this.props.blendOutCancel? <div></div>: <Button onClick={this.cancelModal}>Cancel</Button>}
|
||||
<Button onClick={this.closeModal} disabled={!this.props.valid}>{this.props.buttonTitle}</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
|
|
|
@ -63,7 +63,7 @@ class CustomTable extends Component {
|
|||
|
||||
let cell = [];
|
||||
if (content != null) {
|
||||
content = content.toString();
|
||||
//content = content.toString();
|
||||
|
||||
// check if cell should be a link
|
||||
const linkKey = child.props.linkKey;
|
||||
|
@ -79,21 +79,24 @@ class CustomTable extends Component {
|
|||
// add label to content
|
||||
const labelKey = child.props.labelKey;
|
||||
if (labelKey && data[labelKey] != null) {
|
||||
var labelContent = data[labelKey];
|
||||
let labelContent = data[labelKey];
|
||||
|
||||
if (child.props.labelModifier) {
|
||||
labelContent = child.props.labelModifier(labelContent, data);
|
||||
}
|
||||
|
||||
let labelStyle = child.props.labelStyle(data[labelKey], data)
|
||||
|
||||
cell.push(<span>
|
||||
|
||||
<FormLabel column={false} classes={child.props.labelStyle(data[labelKey], data)}>
|
||||
{labelContent.toString()}
|
||||
<FormLabel column={false} className={labelStyle}>
|
||||
{labelContent}
|
||||
</FormLabel>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if (child.props.dataIndex) {
|
||||
cell.push(index);
|
||||
}
|
||||
|
|
|
@ -65,11 +65,18 @@ class DashboardButtonGroup extends React.Component {
|
|||
);
|
||||
}
|
||||
|
||||
buttons.push(
|
||||
<Button key={key++} onClick={this.props.onEditFiles} style={buttonStyle}>
|
||||
<Icon icon="file" /> Edit Files
|
||||
</Button>
|
||||
);
|
||||
|
||||
buttons.push(
|
||||
<Button key={key++} onClick={this.props.onEdit} style={buttonStyle}>
|
||||
<Icon icon="pen" /> Edit Layout
|
||||
</Button>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
return <div className='section-buttons-group-right'>
|
||||
|
|
|
@ -22,6 +22,7 @@ import classNames from 'classnames';
|
|||
|
||||
import Widget from '../widget/widget';
|
||||
import EditWidget from '../widget/edit-widget/edit-widget';
|
||||
import EditFiles from '../file/edit-files'
|
||||
import WidgetContextMenu from '../widget/widget-context-menu';
|
||||
import WidgetToolbox from '../widget/widget-toolbox';
|
||||
import WidgetArea from '../widget/widget-area';
|
||||
|
@ -121,10 +122,12 @@ class Dashboard extends Component {
|
|||
paused: prevState.paused || false,
|
||||
|
||||
editModal: false,
|
||||
filesEditModal: prevState.filesEditModal || false,
|
||||
filesEditSaveState: prevState.filesEditSaveState || [],
|
||||
modalData: null,
|
||||
modalIndex: null,
|
||||
widgetChangeData: [],
|
||||
widgetAddData:prevState.widgetAddData || [],
|
||||
widgetOrigIDs: prevState.widgetOrigIDs || [],
|
||||
|
||||
maxWidgetHeight: maxHeight || null,
|
||||
dropZoneHeight: maxHeight +80 || null,
|
||||
|
@ -211,10 +214,6 @@ class Dashboard extends Component {
|
|||
|
||||
handleDrop(widget) {
|
||||
widget.dashboardID = this.state.dashboard.id;
|
||||
let tempChanges = this.state.widgetAddData;
|
||||
tempChanges.push(widget);
|
||||
|
||||
this.setState({ widgetAddData: tempChanges})
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'widgets/start-add',
|
||||
|
@ -262,14 +261,20 @@ class Dashboard extends Component {
|
|||
this.setState({ editModal: true, modalData: widget, modalIndex: index });
|
||||
};
|
||||
|
||||
uploadFile(data,widget){
|
||||
AppDispatcher.dispatch({
|
||||
type: 'files/start-upload',
|
||||
data: data,
|
||||
token: this.state.sessionToken,
|
||||
scenarioID: this.state.dashboard.scenarioID,
|
||||
});
|
||||
startEditFiles(){
|
||||
let tempFiles = [];
|
||||
this.state.files.forEach( file => {
|
||||
tempFiles.push({
|
||||
id: file.id,
|
||||
name: file.name
|
||||
});
|
||||
})
|
||||
this.setState({filesEditModal: true, filesEditSaveState: tempFiles});
|
||||
}
|
||||
|
||||
closeEditFiles(){
|
||||
this.setState({ filesEditModal: false });
|
||||
// TODO do we need this if the dispatches happen in the dialog?
|
||||
}
|
||||
|
||||
closeEdit(data){
|
||||
|
@ -279,7 +284,7 @@ class Dashboard extends Component {
|
|||
AppDispatcher.dispatch({
|
||||
type: 'widgets/start-load',
|
||||
token: this.state.sessionToken,
|
||||
param: '?dashboardID=1'
|
||||
param: '?dashboardID=' + this.state.dashboard.id
|
||||
});
|
||||
|
||||
this.setState({ editModal: false });
|
||||
|
@ -308,7 +313,9 @@ class Dashboard extends Component {
|
|||
|
||||
|
||||
startEditing(){
|
||||
let originalIDs = [];
|
||||
this.state.widgets.forEach( widget => {
|
||||
originalIDs.push(widget.id);
|
||||
if(widget.type === 'Slider' || widget.type === 'NumberInput' || widget.type === 'Button'){
|
||||
AppDispatcher.dispatch({
|
||||
type: 'widgets/start-edit',
|
||||
|
@ -317,7 +324,7 @@ class Dashboard extends Component {
|
|||
});
|
||||
}
|
||||
});
|
||||
this.setState({ editing: true });
|
||||
this.setState({ editing: true, widgetOrigIDs: originalIDs });
|
||||
};
|
||||
|
||||
saveEditing() {
|
||||
|
@ -336,7 +343,7 @@ class Dashboard extends Component {
|
|||
data: widget
|
||||
});
|
||||
});
|
||||
this.setState({ editing: false, widgetChangeData: [], widgetAddData: [] });
|
||||
this.setState({ editing: false, widgetChangeData: []});
|
||||
};
|
||||
|
||||
saveChanges() {
|
||||
|
@ -354,28 +361,23 @@ class Dashboard extends Component {
|
|||
|
||||
cancelEditing() {
|
||||
//raw widget has no id -> cannot be deleted in its original form
|
||||
let temp = [];
|
||||
this.state.widgetAddData.forEach(rawWidget => {
|
||||
this.state.widgets.forEach(widget => {
|
||||
if(widget.y === rawWidget.y && widget.x === rawWidget.x && widget.type === rawWidget.type){
|
||||
temp.push(widget);
|
||||
let tempID = this.state.widgetOrigIDs.find(element => element === widget.id);
|
||||
if(typeof tempID === 'undefined'){
|
||||
AppDispatcher.dispatch({
|
||||
type: 'widgets/start-remove',
|
||||
data: widget,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
temp.forEach( widget => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'widgets/start-remove',
|
||||
data: widget,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
});
|
||||
AppDispatcher.dispatch({
|
||||
type: 'widgets/start-load',
|
||||
token: this.state.sessionToken,
|
||||
param: '?dashboardID=1'
|
||||
param: '?dashboardID=' + this.state.dashboard.id
|
||||
});
|
||||
this.setState({ editing: false, widgetChangeData: [], widgetAddData: []});
|
||||
this.setState({ editing: false, widgetChangeData: []});
|
||||
|
||||
};
|
||||
|
||||
|
@ -416,6 +418,7 @@ class Dashboard extends Component {
|
|||
onFullscreen={this.props.toggleFullscreen}
|
||||
onPause={this.pauseData.bind(this)}
|
||||
onUnpause={this.unpauseData.bind(this)}
|
||||
onEditFiles = {this.startEditFiles.bind(this)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -467,12 +470,20 @@ class Dashboard extends Component {
|
|||
sessionToken={this.state.sessionToken}
|
||||
show={this.state.editModal}
|
||||
onClose={this.closeEdit.bind(this)}
|
||||
onUpload = {this.uploadFile.bind(this)}
|
||||
widget={this.state.modalData}
|
||||
signals={this.state.signals}
|
||||
files={this.state.files}
|
||||
/>
|
||||
|
||||
<EditFiles
|
||||
sessionToken={this.state.sessionToken}
|
||||
show={this.state.filesEditModal}
|
||||
onClose={this.closeEditFiles.bind(this)}
|
||||
signals={this.state.signals}
|
||||
files={this.state.files}
|
||||
scenarioID={this.state.dashboard.scenarioID}
|
||||
/>
|
||||
|
||||
|
||||
</div>
|
||||
</div>;
|
||||
|
|
166
src/file/edit-files.js
Normal file
166
src/file/edit-files.js
Normal file
|
@ -0,0 +1,166 @@
|
|||
/**
|
||||
* 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 {FormGroup, FormControl, Button, Col, ProgressBar} from 'react-bootstrap';
|
||||
import Dialog from '../common/dialogs/dialog';
|
||||
import AppDispatcher from "../common/app-dispatcher";
|
||||
import Table from "../common/table";
|
||||
import TableColumn from "../common/table-column";
|
||||
|
||||
|
||||
class EditFilesDialog extends React.Component {
|
||||
valid = true;
|
||||
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
uploadFile: null,
|
||||
uploadProgress: 0
|
||||
};
|
||||
}
|
||||
|
||||
onClose(canceled) {
|
||||
if (canceled === false) {
|
||||
if (true) {
|
||||
this.props.onClose();
|
||||
}
|
||||
} else {
|
||||
this.props.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
selectUploadFile(event) {
|
||||
this.setState({ uploadFile: event.target.files[0] });
|
||||
};
|
||||
|
||||
startFileUpload(){
|
||||
// upload file
|
||||
const formData = new FormData();
|
||||
formData.append("file", this.state.uploadFile);
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'files/start-upload',
|
||||
data: formData,
|
||||
token: this.props.sessionToken,
|
||||
progressCallback: this.updateUploadProgress,
|
||||
finishedCallback: this.clearProgress,
|
||||
scenarioID: this.props.scenarioID,
|
||||
});
|
||||
|
||||
this.setState({ uploadFile: null });
|
||||
};
|
||||
|
||||
updateUploadProgress = (event) => {
|
||||
this.setState({ uploadProgress: parseInt(event.percent.toFixed(), 10) });
|
||||
};
|
||||
|
||||
clearProgress = (newFileID) => {
|
||||
/*if (this.props.onChange != null) {
|
||||
let event = {}
|
||||
event["target"] = {}
|
||||
event.target["value"] = newFileID
|
||||
this.props.onChange(event);
|
||||
}
|
||||
*/
|
||||
this.setState({ uploadProgress: 0 });
|
||||
|
||||
|
||||
};
|
||||
|
||||
deleteFile(index){
|
||||
|
||||
let file = this.props.files[index]
|
||||
AppDispatcher.dispatch({
|
||||
type: 'files/start-remove',
|
||||
data: file,
|
||||
token: this.props.sessionToken
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
|
||||
let fileOptions = [];
|
||||
if (this.props.files.length > 0){
|
||||
fileOptions.push(
|
||||
<option key = {0} default>Select image file</option>
|
||||
)
|
||||
fileOptions.push(this.props.files.map((file, index) => (
|
||||
<option key={index+1} value={file.id}>{file.name}</option>
|
||||
)))
|
||||
} else {
|
||||
fileOptions = <option disabled value style={{ display: 'none' }}>No files found, please upload one first.</option>
|
||||
}
|
||||
|
||||
const progressBarStyle = {
|
||||
marginLeft: '100px',
|
||||
marginTop: '-40px'
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog show={this.props.show} title="Edit Files of scenario" buttonTitle="Close" onClose={(c) => this.onClose(c)} blendOutCancel = {true} valid={true}>
|
||||
<div>
|
||||
|
||||
<div className="edit-table">
|
||||
<Table data={this.props.files} width = {467}>
|
||||
<TableColumn title='ID' dataKey='id' width={42} />
|
||||
<TableColumn title='Name' dataKey='name' width={107}/>
|
||||
<TableColumn title='Size (bytes)' dataKey='size' width={83.3}/>
|
||||
<TableColumn title='Type' dataKey='type' width={159.7}/>
|
||||
<TableColumn
|
||||
title='Delete'
|
||||
width='75'
|
||||
deleteButton
|
||||
onDelete={(index) => this.deleteFile(index)}
|
||||
/>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<FormGroup as={Col} >
|
||||
<FormControl
|
||||
disabled={this.props.disabled}
|
||||
type='file'
|
||||
onChange={(event) => this.selectUploadFile(event)} />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup as={Col} >
|
||||
<Button
|
||||
disabled={this.state.uploadFile === null}
|
||||
onClick={() => this.startFileUpload()}>
|
||||
Upload
|
||||
</Button>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup as={Col} >
|
||||
<ProgressBar
|
||||
striped={true}
|
||||
animated={true}
|
||||
now={this.state.uploadProgress}
|
||||
label={this.state.uploadProgress + '%'}
|
||||
style={progressBarStyle}
|
||||
/>
|
||||
</FormGroup>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default EditFilesDialog;
|
|
@ -55,11 +55,12 @@ class ICAction extends React.Component {
|
|||
));
|
||||
|
||||
return <div>
|
||||
Send command to infrastructure component
|
||||
<ButtonToolbar>
|
||||
<DropdownButton title={this.state.selectedAction != null ? this.state.selectedAction.title : ''} id="action-dropdown" onSelect={this.setAction}>
|
||||
{actionList}
|
||||
</DropdownButton>
|
||||
<Button style={{ marginLeft: '5px' }} disabled={this.props.runDisabled} onClick={() => this.props.runAction(this.state.selectedAction)}>Run</Button>
|
||||
<Button style={{ marginLeft: '5px' }} disabled={this.props.runDisabled} onClick={() => this.props.runAction(this.state.selectedAction)}>Send command</Button>
|
||||
</ButtonToolbar>
|
||||
|
||||
</div>;
|
||||
|
|
|
@ -58,7 +58,7 @@ class IcDataDataManager {
|
|||
if (socket == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log("Sending to IC", identifier, "message: ", message);
|
||||
const data = this.messageToBuffer(message);
|
||||
socket.send(data);
|
||||
|
||||
|
|
|
@ -109,7 +109,7 @@ class ICDataStore extends ReduceStore {
|
|||
// update message properties
|
||||
state[action.ic].input.timestamp = Date.now();
|
||||
state[action.ic].input.sequence++;
|
||||
state[action.ic].input.values[action.signal] = action.data;
|
||||
state[action.ic].input.values[action.signal-1] = action.data;
|
||||
|
||||
ICDataDataManager.send(state[action.ic].input, action.ic);
|
||||
|
||||
|
|
|
@ -25,8 +25,7 @@ class IcsDataManager extends RestDataManager {
|
|||
}
|
||||
|
||||
doActions(ic, action, token = null) {
|
||||
// TODO: Make only infrastructure component id dependent
|
||||
RestAPI.post(this.makeURL(this.url + '/' + ic.id), action, token).then(response => {
|
||||
RestAPI.post(this.makeURL(this.url + '/' + ic.id + '/action'), action, token).then(response => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'ics/action-started',
|
||||
data: response
|
||||
|
|
|
@ -220,39 +220,40 @@ class InfrastructureComponents extends Component {
|
|||
return Date.now() - new Date(component.stateUpdatedAt) > fiveMinutes;
|
||||
}
|
||||
|
||||
static stateLabelStyle(state, component){
|
||||
var style = [ 'label' ];
|
||||
stateLabelStyle(state, component){
|
||||
|
||||
var style = [ 'badge' ];
|
||||
|
||||
if (InfrastructureComponents.isICOutdated(component) && state !== 'shutdown') {
|
||||
style.push('label-outdated');
|
||||
style.push('badge-outdated');
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
case 'running':
|
||||
style.push('label-success');
|
||||
style.push('badge-success');
|
||||
break;
|
||||
|
||||
case 'paused':
|
||||
style.push('label-info');
|
||||
style.push('badge-info');
|
||||
break;
|
||||
|
||||
case 'idle':
|
||||
style.push('label-primary');
|
||||
style.push('badge-primary');
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
style.push('label-danger');
|
||||
style.push('badge-danger');
|
||||
break;
|
||||
|
||||
case 'shutdown':
|
||||
style.push('label-warning');
|
||||
style.push('badge-warning');
|
||||
break;
|
||||
|
||||
default:
|
||||
style.push('label-default');
|
||||
style.push('badge-default');
|
||||
}
|
||||
|
||||
return style.join(' ');
|
||||
return style.join(' ')
|
||||
}
|
||||
|
||||
static stateUpdateModifier(updatedAt) {
|
||||
|
@ -273,7 +274,7 @@ class InfrastructureComponents extends Component {
|
|||
<Table data={this.state.ics}>
|
||||
<TableColumn checkbox onChecked={(index, event) => this.onICChecked(index, event)} width='30' />
|
||||
<TableColumn title='Name' dataKeys={['name', 'rawProperties.name']} />
|
||||
<TableColumn title='State' labelKey='state' tooltipKey='error' labelModifier={InfrastructureComponents.stateLabelModifier} labelStyle={InfrastructureComponents.stateLabelStyle} />
|
||||
<TableColumn title='State' labelKey='state' tooltipKey='error' labelStyle={(state, component) => this.stateLabelStyle(state, component)} />
|
||||
<TableColumn title='Category' dataKeys={['category', 'rawProperties.category']} />
|
||||
<TableColumn title='Type' dataKeys={['type', 'rawProperties.type']} />
|
||||
<TableColumn title='Location' dataKeys={['properties.location', 'rawProperties.location']} />
|
||||
|
|
|
@ -43,17 +43,20 @@ import EditSignalMapping from "../signal/edit-signal-mapping";
|
|||
import FileStore from "../file/file-store"
|
||||
import WidgetStore from "../widget/widget-store";
|
||||
|
||||
|
||||
|
||||
class Scenario extends React.Component {
|
||||
|
||||
static getStores() {
|
||||
return [ScenarioStore, ConfigStore, DashboardStore, ICStore, LoginStore, SignalStore, FileStore, WidgetStore];
|
||||
return [ ScenarioStore, ConfigStore, DashboardStore, ICStore, LoginStore, SignalStore, FileStore, WidgetStore];
|
||||
}
|
||||
|
||||
static calculateState(prevState, props) {
|
||||
if (prevState == null) {
|
||||
prevState = {};
|
||||
}
|
||||
|
||||
// get selected scenario
|
||||
const sessionToken = LoginStore.getState().token;
|
||||
|
||||
const scenario = ScenarioStore.getState().find(s => s.id === parseInt(props.match.params.scenario, 10));
|
||||
if (scenario == null) {
|
||||
AppDispatcher.dispatch({
|
||||
|
@ -87,12 +90,12 @@ class Scenario extends React.Component {
|
|||
deleteConfigModal: false,
|
||||
importConfigModal: false,
|
||||
editConfigModal: false,
|
||||
modalConfigData: {},
|
||||
modalConfigData: (prevState.modalConfigData !== {} && prevState.modalConfigData !== undefined )? prevState.modalConfigData : {},
|
||||
selectedConfigs: [],
|
||||
modalConfigIndex: 0,
|
||||
|
||||
editOutputSignalsModal: false,
|
||||
editInputSignalsModal: false,
|
||||
editOutputSignalsModal: prevState.editOutputSignalsModal || false,
|
||||
editInputSignalsModal: prevState.editInputSignalsModal || false,
|
||||
|
||||
newDashboardModal: false,
|
||||
deleteDashboardModal: false,
|
||||
|
@ -252,12 +255,18 @@ class Scenario extends React.Component {
|
|||
this.setState({ selectedConfigs: selectedConfigs });
|
||||
}
|
||||
|
||||
runAction = action => {
|
||||
runAction(action) {
|
||||
|
||||
if(action.data.action === 'none'){
|
||||
console.warn("No command selected. Nothing was sent.");
|
||||
return;
|
||||
}
|
||||
|
||||
for (let index of this.state.selectedConfigs) {
|
||||
// get IC for component config
|
||||
let ic = null;
|
||||
for (let component of this.state.ics) {
|
||||
if (component._id === this.state.configs[index].icID) {
|
||||
if (component.id === this.state.configs[index].icID) {
|
||||
ic = component;
|
||||
}
|
||||
}
|
||||
|
@ -358,54 +367,14 @@ class Scenario extends React.Component {
|
|||
* Signal modification methods
|
||||
############################################## */
|
||||
|
||||
closeDeleteSignalModal(data) {
|
||||
// data contains the signal to be deleted
|
||||
if (data) {
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'signals/start-remove',
|
||||
data: data,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
|
||||
closeEditSignalsModal(direction){
|
||||
if( direction === "in") {
|
||||
this.setState({editInputSignalsModal: false});
|
||||
} else if( direction === "out"){
|
||||
this.setState({editOutputSignalsModal: false});
|
||||
}
|
||||
}
|
||||
|
||||
closeNewSignalModal(data) {
|
||||
//data contains the new signal incl. configID and direction
|
||||
if (data) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'signals/start-add',
|
||||
data: data,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
closeEditSignalsModal(data, direction) {
|
||||
|
||||
if (direction === "in") {
|
||||
this.setState({ editInputSignalsModal: false });
|
||||
} else if (direction === "out") {
|
||||
this.setState({ editOutputSignalsModal: false });
|
||||
} else {
|
||||
return; // no valid direction
|
||||
}
|
||||
|
||||
if (data) {
|
||||
//data is an array of signals
|
||||
for (let sig of data) {
|
||||
//dispatch changes to signals
|
||||
AppDispatcher.dispatch({
|
||||
type: 'signals/start-edit',
|
||||
data: sig,
|
||||
token: this.state.sessionToken,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* ##############################################
|
||||
* File modification methods
|
||||
############################################## */
|
||||
|
@ -428,11 +397,15 @@ class Scenario extends React.Component {
|
|||
marginLeft: '10px'
|
||||
};
|
||||
|
||||
const tableHeadingStyle = {
|
||||
paddingTop: '30px'
|
||||
}
|
||||
|
||||
return <div className='section'>
|
||||
<h1>{this.state.scenario.name}</h1>
|
||||
|
||||
{/*Scenario Users table*/}
|
||||
<h2>Users</h2>
|
||||
<h2 style={tableHeadingStyle}>Users</h2>
|
||||
<div>
|
||||
<Table data={this.state.scenario.users}>
|
||||
<TableColumn title='Name' dataKey='username' link='/users/' linkKey='id' />
|
||||
|
@ -464,7 +437,7 @@ class Scenario extends React.Component {
|
|||
|
||||
|
||||
{/*Component Configurations table*/}
|
||||
<h2>Component Configurations</h2>
|
||||
<h2 style={tableHeadingStyle}>Component Configurations</h2>
|
||||
<Table data={this.state.configs}>
|
||||
<TableColumn checkbox onChecked={(index, event) => this.onConfigChecked(index, event)} width='30' />
|
||||
<TableColumn title='Name' dataKey='name' />
|
||||
|
@ -497,8 +470,9 @@ class Scenario extends React.Component {
|
|||
<div style={{ float: 'left' }}>
|
||||
<ICAction
|
||||
runDisabled={this.state.selectedConfigs.length === 0}
|
||||
runAction={this.runAction}
|
||||
runAction={(action) => this.runAction(action)}
|
||||
actions={[
|
||||
{ id: '-1', title: 'Select command', data: { action: 'none' } },
|
||||
{ id: '0', title: 'Start', data: { action: 'start' } },
|
||||
{ id: '1', title: 'Stop', data: { action: 'stop' } },
|
||||
{ id: '2', title: 'Pause', data: { action: 'pause' } },
|
||||
|
@ -527,23 +501,23 @@ class Scenario extends React.Component {
|
|||
|
||||
<EditSignalMapping
|
||||
show={this.state.editOutputSignalsModal}
|
||||
onCloseEdit={(data, direction) => this.closeEditSignalsModal(data, direction)}
|
||||
onAdd={(data) => this.closeNewSignalModal(data)}
|
||||
onDelete={(data) => this.closeDeleteSignalModal(data)}
|
||||
onCloseEdit={(direction) => this.closeEditSignalsModal(direction)}
|
||||
direction="Output"
|
||||
signals={this.state.signals}
|
||||
configID={this.state.modalConfigData.id} />
|
||||
configID={this.state.modalConfigData.id}
|
||||
sessionToken={this.state.sessionToken}
|
||||
/>
|
||||
<EditSignalMapping
|
||||
show={this.state.editInputSignalsModal}
|
||||
onCloseEdit={(data, direction) => this.closeEditSignalsModal(data, direction)}
|
||||
onAdd={(data) => this.closeNewSignalModal(data)}
|
||||
onDelete={(data) => this.closeDeleteSignalModal(data)}
|
||||
onCloseEdit={(direction) => this.closeEditSignalsModal(direction)}
|
||||
direction="Input"
|
||||
signals={this.state.signals}
|
||||
configID={this.state.modalConfigData.id} />
|
||||
configID={this.state.modalConfigData.id}
|
||||
sessionToken={this.state.sessionToken}
|
||||
/>
|
||||
|
||||
{/*Dashboard table*/}
|
||||
<h2>Dashboards</h2>
|
||||
<h2 style={tableHeadingStyle}>Dashboards</h2>
|
||||
<Table data={this.state.dashboards}>
|
||||
<TableColumn title='Name' dataKey='name' link='/dashboards/' linkKey='id' />
|
||||
<TableColumn title='Grid' dataKey='grid' />
|
||||
|
|
|
@ -214,6 +214,15 @@ class Scenarios extends Component {
|
|||
FileSaver.saveAs(blob, 'scenario - ' + scenario.name + '.json');
|
||||
}
|
||||
|
||||
modifyRunningColumn(running){
|
||||
|
||||
if(running){
|
||||
return <Icon icon='check' />
|
||||
} else {
|
||||
return <Icon icon='times' />
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
const buttonStyle = {
|
||||
|
@ -227,7 +236,7 @@ class Scenarios extends Component {
|
|||
<Table data={this.state.scenarios}>
|
||||
<TableColumn title='Name' dataKey='name' link='/scenarios/' linkKey='id' />
|
||||
<TableColumn title='ID' dataKey='id' />
|
||||
<TableColumn title='Running' dataKey='running' />
|
||||
<TableColumn title='Running' dataKey='running' modifier={(running) => this.modifyRunningColumn(running)}/>
|
||||
<TableColumn
|
||||
width='200'
|
||||
editButton
|
||||
|
|
|
@ -18,35 +18,29 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Button, FormGroup, FormLabel, FormText} from 'react-bootstrap';
|
||||
|
||||
import Table from '../common/table';
|
||||
import TableColumn from '../common/table-column';
|
||||
import Dialog from "../common/dialogs/dialog";
|
||||
|
||||
import SignalStore from "./signal-store"
|
||||
import Icon from "../common/icon";
|
||||
import AppDispatcher from "../common/app-dispatcher";
|
||||
|
||||
class EditSignalMapping extends React.Component {
|
||||
valid = false;
|
||||
|
||||
static getStores() {
|
||||
return [ SignalStore];
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
super(props);
|
||||
|
||||
let dir = "";
|
||||
if ( this.props.direction === "Output"){
|
||||
dir = "out";
|
||||
} else if ( this.props.direction === "Input" ){
|
||||
dir = "in";
|
||||
}
|
||||
let dir = "";
|
||||
if ( this.props.direction === "Output"){
|
||||
dir = "out";
|
||||
} else if ( this.props.direction === "Input" ){
|
||||
dir = "in";
|
||||
}
|
||||
|
||||
this.state = {
|
||||
dir,
|
||||
signals: [],
|
||||
};
|
||||
|
||||
this.state = {
|
||||
dir,
|
||||
signals: []
|
||||
};
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props, state){
|
||||
|
@ -63,55 +57,51 @@ class EditSignalMapping extends React.Component {
|
|||
|
||||
|
||||
onClose(canceled) {
|
||||
if (canceled === false) {
|
||||
if (this.valid) {
|
||||
let data = this.state.signals;
|
||||
|
||||
//forward modified signals back to callback function
|
||||
this.props.onCloseEdit(data, this.state.dir)
|
||||
}
|
||||
} else {
|
||||
this.props.onCloseEdit(null, this.state.dir);
|
||||
}
|
||||
}
|
||||
|
||||
onDelete(e){
|
||||
console.log("On signal delete");
|
||||
this.props.onCloseEdit(this.state.dir)
|
||||
}
|
||||
|
||||
|
||||
handleMappingChange = (event, row, column) => {
|
||||
const signals = this.state.signals;
|
||||
let sig = {}
|
||||
|
||||
if (column === 1) { // Name change
|
||||
if (event.target.value !== '') {
|
||||
signals[row].name = event.target.value;
|
||||
this.setState({signals: signals});
|
||||
this.valid = true;
|
||||
sig = this.state.signals[row];
|
||||
sig.name = event.target.value;
|
||||
}
|
||||
} else if (column === 2) { // unit change
|
||||
if (event.target.value !== '') {
|
||||
signals[row].unit = event.target.value;
|
||||
this.setState({signals: signals});
|
||||
this.valid = true;
|
||||
sig = this.state.signals[row];
|
||||
sig.unit = event.target.value;
|
||||
}
|
||||
} else if (column === 0) { //index change
|
||||
|
||||
signals[row].index = parseInt(event.target.value, 10);
|
||||
this.setState({signals: signals});
|
||||
this.valid = true;
|
||||
sig = this.state.signals[row];
|
||||
sig.index = parseInt(event.target.value, 10);
|
||||
}
|
||||
|
||||
if (sig !== {}){
|
||||
//dispatch changes to signal
|
||||
AppDispatcher.dispatch({
|
||||
type: 'signals/start-edit',
|
||||
data: sig,
|
||||
token: this.props.sessionToken,
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
handleDelete = (index) => {
|
||||
|
||||
let data = this.state.signals[index]
|
||||
this.props.onDelete(data);
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'signals/start-remove',
|
||||
data: data,
|
||||
token: this.props.sessionToken
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
handleAdd = () => {
|
||||
console.log("add signal");
|
||||
|
||||
let newSignal = {
|
||||
configID: this.props.configID,
|
||||
|
@ -121,12 +111,15 @@ class EditSignalMapping extends React.Component {
|
|||
index: 999
|
||||
};
|
||||
|
||||
this.props.onAdd(newSignal)
|
||||
AppDispatcher.dispatch({
|
||||
type: 'signals/start-add',
|
||||
data: newSignal,
|
||||
token: this.props.sessionToken
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
resetState() {
|
||||
this.valid=false;
|
||||
|
||||
let signals = this.props.signals.filter((sig) => {
|
||||
return (sig.configID === this.props.configID) && (sig.direction === this.state.dir);
|
||||
|
@ -143,7 +136,14 @@ class EditSignalMapping extends React.Component {
|
|||
|
||||
return(
|
||||
|
||||
<Dialog show={this.props.show} title="Edit Signal Mapping" buttonTitle="Save Edits" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
|
||||
<Dialog
|
||||
show={this.props.show}
|
||||
title="Edit Signal Mapping"
|
||||
buttonTitle="Close"
|
||||
blendOutCancel = {true}
|
||||
onClose={(c) => this.onClose(c)}
|
||||
onReset={() => this.resetState()}
|
||||
valid={true}>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{this.props.direction} Mapping</FormLabel>
|
||||
|
|
|
@ -272,6 +272,27 @@ body {
|
|||
supported by Chrome and Opera */
|
||||
}
|
||||
|
||||
.edit-table table {
|
||||
background-color: #fff;
|
||||
table-layout: fixed;
|
||||
word-wrap: break-word;
|
||||
width: 467px;
|
||||
}
|
||||
|
||||
.edit-table th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.edit-table td{
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.edit-table td {
|
||||
padding: 2px 8px !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toolbox
|
||||
*/
|
||||
|
@ -379,6 +400,6 @@ body {
|
|||
margin-right: 0 !important;
|
||||
}
|
||||
|
||||
.label-outdated {
|
||||
.badge-outdated {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
|
|
@ -115,65 +115,6 @@ div[class*="-widget"] .btn[disabled], .btn.disabled, div[class*="-widget"] input
|
|||
|
||||
/* End edit menu: Colors */
|
||||
|
||||
/* PlotTable widget */
|
||||
.plot-table-widget, .plot-widget, .value-widget, .image-widget, .label-widget {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 3px 6px;
|
||||
}
|
||||
|
||||
.plot-table-widget {
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.plot-table-widget .content {
|
||||
-webkit-flex: 1 0 auto;
|
||||
flex: 1 0 auto;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.table-plot-row {
|
||||
-webkit-flex: 1 0 auto;
|
||||
flex: 1 0 auto;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.plot-table-widget .widget-table {
|
||||
-webkit-flex: 1 0 auto;
|
||||
flex: 1 0 auto;
|
||||
flex-basis: 90px;
|
||||
max-width: 50%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.plot-table-widget small {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.plot-table-widget .checkbox label {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 6px 12px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.plot-table-widget .btn {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.plot-table-widget input[type="checkbox"] {
|
||||
display: none;
|
||||
}
|
||||
/* End PlotTable Widget */
|
||||
|
||||
/* Plot Widget */
|
||||
.plot-widget {
|
||||
display: -webkit-flex;
|
||||
|
@ -438,8 +379,3 @@ div[class*="-widget"] label {
|
|||
border: 2px solid;
|
||||
}
|
||||
/* End box widget */
|
||||
|
||||
.plot-table-widget .widget-plot {
|
||||
-webkit-flex: 1 0 auto;
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
|
|
@ -122,6 +122,16 @@ class Users extends Component {
|
|||
}
|
||||
};
|
||||
|
||||
modifyActiveColumn(active){
|
||||
|
||||
if(active){
|
||||
return <Icon icon='check' />
|
||||
} else {
|
||||
return <Icon icon='times' />
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
return (
|
||||
|
@ -132,8 +142,8 @@ class Users extends Component {
|
|||
<TableColumn title='Username' width='150' dataKey='username' />
|
||||
<TableColumn title='ID' width='150' dataKey='id' />
|
||||
<TableColumn title='E-mail' dataKey='mail' />
|
||||
<TableColumn title='Role' dataKey='role' modifier={(role) => this.getHumanRoleName(role)} />
|
||||
<TableColumn title='Active' dataKey='active' />
|
||||
<TableColumn title='Role' dataKey='role' modifier={(role) => this.getHumanRoleName(role)} />
|
||||
<TableColumn title='Active' dataKey='active' modifier={(active) => this.modifyActiveColumn(active)} />
|
||||
<TableColumn width='200' editButton deleteButton onEdit={index => this.setState({ editModal: true, modalData: this.state.users[index] })} onDelete={index => this.setState({ deleteModal: true, modalData: this.state.users[index] })} />
|
||||
</Table>
|
||||
|
||||
|
|
|
@ -75,7 +75,16 @@ class EditWidgetColorControl extends Component {
|
|||
'checked': idx === (isCustomProperty ? this.state.widget[parts[0]][parts[1]] : this.state.widget[this.props.controlId])
|
||||
});
|
||||
|
||||
return (<FormCheck type='radio' key={idx} name={this.props.label} style={colorStyle} className={checkedClass} value={idx} inline onChange={(e) => this.props.handleChange({target: { id: this.props.controlId, value: idx}})} />)
|
||||
return (<FormCheck
|
||||
type='radio'
|
||||
key={idx}
|
||||
name={this.props.label}
|
||||
style={colorStyle}
|
||||
className={checkedClass}
|
||||
value={idx}
|
||||
inline
|
||||
defaultChecked={isCustomProperty ? this.state.widget[parts[0]][parts[1]] ===idx: this.state.widget[this.props.controlId] === idx}
|
||||
onChange={(e) => this.props.handleChange({target: { id: this.props.controlId, value: idx}})} />)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ import EditWidgetMinMaxControl from './edit-widget-min-max-control';
|
|||
import EditWidgetHTMLContent from './edit-widget-html-content';
|
||||
import EditWidgetParametersControl from './edit-widget-parameters-control';
|
||||
|
||||
export default function CreateControls(widgetType = null, widget = null, sessionToken = null, files = null, signals, handleChange, onUpload) {
|
||||
export default function CreateControls(widgetType = null, widget = null, sessionToken = null, files = null, signals, handleChange) {
|
||||
// Use a list to concatenate the controls according to the widget type
|
||||
var DialogControls = [];
|
||||
|
||||
|
@ -47,20 +47,20 @@ export default function CreateControls(widgetType = null, widget = null, session
|
|||
break;
|
||||
case 'Action':
|
||||
DialogControls.push(
|
||||
<EditWidgetSignalControl key={0} widget={widget} controlId={'signalIDs'} signals={signals} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetSignalControl key={0} widget={widget} controlId={'signalIDs'} signals={signals} handleChange={(e) => handleChange(e)} direction={'in'} />,
|
||||
);
|
||||
break;
|
||||
case 'Value':
|
||||
DialogControls.push(
|
||||
<EditWidgetTextControl key={0} widget={widget} controlId={'name'} label={'Signal name'} placeholder={'Enter text'} handleChange={e => handleChange(e)} />,
|
||||
<EditWidgetSignalControl key={1} widget={widget} controlId={'signalIDs'} signals={signals} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetSignalControl key={1} widget={widget} controlId={'signalIDs'} signals={signals} handleChange={(e) => handleChange(e)} direction={'out'}/>,
|
||||
<EditWidgetTextSizeControl key={2} widget={widget} handleChange={e => handleChange(e)} />,
|
||||
<EditWidgetCheckboxControl key={3} widget={widget} controlId={'customProperties.showUnit'} input text="Show unit" handleChange={e => handleChange(e)} />
|
||||
);
|
||||
break;
|
||||
case 'Lamp':
|
||||
DialogControls.push(
|
||||
<EditWidgetSignalControl key={0} widget={widget} controlId={'signalIDs'} signals={signals} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetSignalControl key={0} widget={widget} controlId={'signalIDs'} signals={signals} handleChange={(e) => handleChange(e)} direction={'out'}/>,
|
||||
<EditWidgetTextControl key={1} widget={widget} controlId={'customProperties.threshold'} label={'Threshold'} placeholder={'0.5'} handleChange={e => handleChange(e)} />,
|
||||
<EditWidgetColorControl key={2} widget={widget} controlId={'customProperties.on_color'} label={'Color On'} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetColorControl key={3} widget={widget} controlId={'customProperties.off_color'} label={'Color Off'} handleChange={(e) => handleChange(e)} />,
|
||||
|
@ -69,14 +69,14 @@ export default function CreateControls(widgetType = null, widget = null, session
|
|||
case 'Plot':
|
||||
DialogControls.push(
|
||||
<EditWidgetTimeControl key={0} widget={widget} controlId={'customProperties.time'} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetSignalsControl key={1} widget={widget} controlId={'signalIDs'} signals={signals} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetSignalsControl key={1} widget={widget} controlId={'signalIDs'} signals={signals} handleChange={(e) => handleChange(e)} direction={'out'}/>,
|
||||
<EditWidgetTextControl key={2} widget={widget} controlId={'customProperties.ylabel'} label={'Y-Axis name'} placeholder={'Enter a name for the y-axis'} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetMinMaxControl key={3} widget={widget} controlId="customProperties.y" handleChange={e => handleChange(e)} />
|
||||
);
|
||||
break;
|
||||
case 'Table':
|
||||
DialogControls.push(
|
||||
<EditWidgetSignalsControl key={0} widget={widget} controlId={'signalIDs'} signals={signals} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetSignalsControl key={0} widget={widget} controlId={'signalIDs'} signals={signals} handleChange={(e) => handleChange(e)} direction={'out'}/>,
|
||||
<EditWidgetCheckboxControl key={1} widget={widget} controlId={'customProperties.showUnit'} input text="Show unit" handleChange={e => handleChange(e)} />
|
||||
);
|
||||
break;
|
||||
|
@ -84,32 +84,24 @@ export default function CreateControls(widgetType = null, widget = null, session
|
|||
// Restrict to only image file types (MIME)
|
||||
//let imageControlFiles = files == null? [] : files.filter(file => file.type.includes('image'));
|
||||
DialogControls.push(
|
||||
<EditFileWidgetControl key={0} widget={widget} controlId={"customProperties.file"} files={files} type={'image'} handleChange={(e) => handleChange(e)} onUpload={(f,i) => onUpload(f,i)} />,
|
||||
<EditFileWidgetControl key={0} widget={widget} controlId={"customProperties.file"} files={files} type={'image'} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetAspectControl key={1} widget={widget} controlId={"customProperties.lockAspect"} handleChange={e => handleChange(e)} />
|
||||
);
|
||||
break;
|
||||
case 'Gauge':
|
||||
DialogControls.push(
|
||||
<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)} />,
|
||||
<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)} />,
|
||||
<EditWidgetMinMaxControl key={4} widget={widget} controlId="customProperties.value" handleChange={e => handleChange(e)} />
|
||||
);
|
||||
break;
|
||||
case 'PlotTable':
|
||||
DialogControls.push(
|
||||
<EditWidgetSignalsControl key={0} widget={widget} controlId={'signalIDs'} signals={signals} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetTextControl key={1} widget={widget} controlId={'customProperties.ylabel'} label={'Y-Axis'} placeholder={'Enter a name for the Y-axis'} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetTimeControl key={2} widget={widget} controlId={'customProperties.time'} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetMinMaxControl key={3} widget={widget} controlId="customProperties.y" handleChange={e => handleChange(e)} />
|
||||
);
|
||||
break;
|
||||
case 'Slider':
|
||||
DialogControls.push(
|
||||
<EditWidgetTextControl key={0} widget={widget} controlId={'name'} label={'Text'} placeholder={'Enter text'} handleChange={e => handleChange(e)} />,
|
||||
<EditWidgetOrientation key={1} widget={widget} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetSignalControl key={2} widget={widget} controlId={'signalIDs'} input signals={signals} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetSignalControl key={2} widget={widget} controlId={'signalIDs'} input signals={signals} handleChange={(e) => handleChange(e)} direction={'in'}/>,
|
||||
<EditWidgetCheckboxControl key={3} widget={widget} controlId={'customProperties.continous_update'} input text={'Continous Update'} 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.range'} handleChange={e => handleChange(e)} />,
|
||||
|
@ -120,7 +112,7 @@ export default function CreateControls(widgetType = null, widget = null, session
|
|||
case 'Button':
|
||||
DialogControls.push(
|
||||
<EditWidgetTextControl key={0} widget={widget} controlId={'name'} label={'Text'} placeholder={'Enter text'} handleChange={e => handleChange(e)} />,
|
||||
<EditWidgetSignalControl key={1} widget={widget} controlId={'signalIDs'} input signals={signals} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetSignalControl key={1} widget={widget} controlId={'signalIDs'} input signals={signals} handleChange={(e) => handleChange(e)} direction={'in'}/>,
|
||||
<EditWidgetCheckboxControl key={2} widget={widget} controlId={'customProperties.toggle'} input text="Toggle" handleChange={e => handleChange(e)} />,
|
||||
<EditWidgetNumberControl key={3} widget={widget} controlId={'customProperties.on_value'} label={'On Value'} defaultValue={1} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetNumberControl key={4} widget={widget} controlId={'customProperties.off_value'} label={'Off Value'} defaultValue={0} handleChange={(e) => handleChange(e)} />
|
||||
|
@ -130,7 +122,7 @@ export default function CreateControls(widgetType = null, widget = null, session
|
|||
DialogControls.push(
|
||||
<EditWidgetColorControl key={0} widget={widget} controlId={'customProperties.border_color'} label={'Border color'} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetColorControl key={1} widget={widget} controlId={'customProperties.background_color'} label={'Background color'} handleChange={e => handleChange(e)} />,
|
||||
<EditWidgetNumberControl key={2} widget={widget} controlId={'customProperties.background_color_opacity'} label={'Background opacity'} defaultValue={0.5} handleChange={(e) => handleChange(e)} />
|
||||
<EditWidgetNumberControl key={2} widget={widget} controlId={'customProperties.background_color_opacity'} label={'Background opacity (0.0 - 1.0)'} defaultValue={0.5} handleChange={(e) => handleChange(e)} />
|
||||
);
|
||||
break;
|
||||
case 'Label':
|
||||
|
@ -149,14 +141,14 @@ export default function CreateControls(widgetType = null, widget = null, session
|
|||
// Restrict to only xml files (MIME)
|
||||
//let topologyControlFiles = files == null? [] : files.filter( file => file.type.includes('xml'));
|
||||
DialogControls.push(
|
||||
<EditFileWidgetControl key={0} widget={widget} controlId={"customProperties.file"} files={files} type={'xml'} handleChange={(e) => handleChange(e) } onUpload={(f,i) => onUpload(f,i)} />
|
||||
<EditFileWidgetControl key={0} widget={widget} controlId={"customProperties.file"} files={files} type={'xml'} handleChange={(e) => handleChange(e) } />
|
||||
);
|
||||
break;
|
||||
|
||||
case 'NumberInput':
|
||||
DialogControls.push(
|
||||
<EditWidgetTextControl key={0} widget={widget} controlId={'name'} label={'Text'} placeholder={'Enter text'} handleChange={e => handleChange(e)} />,
|
||||
<EditWidgetSignalControl key={2} widget={widget} controlId={'signalIDs'} input signals={signals} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetSignalControl key={2} widget={widget} controlId={'signalIDs'} input signals={signals} handleChange={(e) => handleChange(e)} direction={'in'}/>,
|
||||
<EditWidgetCheckboxControl key={1} widget={widget} controlId={'customProperties.showUnit'} input text="Show unit" handleChange={e => handleChange(e)} />
|
||||
);
|
||||
break;
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import {FormGroup, FormControl, FormLabel, Button, ProgressBar} from 'react-bootstrap';
|
||||
import {FormGroup, FormControl, FormLabel} from 'react-bootstrap';
|
||||
|
||||
class EditFileWidgetControl extends React.Component {
|
||||
|
||||
|
@ -24,41 +24,16 @@ class EditFileWidgetControl extends React.Component {
|
|||
super(props);
|
||||
|
||||
this.state = {
|
||||
widget: { },
|
||||
files: [],
|
||||
fileList: null,
|
||||
progress: 0
|
||||
};
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props, state){
|
||||
return {
|
||||
widget: props.widget,
|
||||
files: props.files.filter(file => file.type.includes(props.type))
|
||||
};
|
||||
}
|
||||
|
||||
startFileUpload = () => {
|
||||
// get selected file
|
||||
let formData = new FormData();
|
||||
|
||||
for (let key in this.state.fileList) {
|
||||
if (this.state.fileList.hasOwnProperty(key) && this.state.fileList[key] instanceof File) {
|
||||
formData.append("file", this.state.fileList[key]);
|
||||
}
|
||||
}
|
||||
|
||||
this.props.onUpload(formData,this.props.widget);
|
||||
}
|
||||
|
||||
uploadProgress = (e) => {
|
||||
this.setState({ progress: Math.round(e.percent) });
|
||||
}
|
||||
|
||||
clearProgress = () => {
|
||||
this.setState({ progress: 0 });
|
||||
}
|
||||
|
||||
handleFileChange(e){
|
||||
this.props.handleChange({ target: { id: this.props.controlId, value: e.target.value } });
|
||||
}
|
||||
|
@ -88,17 +63,9 @@ class EditFileWidgetControl extends React.Component {
|
|||
<FormLabel>Image</FormLabel>
|
||||
<FormControl
|
||||
as="select"
|
||||
value={isCustomProperty ? this.state.widget[parts[0]][parts[1]] : this.state.widget[this.props.controlId]}
|
||||
value={isCustomProperty ? this.props.widget[parts[0]][parts[1]] : this.props.widget[this.props.controlId]}
|
||||
onChange={(e) => this.handleFileChange(e)}>{fileOptions} </FormControl>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup controlId="upload">
|
||||
<FormLabel>Upload</FormLabel>
|
||||
<FormControl type="file" onChange={(e) => this.setState({ fileList: e.target.files }) } />
|
||||
</FormGroup>
|
||||
|
||||
<ProgressBar striped active={'true'} now={this.state.progress} label={`${this.state.progress}%`} />
|
||||
<Button size='sm' onClick={this.startFileUpload}>Upload</Button>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,25 +25,36 @@ class EditWidgetNumberControl extends Component {
|
|||
this.state = {
|
||||
widget: {
|
||||
customProperties:{}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
static getDerivedStateFromProps(props, state){
|
||||
return{
|
||||
widget: props.widget
|
||||
widget: props.widget
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let step = 1;
|
||||
if(this.props.controlId ==='customProperties.background_color_opacity'){
|
||||
step = 0.1;
|
||||
}
|
||||
}
|
||||
|
||||
let parts = this.props.controlId.split('.');
|
||||
let isCustomProperty = true;
|
||||
if (parts.length === 1){
|
||||
isCustomProperty = false;
|
||||
}
|
||||
|
||||
return (
|
||||
<FormGroup controlId={this.props.controlId}>
|
||||
<FormLabel>{this.props.label}</FormLabel>
|
||||
<FormControl type="number" step={step} value={this.state.widget[this.props.controlId]} onChange={e => this.props.handleChange(e)} />
|
||||
<FormControl
|
||||
type="number"
|
||||
step={step}
|
||||
value={isCustomProperty ? this.state.widget[parts[0]][parts[1]] : this.state.widget[this.props.controlId]}
|
||||
onChange={e => this.props.handleChange(e)} />
|
||||
</FormGroup>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -23,13 +23,15 @@ class EditWidgetSignalControl extends Component {
|
|||
super(props);
|
||||
|
||||
this.state = {
|
||||
widget: {}
|
||||
widget: {},
|
||||
signals: []
|
||||
};
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props, state){
|
||||
return {
|
||||
widget: props.widget
|
||||
widget: props.widget,
|
||||
signals: props.signals.filter(s => s.direction === props.direction)
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -51,10 +53,10 @@ class EditWidgetSignalControl extends Component {
|
|||
<FormControl as="select" value={this.props.widget.signalIDs[0] || ""} onChange={(e) => this.handleSignalChange(e)}>
|
||||
<option default>Select signal</option>
|
||||
{
|
||||
this.props.signals.length === 0 ? (
|
||||
this.state.signals.length === 0 ? (
|
||||
<option disabled value style={{ display: 'none' }}>No signals available.</option>
|
||||
) : (
|
||||
this.props.signals.map((signal, index) => (
|
||||
this.state.signals.map((signal, index) => (
|
||||
<option key={index} value={signal.id}>{signal.name}</option>
|
||||
))
|
||||
)
|
||||
|
|
|
@ -23,7 +23,17 @@ class EditWidgetSignalsControl extends Component {
|
|||
super(props);
|
||||
|
||||
this.state = {
|
||||
widget: {},
|
||||
signals: [],
|
||||
checkedSignals: props.widget[props.controlId]
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
static getDerivedStateFromProps(props, state){
|
||||
return {
|
||||
widget: props.widget,
|
||||
signals: props.signals.filter(s => s.direction === props.direction),
|
||||
checkedSignals: props.widget[props.controlId]
|
||||
};
|
||||
}
|
||||
|
@ -48,10 +58,10 @@ class EditWidgetSignalsControl extends Component {
|
|||
<FormGroup>
|
||||
<FormLabel>Signals</FormLabel>
|
||||
{
|
||||
this.props.signals === 0 || !this.state.widget.hasOwnProperty(this.props.controlId)? (
|
||||
this.state.signals === 0 || !this.state.widget.hasOwnProperty(this.props.controlId)? (
|
||||
<FormLabel>No signals available</FormLabel>
|
||||
) : (
|
||||
this.props.signals.map((signal, index) => (
|
||||
this.state.signals.map((signal, index) => (
|
||||
<FormCheck
|
||||
type={'checkbox'}
|
||||
label={signal.name}
|
||||
|
@ -61,7 +71,7 @@ class EditWidgetSignalsControl extends Component {
|
|||
onChange={(e) => this.handleSignalChange(e.target.checked, signal.id)}>
|
||||
</FormCheck>
|
||||
))
|
||||
)
|
||||
)
|
||||
}
|
||||
</FormGroup>
|
||||
);
|
||||
|
|
|
@ -177,8 +177,7 @@ class EditWidgetDialog extends React.Component {
|
|||
this.props.sessionToken,
|
||||
this.props.files,
|
||||
this.props.signals,
|
||||
(e) => this.handleChange(e),
|
||||
(f,i) => this.props.onUpload(f,i));
|
||||
(e) => this.handleChange(e));
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -51,7 +51,7 @@ class ToolboxItem extends React.Component {
|
|||
if (this.props.disabled === false) {
|
||||
return this.props.connectDragSource(
|
||||
<div className={itemClass}>
|
||||
<span className="btn btn-default">
|
||||
<span className="btn btn-info ">
|
||||
{this.props.icon && <Icon style={{marginRight: '5px'}} icon={this.props.icon} /> }
|
||||
{this.props.name}
|
||||
</span>
|
||||
|
@ -61,7 +61,7 @@ class ToolboxItem extends React.Component {
|
|||
else {
|
||||
return (
|
||||
<div className={itemClass}>
|
||||
<span className="btn btn-default">
|
||||
<span className="btn btn-info">
|
||||
{this.props.icon && <Icon style={{marginRight: '5px'}} icon={this.props.icon} /> }
|
||||
{this.props.name}
|
||||
</span>
|
||||
|
|
|
@ -105,17 +105,6 @@ class WidgetFactory {
|
|||
widget.customProperties.fontColor = 0;
|
||||
widget.customProperties.resizeTopBottomLock = true;
|
||||
break;
|
||||
case 'PlotTable':
|
||||
widget.customProperties.ylabel = '';
|
||||
widget.minWidth = 200;
|
||||
widget.minHeight = 100;
|
||||
widget.width = 600;
|
||||
widget.height = 300;
|
||||
widget.customProperties.time = 60;
|
||||
widget.customProperties.yMin = 0;
|
||||
widget.customProperties.yMax = 10;
|
||||
widget.customProperties.yUseMinMax = false;
|
||||
break;
|
||||
case 'Image':
|
||||
widget.minWidth = 20;
|
||||
widget.minHeight = 20;
|
||||
|
|
|
@ -39,20 +39,19 @@ class WidgetToolbox extends React.Component {
|
|||
const topologyItemMsg = thereIsTopologyWidget? 'Currently only one is supported' : '';
|
||||
|
||||
return <div className='toolbox box-header'>
|
||||
<ToolboxItem name='Lamp' type='widget' />
|
||||
<ToolboxItem name='Value' type='widget' />
|
||||
<ToolboxItem name='Plot' type='widget' />
|
||||
<ToolboxItem name='Table' type='widget' />
|
||||
<ToolboxItem name='Label' type='widget' />
|
||||
<ToolboxItem name='Image' type='widget' />
|
||||
<ToolboxItem name='PlotTable' type='widget' />
|
||||
<ToolboxItem name='Button' type='widget' />
|
||||
<ToolboxItem name='NumberInput' type='widget' />
|
||||
<ToolboxItem name='Slider' type='widget' />
|
||||
<ToolboxItem name='Gauge' type='widget' />
|
||||
<ToolboxItem name='Box' type='widget' />
|
||||
<ToolboxItem name='HTML' type='html' />
|
||||
<ToolboxItem name='Topology' type='widget' disabled={thereIsTopologyWidget} title={topologyItemMsg}/>
|
||||
<ToolboxItem name='Lamp' type='widget' icon = 'plus' />
|
||||
<ToolboxItem name='Value' type='widget' icon = 'plus' />
|
||||
<ToolboxItem name='Plot' type='widget' icon = 'plus'/>
|
||||
<ToolboxItem name='Table' type='widget' icon = 'plus'/>
|
||||
<ToolboxItem name='Label' type='widget' icon = 'plus'/>
|
||||
<ToolboxItem name='Image' type='widget' icon = 'plus'/>
|
||||
<ToolboxItem name='Button' type='widget' icon = 'plus'/>
|
||||
<ToolboxItem name='NumberInput' type='widget' icon = 'plus'/>
|
||||
<ToolboxItem name='Slider' type='widget' icon = 'plus'/>
|
||||
<ToolboxItem name='Gauge' type='widget' icon = 'plus'/>
|
||||
<ToolboxItem name='Box' type='widget' icon = 'plus'/>
|
||||
<ToolboxItem name='HTML' type='html' icon = 'plus'/>
|
||||
<ToolboxItem name='Topology' type='widget' disabled={thereIsTopologyWidget} title={topologyItemMsg} icon = 'plus'/>
|
||||
|
||||
<div className='section-buttons-group-right'>
|
||||
<div>
|
||||
|
|
|
@ -35,7 +35,6 @@ import WidgetValue from './widgets/value';
|
|||
import WidgetPlot from './widgets/plot';
|
||||
import WidgetTable from './widgets/table';
|
||||
import WidgetLabel from './widgets/label';
|
||||
import WidgetPlotTable from './widgets/plot-table';
|
||||
import WidgetImage from './widgets/image';
|
||||
import WidgetButton from './widgets/button';
|
||||
import WidgetInput from './widgets/input';
|
||||
|
@ -72,8 +71,12 @@ class Widget extends React.Component {
|
|||
|
||||
for (let id of props.data.signalIDs){
|
||||
let signal = signals.find(s => s.id === id);
|
||||
let config = configs.find(m => m.id === signal.configID);
|
||||
icIDs[signal.id] = config.icID;
|
||||
if (signal !== undefined) {
|
||||
let config = configs.find(m => m.id === signal.configID);
|
||||
if (config !== undefined){
|
||||
icIDs[signal.id] = config.icID;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -81,19 +84,40 @@ class Widget extends React.Component {
|
|||
signals: signals,
|
||||
icIDs: icIDs,
|
||||
files: FileStore.getState(),
|
||||
|
||||
sequence: prevState != null ? prevState.sequence + 1 : 0,
|
||||
|
||||
sessionToken: LoginStore.getState().token
|
||||
};
|
||||
}
|
||||
|
||||
inputDataChanged(widget, data) {
|
||||
inputDataChanged(widget, data, controlID) {
|
||||
// controlID is the path to the widget customProperty that is changed (for example 'value')
|
||||
|
||||
// modify the widget customProperty
|
||||
if (controlID !== '') {
|
||||
let updatedWidget = JSON.parse(JSON.stringify(widget));
|
||||
updatedWidget.customProperties[controlID] = data;
|
||||
AppDispatcher.dispatch({
|
||||
type: 'widgets/start-edit',
|
||||
token: this.state.sessionToken,
|
||||
data: updatedWidget
|
||||
});
|
||||
}
|
||||
|
||||
// The following assumes that a widget modifies/ uses exactly one signal
|
||||
|
||||
// get the signal with the selected signal ID
|
||||
let signalID = widget.signalIDs[0];
|
||||
let signal = this.state.signals.filter(s => s.id === signalID)
|
||||
if (signal.length === 0){
|
||||
console.warn("Unable to send signal for signal ID", signalID, ". Signal not found.");
|
||||
return;
|
||||
}
|
||||
// determine ID of infrastructure component related to signal[0]
|
||||
// Remark: there is only one selected signal for an input type widget
|
||||
let icID = this.state.icIDs[signal[0].id];
|
||||
AppDispatcher.dispatch({
|
||||
type: 'icData/inputChanged',
|
||||
ic: this.state.icIDs[0],
|
||||
signal: this.state.signals[0].index,
|
||||
ic: icID,
|
||||
signal: signal[0].index,
|
||||
data
|
||||
});
|
||||
}
|
||||
|
@ -101,37 +125,102 @@ class Widget extends React.Component {
|
|||
createWidget(widget) {
|
||||
|
||||
if (widget.type === 'CustomAction') {
|
||||
return <WidgetCustomAction widget={widget} data={this.state.icData} dummy={this.state.sequence} signals={this.state.signals} icIDs={this.state.icIDs} />
|
||||
return <WidgetCustomAction
|
||||
widget={widget}
|
||||
data={this.state.icData}
|
||||
signals={this.state.signals}
|
||||
icIDs={this.state.icIDs}
|
||||
/>
|
||||
} else if (widget.type === 'Action') {
|
||||
return <WidgetAction widget={widget} data={this.state.icData} dummy={this.state.sequence} />
|
||||
return <WidgetAction
|
||||
widget={widget}
|
||||
data={this.state.icData}
|
||||
/>
|
||||
} else if (widget.type === 'Lamp') {
|
||||
return <WidgetLamp widget={widget} data={this.state.icData} dummy={this.state.sequence} signals={this.state.signals} icIDs={this.state.icIDs} />
|
||||
return <WidgetLamp
|
||||
widget={widget}
|
||||
data={this.state.icData}
|
||||
signals={this.state.signals}
|
||||
icIDs={this.state.icIDs}
|
||||
/>
|
||||
} else if (widget.type === 'Value') {
|
||||
return <WidgetValue widget={widget} data={this.state.icData} dummy={this.state.sequence} signals={this.state.signals} icIDs={this.state.icIDs} />
|
||||
return <WidgetValue
|
||||
widget={widget}
|
||||
data={this.state.icData}
|
||||
signals={this.state.signals}
|
||||
icIDs={this.state.icIDs}
|
||||
/>
|
||||
} else if (widget.type === 'Plot') {
|
||||
return <WidgetPlot widget={widget} data={this.state.icData} dummy={this.state.sequence} signals={this.state.signals} icIDs={this.state.icIDs} paused={this.props.paused} />
|
||||
return <WidgetPlot
|
||||
widget={widget}
|
||||
data={this.state.icData}
|
||||
signals={this.state.signals}
|
||||
icIDs={this.state.icIDs}
|
||||
paused={this.props.paused}
|
||||
/>
|
||||
} else if (widget.type === 'Table') {
|
||||
return <WidgetTable widget={widget} data={this.state.icData} dummy={this.state.sequence} signals={this.state.signals} icIDs={this.state.icIDs} />
|
||||
return <WidgetTable
|
||||
widget={widget}
|
||||
data={this.state.icData}
|
||||
signals={this.state.signals}
|
||||
icIDs={this.state.icIDs}
|
||||
/>
|
||||
} else if (widget.type === 'Label') {
|
||||
return <WidgetLabel widget={widget} />
|
||||
} else if (widget.type === 'PlotTable') {
|
||||
return <WidgetPlotTable widget={widget} data={this.state.icData} dummy={this.state.sequence} signals={this.state.signals} icIDs={this.state.icIDs} editing={this.props.editing} onWidgetChange={(w) => this.props.onWidgetStatusChange(w, this.props.index)} paused={this.props.paused} />
|
||||
return <WidgetLabel
|
||||
widget={widget}
|
||||
/>
|
||||
} else if (widget.type === 'Image') {
|
||||
return <WidgetImage widget={widget} files={this.state.files} token={this.state.sessionToken} />
|
||||
return <WidgetImage
|
||||
widget={widget}
|
||||
files={this.state.files}
|
||||
token={this.state.sessionToken}
|
||||
/>
|
||||
} else if (widget.type === 'Button') {
|
||||
return <WidgetButton widget={widget} editing={this.props.editing} onInputChanged={(value) => this.inputDataChanged(widget, value)} signals={this.state.signals} />
|
||||
return <WidgetButton
|
||||
widget={widget}
|
||||
editing={this.props.editing}
|
||||
onInputChanged={(value, controlID) => this.inputDataChanged(widget, value, controlID)}
|
||||
signals={this.state.signals}
|
||||
/>
|
||||
} else if (widget.type === 'NumberInput') {
|
||||
return <WidgetInput widget={widget} editing={this.props.editing} onInputChanged={(value) => this.inputDataChanged(widget, value)} signals={this.state.signals} />
|
||||
return <WidgetInput
|
||||
widget={widget}
|
||||
editing={this.props.editing}
|
||||
onInputChanged={(value, controlID) => this.inputDataChanged(widget, value, controlID)}
|
||||
signals={this.state.signals}
|
||||
/>
|
||||
} else if (widget.type === 'Slider') {
|
||||
return <WidgetSlider widget={widget} editing={this.props.editing} onWidgetChange={(w) => this.props.onWidgetStatusChange(w, this.props.index) } onInputChanged={value => this.inputDataChanged(widget, value)} signals={this.state.signals}/>
|
||||
return <WidgetSlider
|
||||
widget={widget}
|
||||
editing={this.props.editing}
|
||||
onWidgetChange={(w) => this.props.onWidgetStatusChange(w, this.props.index) }
|
||||
onInputChanged={(value, controlID) => this.inputDataChanged(widget, value, controlID)}
|
||||
signals={this.state.signals}
|
||||
/>
|
||||
} else if (widget.type === 'Gauge') {
|
||||
return <WidgetGauge widget={widget} data={this.state.icData} editing={this.props.editing} signals={this.state.signals} icIDs={this.state.icIDs} />
|
||||
return <WidgetGauge
|
||||
widget={widget}
|
||||
data={this.state.icData}
|
||||
editing={this.props.editing}
|
||||
signals={this.state.signals}
|
||||
icIDs={this.state.icIDs}
|
||||
/>
|
||||
} else if (widget.type === 'Box') {
|
||||
return <WidgetBox widget={widget} editing={this.props.editing} />
|
||||
return <WidgetBox
|
||||
widget={widget}
|
||||
editing={this.props.editing}
|
||||
/>
|
||||
} else if (widget.type === 'HTML') {
|
||||
return <WidgetHTML widget={widget} editing={this.props.editing} />
|
||||
return <WidgetHTML
|
||||
widget={widget}
|
||||
editing={this.props.editing}
|
||||
/>
|
||||
} else if (widget.type === 'Topology') {
|
||||
return <WidgetTopology widget={widget} files={this.state.files} token={this.state.sessionToken} />
|
||||
return <WidgetTopology
|
||||
widget={widget}
|
||||
files={this.state.files}
|
||||
token={this.state.sessionToken}
|
||||
/>
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
|
@ -30,32 +30,41 @@ class WidgetButton extends Component {
|
|||
|
||||
onPress(e) {
|
||||
|
||||
if (!this.props.widget.customProperties.toggle) {
|
||||
if (e.button === 0 && !this.props.widget.customProperties.toggle) {
|
||||
this.setState({ pressed: true });
|
||||
this.valueChanged(this.props.widget.customProperties.on_value);
|
||||
}
|
||||
}
|
||||
|
||||
onRelease(e) {
|
||||
|
||||
let nextState = false;
|
||||
if (this.props.widget.customProperties.toggle) {
|
||||
nextState = !this.state.pressed;
|
||||
|
||||
if (e.button === 0) {
|
||||
let nextState = false;
|
||||
if (this.props.widget.customProperties.toggle) {
|
||||
nextState = !this.state.pressed;
|
||||
}
|
||||
this.props.widget.customProperties.pressed = nextState;
|
||||
this.setState({pressed: nextState});
|
||||
this.valueChanged(nextState ? this.props.widget.customProperties.on_value : this.props.widget.customProperties.off_value);
|
||||
}
|
||||
this.props.widget.customProperties.pressed = nextState;
|
||||
this.setState({ pressed: nextState });
|
||||
this.valueChanged(nextState ? this.props.widget.customProperties.on_value : this.props.widget.customProperties.off_value);
|
||||
}
|
||||
|
||||
valueChanged(newValue) {
|
||||
if (this.props.onInputChanged)
|
||||
this.props.onInputChanged(newValue);
|
||||
this.props.onInputChanged(newValue, 'pressed');
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="button-widget full">
|
||||
<Button className="full" active={ this.state.pressed } disabled={ this.props.editing } onMouseDown={ (e) => this.onPress(e) } onMouseUp={ (e) => this.onRelease(e) }>{this.props.widget.name}</Button>
|
||||
<Button
|
||||
className="full"
|
||||
active={ this.state.pressed }
|
||||
disabled={ this.props.editing }
|
||||
onMouseDown={ (e) => this.onPress(e) }
|
||||
onMouseUp={ (e) => this.onRelease(e) }>
|
||||
{this.props.widget.name}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -29,9 +29,10 @@ class WidgetGauge extends Component {
|
|||
this.state = {
|
||||
value: 0,
|
||||
unit: '',
|
||||
signalID: '',
|
||||
minValue: null,
|
||||
maxValue: null,
|
||||
useColorZones: false,
|
||||
colorZones: [],
|
||||
useMinMax: false,
|
||||
useMinMaxChange: false,
|
||||
};
|
||||
|
@ -48,13 +49,7 @@ class WidgetGauge extends Component {
|
|||
}
|
||||
|
||||
componentDidUpdate(prevProps: Readonly<P>, prevState: Readonly<S>, snapshot: SS): void {
|
||||
if(prevState.minValue !== this.state.minValue){
|
||||
this.gauge.setMinValue(this.state.minValue);
|
||||
}
|
||||
if(prevState.maxValue !== this.state.maxValue){
|
||||
this.gauge.maxValue = this.state.maxValue
|
||||
}
|
||||
|
||||
|
||||
// update gauge's value
|
||||
if(prevState.value !== this.state.value){
|
||||
this.gauge.set(this.state.value)
|
||||
|
@ -65,8 +60,8 @@ class WidgetGauge extends Component {
|
|||
}
|
||||
|
||||
// update labels
|
||||
if(prevState.minValue !== this.state.minValue || prevState.maxValue !== this.state.maxValue || prevState.useColorZones !== this.state.useColorZones
|
||||
|| prevState.useMinMax !== this.state.useMinMax){
|
||||
if(prevState.minValue !== this.state.minValue || prevState.maxValue !== this.state.maxValue || prevState.colorZones !== this.state.colorZones
|
||||
|| prevState.useMinMax !== this.state.useMinMax || prevState.signalID !== this.state.signalID){
|
||||
this.gauge = new Gauge(this.gaugeCanvas).setOptions(this.computeGaugeOptions(this.props.widget));
|
||||
this.gauge.maxValue = this.state.maxValue;
|
||||
this.gauge.setMinValue(this.state.minValue);
|
||||
|
@ -80,30 +75,35 @@ class WidgetGauge extends Component {
|
|||
static getDerivedStateFromProps(props, state){
|
||||
|
||||
if(props.widget.signalIDs.length === 0){
|
||||
return null;
|
||||
return{ value: 0, minValue: 0, maxValue: 10};
|
||||
}
|
||||
|
||||
// get the signal with the selected signal ID
|
||||
let signalID = props.widget.signalIDs[0];
|
||||
let signal = props.signals.filter(s => s.id === signalID)
|
||||
// determine ID of infrastructure component related to signal[0] (there is only one signal for a lamp widget)
|
||||
let icID = props.icIDs[signal[0].id];
|
||||
|
||||
let returnState = {}
|
||||
|
||||
returnState["useColorZones"] = props.widget.customProperties.colorZones;
|
||||
returnState["colorZones"] = props.widget.customProperties.zones;
|
||||
|
||||
if(signalID){
|
||||
returnState["signalID"] = signalID;
|
||||
}
|
||||
// Update unit (assuming there is exactly one signal for this widget)
|
||||
let signalID = props.widget.signalIDs[0];
|
||||
let widgetSignal = props.signals.find(sig => sig.id === signalID);
|
||||
if(widgetSignal !== undefined){
|
||||
returnState["unit"] = widgetSignal.unit;
|
||||
if(signal !== undefined){
|
||||
returnState["unit"] = signal[0].unit;
|
||||
}
|
||||
|
||||
const ICid = props.icIDs[0];
|
||||
|
||||
// update value
|
||||
|
||||
// check if data available
|
||||
if (props.data == null
|
||||
|| props.data[ICid] == null
|
||||
|| props.data[ICid].output == null
|
||||
|| props.data[ICid].output.values == null
|
||||
|| props.data[ICid].output.values.length === 0
|
||||
|| props.data[ICid].output.values[0].length === 0) {
|
||||
returnState["value"] = 0;
|
||||
return returnState;
|
||||
|| props.data[icID] == null
|
||||
|| props.data[icID].output == null
|
||||
|| props.data[icID].output.values == null) {
|
||||
return{ value: 0, minValue: 0, maxValue: 10};
|
||||
}
|
||||
|
||||
// memorize if min or max value is updated
|
||||
|
@ -112,14 +112,14 @@ class WidgetGauge extends Component {
|
|||
let updateMaxValue = false;
|
||||
|
||||
// check if value has changed
|
||||
const signalData = props.data[ICid].output.values[widgetSignal.index];
|
||||
const data = props.data[icID].output.values[signal[0].index-1];
|
||||
// Take just 3 decimal positions
|
||||
// Note: Favor this method over Number.toFixed(n) in order to avoid a type conversion, since it returns a String
|
||||
if (signalData != null) {
|
||||
const value = Math.round(signalData[signalData.length - 1].y * 1e3) / 1e3;
|
||||
if (data != null) {
|
||||
const value = Math.round(data[data.length - 1].y * 1e3) / 1e3;
|
||||
let minValue = null;
|
||||
let maxValue = null;
|
||||
|
||||
|
||||
if ((state.value !== value && value != null) || props.widget.customProperties.valueUseMinMax || state.useMinMaxChange) {
|
||||
//value has changed
|
||||
updateValue = true;
|
||||
|
@ -129,14 +129,14 @@ class WidgetGauge extends Component {
|
|||
|
||||
minValue = state.minValue;
|
||||
maxValue = state.maxValue;
|
||||
|
||||
if (minValue == null || state.useMinMaxChange) {
|
||||
|
||||
if (minValue == null || (!props.widget.customProperties.valueUseMinMax && (value < minValue || signalID !== state.signalID)) ||state.useMinMaxChange) {
|
||||
minValue = value - 0.5;
|
||||
updateLabels = true;
|
||||
updateMinValue = true;
|
||||
}
|
||||
|
||||
if (maxValue == null || state.useMinMaxChange) {
|
||||
if (maxValue == null || (!props.widget.customProperties.valueUseMinMax && (value > maxValue || signalID !== state.signalID)) || state.useMinMaxChange) {
|
||||
maxValue = value + 0.5;
|
||||
updateLabels = true;
|
||||
updateMaxValue = true;
|
||||
|
@ -144,17 +144,12 @@ class WidgetGauge extends Component {
|
|||
}
|
||||
|
||||
if (props.widget.customProperties.valueUseMinMax) {
|
||||
if (state.minValue > props.widget.customProperties.valueMin) {
|
||||
minValue = props.widget.customProperties.valueMin;
|
||||
updateMinValue = true;
|
||||
updateLabels = true;
|
||||
}
|
||||
|
||||
if (state.maxValue < props.widget.customProperties.valueMax) {
|
||||
maxValue = props.widget.customProperties.valueMax;
|
||||
updateMaxValue = true;
|
||||
updateLabels = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (updateLabels === false && state.gauge) {
|
||||
|
@ -174,10 +169,7 @@ class WidgetGauge extends Component {
|
|||
if(props.widget.customProperties.valueUseMinMax !== state.useMinMax){
|
||||
returnState["useMinMax"] = props.widget.customProperties.valueUseMinMax;
|
||||
}
|
||||
if(props.widget.customProperties.colorZones !== state.useColorZones){
|
||||
returnState["useColorZones"] = props.widget.customProperties.colorZones;
|
||||
}
|
||||
|
||||
|
||||
// prepare returned state
|
||||
if(updateValue === true){
|
||||
returnState["value"] = value;
|
||||
|
@ -209,18 +201,19 @@ class WidgetGauge extends Component {
|
|||
for (let i = 0; i < labelCount; i++) {
|
||||
labels.push(minValue + labelStep * i);
|
||||
}
|
||||
|
||||
|
||||
// calculate zones
|
||||
let zones = this.props.widget.customProperties.colorZones ? this.props.widget.customProperties.zones : null;
|
||||
if (zones != null) {
|
||||
// adapt range 0-100 to actual min-max
|
||||
const step = (maxValue - minValue) / 100;
|
||||
|
||||
|
||||
zones = zones.map(zone => {
|
||||
return Object.assign({}, zone, { min: (zone.min * step) + +minValue, max: zone.max * step + +minValue, strokeStyle: '#' + zone.strokeStyle });
|
||||
});
|
||||
}
|
||||
|
||||
if(this.state.signalID !== ''){
|
||||
this.gauge.setOptions({
|
||||
staticLabels: {
|
||||
font: '10px "Helvetica Neue"',
|
||||
|
@ -231,6 +224,7 @@ class WidgetGauge extends Component {
|
|||
staticZones: zones
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
computeGaugeOptions(widget) {
|
||||
return {
|
||||
|
@ -245,8 +239,8 @@ class WidgetGauge extends Component {
|
|||
colorStop: '#6EA2B0',
|
||||
strokeColor: '#E0E0E0',
|
||||
highDpiSupport: true,
|
||||
limitMax: false,
|
||||
limitMin: false
|
||||
limitMax: widget.customProperties.valueUseMinMax || false,
|
||||
limitMin: widget.customProperties.valueUseMinMax || false
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -31,53 +31,47 @@ class WidgetInput extends Component {
|
|||
|
||||
static getDerivedStateFromProps(props, state){
|
||||
|
||||
let returnState = {};
|
||||
let value = ''
|
||||
let unit = ''
|
||||
|
||||
if(props.widget.customProperties.value !== ''){
|
||||
returnState["value"] = props.widget.customProperties.value;
|
||||
}
|
||||
|
||||
if(props.widget.signalIDs.length === 0){
|
||||
if (props.widget.customProperties.default_value && state.value === undefined && props.widget.customProperties.value === '') {
|
||||
returnState["value"] = props.widget.customProperties.default_value;
|
||||
} else { // if no default available
|
||||
if (returnState !== {}){
|
||||
return returnState;
|
||||
}
|
||||
else{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update value
|
||||
if (props.widget.customProperties.default_value && this.state.value === undefined && props.widget.customProperties.value === '') {
|
||||
returnState["value"] = props.widget.customProperties.default_value;
|
||||
if(props.widget.customProperties.hasOwnProperty('value') && props.widget.customProperties.value !== state.value){
|
||||
// set value to customProperties.value if this property exists and the value is different from current state
|
||||
value = Number(props.widget.customProperties.value);
|
||||
} else if (props.widget.customProperties.hasOwnProperty('default_value') && state.value === ''){
|
||||
// if customProperties.default_value exists and value has been assigned yet, set the value to the default_value
|
||||
value = Number(props.widget.customProperties.default_value)
|
||||
}
|
||||
|
||||
// Update unit (assuming there is exactly one signal for this widget)
|
||||
let signalID = props.widget.signalIDs[0];
|
||||
let signal = props.signals.find(sig => sig.id === signalID);
|
||||
if(signal !== undefined){
|
||||
returnState["unit"] = signal.unit;
|
||||
unit = signal.unit;
|
||||
}
|
||||
|
||||
if (returnState !== {}){
|
||||
return returnState;
|
||||
}
|
||||
else{
|
||||
return null;
|
||||
if (unit !== '' && value !== ''){
|
||||
// unit and value have changed
|
||||
return {unit: unit, value: value};
|
||||
} else if (unit !== ''){
|
||||
// only unit has changed
|
||||
return {unit: unit}
|
||||
} else if (value !== ''){
|
||||
// only value has changed
|
||||
return {value: value}
|
||||
} else{
|
||||
// nothing has changed
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
valueIsChanging(newValue) {
|
||||
this.setState({ value: newValue });
|
||||
this.props.widget.customProperties.value = newValue;
|
||||
this.setState({ value: Number(newValue) });
|
||||
this.props.widget.customProperties.value = Number(newValue);
|
||||
}
|
||||
|
||||
valueChanged(newValue) {
|
||||
if (this.props.onInputChanged) {
|
||||
this.props.onInputChanged(newValue);
|
||||
this.props.onInputChanged(Number(newValue), 'value');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,7 +91,16 @@ class WidgetInput extends Component {
|
|||
</Col>
|
||||
<Col>
|
||||
<InputGroup>
|
||||
<FormControl type="number" step="any" disabled={ this.props.editing } onKeyPress={ (e) => this.handleKeyPress(e) } onBlur={ (e) => this.valueChanged(this.state.value) } onChange={ (e) => this.valueIsChanging(e.target.value) } placeholder="Enter value" value={ this.state.value } />
|
||||
<FormControl
|
||||
type="number"
|
||||
step="any"
|
||||
disabled={ this.props.editing }
|
||||
onKeyPress={ (e) => this.handleKeyPress(e) }
|
||||
onBlur={ (e) => this.valueChanged(this.state.value) }
|
||||
onChange={ (e) => this.valueIsChanging(e.target.value) }
|
||||
placeholder="Enter value"
|
||||
value={ this.state.value }
|
||||
/>
|
||||
{this.props.widget.customProperties.showUnit? (
|
||||
<InputGroup.Append>
|
||||
<InputGroup.Text>{this.state.unit}</InputGroup.Text>
|
||||
|
|
|
@ -25,7 +25,6 @@ class WidgetLamp extends Component {
|
|||
|
||||
this.state = {
|
||||
value: '',
|
||||
threshold: 0
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -34,28 +33,31 @@ class WidgetLamp extends Component {
|
|||
return{ value: ''};
|
||||
}
|
||||
|
||||
const ic = props.icIDs[0];
|
||||
// get the signal with the selected signal ID
|
||||
let signalID = props.widget.signalIDs[0];
|
||||
let widgetSignal = props.signals.find(sig => sig.id === signalID);
|
||||
let signal = props.signals.filter(s => s.id === signalID)
|
||||
// determine ID of infrastructure component related to signal[0] (there is only one signal for a lamp widget)
|
||||
let icID = props.icIDs[signal[0].id];
|
||||
|
||||
// update value
|
||||
// check if data available
|
||||
if (props.data == null
|
||||
|| props.data[ic] == null
|
||||
|| props.data[ic].output == null
|
||||
|| props.data[ic].output.values == null) {
|
||||
|| props.data[icID] == null
|
||||
|| props.data[icID].output == null
|
||||
|| props.data[icID].output.values == null) {
|
||||
return{value:''};
|
||||
}
|
||||
|
||||
// check if value has changed
|
||||
const signalData = props.data[ic].output.values[widgetSignal.index];
|
||||
if (signalData != null && state.value !== signalData[signalData.length - 1].y) {
|
||||
return { value: signalData[signalData.length - 1].y };
|
||||
const data = props.data[icID].output.values[signal[0].index-1];
|
||||
if (data != null && Number(state.value) !== data[data.length - 1].y) {
|
||||
return { value: data[data.length - 1].y };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
let colors = EditWidgetColorControl.ColorPalette;
|
||||
let color;
|
||||
|
||||
|
|
|
@ -1,130 +0,0 @@
|
|||
/**
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { FormGroup } from 'react-bootstrap';
|
||||
import Plot from '../widget-plot/plot';
|
||||
import PlotLegend from '../widget-plot/plot-legend';
|
||||
|
||||
class WidgetPlotTable extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
signals: [],
|
||||
data: []
|
||||
};
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props, state){
|
||||
let intersection = []
|
||||
let data = [];
|
||||
let signalID, sig;
|
||||
for (signalID of props.widget.signalIDs) {
|
||||
for (sig of props.signals) {
|
||||
if (signalID === sig.id) {
|
||||
intersection.push(sig);
|
||||
|
||||
// sig is a selected signal, get data
|
||||
// determine ID of infrastructure component related to signal (via config)
|
||||
let icID = props.icIDs[sig.id]
|
||||
|
||||
// distinguish between input and output signals
|
||||
if (sig.direction === "out") {
|
||||
if (props.data[icID] != null && props.data[icID].output != null && props.data[icID].output.values != null) {
|
||||
if (props.data[icID].output.values[sig.index-1] !== undefined) {
|
||||
data.push(props.data[icID].output.values[sig.index-1]);
|
||||
}
|
||||
}
|
||||
} else if (sig.direction === "in") {
|
||||
if (props.data[icID] != null && props.data[icID].input != null && props.data[icID].input.values != null) {
|
||||
if (props.data[icID].input.values[sig.index-1] !== undefined) {
|
||||
data.push(props.data[icID].input.values[sig.index-1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // sig is selected signal
|
||||
} // loop over props.signals
|
||||
} // loop over selected signals
|
||||
|
||||
return {signals: intersection, data: data}
|
||||
}
|
||||
|
||||
// updateSignalSelection(signal, checked) {
|
||||
// // Update the selected signals and propagate to parent component
|
||||
// var new_widget = Object.assign({}, this.props.widget, {
|
||||
// checkedSignals: checked ? this.state.signals.concat(signal) : this.state.signals.filter((idx) => idx !== signal)
|
||||
// });
|
||||
// this.props.onWidgetChange(new_widget);
|
||||
// }
|
||||
|
||||
render() {
|
||||
let checkBoxes = [];
|
||||
|
||||
let showLegend = false;
|
||||
if (this.state.signals.length > 0) {
|
||||
|
||||
showLegend = true;
|
||||
|
||||
// Create checkboxes using the signal indices from component config
|
||||
// checkBoxes = this.state.signals.map((signal) => {
|
||||
// let checked = this.state.signals.indexOf(signal) > -1;
|
||||
// let chkBxClasses = classNames({
|
||||
// 'btn': true,
|
||||
// 'btn-default': true,
|
||||
// 'active': checked
|
||||
// });
|
||||
// return <FormCheck key={signal.index} className={chkBxClasses} checked={checked} disabled={this.props.editing}
|
||||
// onChange={(e) => this.updateSignalSelection(signal, e.target.checked)}> {signal.name} </FormCheck>
|
||||
// });
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="plot-table-widget" ref="wrapper">
|
||||
<div className="content">
|
||||
<div className="table-plot-row">
|
||||
<div className="widget-table">
|
||||
{checkBoxes.length > 0 ? (
|
||||
<FormGroup className="btn-group-vertical">
|
||||
{checkBoxes}
|
||||
</FormGroup>
|
||||
) : (<small>Use edit menu to change selected signals.</small>)
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className="widget-plot">
|
||||
<Plot
|
||||
data={this.state.data}
|
||||
time={this.props.widget.customProperties.time}
|
||||
width={this.props.widget.width - 100}
|
||||
height={this.props.widget.height - 55}
|
||||
yMin={this.props.widget.customProperties.yMin}
|
||||
yMax={this.props.widget.customProperties.yMax}
|
||||
yUseMinMax={this.props.widget.customProperties.yUseMinMax}
|
||||
paused={this.props.paused}
|
||||
yLabel={this.props.widget.customProperties.ylabel}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{showLegend ? (
|
||||
<PlotLegend signals={this.state.signals}/>) : (<div></div>)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
export default WidgetPlotTable;
|
|
@ -40,47 +40,38 @@ class WidgetSlider extends Component {
|
|||
}
|
||||
|
||||
static getDerivedStateFromProps(props, state){
|
||||
let returnState = {};
|
||||
|
||||
if(props.widget.customProperties.value !== ''){
|
||||
returnState["value"] = props.widget.customProperties.value;
|
||||
let value = ''
|
||||
let unit = ''
|
||||
|
||||
if(props.widget.customProperties.hasOwnProperty('value') && props.widget.customProperties.value !== state.value){
|
||||
// set value to customProperties.value if this property exists and the value is different from current state
|
||||
value = Number(props.widget.customProperties.value);
|
||||
} else if (props.widget.customProperties.hasOwnProperty('default_value') && state.value === ''){
|
||||
// if customProperties.default_value exists and value has been assigned yet, set the value to the default_value
|
||||
value = Number(props.widget.customProperties.default_value)
|
||||
}
|
||||
|
||||
if(props.widget.signalIDs.length === 0){
|
||||
|
||||
// set value to default
|
||||
if (props.widget.customProperties.default_value && state.value === undefined && props.widget.customProperties.value === '') {
|
||||
returnState["value"] = props.widget.customProperties.default_value;
|
||||
} else { // if no default available
|
||||
if (returnState !== {}){
|
||||
return returnState;
|
||||
}
|
||||
else{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Update value
|
||||
if (props.widget.customProperties.default_value && state.value === undefined && props.widget.customProperties.value === '') {
|
||||
returnState["value"] = props.widget.customProperties.default_value;
|
||||
}
|
||||
|
||||
// Update unit (assuming there is exactly one signal for this widget)
|
||||
let signalID = props.widget.signalIDs[0];
|
||||
let signal = props.signals.find(sig => sig.id === signalID);
|
||||
if(signal !== undefined){
|
||||
returnState["unit"] = signal.unit;
|
||||
unit = signal.unit;
|
||||
}
|
||||
|
||||
if (returnState !== {}){
|
||||
return returnState;
|
||||
if (unit !== '' && value !== ''){
|
||||
// unit and value have changed
|
||||
return {unit: unit, value: value};
|
||||
} else if (unit !== ''){
|
||||
// only unit has changed
|
||||
return {unit: unit}
|
||||
} else if (value !== ''){
|
||||
// only value has changed
|
||||
return {value: value}
|
||||
} else {
|
||||
// nothing has changed
|
||||
return null
|
||||
}
|
||||
else{
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Readonly<P>, prevState: Readonly<S>, snapshot: SS): void {
|
||||
|
@ -114,7 +105,7 @@ class WidgetSlider extends Component {
|
|||
|
||||
valueChanged(newValue) {
|
||||
if (this.props.onInputChanged) {
|
||||
this.props.onInputChanged(newValue);
|
||||
this.props.onInputChanged(newValue, 'value');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,7 +115,7 @@ class WidgetSlider extends Component {
|
|||
let fields = {
|
||||
name: this.props.widget.name,
|
||||
control: <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) }/>,
|
||||
value: <span>{ format('.3s')(Number.parseFloat(this.state.value)) }</span>,
|
||||
value: <span>{ format('.2f')(Number.parseFloat(this.state.value)) }</span>,
|
||||
unit: <span className="signal-unit">{ this.state.unit }</span>
|
||||
}
|
||||
|
||||
|
|
|
@ -27,72 +27,55 @@ class WidgetTable extends Component {
|
|||
|
||||
this.state = {
|
||||
rows: [],
|
||||
sequence: null,
|
||||
showUnit: false
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
static getDerivedStateFromProps(props, state){
|
||||
|
||||
if(props.widget.signalIDs.length === 0){
|
||||
return{
|
||||
rows: [],
|
||||
sequence: null,
|
||||
};
|
||||
}
|
||||
let rows = [];
|
||||
let signalID, sig;
|
||||
for (signalID of props.widget.signalIDs) {
|
||||
for (sig of props.signals) {
|
||||
if (signalID === sig.id) {
|
||||
// sig is a selected signal, get data
|
||||
// determine ID of infrastructure component related to signal (via config)
|
||||
let icID = props.icIDs[sig.id]
|
||||
|
||||
// distinguish between input and output signals
|
||||
if (sig.direction === "out") {
|
||||
if (props.data[icID] != null && props.data[icID].output != null && props.data[icID].output.values != null) {
|
||||
if (props.data[icID].output.values[sig.index-1] !== undefined) {
|
||||
let data = props.data[icID].output.values[sig.index-1];
|
||||
rows.push({
|
||||
name: sig.name,
|
||||
unit: sig.unit,
|
||||
value: data[data.length - 1].y
|
||||
});
|
||||
|
||||
const ICid = props.icIDs[0];
|
||||
let widgetSignals = props.signals.find(sig => {
|
||||
for (let id of props.widget.signalIDs){
|
||||
if (id === sig.id){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (sig.direction === "in") {
|
||||
if (props.data[icID] != null && props.data[icID].input != null && props.data[icID].input.values != null) {
|
||||
if (props.data[icID].input.values[sig.index-1] !== undefined) {
|
||||
let data = props.data[icID].input.values[sig.index-1];
|
||||
rows.push({
|
||||
name: sig.name,
|
||||
unit: sig.unit,
|
||||
value: data[data.length - 1].y
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} // sig is selected signal
|
||||
} // loop over props.signals
|
||||
} // loop over selected signals
|
||||
|
||||
// check data
|
||||
if (props.data == null
|
||||
|| props.data[ICid] == null
|
||||
|| props.data[ICid].output == null
|
||||
|| props.data[ICid].output.values.length === 0
|
||||
|| props.data[ICid].output.values[0].length === 0) {
|
||||
return {rows: rows}
|
||||
|
||||
// clear values
|
||||
return{
|
||||
rows: [],
|
||||
sequence: null,
|
||||
showUnit: false,
|
||||
};
|
||||
}
|
||||
|
||||
// get rows
|
||||
const rows = [];
|
||||
|
||||
props.data[ICid].output.values.forEach((signal, index) => {
|
||||
let s = widgetSignals.find( sig => sig.index === index);
|
||||
// if the signal is used by the widget
|
||||
if (s !== undefined) {
|
||||
// push data of the signal
|
||||
rows.push({
|
||||
name: s.name,
|
||||
unit: s.unit,
|
||||
value: signal[signal.length - 1].y
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
showUnit: props.showUnit,
|
||||
rows: rows,
|
||||
sequence: props.data[ICid].output.sequence
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
|
||||
let rows = this.state.rows;
|
||||
|
||||
if(rows.length === 0){
|
||||
|
|
|
@ -33,31 +33,28 @@ class WidgetValue extends Component {
|
|||
return null;
|
||||
}
|
||||
|
||||
// TODO does the following line make sense?
|
||||
const ICid = props.icIDs[0];
|
||||
// get the signal with the selected signal ID
|
||||
let signalID = props.widget.signalIDs[0];
|
||||
let signal = props.signals.find(sig => sig.id === signalID);
|
||||
let signal = props.signals.filter(s => s.id === signalID)
|
||||
// determine ID of infrastructure component related to signal[0] (there is only one signal for a value widget)
|
||||
let icID = props.icIDs[signal[0].id];
|
||||
|
||||
|
||||
// update value
|
||||
let value = '';
|
||||
if (props.data == null
|
||||
|| props.data[ICid] == null
|
||||
|| props.data[ICid].output == null
|
||||
|| props.data[ICid].output.values == null) {
|
||||
// check if data available
|
||||
let value = ''
|
||||
if (props.data == null || props.data[icID] == null || props.data[icID].output == null || props.data[icID].output.values == null) {
|
||||
value = '';
|
||||
} else {
|
||||
// check if value has changed
|
||||
const signalData = props.data[ICid].output.values[signal.index];
|
||||
if (signalData != null && state.value !== signalData[signalData.length - 1].y) {
|
||||
value = signalData[signalData.length - 1].y
|
||||
const data = props.data[icID].output.values[signal[0].index - 1];
|
||||
if (data != null && Number(state.value) !== data[data.length - 1].y) {
|
||||
value = data[data.length - 1].y;
|
||||
}
|
||||
}
|
||||
|
||||
// Update unit (assuming there is exactly one signal for this widget)
|
||||
let unit = '';
|
||||
if(signal !== undefined){
|
||||
unit = signal.unit;
|
||||
unit = signal[0].unit;
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -77,7 +74,7 @@ class WidgetValue extends Component {
|
|||
<span style={{ fontSize: this.props.widget.customProperties.textSize + 'px', flex: 'none', width: value_width }}>{Number.isNaN(value_to_render) ? NaN : format('.3s')(value_to_render)}</span>
|
||||
{this.props.widget.customProperties.showUnit &&
|
||||
<span style={{ fontSize: this.props.widget.customProperties.textSize + 'px', flex: 'none', width: unit_width}}>[{this.state.unit}]</span>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue