mirror of
https://git.rwth-aachen.de/acs/public/villas/web/
synced 2025-03-09 00:00:01 +01:00
Merge branch 'develop' into signal-auto-config
# Conflicts: # src/ic/ics.js
This commit is contained in:
commit
ca4832b6d6
36 changed files with 725 additions and 324 deletions
14
package-lock.json
generated
14
package-lock.json
generated
|
@ -8870,6 +8870,11 @@
|
|||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"moment": {
|
||||
"version": "2.27.0",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz",
|
||||
"integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ=="
|
||||
},
|
||||
"move-concurrently": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
|
||||
|
@ -8902,6 +8907,15 @@
|
|||
"resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz",
|
||||
"integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE="
|
||||
},
|
||||
"multiselect-react-dropdown": {
|
||||
"version": "1.5.7",
|
||||
"resolved": "https://registry.npmjs.org/multiselect-react-dropdown/-/multiselect-react-dropdown-1.5.7.tgz",
|
||||
"integrity": "sha512-bDlXYEzpV/5p5G5nIFRrZ/Ndf8CSYWliZ62n/5imfjLy0K2/dNx6sFmk4W/Phq83bzPUDK/RI4553yCk6YzZwg==",
|
||||
"requires": {
|
||||
"react": "^16.7.0",
|
||||
"react-dom": "^16.7.0"
|
||||
}
|
||||
},
|
||||
"mute-stream": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
"jszip": "^3.5.0",
|
||||
"libcimsvg": "git+https://git.rwth-aachen.de/acs/public/cim/pintura-npm-package.git",
|
||||
"lodash": "^4.17.15",
|
||||
"moment": "^2.27.0",
|
||||
"multiselect-react-dropdown": "^1.5.7",
|
||||
"node-sass": "^4.14.1",
|
||||
"popper.js": "^1.16.1",
|
||||
"prop-types": "^15.7.2",
|
||||
|
|
|
@ -128,7 +128,12 @@ class ArrayStore extends ReduceStore {
|
|||
return super.reduce(state, action);
|
||||
|
||||
case this.type + '/start-edit':
|
||||
if(action.id){
|
||||
this.dataManager.update(action.data, action.token,action.param,action.id);
|
||||
}
|
||||
else{
|
||||
this.dataManager.update(action.data, action.token,action.param);
|
||||
}
|
||||
return state;
|
||||
|
||||
case this.type + '/edited':
|
||||
|
|
|
@ -68,7 +68,11 @@ class RestDataManager {
|
|||
}
|
||||
}
|
||||
case 'remove/update':
|
||||
if(param === null){
|
||||
if(id !== null){
|
||||
return this.makeURL(this.url + '/' + id);
|
||||
|
||||
}
|
||||
else if(param === null){
|
||||
return this.makeURL(this.url + '/' + object.id);
|
||||
}
|
||||
else{
|
||||
|
@ -201,11 +205,11 @@ class RestDataManager {
|
|||
});
|
||||
}
|
||||
|
||||
update(object, token = null, param = null) {
|
||||
update(object, token = null, param = null, id = null) {
|
||||
var obj = {};
|
||||
obj[this.type] = this.filterKeys(object);
|
||||
|
||||
RestAPI.put(this.requestURL('remove/update',null,param,object), obj, token).then(response => {
|
||||
RestAPI.put(this.requestURL('remove/update',id,param,object), obj, token).then(response => {
|
||||
AppDispatcher.dispatch({
|
||||
type: this.type + 's/edited',
|
||||
data: response[this.type]
|
||||
|
|
|
@ -46,7 +46,7 @@ class Dialog extends React.Component {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<Modal keyboard show={this.props.show} onEnter={this.props.onReset} onHide={this.cancelModal} onKeyPress={this.onKeyPress}>
|
||||
<Modal size={this.props.size || 'sm'} keyboard show={this.props.show} onEnter={this.props.onReset} onHide={this.cancelModal} onKeyPress={this.onKeyPress}>
|
||||
<Modal.Header>
|
||||
<Modal.Title>{this.props.title}</Modal.Title>
|
||||
</Modal.Header>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
import React, { Component } from 'react';
|
||||
import _ from 'lodash';
|
||||
import { Table, Button, FormControl, FormLabel, FormCheck } from 'react-bootstrap';
|
||||
import { Table, Button, FormControl, FormLabel, FormCheck, Tooltip, OverlayTrigger } from 'react-bootstrap';
|
||||
import { Link } from 'react-router-dom';
|
||||
import Icon from './icon';
|
||||
|
||||
|
@ -103,11 +103,13 @@ class CustomTable extends Component {
|
|||
|
||||
// add buttons
|
||||
if (child.props.editButton) {
|
||||
cell.push(<Button variant='table-control-button' onClick={() => child.props.onEdit(index)} disabled={child.props.onEdit == null}><Icon icon='edit' /></Button>);
|
||||
cell.push(<OverlayTrigger key={0} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"edit"}`}> Edit </Tooltip>} >
|
||||
<Button variant='table-control-button' onClick={() => child.props.onEdit(index)} disabled={child.props.onEdit == null}><Icon icon='edit' /></Button></OverlayTrigger>);
|
||||
}
|
||||
|
||||
if (child.props.deleteButton) {
|
||||
cell.push(<Button variant='table-control-button' onClick={() => child.props.onDelete(index)} disabled={child.props.onDelete == null}><Icon icon='trash' /></Button>);
|
||||
cell.push(<OverlayTrigger key={1} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"delete"}`}> Delete </Tooltip>} >
|
||||
<Button variant='table-control-button' onClick={() => child.props.onDelete(index)} disabled={child.props.onDelete == null}><Icon icon='trash' /></Button></OverlayTrigger>);
|
||||
}
|
||||
|
||||
if (child.props.checkbox) {
|
||||
|
@ -117,7 +119,8 @@ class CustomTable extends Component {
|
|||
}
|
||||
|
||||
if (child.props.exportButton) {
|
||||
cell.push(<Button variant='table-control-button' onClick={() => child.props.onExport(index)} disabled={child.props.onExport == null}><Icon icon='download' /></Button>);
|
||||
cell.push(<OverlayTrigger key={2} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"export"}`}> Export </Tooltip>} >
|
||||
<Button variant='table-control-button' onClick={() => child.props.onExport(index)} disabled={child.props.onExport == null}><Icon icon='download' /></Button></OverlayTrigger>);
|
||||
}
|
||||
|
||||
return cell;
|
||||
|
|
|
@ -17,9 +17,9 @@
|
|||
|
||||
import React from 'react';
|
||||
import {FormGroup, FormControl, FormLabel} from 'react-bootstrap';
|
||||
import { Multiselect } from 'multiselect-react-dropdown'
|
||||
import Dialog from '../common/dialogs/dialog';
|
||||
import ParametersEditor from '../common/parameters-editor';
|
||||
import SelectFile from "../file/select-file";
|
||||
|
||||
class EditConfigDialog extends React.Component {
|
||||
valid = false;
|
||||
|
@ -28,12 +28,11 @@ class EditConfigDialog extends React.Component {
|
|||
super(props);
|
||||
|
||||
this.state = {
|
||||
selectedFile: null,
|
||||
name: '',
|
||||
icID: '',
|
||||
configuration: null,
|
||||
startParameters: this.props.config.startParameters,
|
||||
selectedFileID: this.props.config.selectedFileID
|
||||
fileIDs: this.props.config.fileIDs
|
||||
|
||||
};
|
||||
}
|
||||
|
@ -52,9 +51,13 @@ class EditConfigDialog extends React.Component {
|
|||
if(this.state.startParameters !== {} && JSON.stringify(this.props.config.startParameters) !== JSON.stringify(this.state.startParameters)){
|
||||
data.startParameters = this.state.startParameters;
|
||||
}
|
||||
if (parseInt(this.state.selectedFileID, 10) !== 0 &&
|
||||
this.props.config.selectedFileID !== parseInt(this.state.selectedFileID)) {
|
||||
data.selectedFileID = parseInt(this.state.selectedFileID, 10);
|
||||
|
||||
let IDs = []
|
||||
for(let e of this.state.fileIDs){
|
||||
IDs.push(e.id)
|
||||
}
|
||||
if (JSON.stringify(IDs) !== JSON.stringify(this.props.config.fileIDs)){
|
||||
data.fileIDs = IDs;
|
||||
}
|
||||
|
||||
//forward modified config to callback function
|
||||
|
@ -79,10 +82,19 @@ class EditConfigDialog extends React.Component {
|
|||
this.valid = this.isValid()
|
||||
}
|
||||
|
||||
handleSelectedFileChange(event){
|
||||
//console.log("Config file change to: ", event.target.value);
|
||||
this.setState({selectedFileID: event.target.value})
|
||||
onFileSelect(selectedList, selectedItem) {
|
||||
|
||||
this.setState({
|
||||
fileIDs: selectedList
|
||||
})
|
||||
this.valid = this.isValid()
|
||||
}
|
||||
|
||||
onFileRemove(selectedList, removedItem) {
|
||||
|
||||
this.setState({
|
||||
fileIDs: selectedList
|
||||
})
|
||||
this.valid = this.isValid()
|
||||
}
|
||||
|
||||
|
@ -91,9 +103,7 @@ class EditConfigDialog extends React.Component {
|
|||
return this.state.name !== ''
|
||||
|| this.state.icID !== ''
|
||||
|| this.state.startParameters !== {}
|
||||
|| this.state.selectedFile != null
|
||||
|| this.state.configuration != null
|
||||
|| this.state.selectedFileID !== 0;
|
||||
}
|
||||
|
||||
resetState() {
|
||||
|
@ -105,6 +115,15 @@ class EditConfigDialog extends React.Component {
|
|||
<option key={s.id} value={s.id}>{s.name}</option>
|
||||
);
|
||||
|
||||
let configFileOptions = [];
|
||||
for(let file of this.props.files) {
|
||||
configFileOptions.push(
|
||||
{name: file.name, id: file.id}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<Dialog show={this.props.show} title="Edit Component Configuration" buttonTitle="Save" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
|
||||
<form>
|
||||
|
@ -121,14 +140,14 @@ class EditConfigDialog extends React.Component {
|
|||
</FormControl>
|
||||
</FormGroup>
|
||||
|
||||
<SelectFile
|
||||
type='config'
|
||||
name='Configuration File'
|
||||
onChange={(e) => this.handleSelectedFileChange(e)}
|
||||
files={this.props.files}
|
||||
value={this.state.selectedFileID}
|
||||
scenarioID={this.props.config.scenarioID}
|
||||
sessionToken={this.props.sessionToken}
|
||||
<Multiselect
|
||||
options={configFileOptions}
|
||||
showCheckbox={true}
|
||||
selectedValues={this.state.fileIDs}
|
||||
onSelect={(selectedList, selectedItem) => this.onFileSelect(selectedList, selectedItem)}
|
||||
onRemove={(selectedList, removedItem) => this.onFileRemove(selectedList, removedItem)}
|
||||
displayValue={'name'}
|
||||
placeholder={'Select file(s)...'}
|
||||
/>
|
||||
|
||||
<FormGroup controlId='startParameters'>
|
||||
|
|
|
@ -17,15 +17,23 @@
|
|||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import { Button,OverlayTrigger, Tooltip } from 'react-bootstrap';
|
||||
import Icon from "../common/icon";
|
||||
|
||||
class DashboardButtonGroup extends React.Component {
|
||||
render() {
|
||||
const buttonStyle = {
|
||||
marginLeft: '8px'
|
||||
marginLeft: '12px',
|
||||
height: '44px',
|
||||
width : '35px'
|
||||
};
|
||||
|
||||
const iconStyle = {
|
||||
color: '#007bff',
|
||||
height: '25px',
|
||||
width : '25px'
|
||||
}
|
||||
|
||||
const buttons = [];
|
||||
let key = 0;
|
||||
|
||||
|
@ -35,46 +43,60 @@ class DashboardButtonGroup extends React.Component {
|
|||
|
||||
if (this.props.editing) {
|
||||
buttons.push(
|
||||
<Button key={key++} onClick={this.props.onSave} style={buttonStyle}>
|
||||
<Icon icon="save" /> Save
|
||||
</Button>,
|
||||
<Button key={key++} onClick={this.props.onCancel} style={buttonStyle}>
|
||||
<Icon icon="times" /> Cancel
|
||||
<OverlayTrigger key={key++} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"save"}`}> Save changes </Tooltip>} >
|
||||
<Button variant= 'light' size="lg" key={key} onClick={this.props.onSave} style={buttonStyle}>
|
||||
<Icon icon="save" style={iconStyle} />
|
||||
</Button>
|
||||
</OverlayTrigger>,
|
||||
<OverlayTrigger key={key++} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"cancel"}`}> Discard changes </Tooltip>} >
|
||||
<Button key={key} variant= 'light' size="lg" onClick={this.props.onCancel} style={buttonStyle}>
|
||||
<Icon icon="times" style={iconStyle}/>
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
} else {
|
||||
if (this.props.fullscreen !== true) {
|
||||
buttons.push(
|
||||
<Button key={key++} onClick={this.props.onFullscreen} style={buttonStyle}>
|
||||
<Icon icon="expand" /> Fullscreen
|
||||
<OverlayTrigger key={key++} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"expand"}`}> Change to fullscreen view </Tooltip>} >
|
||||
<Button key={key} variant= 'light' size="lg" onClick={this.props.onFullscreen} style={buttonStyle}>
|
||||
<Icon icon="expand" style={iconStyle}/>
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.paused) {
|
||||
buttons.push(
|
||||
<Button key={key++} onClick={this.props.onUnpause} style={buttonStyle}>
|
||||
<Icon icon="play" /> Live
|
||||
<OverlayTrigger key={key++} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"play"}`}> Continue simulation </Tooltip>} >
|
||||
<Button key={key} variant= 'light' size="lg" onClick={this.props.onUnpause} style={buttonStyle}>
|
||||
<Icon icon="play" style={iconStyle}/>
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
} else {
|
||||
buttons.push(
|
||||
<Button key={key++} onClick={this.props.onPause} style={buttonStyle}>
|
||||
<Icon icon="pause" /> Pause
|
||||
<OverlayTrigger key={key++} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"pause"}`}> Pause simulation </Tooltip>} >
|
||||
<Button key={key} variant= 'light' size="lg" onClick={this.props.onPause} style={buttonStyle}>
|
||||
<Icon icon="pause" style={iconStyle}/>
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
}
|
||||
|
||||
buttons.push(
|
||||
<Button key={key++} onClick={this.props.onEditFiles} style={buttonStyle}>
|
||||
<Icon icon="file" /> Edit Files
|
||||
<OverlayTrigger key={key++} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"file"}`}> Add, edit or delete files of scenario </Tooltip>} >
|
||||
<Button key={key} variant= 'light' size="lg" onClick={this.props.onEditFiles} style={buttonStyle}>
|
||||
<Icon icon="file" style={iconStyle}/>
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
|
||||
buttons.push(
|
||||
<Button key={key++} onClick={this.props.onEdit} style={buttonStyle}>
|
||||
<Icon icon="pen" /> Edit Layout
|
||||
<OverlayTrigger key={key++} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"layout"}`}> Add widgets and edit layout </Tooltip>} >
|
||||
<Button key={key} variant= 'light' size="lg" onClick={this.props.onEdit} style={buttonStyle}>
|
||||
<Icon icon="pen" style={iconStyle} />
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
|
||||
}
|
||||
|
|
|
@ -73,7 +73,10 @@ class Dashboard extends Component {
|
|||
|
||||
return thisWidgetHeight > maxHeightSoFar? thisWidgetHeight : maxHeightSoFar;
|
||||
}, 0);
|
||||
if(dashboard.height === 0 || maxHeight + 80 > dashboard.height)
|
||||
if(dashboard.height === 0){
|
||||
dashboard.height = 400;
|
||||
}
|
||||
else if(maxHeight + 80 > dashboard.height)
|
||||
{
|
||||
dashboard.height = maxHeight + 80;
|
||||
}
|
||||
|
@ -112,7 +115,7 @@ class Dashboard extends Component {
|
|||
return ICused;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
dashboard,
|
||||
widgets,
|
||||
|
@ -125,7 +128,7 @@ class Dashboard extends Component {
|
|||
editing: prevState.editing || false,
|
||||
paused: prevState.paused || false,
|
||||
|
||||
editModal: false,
|
||||
editModal: prevState.editModal || false,
|
||||
filesEditModal: prevState.filesEditModal || false,
|
||||
filesEditSaveState: prevState.filesEditSaveState || [],
|
||||
modalData: null,
|
||||
|
@ -240,7 +243,6 @@ class Dashboard extends Component {
|
|||
|
||||
closeEditFiles(){
|
||||
this.setState({ filesEditModal: false });
|
||||
// TODO do we need this if the dispatches happen in the dialog?
|
||||
}
|
||||
|
||||
closeEdit(data){
|
||||
|
@ -368,11 +370,12 @@ class Dashboard extends Component {
|
|||
return absolutHeight > currentHeight ? absolutHeight : currentHeight;
|
||||
}, 0);
|
||||
let dashboard = this.state.dashboard;
|
||||
|
||||
if(value === -1){
|
||||
|
||||
let tempHeight = this.state.dashboard.height - 50;
|
||||
|
||||
if(tempHeight > (maxHeight + 80)){
|
||||
if(tempHeight >= 400 && tempHeight >= (maxHeight + 80)){
|
||||
dashboard.height = tempHeight;
|
||||
this.setState({dashboard});
|
||||
}
|
||||
|
@ -421,7 +424,7 @@ class Dashboard extends Component {
|
|||
|
||||
<div className="box box-content" onContextMenu={ (e) => e.preventDefault() }>
|
||||
{this.state.editing &&
|
||||
<WidgetToolbox grid={grid} onGridChange={this.setGrid.bind(this)} onDashboardSizeChange={this.setDashboardSize.bind(this)} widgets={this.state.widgets} />
|
||||
<WidgetToolbox grid={grid} onGridChange={this.setGrid.bind(this)} dashboard={this.state.dashboard} onDashboardSizeChange={this.setDashboardSize.bind(this)} widgets={this.state.widgets} />
|
||||
}
|
||||
{!draggable?(
|
||||
<WidgetArea widgets={this.state.widgets} dropZoneHeight = {dropZoneHeight} editing={this.state.editing} grid={grid} onWidgetAdded={this.handleDrop.bind(this)}>
|
||||
|
|
|
@ -21,14 +21,14 @@ import { FormGroup, FormControl, FormLabel } from 'react-bootstrap';
|
|||
import Dialog from '../common/dialogs/dialog';
|
||||
|
||||
class EditDashboardDialog extends React.Component {
|
||||
valid: false;
|
||||
valid = true;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
name: '',
|
||||
_id: ''
|
||||
id: ''
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,7 @@ class EditDashboardDialog extends React.Component {
|
|||
resetState() {
|
||||
this.setState({
|
||||
name: this.props.dashboard.name,
|
||||
_id: this.props.dashboard._id
|
||||
id: this.props.dashboard.id
|
||||
});
|
||||
}
|
||||
|
||||
|
|
79
src/file/edit-file-name.js
Normal file
79
src/file/edit-file-name.js
Normal file
|
@ -0,0 +1,79 @@
|
|||
/**
|
||||
* 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, FormLabel, Col} from 'react-bootstrap';
|
||||
|
||||
import Dialog from '../common/dialogs/dialog';
|
||||
|
||||
class EditFileName extends React.Component {
|
||||
valid = true;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
file: {},
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
onClose = canceled => {
|
||||
if (canceled) {
|
||||
if (this.props.onClose != null) {
|
||||
this.props.onClose();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.valid && this.props.onClose != null) {
|
||||
this.props.onClose(this.state.file);
|
||||
}
|
||||
};
|
||||
|
||||
handleChange = event => {
|
||||
this.props.file.name = event.target.value;
|
||||
this.setState({file: this.props.file});
|
||||
|
||||
let name = true;
|
||||
|
||||
if (this.state.name === '') {
|
||||
name = false;
|
||||
}
|
||||
|
||||
this.valid = name;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
render() {
|
||||
return <Dialog show={this.props.show} title='Edit File' buttonTitle='Save' onClose={(c) => this.onClose(c)} valid={true}>
|
||||
<form>
|
||||
<FormGroup as={Col} controlId='name'>
|
||||
<FormLabel column={false}>Name</FormLabel>
|
||||
<FormControl type='text' value={this.props.file.name} onChange={this.handleChange} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
|
||||
</form>
|
||||
</Dialog>;
|
||||
}
|
||||
}
|
||||
|
||||
export default EditFileName;
|
|
@ -21,6 +21,7 @@ import Dialog from '../common/dialogs/dialog';
|
|||
import AppDispatcher from "../common/app-dispatcher";
|
||||
import Table from "../common/table";
|
||||
import TableColumn from "../common/table-column";
|
||||
import EditFileName from "./edit-file-name";
|
||||
|
||||
|
||||
class EditFilesDialog extends React.Component {
|
||||
|
@ -32,7 +33,9 @@ class EditFilesDialog extends React.Component {
|
|||
|
||||
this.state = {
|
||||
uploadFile: null,
|
||||
uploadProgress: 0
|
||||
uploadProgress: 0,
|
||||
editModal: false,
|
||||
modalFile: {}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -84,6 +87,22 @@ class EditFilesDialog extends React.Component {
|
|||
|
||||
};
|
||||
|
||||
closeEditModal(data){
|
||||
if(data !== {} || data !== "undefined"){
|
||||
const formData = new FormData();
|
||||
formData.append("object", data);
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'files/start-edit',
|
||||
data: formData,
|
||||
token: this.props.sessionToken,
|
||||
id: data.id
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({editModal: false});
|
||||
}
|
||||
|
||||
deleteFile(index){
|
||||
|
||||
let file = this.props.files[index]
|
||||
|
@ -114,24 +133,24 @@ class EditFilesDialog extends React.Component {
|
|||
marginTop: '-40px'
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<Dialog show={this.props.show} title="Edit Files of scenario" buttonTitle="Close" onClose={(c) => this.onClose(c)} blendOutCancel = {true} valid={true}>
|
||||
<Dialog show={this.props.show} title="Edit Files of scenario" buttonTitle="Close" onClose={(c) => this.onClose(c)} blendOutCancel = {true} valid={true} size = 'lg'>
|
||||
<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}/>
|
||||
<Table data={this.props.files}>
|
||||
<TableColumn title='ID' dataKey='id'/>
|
||||
<TableColumn title='Name' dataKey='name'/>
|
||||
<TableColumn title='Size (bytes)' dataKey='size'/>
|
||||
<TableColumn title='Type' dataKey='type'/>
|
||||
<TableColumn
|
||||
title='Delete'
|
||||
width='75'
|
||||
title=''
|
||||
deleteButton
|
||||
onDelete={(index) => this.deleteFile(index)}
|
||||
editButton
|
||||
onEdit={index => this.setState({ editModal: true, modalFile: this.props.files[index] })}
|
||||
/>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<FormGroup as={Col} >
|
||||
<FormControl
|
||||
|
@ -157,8 +176,14 @@ class EditFilesDialog extends React.Component {
|
|||
style={progressBarStyle}
|
||||
/>
|
||||
</FormGroup>
|
||||
</div>
|
||||
<div style={{ clear: 'both' }} />
|
||||
|
||||
<EditFileName show={this.state.editModal} onClose={(data) => this.closeEditModal(data)} file={this.state.modalFile} />
|
||||
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,6 +78,10 @@ class EditICDialog extends React.Component {
|
|||
this.setState({ [e.target.id]: e.target.value });
|
||||
}
|
||||
|
||||
handlePropertiesChange(data) {
|
||||
this.setState({ properties: data });
|
||||
}
|
||||
|
||||
resetState() {
|
||||
this.setState({
|
||||
name: this.props.ic.name,
|
||||
|
@ -91,8 +95,17 @@ class EditICDialog extends React.Component {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<Dialog show={this.props.show} title="Edit Infrastructure Component" buttonTitle="Save" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
|
||||
<Dialog
|
||||
show={this.props.show}
|
||||
title="Edit Infrastructure Component"
|
||||
buttonTitle="Save"
|
||||
onClose={(c) => this.onClose(c)}
|
||||
onReset={() => this.resetState()}
|
||||
valid={this.valid}
|
||||
size='lg'
|
||||
>
|
||||
<form>
|
||||
<FormLabel column={false}>UUID: {this.props.ic.uuid}</FormLabel>
|
||||
<FormGroup controlId="name">
|
||||
<FormLabel column={false}>Name</FormLabel>
|
||||
<FormControl type="text" placeholder={this.props.ic.name} value={this.state.name} onChange={(e) => this.handleChange(e)} />
|
||||
|
@ -120,7 +133,11 @@ class EditICDialog extends React.Component {
|
|||
</FormGroup>
|
||||
<FormGroup controlId='properties'>
|
||||
<FormLabel column={false}>Properties</FormLabel>
|
||||
<ParametersEditor content={this.state.properties} disabled={false} />
|
||||
<ParametersEditor
|
||||
content={this.state.properties}
|
||||
disabled={false}
|
||||
onChange={(data) => this.handlePropertiesChange(data)}
|
||||
/>
|
||||
</FormGroup>
|
||||
</form>
|
||||
</Dialog>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { Button, ButtonToolbar, DropdownButton, DropdownItem } from 'react-bootstrap';
|
||||
import { Button, ButtonToolbar, DropdownButton, Dropdown, Tooltip, OverlayTrigger } from 'react-bootstrap';
|
||||
|
||||
class ICAction extends React.Component {
|
||||
constructor(props) {
|
||||
|
@ -48,21 +48,34 @@ class ICAction extends React.Component {
|
|||
};
|
||||
|
||||
render() {
|
||||
let showTooltip = this.state.selectedAction.id === '-1';
|
||||
|
||||
const actionList = this.props.actions.map(action => (
|
||||
<DropdownItem key={action.id} eventKey={action.id} active={this.state.selectedAction === action.id}>
|
||||
<Dropdown.Item key={action.id} eventKey={action.id} active={this.state.selectedAction === action.id}>
|
||||
{action.title}
|
||||
</DropdownItem>
|
||||
</Dropdown.Item>
|
||||
));
|
||||
|
||||
return <div>
|
||||
Send command to infrastructure component
|
||||
{showTooltip ?
|
||||
<ButtonToolbar>
|
||||
<OverlayTrigger key={0} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"select"}`}> Select command for infrastructure component </Tooltip>} >
|
||||
<DropdownButton title={this.state.selectedAction != null ? this.state.selectedAction.title : ''} id="action-dropdown" onSelect={this.setAction}>
|
||||
{actionList}
|
||||
</DropdownButton>
|
||||
</OverlayTrigger>
|
||||
<OverlayTrigger key={1} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"send"}`}> Send command to infrastructure component </Tooltip>} >
|
||||
<Button style={{ marginLeft: '5px' }} disabled={this.props.runDisabled} onClick={() => this.props.runAction(this.state.selectedAction)}>Send command</Button>
|
||||
</OverlayTrigger>
|
||||
</ButtonToolbar>
|
||||
:
|
||||
<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)}>Send command</Button>
|
||||
</ButtonToolbar>
|
||||
|
||||
}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import { Container } from 'flux/utils';
|
|||
import { Button } from 'react-bootstrap';
|
||||
import FileSaver from 'file-saver';
|
||||
import _ from 'lodash';
|
||||
import moment from 'moment'
|
||||
|
||||
import AppDispatcher from '../common/app-dispatcher';
|
||||
import InfrastructureComponentStore from './ic-store';
|
||||
|
@ -256,10 +257,10 @@ class InfrastructureComponents extends Component {
|
|||
return style.join(' ')
|
||||
}
|
||||
|
||||
static stateUpdateModifier(updatedAt) {
|
||||
const date = new Date(updatedAt);
|
||||
|
||||
return date.toLocaleString('de-DE');
|
||||
stateUpdateModifier(updatedAt) {
|
||||
let dateFormat = 'ddd, DD MMM YYYY HH:mm:ss';
|
||||
let dateTime = moment.utc(updatedAt, dateFormat);
|
||||
return dateTime.toLocaleString('de-DE');
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -279,9 +280,9 @@ class InfrastructureComponents extends Component {
|
|||
<TableColumn title='Type' dataKeys={['type', 'rawProperties.type']} />
|
||||
<TableColumn title='Location' dataKeys={['properties.location', 'rawProperties.location']} />
|
||||
{/* <TableColumn title='Realm' dataKeys={['properties.realm', 'rawProperties.realm']} /> */}
|
||||
<TableColumn title='Host' dataKey='host' />
|
||||
<TableColumn title='WebSocket Endpoint' dataKey='host' />
|
||||
<TableColumn title='API Host' dataKey='apihost' />
|
||||
<TableColumn title='Last Update' dataKey='stateUpdatedAt' modifier={InfrastructureComponents.stateUpdateModifier} />
|
||||
<TableColumn title='Last Update' dataKey='stateUpdateAt' modifier={(stateUpdateAt) => this.stateUpdateModifier(stateUpdateAt)} />
|
||||
<TableColumn
|
||||
width='200'
|
||||
editButton
|
||||
|
|
|
@ -80,10 +80,6 @@ class NewICDialog extends React.Component {
|
|||
uuid = false;
|
||||
}
|
||||
|
||||
if (this.state.host === '') {
|
||||
host = false;
|
||||
}
|
||||
|
||||
if (this.state.type === '') {
|
||||
type = false;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import { Container } from 'flux/utils';
|
||||
import { Button, InputGroup, FormControl } from 'react-bootstrap';
|
||||
import { Button, InputGroup, FormControl, Tooltip, OverlayTrigger } from 'react-bootstrap';
|
||||
|
||||
import FileSaver from 'file-saver';
|
||||
|
||||
|
@ -35,6 +35,8 @@ import TableColumn from '../common/table-column';
|
|||
import ImportConfigDialog from '../componentconfig/import-config';
|
||||
import ImportDashboardDialog from "../dashboard/import-dashboard";
|
||||
import NewDashboardDialog from "../dashboard/new-dashboard";
|
||||
import EditDashboardDialog from '../dashboard/edit-dashboard';
|
||||
import EditFiles from '../file/edit-files'
|
||||
|
||||
import ICAction from '../ic/ic-action';
|
||||
import DeleteDialog from '../common/dialogs/delete-dialog';
|
||||
|
@ -94,15 +96,19 @@ class Scenario extends React.Component {
|
|||
modalConfigData: (prevState.modalConfigData !== {} && prevState.modalConfigData !== undefined )? prevState.modalConfigData : {},
|
||||
selectedConfigs: [],
|
||||
modalConfigIndex: 0,
|
||||
filesEditModal: prevState.filesEditModal || false,
|
||||
filesEditSaveState: prevState.filesEditSaveState || [],
|
||||
|
||||
editOutputSignalsModal: prevState.editOutputSignalsModal || false,
|
||||
editInputSignalsModal: prevState.editInputSignalsModal || false,
|
||||
|
||||
newDashboardModal: false,
|
||||
dashboardEditModal: prevState.dashboardEditModal || false,
|
||||
deleteDashboardModal: false,
|
||||
importDashboardModal: false,
|
||||
modalDashboardData: {},
|
||||
|
||||
userToAdd: '',
|
||||
deleteUserName: '',
|
||||
deleteUserModal: false,
|
||||
}
|
||||
|
@ -135,13 +141,19 @@ class Scenario extends React.Component {
|
|||
* User modification methods
|
||||
############################################## */
|
||||
|
||||
onUserInputChange(e) {
|
||||
this.setState({userToAdd: e.target.value});
|
||||
}
|
||||
|
||||
addUser() {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'scenarios/add-user',
|
||||
data: this.state.scenario.id,
|
||||
username: this.userToAdd,
|
||||
username: this.state.userToAdd,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
|
||||
this.setState({userToAdd: ''});
|
||||
}
|
||||
|
||||
closeDeleteUserModal() {
|
||||
|
@ -334,6 +346,21 @@ class Scenario extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
closeEditDashboardModal(data) {
|
||||
this.setState({ dashboardEditModal: false });
|
||||
|
||||
let editDashboard = this.state.modalDashboardData;
|
||||
|
||||
if (data != null) {
|
||||
editDashboard.name = data.name;
|
||||
AppDispatcher.dispatch({
|
||||
type: 'dashboards/start-edit',
|
||||
data: editDashboard,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
closeDeleteDashboardModal(confirmDelete) {
|
||||
this.setState({ deleteDashboardModal: false });
|
||||
|
||||
|
@ -392,6 +419,22 @@ class Scenario extends React.Component {
|
|||
this.setState({editOutputSignalsModal: false});
|
||||
}
|
||||
}
|
||||
|
||||
onEditFiles(){
|
||||
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?
|
||||
}
|
||||
|
||||
signalsAutoConf(index){
|
||||
let componentConfig = this.state.configs[index];
|
||||
|
@ -438,12 +481,57 @@ class Scenario extends React.Component {
|
|||
* File modification methods
|
||||
############################################## */
|
||||
|
||||
getFileName(id) {
|
||||
for (let file of this.state.files) {
|
||||
if (file.id === id) {
|
||||
return file.name;
|
||||
getListOfFiles(fileIDs, types) {
|
||||
|
||||
let fileList = '';
|
||||
|
||||
for (let id of fileIDs){
|
||||
for (let file of this.state.files) {
|
||||
if (file.id === id && types.some(e => file.type.includes(e))) {
|
||||
if (fileList === ''){
|
||||
fileList = file.name
|
||||
} else {
|
||||
fileList = fileList + ';' + file.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return fileList;
|
||||
}
|
||||
|
||||
startPintura(configIndex){
|
||||
let config = this.state.configs[configIndex];
|
||||
|
||||
// get xml / CIM file
|
||||
let files = []
|
||||
for (let id of config.fileIDs){
|
||||
for (let file of this.state.files) {
|
||||
if (file.id === id && ['xml'].some(e => file.type.includes(e))) {
|
||||
files.push(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(files.length > 1){
|
||||
// more than one CIM file...
|
||||
console.warn("There is more than one CIM file selected in this component configuration. I will open them all in a separate tab.")
|
||||
}
|
||||
|
||||
let base_host = 'aaa.bbb.ccc.ddd/api/v2/files/'
|
||||
for (let file of files) {
|
||||
// endpoint param serves for download and upload of CIM file, token is required for authentication
|
||||
let params = {
|
||||
token: this.state.sessionToken,
|
||||
endpoint: base_host + String(file.id),
|
||||
}
|
||||
|
||||
// TODO start Pintura for editing CIM/ XML file from here
|
||||
console.warn("Starting Pintura... and nothing happens so far :-) ", params)
|
||||
}
|
||||
}
|
||||
|
||||
/* ##############################################
|
||||
|
@ -460,9 +548,31 @@ class Scenario extends React.Component {
|
|||
paddingTop: '30px'
|
||||
}
|
||||
|
||||
const iconStyle = {
|
||||
color: '#007bff',
|
||||
height: '25px',
|
||||
width : '25px'
|
||||
}
|
||||
|
||||
return <div className='section'>
|
||||
<div className='section-buttons-group-right'>
|
||||
<OverlayTrigger key={0} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"file"}`}> Add, edit or delete files of scenario </Tooltip>} >
|
||||
<Button key={0} variant= 'light' size="lg" onClick={this.onEditFiles.bind(this)} style={buttonStyle}>
|
||||
<Icon icon="file" style= {iconStyle}/>
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
</div>
|
||||
<h1>{this.state.scenario.name}</h1>
|
||||
|
||||
<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.scenario.id}
|
||||
/>
|
||||
|
||||
|
||||
|
||||
{/*Component Configurations table*/}
|
||||
|
@ -470,7 +580,14 @@ class Scenario extends React.Component {
|
|||
<Table data={this.state.configs}>
|
||||
<TableColumn checkbox onChecked={(index, event) => this.onConfigChecked(index, event)} width='30' />
|
||||
<TableColumn title='Name' dataKey='name' />
|
||||
<TableColumn title='Selected configuration file' dataKey='selectedFileID' modifier={(selectedFileID) => this.getFileName(selectedFileID)} />
|
||||
<TableColumn title='Configuration file(s)' dataKey='fileIDs' modifier={(fileIDs) => this.getListOfFiles(fileIDs, ['json', 'JSON'])} />
|
||||
<TableColumn
|
||||
title='Model file(s)'
|
||||
dataKey='fileIDs'
|
||||
modifier={(fileIDs) => this.getListOfFiles(fileIDs, ['xml'])}
|
||||
editButton
|
||||
onEdit={(index) => this.startPintura(index)}
|
||||
/>
|
||||
<TableColumn
|
||||
title='# Output Signals'
|
||||
dataKey='outputLength'
|
||||
|
@ -490,7 +607,7 @@ class Scenario extends React.Component {
|
|||
/>
|
||||
<TableColumn title='Infrastructure Component' dataKey='icID' modifier={(icID) => this.getICName(icID)} />
|
||||
<TableColumn
|
||||
title='Edit/ Delete/ Export'
|
||||
title=''
|
||||
width='200'
|
||||
editButton
|
||||
deleteButton
|
||||
|
@ -558,8 +675,10 @@ class Scenario extends React.Component {
|
|||
<TableColumn
|
||||
title=''
|
||||
width='200'
|
||||
editButton
|
||||
deleteButton
|
||||
exportButton
|
||||
onEdit={index => this.setState({ dashboardEditModal: true, modalDashboardData: this.state.dashboards[index] })}
|
||||
onDelete={(index) => this.setState({ deleteDashboardModal: true, modalDashboardData: this.state.dashboards[index], modalDashboardIndex: index })}
|
||||
onExport={index => this.exportDashboard(index)}
|
||||
/>
|
||||
|
@ -573,6 +692,7 @@ class Scenario extends React.Component {
|
|||
<div style={{ clear: 'both' }} />
|
||||
|
||||
<NewDashboardDialog show={this.state.newDashboardModal} onClose={data => this.closeNewDashboardModal(data)} />
|
||||
<EditDashboardDialog show={this.state.dashboardEditModal} dashboard={this.state.modalDashboardData} onClose={data => this.closeEditDashboardModal(data)} />
|
||||
<ImportDashboardDialog show={this.state.importDashboardModal} onClose={data => this.closeImportDashboardModal(data)} />
|
||||
|
||||
<DeleteDialog title="dashboard" name={this.state.modalDashboardData.name} show={this.state.deleteDashboardModal} onClose={(e) => this.closeDeleteDashboardModal(e)} />
|
||||
|
@ -595,7 +715,9 @@ class Scenario extends React.Component {
|
|||
<InputGroup style={{ width: 400, float: 'right' }}>
|
||||
<FormControl
|
||||
placeholder="Username"
|
||||
onChange={(e) => this.userToAdd = e.target.value}
|
||||
onChange={(e) => this.onUserInputChange(e)}
|
||||
value={this.state.userToAdd}
|
||||
type="text"
|
||||
/>
|
||||
<InputGroup.Append>
|
||||
<Button
|
||||
|
|
|
@ -117,19 +117,6 @@ class Scenarios extends Component {
|
|||
});
|
||||
};
|
||||
|
||||
showEditModal(id) {
|
||||
// get scenario by id
|
||||
var editScenario;
|
||||
|
||||
this.state.scenarios.forEach((scenario) => {
|
||||
if (scenario.id === id) {
|
||||
editScenario = scenario;
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({ editModal: true, modalScenario: editScenario });
|
||||
}
|
||||
|
||||
closeEditModal(data) {
|
||||
this.setState({ editModal: false });
|
||||
|
||||
|
|
|
@ -164,7 +164,8 @@ class EditSignalMapping extends React.Component {
|
|||
blendOutCancel = {true}
|
||||
onClose={(c) => this.onClose(c)}
|
||||
onReset={() => this.resetState()}
|
||||
valid={true}>
|
||||
valid={true}
|
||||
size='lg'>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{this.props.direction} Mapping</FormLabel>
|
||||
|
|
|
@ -272,27 +272,6 @@ 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
|
||||
*/
|
||||
|
@ -305,7 +284,7 @@ body {
|
|||
}
|
||||
|
||||
.toolbox-dropzone-editing {
|
||||
border: 3px dashed #e1e1e1;
|
||||
outline: 3px dashed #e1e1e1;
|
||||
}
|
||||
|
||||
.toolbox-dropzone-active {
|
||||
|
|
|
@ -379,3 +379,14 @@ div[class*="-widget"] label {
|
|||
border: 2px solid lightgray;
|
||||
}
|
||||
/* End box widget */
|
||||
|
||||
/* Begin line widget */
|
||||
.line-widget {
|
||||
width: 100%;
|
||||
height: 1%;
|
||||
border: 2px solid red;
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
|
||||
/* End line widget */
|
||||
|
|
|
@ -144,7 +144,6 @@ export default function CreateControls(widgetType = null, widget = null, session
|
|||
<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)} />,
|
||||
|
@ -152,6 +151,13 @@ export default function CreateControls(widgetType = null, widget = null, session
|
|||
<EditWidgetCheckboxControl key={1} widget={widget} controlId={'customProperties.showUnit'} input text="Show unit" handleChange={e => handleChange(e)} />
|
||||
);
|
||||
break;
|
||||
case 'Line':
|
||||
DialogControls.push(
|
||||
<EditWidgetColorControl key={0} widget={widget} controlId={'customProperties.border_color'} label={'Line color'} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetNumberControl key={1} widget={widget} controlId={'customProperties.rotation'} label={'Rotation (degrees)'} defaultValue={0} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetNumberControl key={2} widget={widget} controlId={'customProperties.border_width'} label={'Line width'} defaultValue={0} handleChange={(e) => handleChange(e)} />
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log('Non-valid widget type: ' + widgetType);
|
||||
|
|
|
@ -49,7 +49,7 @@ class EditFileWidgetControl extends React.Component {
|
|||
let fileOptions = [];
|
||||
if (this.state.files.length > 0){
|
||||
fileOptions.push(
|
||||
<option key = {0} default>Select image file</option>
|
||||
<option key = {0} default>Select file</option>
|
||||
)
|
||||
fileOptions.push(this.state.files.map((file, index) => (
|
||||
<option key={index+1} value={file.id}>{file.name}</option>
|
||||
|
@ -60,7 +60,7 @@ class EditFileWidgetControl extends React.Component {
|
|||
|
||||
return <div>
|
||||
<FormGroup controlId="file">
|
||||
<FormLabel>Image</FormLabel>
|
||||
<FormLabel>File</FormLabel>
|
||||
<FormControl
|
||||
as="select"
|
||||
value={isCustomProperty ? this.props.widget[parts[0]][parts[1]] : this.props.widget[this.props.controlId]}
|
||||
|
|
|
@ -23,7 +23,7 @@ class EditWidgetTimeControl extends Component {
|
|||
super(props);
|
||||
|
||||
this.state = {
|
||||
widget: {
|
||||
widget: {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -36,10 +36,23 @@ class EditWidgetTimeControl extends Component {
|
|||
|
||||
render() {
|
||||
|
||||
let parts = this.props.controlId.split('.');
|
||||
let isCustomProperty = true;
|
||||
if (parts.length === 1){
|
||||
isCustomProperty = false;
|
||||
}
|
||||
|
||||
return (
|
||||
<FormGroup controlId= {this.props.controlId}>
|
||||
<FormLabel>Time</FormLabel>
|
||||
<FormControl type="number" min="1" max="300" placeholder="Enter time" value={this.state.widget[this.props.controlId]} onChange={(e) => this.props.handleChange(e)} />
|
||||
<FormControl
|
||||
type="number"
|
||||
min="1"
|
||||
max="300"
|
||||
placeholder="Enter time"
|
||||
value={isCustomProperty ? this.state.widget[parts[0]][parts[1]] : this.state.widget[this.props.controlId]}
|
||||
onChange={(e) => this.props.handleChange(e)}
|
||||
/>
|
||||
<FormText>Time in seconds</FormText>
|
||||
</FormGroup>
|
||||
);
|
||||
|
|
|
@ -31,7 +31,6 @@ class EditableWidgetContainer extends React.Component {
|
|||
if (this.props.grid === 1) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return Math.round(value / this.props.grid) * this.props.grid;
|
||||
}
|
||||
|
||||
|
@ -41,28 +40,22 @@ class EditableWidgetContainer extends React.Component {
|
|||
}
|
||||
};
|
||||
|
||||
drag = (event, data) => {
|
||||
const x = this.snapToGrid(data.x);
|
||||
const y = this.snapToGrid(data.y);
|
||||
|
||||
if (x !== data.x || y !== data.y) {
|
||||
this.rnd.updatePosition({ x, y });
|
||||
}
|
||||
};
|
||||
|
||||
dragStop = (event, data) => {
|
||||
const widget = this.props.widget;
|
||||
|
||||
widget.x = this.snapToGrid(data.x);
|
||||
widget.y = this.snapToGrid(data.y);
|
||||
|
||||
|
||||
if (widget.x !== data.x || widget.y !== data.y) {
|
||||
this.rnd.updatePosition({ x: widget.x, y: widget.y});
|
||||
}
|
||||
|
||||
if (this.props.onWidgetChange != null) {
|
||||
this.props.onWidgetChange(widget, this.props.index);
|
||||
}
|
||||
};
|
||||
|
||||
resizeStop = (event, direction, ref,delta, position) => {
|
||||
|
||||
const widget = this.props.widget;
|
||||
|
||||
// resize depends on direction
|
||||
|
@ -119,7 +112,6 @@ class EditableWidgetContainer extends React.Component {
|
|||
className={widgetClasses}
|
||||
onResizeStart={this.borderWasClicked}
|
||||
onResizeStop={this.resizeStop}
|
||||
onDrag={this.drag}
|
||||
onDragStop={this.dragStop}
|
||||
dragGrid={gridArray}
|
||||
resizeGrid={gridArray}
|
||||
|
|
|
@ -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-info ">
|
||||
<span className="btn " style={{marginTop: '5px', color: '#6ea2b0', borderColor: '#6ea2b0'}}>
|
||||
{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-info">
|
||||
<span className="btn btn-info" style={{marginTop: '5px'}}>
|
||||
{this.props.icon && <Icon style={{marginRight: '5px'}} icon={this.props.icon} /> }
|
||||
{this.props.name}
|
||||
</span>
|
||||
|
|
|
@ -180,6 +180,14 @@ class WidgetFactory {
|
|||
widget.height = 400;
|
||||
widget.customProperties.file = -1; // ID of file, -1 means non selected
|
||||
break;
|
||||
case 'Line':
|
||||
widget.height = 30;
|
||||
widget.width = 150;
|
||||
widget.customProperties.border_color = 0;
|
||||
widget.customProperties.border_width = 2;
|
||||
widget.customProperties.margin_top = 15;
|
||||
widget.customProperties.rotation = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
widget.width = 100;
|
||||
|
|
|
@ -35,8 +35,30 @@ class WidgetToolbox extends React.Component {
|
|||
}
|
||||
};
|
||||
|
||||
disableDecrease(){
|
||||
const maxHeight = Object.values(this.props.widgets).reduce((currentHeight, widget) => {
|
||||
const absolutHeight = widget.y + widget.height;
|
||||
|
||||
return absolutHeight > currentHeight ? absolutHeight : currentHeight;
|
||||
}, 0);
|
||||
|
||||
if(this.props.dashboard.height <= 400 || this.props.dashboard.height <= maxHeight + 80){
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
const disableDecrease = this.disableDecrease();
|
||||
// Only one topology widget at the time is supported
|
||||
const iconStyle = {
|
||||
color: '#007bff',
|
||||
height: '25px',
|
||||
width : '25px'
|
||||
}
|
||||
|
||||
const thereIsTopologyWidget = this.props.widgets != null && Object.values(this.props.widgets).filter(w => w.type === 'Topology').length > 0;
|
||||
const topologyItemMsg = thereIsTopologyWidget? 'Currently only one is supported' : '';
|
||||
|
||||
|
@ -47,6 +69,7 @@ class WidgetToolbox extends React.Component {
|
|||
<ToolboxItem name='Table' type='widget' icon = 'plus'/>
|
||||
<ToolboxItem name='Label' type='widget' icon = 'plus'/>
|
||||
<ToolboxItem name='Image' type='widget' icon = 'plus'/>
|
||||
<ToolboxItem name='Line' 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'/>
|
||||
|
@ -54,6 +77,11 @@ class WidgetToolbox extends React.Component {
|
|||
<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'/>
|
||||
<OverlayTrigger key={0} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"?"}`}> Drag and drop widgets onto the dashboard </Tooltip>} >
|
||||
<Button variant="light" size="sm" key={0} >
|
||||
<Icon icon="question" />
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
|
||||
<div className='section-buttons-group-right'>
|
||||
<div>
|
||||
|
@ -65,13 +93,13 @@ class WidgetToolbox extends React.Component {
|
|||
<div className='section-buttons-group-right'>
|
||||
<div>
|
||||
<OverlayTrigger key={0} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"increase"}`}> Increase dashboard height </Tooltip>} >
|
||||
<Button variant="dark" key={0} onClick={() => this.props.onDashboardSizeChange(1)} >
|
||||
<Icon icon="plus" />
|
||||
<Button variant="light" key={0} style={{marginRight: '3px', height: '40px'}} onClick={() => this.props.onDashboardSizeChange(1)} >
|
||||
<Icon icon="plus" style={iconStyle}/>
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
<OverlayTrigger key={1} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"decrease"}`}> Decrease dashboard height </Tooltip>} >
|
||||
<Button variant="dark" key={1} onClick={() => this.props.onDashboardSizeChange(-1)} >
|
||||
<Icon icon="minus" />
|
||||
<Button variant="light" key={1} disabled={disableDecrease} style={{marginRight: '3px', height: '40px'}} onClick={() => this.props.onDashboardSizeChange(-1)} >
|
||||
<Icon icon="minus" style={iconStyle}/>
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
</div>
|
||||
|
|
|
@ -43,6 +43,7 @@ import WidgetGauge from './widgets/gauge';
|
|||
import WidgetBox from './widgets/box';
|
||||
import WidgetHTML from './widgets/html';
|
||||
import WidgetTopology from './widgets/topology';
|
||||
import WidgetLine from './widgets/line';
|
||||
|
||||
import '../styles/widgets.css';
|
||||
|
||||
|
@ -221,6 +222,11 @@ class Widget extends React.Component {
|
|||
files={this.state.files}
|
||||
token={this.state.sessionToken}
|
||||
/>
|
||||
} else if (widget.type === 'Line') {
|
||||
return <WidgetLine
|
||||
widget={widget}
|
||||
editing={this.props.editing}
|
||||
/>
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
|
@ -81,114 +81,116 @@ class WidgetGauge extends Component {
|
|||
// 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 = {}
|
||||
if(signal.length > 0) {
|
||||
// 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];
|
||||
|
||||
returnState["colorZones"] = props.widget.customProperties.zones;
|
||||
let returnState = {}
|
||||
|
||||
if(signalID){
|
||||
returnState["signalID"] = signalID;
|
||||
}
|
||||
// Update unit (assuming there is exactly one signal for this widget)
|
||||
if(signal !== undefined){
|
||||
returnState["unit"] = signal[0].unit;
|
||||
}
|
||||
returnState["colorZones"] = props.widget.customProperties.zones;
|
||||
|
||||
// update value
|
||||
if (signalID) {
|
||||
returnState["signalID"] = signalID;
|
||||
}
|
||||
// Update unit (assuming there is exactly one signal for this widget)
|
||||
if (signal !== undefined) {
|
||||
returnState["unit"] = signal[0].unit;
|
||||
}
|
||||
|
||||
// check if data available
|
||||
if (props.data == null
|
||||
|| props.data[icID] == null
|
||||
|| props.data[icID].output == null
|
||||
|| props.data[icID].output.values == null) {
|
||||
return{ value: 0, minValue: 0, maxValue: 10};
|
||||
}
|
||||
// update value
|
||||
|
||||
// memorize if min or max value is updated
|
||||
let updateValue = false;
|
||||
let updateMinValue = false;
|
||||
let updateMaxValue = false;
|
||||
// check if data available
|
||||
if (props.data == null
|
||||
|| props.data[icID] == null
|
||||
|| props.data[icID].output == null
|
||||
|| props.data[icID].output.values == null) {
|
||||
return {value: 0, minValue: 0, maxValue: 10};
|
||||
}
|
||||
|
||||
// check if value has changed
|
||||
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 (data != null) {
|
||||
const value = signal[0].scalingFactor * Math.round(data[data.length - 1].y * 1e3) / 1e3;
|
||||
let minValue = null;
|
||||
let maxValue = null;
|
||||
// memorize if min or max value is updated
|
||||
let updateValue = false;
|
||||
let updateMinValue = false;
|
||||
let updateMaxValue = false;
|
||||
|
||||
if ((state.value !== value && value != null) || props.widget.customProperties.valueUseMinMax || state.useMinMaxChange) {
|
||||
//value has changed
|
||||
updateValue = true;
|
||||
// check if value has changed
|
||||
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 (data != null) {
|
||||
const value = signal[0].scalingFactor * Math.round(data[data.length - 1].y * 1e3) / 1e3;
|
||||
let minValue = null;
|
||||
let maxValue = null;
|
||||
|
||||
// update min-max if needed
|
||||
let updateLabels = false;
|
||||
if ((state.value !== value && value != null) || props.widget.customProperties.valueUseMinMax || state.useMinMaxChange) {
|
||||
//value has changed
|
||||
updateValue = true;
|
||||
|
||||
minValue = state.minValue;
|
||||
maxValue = state.maxValue;
|
||||
// update min-max if needed
|
||||
let updateLabels = false;
|
||||
|
||||
if (minValue == null || (!props.widget.customProperties.valueUseMinMax && (value < minValue || signalID !== state.signalID)) ||state.useMinMaxChange) {
|
||||
minValue = value - 0.5;
|
||||
updateLabels = true;
|
||||
updateMinValue = true;
|
||||
}
|
||||
minValue = state.minValue;
|
||||
maxValue = state.maxValue;
|
||||
|
||||
if (maxValue == null || (!props.widget.customProperties.valueUseMinMax && (value > maxValue || signalID !== state.signalID)) || state.useMinMaxChange) {
|
||||
maxValue = value + 0.5;
|
||||
updateLabels = true;
|
||||
updateMaxValue = true;
|
||||
returnState["useMinMaxChange"] = false;
|
||||
}
|
||||
if (minValue == null || (!props.widget.customProperties.valueUseMinMax && (value < minValue || signalID !== state.signalID)) || state.useMinMaxChange) {
|
||||
minValue = value - 0.5;
|
||||
updateLabels = true;
|
||||
updateMinValue = true;
|
||||
}
|
||||
|
||||
if (props.widget.customProperties.valueUseMinMax) {
|
||||
if (maxValue == null || (!props.widget.customProperties.valueUseMinMax && (value > maxValue || signalID !== state.signalID)) || state.useMinMaxChange) {
|
||||
maxValue = value + 0.5;
|
||||
updateLabels = true;
|
||||
updateMaxValue = true;
|
||||
returnState["useMinMaxChange"] = false;
|
||||
}
|
||||
|
||||
if (props.widget.customProperties.valueUseMinMax) {
|
||||
minValue = props.widget.customProperties.valueMin;
|
||||
updateMinValue = true;
|
||||
maxValue = props.widget.customProperties.valueMax;
|
||||
updateMaxValue = true;
|
||||
updateLabels = true;
|
||||
|
||||
}
|
||||
|
||||
if (updateLabels === false && state.gauge) {
|
||||
// check if min/max changed
|
||||
if (minValue > state.gauge.minValue) {
|
||||
minValue = state.gauge.minValue;
|
||||
updateMinValue = true;
|
||||
}
|
||||
|
||||
if (maxValue < state.gauge.maxValue) {
|
||||
maxValue = state.gauge.maxValue;
|
||||
updateMaxValue = true;
|
||||
if (updateLabels === false && state.gauge) {
|
||||
// check if min/max changed
|
||||
if (minValue > state.gauge.minValue) {
|
||||
minValue = state.gauge.minValue;
|
||||
updateMinValue = true;
|
||||
}
|
||||
|
||||
if (maxValue < state.gauge.maxValue) {
|
||||
maxValue = state.gauge.maxValue;
|
||||
updateMaxValue = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(props.widget.customProperties.valueUseMinMax !== state.useMinMax){
|
||||
returnState["useMinMax"] = props.widget.customProperties.valueUseMinMax;
|
||||
}
|
||||
if (props.widget.customProperties.valueUseMinMax !== state.useMinMax) {
|
||||
returnState["useMinMax"] = props.widget.customProperties.valueUseMinMax;
|
||||
}
|
||||
|
||||
// prepare returned state
|
||||
if(updateValue === true){
|
||||
returnState["value"] = value;
|
||||
}
|
||||
if(updateMinValue === true){
|
||||
returnState["minValue"] = minValue;
|
||||
}
|
||||
if(updateMaxValue === true){
|
||||
returnState["maxValue"] = maxValue;
|
||||
}
|
||||
|
||||
if (returnState !== {}){
|
||||
return returnState;
|
||||
}
|
||||
else{
|
||||
return null;
|
||||
}
|
||||
} // if there is signal data
|
||||
// prepare returned state
|
||||
if (updateValue === true) {
|
||||
returnState["value"] = value;
|
||||
}
|
||||
if (updateMinValue === true) {
|
||||
returnState["minValue"] = minValue;
|
||||
}
|
||||
if (updateMaxValue === true) {
|
||||
returnState["maxValue"] = maxValue;
|
||||
}
|
||||
|
||||
if (returnState !== {}) {
|
||||
return returnState;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} // if there is signal data
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -42,11 +42,13 @@ class WidgetInput extends Component {
|
|||
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){
|
||||
unit = signal.unit;
|
||||
if (props.widget.signalIDs.length > 0) {
|
||||
// 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) {
|
||||
unit = signal.unit;
|
||||
}
|
||||
}
|
||||
|
||||
if (unit !== '' && value !== ''){
|
||||
|
|
|
@ -36,21 +36,24 @@ class WidgetLamp extends Component {
|
|||
// 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];
|
||||
|
||||
// check if data available
|
||||
if (props.data == null
|
||||
|| props.data[icID] == null
|
||||
|| props.data[icID].output == null
|
||||
|| props.data[icID].output.values == null) {
|
||||
return{value:''};
|
||||
}
|
||||
if(signal.length>0) {
|
||||
// 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];
|
||||
|
||||
// check if value has changed
|
||||
const data = props.data[icID].output.values[signal[0].index-1];
|
||||
if (data != null && Number(state.value) !== signal[0].scalingFactor * data[data.length - 1].y) {
|
||||
return { value: signal[0].scalingFactor * data[data.length - 1].y };
|
||||
// check if data available
|
||||
if (props.data == null
|
||||
|| props.data[icID] == null
|
||||
|| props.data[icID].output == null
|
||||
|| props.data[icID].output.values == null) {
|
||||
return {value: ''};
|
||||
}
|
||||
|
||||
// check if value has changed
|
||||
const data = props.data[icID].output.values[signal[0].index - 1];
|
||||
if (data != null && Number(state.value) !== signal[0].scalingFactor * data[data.length - 1].y) {
|
||||
return {value: signal[0].scalingFactor * data[data.length - 1].y};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
38
src/widget/widgets/line.js
Normal file
38
src/widget/widgets/line.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* 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 EditWidgetColorControl from '../edit-widget/edit-widget-color-control';
|
||||
|
||||
class WidgetLine extends Component {
|
||||
render() {
|
||||
const lineStyle = {
|
||||
borderColor: EditWidgetColorControl.ColorPalette[this.props.widget.customProperties.border_color],
|
||||
transform: 'rotate(' + this.props.widget.customProperties.rotation + 'deg)',
|
||||
borderWidth: '' + this.props.widget.customProperties.border_width + 'px'
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="line-widget" style={lineStyle}>
|
||||
{ }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default WidgetLine;
|
|
@ -53,10 +53,12 @@ class WidgetSlider extends Component {
|
|||
}
|
||||
|
||||
// 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){
|
||||
unit = signal.unit;
|
||||
if (props.widget.signalIDs.length > 0) {
|
||||
let signalID = props.widget.signalIDs[0];
|
||||
let signal = props.signals.find(sig => sig.id === signalID);
|
||||
if (signal !== undefined) {
|
||||
unit = signal.unit;
|
||||
}
|
||||
}
|
||||
|
||||
if (unit !== '' && value !== ''){
|
||||
|
|
|
@ -65,19 +65,12 @@ function show(element) {
|
|||
if(element !== undefined) {
|
||||
element.style.visibility = 'inherit';
|
||||
}
|
||||
else{
|
||||
console.log("MouseOver, show, element undefined.")
|
||||
}
|
||||
}
|
||||
|
||||
function hide(element) {
|
||||
if (element !== undefined) {
|
||||
element.style.visibility = 'hidden';
|
||||
} else {
|
||||
console.log("MouseLeave, hide, element undefined.")
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// De-initialize functions
|
||||
|
@ -91,25 +84,45 @@ class WidgetTopology extends React.Component {
|
|||
super(props);
|
||||
this.svgElem = React.createRef();
|
||||
this.Viewer = null;
|
||||
this.dashboardState = 'initial'
|
||||
this.message = ''
|
||||
let file = this.props.files.find(file => file.id === parseInt(this.props.widget.customProperties.file, 10));
|
||||
|
||||
|
||||
this.state = {
|
||||
file: file
|
||||
file: this.props.files.find(file => file.id === parseInt(this.props.widget.customProperties.file, 10)),
|
||||
dashboardState: 'initial',
|
||||
message: ''
|
||||
};
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props, state){
|
||||
// find the selected file of the widget, is undefined if no file is selected
|
||||
let file = props.files.find(file => file.id === parseInt(props.widget.customProperties.file, 10));
|
||||
|
||||
if (state.file === undefined || state.file.id !== file.id) {
|
||||
return{
|
||||
file: file
|
||||
};
|
||||
let dashboardState = state.dashboardState;
|
||||
let message = state.message;
|
||||
|
||||
if(file === undefined){
|
||||
dashboardState = 'show_message';
|
||||
message = 'Select a topology model.'
|
||||
} else if (!file.hasOwnProperty('data') && dashboardState === 'show_message'){
|
||||
// data of file is missing, start download
|
||||
dashboardState = 'loading';
|
||||
message = '';
|
||||
AppDispatcher.dispatch({
|
||||
type: 'files/start-download',
|
||||
data: file.id,
|
||||
token: props.token
|
||||
});
|
||||
} else if (file.hasOwnProperty('data') && (dashboardState === 'loading'|| dashboardState === 'show_message')){
|
||||
//file is available set state to ready
|
||||
dashboardState = 'ready'
|
||||
message = '';
|
||||
}
|
||||
return null
|
||||
|
||||
return{
|
||||
file: file,
|
||||
dashboardState:dashboardState,
|
||||
message: message,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -124,13 +137,12 @@ class WidgetTopology extends React.Component {
|
|||
|
||||
//this.Viewer.fitToViewer();
|
||||
|
||||
// Query the file referenced by the widget
|
||||
let widgetFile = parseInt(this.props.widget.customProperties.file, 10);
|
||||
if (widgetFile !== -1 && this.state.file === undefined) {
|
||||
this.dashboardState = 'loading';
|
||||
// Query the file referenced by the widget (if any)
|
||||
if (this.state.file !== undefined) {
|
||||
this.setState({dashboardState: 'loading'});
|
||||
AppDispatcher.dispatch({
|
||||
type: 'files/start-download',
|
||||
data: widgetFile,
|
||||
data: this.state.file.id,
|
||||
token: this.props.token
|
||||
});
|
||||
}
|
||||
|
@ -142,43 +154,25 @@ class WidgetTopology extends React.Component {
|
|||
|
||||
componentDidUpdate(prevProps: Readonly<P>, prevState: Readonly<S>, snapshot: SS): void {
|
||||
|
||||
if(this.state.file === undefined) {
|
||||
// No file has been selected
|
||||
this.dashboardState = 'show_message';
|
||||
this.message = 'Select a topology model first.';
|
||||
return;
|
||||
}
|
||||
|
||||
if((prevState.file === undefined && this.state.file !== undefined)
|
||||
|| (this.state.file.id !== prevState.file.id && this.state.file.id !== -1)) {
|
||||
// if file has changed, download new file
|
||||
this.dashboardState = 'loading';
|
||||
AppDispatcher.dispatch({
|
||||
type: 'files/start-download',
|
||||
data: this.state.file.id,
|
||||
token: this.props.token
|
||||
});
|
||||
} else if (this.state.file.hasOwnProperty("data") && this.dashboardState === 'loading') {
|
||||
// data of file has been newly downloaded (did not exist in previous state)
|
||||
this.dashboardState = 'ready';
|
||||
|
||||
} else if(this.state.file.hasOwnProperty("data") && this.dashboardState === 'ready'){
|
||||
if(this.state.dashboardState === 'ready'){
|
||||
//Topology file incl data downloaded, init SVG (should happen only once!)
|
||||
if (this.svgElem) {
|
||||
let cimsvgInstance = new cimsvg(this.svgElem.current);
|
||||
cimsvg.setCimsvg(cimsvgInstance);
|
||||
cimsvgInstance.setFileCount(1);
|
||||
// transform data blob into string format
|
||||
this.state.file.data.text().then(function(content) {
|
||||
cimsvgInstance.loadFile(content);
|
||||
cimsvgInstance.fit();
|
||||
attachComponentEvents();
|
||||
cimsvgInstance.loadFile(content);
|
||||
cimsvgInstance.fit();
|
||||
attachComponentEvents();
|
||||
});
|
||||
this.setState({dashboardState: 'loaded'});
|
||||
}
|
||||
else {
|
||||
console.error("The svgElem variable is not initialized before the attempt to create the cimsvg instance.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -192,11 +186,11 @@ class WidgetTopology extends React.Component {
|
|||
position: "right"
|
||||
}
|
||||
|
||||
switch(this.dashboardState) {
|
||||
switch(this.state.dashboardState) {
|
||||
case 'loading':
|
||||
markup = <div style={spinnerContainerStyle}><div className="loader" /></div>; break;
|
||||
case 'show_message':
|
||||
markup = <div style={msgContainerStyle}><div style={msgStyle}>{ this.message }</div></div>; break;
|
||||
markup = <div style={msgContainerStyle}><div style={msgStyle}>{ this.state.message }</div></div>; break;
|
||||
default:
|
||||
markup = (<div>
|
||||
<UncontrolledReactSVGPanZoom
|
||||
|
|
|
@ -36,31 +36,35 @@ class WidgetValue extends Component {
|
|||
// 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 value widget)
|
||||
let icID = props.icIDs[signal[0].id];
|
||||
if(signal.length>0) {
|
||||
// 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];
|
||||
|
||||
// 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 data = props.data[icID].output.values[signal[0].index - 1];
|
||||
if (data != null && Number(state.value) !== data[data.length - 1].y) {
|
||||
value = signal[0].scalingFactor * data[data.length - 1].y;
|
||||
// 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 data = props.data[icID].output.values[signal[0].index - 1];
|
||||
if (data != null && Number(state.value) !== data[data.length - 1].y) {
|
||||
value = signal[0].scalingFactor * data[data.length - 1].y;
|
||||
}
|
||||
}
|
||||
|
||||
// Update unit (assuming there is exactly one signal for this widget)
|
||||
let unit = '';
|
||||
if (signal !== undefined) {
|
||||
unit = signal[0].unit;
|
||||
}
|
||||
|
||||
return {
|
||||
value: value,
|
||||
unit: unit,
|
||||
};
|
||||
}
|
||||
|
||||
// Update unit (assuming there is exactly one signal for this widget)
|
||||
let unit = '';
|
||||
if(signal !== undefined){
|
||||
unit = signal[0].unit;
|
||||
}
|
||||
|
||||
return {
|
||||
value: value,
|
||||
unit: unit,
|
||||
};
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue