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

Merge branch 'develop' into signal-auto-config

# Conflicts:
#	src/ic/ics.js
This commit is contained in:
Sonja Happ 2020-07-20 08:48:16 +02:00
commit ca4832b6d6
36 changed files with 725 additions and 324 deletions

14
package-lock.json generated
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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;

View file

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

View file

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

View file

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

View file

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

View file

@ -80,10 +80,6 @@ class NewICDialog extends React.Component {
uuid = false;
}
if (this.state.host === '') {
host = false;
}
if (this.state.type === '') {
type = false;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 !== ''){

View file

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

View 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;

View file

@ -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 !== ''){

View file

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

View file

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