mirror of
https://git.rwth-aachen.de/acs/public/villas/web/
synced 2025-03-09 00:00:01 +01:00
Merge branch 'master' into villasnode-api
This commit is contained in:
commit
eba6a4a3b0
26 changed files with 1727 additions and 1616 deletions
2729
package-lock.json
generated
2729
package-lock.json
generated
File diff suppressed because it is too large
Load diff
23
package.json
23
package.json
|
@ -5,10 +5,10 @@
|
|||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.32",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.1",
|
||||
"@fortawesome/react-fontawesome": "^0.1.12",
|
||||
"@fortawesome/react-fontawesome": "^0.1.13",
|
||||
"babel-runtime": "^6.26.0",
|
||||
"bootstrap": "^4.5.3",
|
||||
"bufferutil": "^4.0.1",
|
||||
"bufferutil": "^4.0.2",
|
||||
"canvas": "^2.6.1",
|
||||
"classnames": "^2.2.6",
|
||||
"d3-array": "^2.8.0",
|
||||
|
@ -20,7 +20,7 @@
|
|||
"d3-time-format": "^3.0.0",
|
||||
"es6-promise": "^4.2.8",
|
||||
"fibers": "^5.0.0",
|
||||
"file-saver": "^2.0.2",
|
||||
"file-saver": "^2.0.5",
|
||||
"flux": "^3.1.3",
|
||||
"gaugeJS": "^1.3.7",
|
||||
"handlebars": "^4.7.6",
|
||||
|
@ -29,15 +29,15 @@
|
|||
"libcimsvg": "git+https://git.rwth-aachen.de/acs/public/cim/pintura-npm-package.git",
|
||||
"lodash": "^4.17.20",
|
||||
"moment": "^2.29.1",
|
||||
"multiselect-react-dropdown": "^1.6.1",
|
||||
"multiselect-react-dropdown": "^1.6.2",
|
||||
"node-sass": "^4.14.1",
|
||||
"popper.js": "^1.16.1",
|
||||
"prop-types": "^15.7.2",
|
||||
"rc-slider": "^9.6.0",
|
||||
"rc-slider": "^9.6.4",
|
||||
"react": "^16.14.0",
|
||||
"react-bootstrap": "^1.4.0",
|
||||
"react-bootstrap-time-picker": "^2.0.1",
|
||||
"react-collapse": "^5.0.1",
|
||||
"react-collapse": "^5.1.0",
|
||||
"react-color": "^2.19.3",
|
||||
"react-contexify": "^4.1.1",
|
||||
"react-d3": "^0.4.0",
|
||||
|
@ -51,17 +51,18 @@
|
|||
"react-rnd": "^10.2.3",
|
||||
"react-router": "^5.2.0",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-scripts": "^4.0.0",
|
||||
"react-scripts": "^4.0.1",
|
||||
"react-svg-pan-zoom": "^3.8.1",
|
||||
"sass": "^1.28.0",
|
||||
"react-trafficlight": "^5.2.1",
|
||||
"sass": "^1.29.0",
|
||||
"superagent": "^6.1.0",
|
||||
"ts-node": "^9.0.0",
|
||||
"type-fest": "^0.13.1",
|
||||
"typescript": "^4.0.5",
|
||||
"utf-8-validate": "^5.0.2",
|
||||
"typescript": "^4.1.2",
|
||||
"utf-8-validate": "^5.0.3",
|
||||
"validator": "^13.1.17",
|
||||
"webpack-hot-middleware": "^2.25.0",
|
||||
"webpack-plugin-serve": "^1.2.0"
|
||||
"webpack-plugin-serve": "^1.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "^4.2.0"
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
<link rel="shortcut icon" type=image/x-icon href="%PUBLIC_URL%/favicon.ico">
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tag above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
import NotificationsDataManager from "../data-managers/notifications-data-manager";
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
|
||||
class WebsocketAPI {
|
||||
constructor(websocketurl, callbacks) {
|
||||
|
@ -65,6 +67,10 @@ class WebsocketAPI {
|
|||
}
|
||||
|
||||
onOpen = e => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'websocket/connected',
|
||||
data: this.websocketurl,
|
||||
});
|
||||
this.wasConnected = true;
|
||||
|
||||
if ('onOpen' in this.callbacks)
|
||||
|
@ -78,6 +84,16 @@ class WebsocketAPI {
|
|||
}
|
||||
else {
|
||||
if (this.wasConnected) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'websocket/connection-error',
|
||||
data: this.websocketurl,
|
||||
});
|
||||
const IC_WEBSOCKET_CONNECTION_ERROR = {
|
||||
title: 'Websocket connection warning',
|
||||
message: "Connection to " + this.websocketurl + " dropped. Attempt reconnect in 1 sec",
|
||||
level: 'warning'
|
||||
};
|
||||
NotificationsDataManager.addNotification(IC_WEBSOCKET_CONNECTION_ERROR);
|
||||
console.log("Connection to " + this.websocketurl + " dropped. Attempt reconnect in 1 sec");
|
||||
window.setTimeout(() => { this.reconnect(); }, 1000);
|
||||
}
|
||||
|
|
|
@ -249,12 +249,6 @@ class Dashboard extends Component {
|
|||
|
||||
};
|
||||
|
||||
|
||||
widgetStatusChange(updated_widget, key) {
|
||||
// Widget changed internally, make changes effective then save them
|
||||
this.widgetChange(updated_widget, key, this.saveChanges);
|
||||
}
|
||||
|
||||
widgetChange(widget, index, callback = null) {
|
||||
let temp = this.state.widgetChangeData;
|
||||
temp.push(widget);
|
||||
|
@ -352,11 +346,19 @@ class Dashboard extends Component {
|
|||
data: widget
|
||||
});
|
||||
}
|
||||
else if (widget.type === 'Image'){
|
||||
widget.customProperties.update = true;
|
||||
}
|
||||
});
|
||||
this.setState({ editing: true, widgetOrigIDs: originalIDs });
|
||||
};
|
||||
|
||||
saveEditing() {
|
||||
this.state.widgets.forEach(widget => {
|
||||
if (widget.type === 'Image'){
|
||||
widget.customProperties.update = true;
|
||||
}
|
||||
});
|
||||
// Provide the callback so it can be called when state change is applied
|
||||
// TODO: Check if callback is needed
|
||||
AppDispatcher.dispatch({
|
||||
|
@ -375,22 +377,12 @@ class Dashboard extends Component {
|
|||
this.setState({ editing: false, widgetChangeData: [] });
|
||||
};
|
||||
|
||||
saveChanges() {
|
||||
// Transform to a list
|
||||
const dashboard = Object.assign({}, this.state.dashboard.toJS(), {
|
||||
widgets: this.transformToWidgetsList(this.state.widgets)
|
||||
});
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'dashboards/start-edit',
|
||||
data: dashboard,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
|
||||
cancelEditing() {
|
||||
//raw widget has no id -> cannot be deleted in its original form
|
||||
this.state.widgets.forEach(widget => {
|
||||
if (widget.type === 'Image'){
|
||||
widget.customProperties.update = true;
|
||||
}
|
||||
let tempID = this.state.widgetOrigIDs.find(element => element === widget.id);
|
||||
if (typeof tempID === 'undefined') {
|
||||
AppDispatcher.dispatch({
|
||||
|
@ -512,7 +504,7 @@ class Dashboard extends Component {
|
|||
{!draggable ? (
|
||||
<WidgetArea widgets={this.state.widgets} dropZoneHeight={dropZoneHeight} editing={this.state.editing} grid={grid} onWidgetAdded={this.handleDrop.bind(this)}>
|
||||
{this.state.widgets != null && Object.keys(this.state.widgets).map(widgetKey => (
|
||||
<WidgetContainer widget={this.state.widgets[widgetKey]}>
|
||||
<WidgetContainer widget={this.state.widgets[widgetKey]} key={widgetKey}>
|
||||
<WidgetContextMenu
|
||||
key={widgetKey}
|
||||
index={parseInt(widgetKey, 10)}
|
||||
|
@ -523,7 +515,6 @@ class Dashboard extends Component {
|
|||
onChange={this.widgetChange.bind(this)}
|
||||
|
||||
onWidgetChange={this.widgetChange.bind(this)}
|
||||
onWidgetStatusChange={this.widgetStatusChange.bind(this)}
|
||||
editing={this.state.editing}
|
||||
grid={grid}
|
||||
paused={this.state.paused}
|
||||
|
@ -536,6 +527,7 @@ class Dashboard extends Component {
|
|||
{this.state.widgets != null && Object.keys(this.state.widgets).map(widgetKey => (
|
||||
<EditableWidgetContainer
|
||||
widget={this.state.widgets[widgetKey]}
|
||||
key={widgetKey}
|
||||
grid={grid}
|
||||
index={parseInt(widgetKey, 10)}
|
||||
onWidgetChange={this.widgetChange.bind(this)}>
|
||||
|
@ -549,7 +541,6 @@ class Dashboard extends Component {
|
|||
onChange={this.widgetChange.bind(this)}
|
||||
|
||||
onWidgetChange={this.widgetChange.bind(this)}
|
||||
onWidgetStatusChange={this.widgetStatusChange.bind(this)}
|
||||
editing={this.state.editing}
|
||||
paused={this.state.paused}
|
||||
/>
|
||||
|
@ -566,6 +557,7 @@ class Dashboard extends Component {
|
|||
widget={this.state.modalData}
|
||||
signals={this.state.signals}
|
||||
files={this.state.files}
|
||||
ics={this.state.ics}
|
||||
/>
|
||||
|
||||
<EditFiles
|
||||
|
|
|
@ -73,7 +73,7 @@ class EditDashboardDialog extends React.Component {
|
|||
return (
|
||||
<Dialog show={this.props.show} title="Edit Dashboard" buttonTitle="Save" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
|
||||
<form>
|
||||
<FormGroup controlId="name" validationState={this.validateForm('name')}>
|
||||
<FormGroup controlId="name" validationstate={this.validateForm('name')}>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl type="text" placeholder="Enter name" value={this.state.name} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
|
|
|
@ -302,9 +302,9 @@ class InfrastructureComponents extends Component {
|
|||
}
|
||||
|
||||
stateUpdateModifier(updatedAt) {
|
||||
let dateFormat = 'ddd, DD MMM YYYY HH:mm:ss';
|
||||
let dateTime = moment.utc(updatedAt, dateFormat);
|
||||
return dateTime.toLocaleString('de-DE');
|
||||
let dateFormat = 'DD MMM YYYY HH:mm:ss';
|
||||
let dateTime = moment(updatedAt, dateFormat);
|
||||
return dateTime.fromNow()
|
||||
}
|
||||
|
||||
modifyManagedExternallyColumn(managedExternally){
|
||||
|
|
|
@ -394,4 +394,19 @@ div[class*="-widget"] label {
|
|||
height: 100%;
|
||||
border: 2px solid lightgray;
|
||||
}
|
||||
/* End box widget */
|
||||
/* End box widget */
|
||||
|
||||
/* Begin time offset widget */
|
||||
.time-offset {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.time-offset span {
|
||||
text-align: center;
|
||||
font-size: 1em;
|
||||
font-weight: 600;
|
||||
}
|
||||
/* End time offset widget */
|
|
@ -55,6 +55,9 @@ class ColorPicker extends React.Component {
|
|||
if(this.props.controlId === 'strokeStyle'){
|
||||
temp.customProperties.zones[this.props.zoneIndex]['strokeStyle'] = color.hex;
|
||||
}
|
||||
else if(this.props.controlId === 'lineColor'){
|
||||
temp.customProperties.lineColors[this.props.lineIndex] = color.hex;
|
||||
}
|
||||
else{
|
||||
let parts = this.props.controlId.split('.');
|
||||
let isCustomProperty = true;
|
||||
|
@ -85,7 +88,7 @@ class ColorPicker extends React.Component {
|
|||
};
|
||||
|
||||
render() {
|
||||
let disableOpacity = false;
|
||||
|
||||
let hexColor;
|
||||
let opacity = 1;
|
||||
let parts = this.props.controlId.split('.');
|
||||
|
@ -94,12 +97,14 @@ class ColorPicker extends React.Component {
|
|||
isCustomProperty = false;
|
||||
}
|
||||
|
||||
if((this.state.widget.type === "Box" && parts[1] === "border_color") || this.props.controlId === 'strokeStyle'){
|
||||
disableOpacity = true;
|
||||
}
|
||||
if(this.props.controlId === 'strokeStyle'){
|
||||
if(typeof this.state.widget.customProperties.zones[this.props.zoneIndex] !== 'undefined'){
|
||||
hexColor = this.state.widget.customProperties.zones[this.props.zoneIndex]['strokeStyle'];
|
||||
hexColor = this.state.widget.customProperties.zones[this.props.zoneIndex]['strokeStyle'];
|
||||
}
|
||||
}
|
||||
else if(this.props.controlId === 'lineColor'){
|
||||
if(typeof this.state.widget.customProperties.lineColors[this.props.lineIndex] !== 'undefined'){
|
||||
hexColor = this.state.widget.customProperties.lineColors[this.props.lineIndex];
|
||||
}
|
||||
}
|
||||
else{
|
||||
|
@ -117,7 +122,7 @@ class ColorPicker extends React.Component {
|
|||
<form>
|
||||
<SketchPicker
|
||||
color={rgbColor}
|
||||
disableAlpha={disableOpacity}
|
||||
disableAlpha={this.props.disableOpacity}
|
||||
onChangeComplete={ this.handleChangeComplete }
|
||||
width={"300"}
|
||||
/>
|
||||
|
|
|
@ -82,7 +82,7 @@ class EditWidgetColorControl extends Component {
|
|||
}
|
||||
|
||||
let tooltipText = "Change color and opacity";
|
||||
if(this.state.widget.type === "Box" && parts[1] === "border_color"){
|
||||
if(this.props.disableOpacity){
|
||||
tooltipText = "Change border color";
|
||||
}
|
||||
|
||||
|
@ -99,7 +99,7 @@ class EditWidgetColorControl extends Component {
|
|||
</OverlayTrigger>
|
||||
</div>
|
||||
|
||||
<ColorPicker show={this.state.showColorPicker} onClose={(data) => this.closeEditModal(data)} widget={this.state.widget} controlId={this.props.controlId} />
|
||||
<ColorPicker show={this.state.showColorPicker} onClose={(data) => this.closeEditModal(data)} widget={this.state.widget} controlId={this.props.controlId} disableOpacity={this.props.disableOpacity}/>
|
||||
</FormGroup>
|
||||
|
||||
)
|
||||
|
|
|
@ -191,7 +191,7 @@ class EditWidgetColorZonesControl extends React.Component {
|
|||
}
|
||||
|
||||
return <FormGroup>
|
||||
<FormLabel>Color zones</FormLabel>
|
||||
<FormLabel>Color Zones</FormLabel>
|
||||
<Button onClick={this.addZone} style={{marginBottom: '10px', marginLeft: '120px'}} disabled={!this.props.widget.customProperties.colorZones}><Icon size='xs' icon="plus" /></Button>
|
||||
|
||||
<div>
|
||||
|
@ -205,8 +205,6 @@ class EditWidgetColorZonesControl extends React.Component {
|
|||
height: '40px'
|
||||
}
|
||||
|
||||
|
||||
|
||||
return (<Button
|
||||
style={style} key={idx} onClick={i => this.editColorZone(idx)} disabled={!this.props.widget.customProperties.colorZones}><Icon icon="pen" /></Button>
|
||||
)
|
||||
|
|
|
@ -31,9 +31,11 @@ import EditWidgetCheckboxControl from './edit-widget-checkbox-control';
|
|||
import EditWidgetColorZonesControl from './edit-widget-color-zones-control';
|
||||
import EditWidgetMinMaxControl from './edit-widget-min-max-control';
|
||||
import EditWidgetParametersControl from './edit-widget-parameters-control';
|
||||
import EditWidgetICControl from './edit-widget-ic-control';
|
||||
import EditWidgetPlotColorsControl from './edit-widget-plot-colors-control';
|
||||
//import EditWidgetHTMLContent from './edit-widget-html-content';
|
||||
|
||||
export default function CreateControls(widgetType = null, widget = null, sessionToken = null, files = null, signals, handleChange) {
|
||||
export default function CreateControls(widgetType = null, widget = null, sessionToken = null, files = null,ics = null, signals, handleChange) {
|
||||
// Use a list to concatenate the controls according to the widget type
|
||||
var DialogControls = [];
|
||||
|
||||
|
@ -62,16 +64,17 @@ export default function CreateControls(widgetType = null, widget = null, session
|
|||
DialogControls.push(
|
||||
<EditWidgetSignalControl key={0} widget={widget} controlId={'signalIDs'} signals={signals} handleChange={(e) => handleChange(e)} direction={'out'}/>,
|
||||
<EditWidgetTextControl key={1} widget={widget} controlId={'customProperties.threshold'} label={'Threshold'} placeholder={'0.5'} handleChange={e => handleChange(e)} />,
|
||||
<EditWidgetColorControl key={2} widget={widget} controlId={'customProperties.on_color'} label={'Color On'} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetColorControl key={3} widget={widget} controlId={'customProperties.off_color'} label={'Color Off'} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetColorControl key={2} widget={widget} controlId={'customProperties.on_color'} label={'Color On'} handleChange={(e) => handleChange(e)} disableOpacity={false}/>,
|
||||
<EditWidgetColorControl key={3} widget={widget} controlId={'customProperties.off_color'} label={'Color Off'} handleChange={(e) => handleChange(e)} disableOpacity={false}/>,
|
||||
);
|
||||
break;
|
||||
case 'Plot':
|
||||
DialogControls.push(
|
||||
<EditWidgetTimeControl key={0} widget={widget} controlId={'customProperties.time'} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetSignalsControl key={1} widget={widget} controlId={'signalIDs'} signals={signals} handleChange={(e) => handleChange(e)} direction={'out'}/>,
|
||||
<EditWidgetTextControl key={2} widget={widget} controlId={'customProperties.ylabel'} label={'Y-Axis name'} placeholder={'Enter a name for the y-axis'} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetMinMaxControl key={3} widget={widget} controlId="customProperties.y" handleChange={e => handleChange(e)} />
|
||||
<EditWidgetPlotColorsControl key={2} widget={widget} controlId={'customProperties.lineColors'} signals={signals} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetTextControl key={3} widget={widget} controlId={'customProperties.ylabel'} label={'Y-Axis name'} placeholder={'Enter a name for the y-axis'} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetMinMaxControl key={4} widget={widget} controlId="customProperties.y" handleChange={e => handleChange(e)} />
|
||||
);
|
||||
break;
|
||||
case 'Table':
|
||||
|
@ -115,20 +118,23 @@ export default function CreateControls(widgetType = null, widget = null, session
|
|||
<EditWidgetSignalControl key={1} widget={widget} controlId={'signalIDs'} input signals={signals} handleChange={(e) => handleChange(e)} direction={'in'}/>,
|
||||
<EditWidgetCheckboxControl key={2} widget={widget} controlId={'customProperties.toggle'} input text="Toggle" handleChange={e => handleChange(e)} />,
|
||||
<EditWidgetNumberControl key={3} widget={widget} controlId={'customProperties.on_value'} label={'On Value'} defaultValue={1} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetNumberControl key={4} widget={widget} controlId={'customProperties.off_value'} label={'Off Value'} defaultValue={0} handleChange={(e) => handleChange(e)} />
|
||||
<EditWidgetNumberControl key={4} widget={widget} controlId={'customProperties.off_value'} label={'Off Value'} defaultValue={0} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetColorControl key={5} widget={widget} controlId={'customProperties.background_color'} label={'Background Color and Opacity'} handleChange={(e) => handleChange(e)} disableOpacity={false}/>,
|
||||
<EditWidgetColorControl key={6} widget={widget} controlId={'customProperties.border_color'} label={'Border Color'} handleChange={(e) => handleChange(e)} disableOpacity={true}/>,
|
||||
<EditWidgetColorControl key={7} widget={widget} controlId={'customProperties.font_color'} label={'Font Color'} handleChange={(e) => handleChange(e)} disableOpacity={true}/>,
|
||||
);
|
||||
break;
|
||||
case 'Box':
|
||||
DialogControls.push(
|
||||
<EditWidgetColorControl key={0} widget={widget} controlId={'customProperties.border_color'} label={'Border color'} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetColorControl key={1} widget={widget} controlId={'customProperties.background_color'} label={'Background color'} handleChange={e => handleChange(e)} />,
|
||||
<EditWidgetColorControl key={0} widget={widget} controlId={'customProperties.background_color'} label={'Background Color and Opacity'} handleChange={e => handleChange(e)} disableOpacity={false}/>,
|
||||
<EditWidgetColorControl key={1} widget={widget} controlId={'customProperties.border_color'} label={'Border Color'} handleChange={(e) => handleChange(e)} disableOpacity={true}/>,
|
||||
);
|
||||
break;
|
||||
case 'Label':
|
||||
DialogControls.push(
|
||||
<EditWidgetTextControl key={0} widget={widget} controlId={'name'} label={'Text'} placeholder={'Enter text'} handleChange={e => handleChange(e)} />,
|
||||
<EditWidgetTextSizeControl key={1} widget={widget} handleChange={e => handleChange(e)} />,
|
||||
<EditWidgetColorControl key={2} widget={widget} controlId={'customProperties.fontColor'} label={'Text color'} handleChange={e => handleChange(e)} />
|
||||
<EditWidgetColorControl key={2} widget={widget} controlId={'customProperties.fontColor'} label={'Text color'} handleChange={e => handleChange(e)} disableOpacity={false}/>
|
||||
);
|
||||
break;
|
||||
/*case 'HTML':
|
||||
|
@ -152,12 +158,22 @@ export default function CreateControls(widgetType = null, widget = null, session
|
|||
break;
|
||||
case 'Line':
|
||||
DialogControls.push(
|
||||
<EditWidgetColorControl key={0} widget={widget} controlId={'customProperties.border_color'} label={'Line color'} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetColorControl key={0} widget={widget} controlId={'customProperties.border_color'} label={'Line color'} handleChange={(e) => handleChange(e)} disableOpacity={false}/>,
|
||||
<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;
|
||||
|
||||
case 'TimeOffset':
|
||||
DialogControls.push(
|
||||
<EditWidgetICControl key={0} widget={widget} controlId={'customProperties.icID'} input ics={ics} handleChange={(e) => handleChange(e)}/>,
|
||||
<EditWidgetNumberControl key={1} widget={widget} controlId={'customProperties.threshold_yellow'} label={'Threshold yellow'} defaultValue={0} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetNumberControl key={2} widget={widget} controlId={'customProperties.threshold_red'} label={'Threshold red'} defaultValue={0} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetCheckboxControl key={3} widget={widget} controlId={'customProperties.horizontal'} input text="Horizontal" handleChange={e => handleChange(e)} />,
|
||||
<EditWidgetCheckboxControl key={4} widget={widget} controlId={'customProperties.showOffset'} input text="showOffset" handleChange={e => handleChange(e)} />,
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log('Non-valid widget type: ' + widgetType);
|
||||
}
|
||||
|
|
74
src/widget/edit-widget/edit-widget-ic-control.js
Normal file
74
src/widget/edit-widget/edit-widget-ic-control.js
Normal file
|
@ -0,0 +1,74 @@
|
|||
/**
|
||||
* 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} from 'react-bootstrap';
|
||||
|
||||
class EditWidgetICControl extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
ics: [],
|
||||
};
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props, state){
|
||||
return {
|
||||
ics: props.ics
|
||||
};
|
||||
}
|
||||
|
||||
handleICChange(e) {
|
||||
let value = e.target.value === "Select IC" ? (-1) : (e.target.value);
|
||||
this.props.handleChange({ target: { id: this.props.controlId, value: value } });
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
let parts = this.props.controlId.split('.');
|
||||
let isCustomProperty = true;
|
||||
if(parts.length === 1){
|
||||
isCustomProperty = false;
|
||||
}
|
||||
|
||||
let icOptions = [];
|
||||
if (this.state.ics !== null && this.state.ics.length > 0){
|
||||
icOptions.push(
|
||||
<option key = {0} default>Select IC</option>
|
||||
)
|
||||
icOptions.push(this.state.ics.map((ic, index) => (
|
||||
<option key={index+1} value={ic.id}>{ic.name}</option>
|
||||
)))
|
||||
} else {
|
||||
icOptions = <option style={{ display: 'none' }}>No ics found</option>
|
||||
}
|
||||
|
||||
return <div>
|
||||
<FormGroup controlId="ic">
|
||||
<FormLabel>IC</FormLabel>
|
||||
<FormControl
|
||||
as="select"
|
||||
value={isCustomProperty ? this.props.widget[parts[0]][parts[1]] : this.props.widget[this.props.controlId]}
|
||||
onChange={(e) => this.handleICChange(e)}>{icOptions} </FormControl>
|
||||
</FormGroup>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export default EditWidgetICControl;
|
101
src/widget/edit-widget/edit-widget-plot-colors-control.js
Normal file
101
src/widget/edit-widget/edit-widget-plot-colors-control.js
Normal file
|
@ -0,0 +1,101 @@
|
|||
/**
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { FormGroup, OverlayTrigger, Tooltip , FormLabel, Button } from 'react-bootstrap';
|
||||
import ColorPicker from './color-picker'
|
||||
import Icon from "../../common/icon";
|
||||
|
||||
// schemeCategory20 no longer available in d3
|
||||
|
||||
class EditWidgetPlotColorsControl extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
widget: {},
|
||||
showColorPicker: false,
|
||||
originalColor: null,
|
||||
selectedIndex: null
|
||||
};
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props, state){
|
||||
return {
|
||||
widget: props.widget
|
||||
};
|
||||
}
|
||||
|
||||
//same here
|
||||
|
||||
closeEditModal = (data) => {
|
||||
this.setState({showColorPicker: false})
|
||||
if(typeof data === 'undefined'){
|
||||
|
||||
let temp = this.state.widget;
|
||||
temp.customProperties.lineColors[this.state.selectedIndex] = this.state.originalColor;
|
||||
this.setState({ widget: temp });
|
||||
}
|
||||
}
|
||||
|
||||
editLineColor = (index) => {
|
||||
if(this.state.selectedIndex !== index){
|
||||
let color = this.state.widget.customProperties.lineColors[index];
|
||||
this.setState({selectedIndex: index, showColorPicker: true, originalColor: color});
|
||||
}
|
||||
else{
|
||||
this.setState({selectedIndex: null});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
return (
|
||||
<FormGroup>
|
||||
<FormLabel>Line Colors</FormLabel>
|
||||
|
||||
<div>
|
||||
{
|
||||
this.state.widget.signalIDs.map((signalID, idx) => {
|
||||
let color = this.state.widget.customProperties.lineColors[signalID];
|
||||
let width = 260 / this.state.widget.signalIDs.length;
|
||||
let style = {
|
||||
backgroundColor: color,
|
||||
width: width,
|
||||
height: '40px'
|
||||
}
|
||||
|
||||
let signal = this.props.signals.find(signal => signal.id === signalID);
|
||||
|
||||
return (<OverlayTrigger key={idx} placement={'bottom'} overlay={<Tooltip id={'tooltip-${"signal name"}'}>{signal.name}</Tooltip>}>
|
||||
<Button
|
||||
style={style} key={idx} onClick={i => this.editLineColor(signalID)} ><Icon icon="pen" /></Button>
|
||||
</OverlayTrigger>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
|
||||
<ColorPicker show={this.state.showColorPicker} onClose={(data) => this.closeEditModal(data)} widget={this.state.widget} lineIndex={this.state.selectedIndex} controlId={'lineColor'} disableOpacity={true}/>
|
||||
</FormGroup>
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default EditWidgetPlotColorsControl;
|
|
@ -52,9 +52,9 @@ class EditWidgetDialog extends React.Component {
|
|||
const file = this.props.files.find(element => element.id === fileId);
|
||||
|
||||
// scale width to match aspect
|
||||
if(file.dimensions){
|
||||
const aspectRatio = file.dimensions.width / file.dimensions.height;
|
||||
changeObject.width = this.state.temporal.height * aspectRatio;
|
||||
if(file.imageWidth && file.imageHeight){
|
||||
const aspectRatio = file.imageWidth / file.imageHeight;
|
||||
changeObject.width = parseInt(this.state.temporal.height * aspectRatio,10);
|
||||
}
|
||||
|
||||
return changeObject;
|
||||
|
@ -72,13 +72,14 @@ class EditWidgetDialog extends React.Component {
|
|||
setMaxWidth(changeObject){
|
||||
if(changeObject.type === 'Label'){
|
||||
changeObject.customProperties.maxWidth = Math.ceil(this.getTextWidth(changeObject.name, changeObject.customProperties.textSize));
|
||||
changeObject.width = changeObject.customProperties.maxWidth;
|
||||
}
|
||||
/*else if (changeObject.type === 'Value'){
|
||||
changeObject.customProperties.maxWidth = Math.ceil(this.getTextWidth(changeObject.name, changeObject.customProperties.textSize));
|
||||
}*/
|
||||
}
|
||||
if(this.state.temporal.width > changeObject.customProperties.maxWidth){
|
||||
changeObject.width = changeObject.customProperties.maxWidth;
|
||||
}
|
||||
}*/
|
||||
return changeObject;
|
||||
}
|
||||
|
||||
|
@ -118,14 +119,12 @@ class EditWidgetDialog extends React.Component {
|
|||
{
|
||||
customProperty ? changeObject[parts[0]][parts[1]] = -1 : changeObject[e.target.id] = -1;
|
||||
} else {
|
||||
if(this.state.temporal.customProperties.lockAspect){
|
||||
changeObject = this.assignAspectRatio(changeObject, e.target.value);
|
||||
}
|
||||
customProperty ? changeObject[parts[0]][parts[1]] = e.target.value : changeObject[e.target.id] = e.target.value;
|
||||
}
|
||||
|
||||
// get file and update size (if it's an image)
|
||||
/*if ((changeObject.customProperties.file !== -1)&&('lockAspect' in this.state.temporal && this.state.temporal.lockAspect)) {
|
||||
// TODO this if condition requires changes to work!!!
|
||||
changeObject = this.assignAspectRatio(changeObject, e.target.value);
|
||||
}*/
|
||||
} else if (parts[1] === 'textSize'){
|
||||
changeObject[parts[0]][parts[1]] = Number(e.target.value);
|
||||
changeObject = this.setMaxWidth(changeObject);
|
||||
|
@ -142,6 +141,11 @@ class EditWidgetDialog extends React.Component {
|
|||
customProperty ? changeObject[parts[0]][parts[1]]= 'default' : changeObject[e.target.id] = 'default';
|
||||
}
|
||||
changeObject = this.setMaxWidth(changeObject);
|
||||
} else if (parts[1] === 'horizontal'){
|
||||
customProperty ? changeObject[parts[0]][parts[1]] = e.target.value : changeObject[e.target.id] = e.target.value ;
|
||||
let tempWidth = changeObject.width;
|
||||
changeObject.width = changeObject.height;
|
||||
changeObject.height = tempWidth;
|
||||
} else {
|
||||
customProperty ? changeObject[parts[0]][parts[1]] = e.target.value : changeObject[e.target.id] = e.target.value ;
|
||||
}
|
||||
|
@ -164,6 +168,7 @@ class EditWidgetDialog extends React.Component {
|
|||
this.state.temporal,
|
||||
this.props.sessionToken,
|
||||
this.props.files,
|
||||
this.props.ics,
|
||||
this.props.signals,
|
||||
(e) => this.handleChange(e));
|
||||
}
|
||||
|
|
59
src/widget/websocket-store.js
Normal file
59
src/widget/websocket-store.js
Normal file
|
@ -0,0 +1,59 @@
|
|||
/**
|
||||
* 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 ArrayStore from '../common/array-store';
|
||||
|
||||
class WebsocketStore extends ArrayStore {
|
||||
|
||||
updateSocketStatus(state, socket) {
|
||||
let checkInclusion = false;
|
||||
state.forEach((element) => {
|
||||
if (element.url === socket.url) {
|
||||
element.connected = socket.connected;
|
||||
checkInclusion = true;
|
||||
}
|
||||
})
|
||||
if (!checkInclusion) {
|
||||
state.push(socket);
|
||||
}
|
||||
this.__emitChange();
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
reduce(state, action) {
|
||||
let tempSocket = {};
|
||||
switch (action.type) {
|
||||
|
||||
case 'websocket/connected':
|
||||
tempSocket.url = action.data;
|
||||
tempSocket.connected = true;
|
||||
return this.updateSocketStatus(state, tempSocket);
|
||||
|
||||
case 'websocket/connection-error':
|
||||
tempSocket.url = action.data;
|
||||
tempSocket.connected = false;
|
||||
return this.updateSocketStatus(state, tempSocket);
|
||||
|
||||
|
||||
default:
|
||||
return super.reduce(state, action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new WebsocketStore();
|
|
@ -125,7 +125,6 @@ class WidgetContextMenu extends React.Component {
|
|||
<Widget
|
||||
data={this.props.widget}
|
||||
onWidgetChange={this.props.onWidgetChange}
|
||||
onWidgetStatusChange={this.props.onWidgetStatusChange}
|
||||
editing={this.props.editing}
|
||||
index={this.props.index}
|
||||
paused={this.props.paused}
|
||||
|
|
|
@ -89,6 +89,7 @@ class WidgetFactory {
|
|||
widget.customProperties.yMin = 0;
|
||||
widget.customProperties.yMax = 10;
|
||||
widget.customProperties.yUseMinMax = false;
|
||||
widget.customProperties.lineColors = [];
|
||||
break;
|
||||
case 'Table':
|
||||
widget.minWidth = 200;
|
||||
|
@ -122,8 +123,10 @@ class WidgetFactory {
|
|||
widget.minHeight = 50;
|
||||
widget.width = 100;
|
||||
widget.height = 100;
|
||||
widget.customProperties.background_color = '#4287f5';
|
||||
widget.customProperties.background_color = '#527984';
|
||||
widget.customProperties.font_color = '#4287f5';
|
||||
widget.customProperties.border_color = '#4287f5';
|
||||
widget.customProperties.background_color_opacity = 1;
|
||||
widget.customProperties.on_value = 1;
|
||||
widget.customProperties.off_value = 0;
|
||||
widget.customProperties.toggle = false;
|
||||
|
@ -195,6 +198,19 @@ class WidgetFactory {
|
|||
widget.customProperties.lockAspect = true;
|
||||
break;
|
||||
|
||||
case 'TimeOffset':
|
||||
widget.minWidth = 20;
|
||||
widget.minHeight = 20;
|
||||
widget.width = 100;
|
||||
widget.height = 40;
|
||||
widget.customProperties.threshold_yellow = 1;
|
||||
widget.customProperties.threshold_red = 2;
|
||||
widget.customProperties.icID = -1;
|
||||
widget.customProperties.horizontal = true;
|
||||
widget.customProperties.showOffset = true;
|
||||
widget.customProperties.lockAspect = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
widget.width = 100;
|
||||
widget.height = 100;
|
||||
|
|
|
@ -20,13 +20,17 @@ import { scaleOrdinal} from 'd3-scale';
|
|||
import {schemeCategory10} from 'd3-scale-chromatic'
|
||||
|
||||
function Legend(props){
|
||||
|
||||
const signal = props.sig;
|
||||
const hasScalingFactor = (signal.scalingFactor !== 1);
|
||||
|
||||
const newLineColor = scaleOrdinal(schemeCategory10);
|
||||
|
||||
let color = typeof props.lineColor === "undefined" ? newLineColor(signal.id) : props.lineColor;
|
||||
|
||||
if(hasScalingFactor){
|
||||
return (
|
||||
<li key={signal.id} className="signal-legend" style={{ color: props.colorScale(signal.id) }}>
|
||||
<li key={signal.id} className="signal-legend" style={{ color: color }}>
|
||||
<span className="signal-legend-name">{signal.name}</span>
|
||||
<span style={{ marginLeft: '0.3em' }} className="signal-unit">{signal.unit}</span>
|
||||
<span style={{ marginLeft: '0.3em' }} className="signal-scale">{signal.scalingFactor}</span>
|
||||
|
@ -34,7 +38,7 @@ function Legend(props){
|
|||
)
|
||||
} else {
|
||||
return (
|
||||
<li key={signal.id} className="signal-legend" style={{ color: props.colorScale(signal.id) }}>
|
||||
<li key={signal.id} className="signal-legend" style={{ color: color }}>
|
||||
<span className="signal-legend-name">{signal.name}</span>
|
||||
<span style={{ marginLeft: '0.3em' }} className="signal-unit">{signal.unit}</span>
|
||||
</li>
|
||||
|
@ -45,13 +49,12 @@ function Legend(props){
|
|||
|
||||
class PlotLegend extends React.Component {
|
||||
render() {
|
||||
const colorScale = scaleOrdinal(schemeCategory10);
|
||||
|
||||
return <div className="plot-legend">
|
||||
<ul>
|
||||
{
|
||||
this.props.signals.map( signal =>
|
||||
<Legend key={signal.id} sig={signal} colorScale={colorScale}/>
|
||||
<Legend key={signal.id} sig={signal} lineColor={this.props.lineColors[signal.id]}/>
|
||||
)}
|
||||
</ul>
|
||||
</div>;
|
||||
|
|
|
@ -203,14 +203,21 @@ class Plot extends React.Component {
|
|||
|
||||
// generate paths from data
|
||||
const sparkLine = line().x(p => xScale(p.x)).y(p => yScale(p.y));
|
||||
const lineColor = scaleOrdinal(schemeCategory10);
|
||||
const newLineColor = scaleOrdinal(schemeCategory10);
|
||||
|
||||
const lines = this.state.data.map((values, index) => <path d={sparkLine(values)} key={index} style={{ fill: 'none', stroke: lineColor(index) }} />);
|
||||
const lines = this.state.data.map((values, index) => {
|
||||
let signalID = this.props.signalIDs[index];
|
||||
if (typeof this.props.lineColors[signalID] === "undefined") {
|
||||
this.props.lineColors[signalID] = newLineColor(signalID);
|
||||
}
|
||||
return <path d={sparkLine(values)} key={index} style={{ fill: 'none', stroke: this.props.lineColors[signalID] }} />
|
||||
});
|
||||
|
||||
this.setState({ lines, xAxis, yAxis });
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
const yLabelPos = {
|
||||
x: 12,
|
||||
y: this.props.height / 2
|
||||
|
|
|
@ -164,6 +164,7 @@ class WidgetToolbox extends React.Component {
|
|||
<ToolboxItem name='Lamp' type='widget' icon = 'plus' />
|
||||
<ToolboxItem name='Gauge' type='widget' icon = 'plus'/>
|
||||
<ToolboxItem name='Topology' type='widget' disabled={thereIsTopologyWidget} title={topologyItemMsg} icon = 'plus'/>
|
||||
<ToolboxItem name='TimeOffset' type='widget' icon = 'plus' />
|
||||
<OverlayTrigger key={0} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"?"}`}> Drag and drop widgets onto the dashboard </Tooltip>} >
|
||||
<Button disabled={true} variant="light" size="sm" key={0} >
|
||||
<Icon icon="question" />
|
||||
|
|
|
@ -23,6 +23,8 @@ import ICDataStore from '../ic/ic-data-store';
|
|||
import ConfigsStore from '../componentconfig/config-store';
|
||||
import FileStore from '../file/file-store';
|
||||
import SignalStore from '../signal/signal-store'
|
||||
import WebsocketStore from './websocket-store'
|
||||
import ICStore from '../ic/ic-store';
|
||||
|
||||
import WidgetCustomAction from './widgets/custom-action';
|
||||
import WidgetAction from './widgets/action';
|
||||
|
@ -39,6 +41,7 @@ import WidgetGauge from './widgets/gauge';
|
|||
import WidgetBox from './widgets/box';
|
||||
import WidgetTopology from './widgets/topology';
|
||||
import WidgetLine from './widgets/line';
|
||||
import WidgetTimeOffset from './widgets/time-offset'
|
||||
//import WidgetHTML from './widgets/html';
|
||||
|
||||
|
||||
|
@ -46,11 +49,14 @@ import '../styles/widgets.css';
|
|||
|
||||
class Widget extends React.Component {
|
||||
static getStores() {
|
||||
return [ ICDataStore, ConfigsStore, FileStore, SignalStore];
|
||||
return [ ICDataStore, ConfigsStore, FileStore, SignalStore, WebsocketStore, ICStore];
|
||||
}
|
||||
|
||||
static calculateState(prevState, props) {
|
||||
|
||||
let websockets = WebsocketStore.getState();
|
||||
let ics = ICStore.getState();
|
||||
|
||||
let icData = {};
|
||||
|
||||
if (props.paused) {
|
||||
|
@ -78,6 +84,8 @@ class Widget extends React.Component {
|
|||
}
|
||||
|
||||
return {
|
||||
ics: ics,
|
||||
websockets: websockets,
|
||||
icData: icData,
|
||||
signals: signals,
|
||||
icIDs: icIDs,
|
||||
|
@ -191,7 +199,6 @@ class Widget extends React.Component {
|
|||
return <WidgetSlider
|
||||
widget={widget}
|
||||
editing={this.props.editing}
|
||||
onWidgetChange={(w) => this.props.onWidgetStatusChange(w, this.props.index) }
|
||||
onInputChanged={(value, controlID, controlValue) => this.inputDataChanged(widget, value, controlID, controlValue)}
|
||||
signals={this.state.signals}
|
||||
/>
|
||||
|
@ -224,6 +231,13 @@ class Widget extends React.Component {
|
|||
widget={widget}
|
||||
editing={this.props.editing}
|
||||
/>
|
||||
} else if (widget.type === 'TimeOffset') {
|
||||
return <WidgetTimeOffset
|
||||
widget={widget}
|
||||
data={this.state.icData}
|
||||
websockets={this.state.websockets}
|
||||
ics={this.state.ics}
|
||||
/>
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
|
@ -59,10 +59,19 @@ class WidgetButton extends Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
|
||||
const buttonStyle = {
|
||||
backgroundColor: this.props.widget.customProperties.background_color,
|
||||
borderColor: this.props.widget.customProperties.border_color,
|
||||
color: this.props.widget.customProperties.font_color,
|
||||
opacity: this.props.widget.customProperties.background_color_opacity
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="button-widget full">
|
||||
<Button
|
||||
className="full"
|
||||
style={buttonStyle}
|
||||
active={ this.state.pressed }
|
||||
disabled={ this.props.editing }
|
||||
onMouseDown={ (e) => this.onPress(e) }
|
||||
|
|
|
@ -103,9 +103,11 @@ class WidgetPlot extends React.Component {
|
|||
yUseMinMax={this.props.widget.customProperties.yUseMinMax}
|
||||
paused={this.props.paused}
|
||||
yLabel={this.props.widget.customProperties.ylabel}
|
||||
lineColors={this.props.widget.customProperties.lineColors}
|
||||
signalIDs={this.props.widget.signalIDs}
|
||||
/>
|
||||
</div>
|
||||
<PlotLegend signals={this.state.signals} />
|
||||
<PlotLegend signals={this.state.signals} lineColors={this.props.widget.customProperties.lineColors} />
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,27 +76,6 @@ class WidgetSlider extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Readonly<P>, prevState: Readonly<S>, snapshot: SS): void {
|
||||
// Check if the orientation changed, update the size if it did
|
||||
// this part didn't work -> dimensions and constraints are now handled by the edit orientation component
|
||||
if (this.props.widget.customProperties.orientation !== prevProps.widget.customProperties.orientation) {
|
||||
let baseWidget = this.props.widget;
|
||||
|
||||
// Exchange dimensions and constraints
|
||||
let newWidget = Object.assign({}, baseWidget, {
|
||||
width: baseWidget.height,
|
||||
height: baseWidget.width,
|
||||
minWidth: baseWidget.minHeight,
|
||||
minHeight: baseWidget.minWidth,
|
||||
maxWidth: baseWidget.customProperties.maxHeight,
|
||||
maxHeight: baseWidget.customProperties.maxWidth
|
||||
});
|
||||
|
||||
this.props.onWidgetChange(newWidget);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
valueIsChanging(newValue) {
|
||||
this.props.widget.customProperties.value = newValue;
|
||||
if (this.props.widget.customProperties.continous_update)
|
||||
|
|
98
src/widget/widgets/time-offset.js
Normal file
98
src/widget/widgets/time-offset.js
Normal file
|
@ -0,0 +1,98 @@
|
|||
/**
|
||||
* 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 TrafficLight from 'react-trafficlight';
|
||||
import {OverlayTrigger, Tooltip } from 'react-bootstrap';
|
||||
|
||||
|
||||
class WidgetTimeOffset extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
timeOffset: '',
|
||||
icID: '',
|
||||
icName: '',
|
||||
websocketOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props, state){
|
||||
|
||||
if(typeof props.widget.customProperties.icID !== "undefined" && state.icID !== props.widget.customProperties.icID){
|
||||
return {icID: props.widget.customProperties.icID};
|
||||
}
|
||||
|
||||
if (props.data == null
|
||||
|| props.data[state.icID] == null
|
||||
|| props.data[state.icID].output == null
|
||||
|| props.data[state.icID].output.timestamp == null) {
|
||||
return {timeOffset: -1};
|
||||
}
|
||||
|
||||
let ic = props.ics.find(ic => ic.id === parseInt(state.icID, 10));
|
||||
let websocket = props.websockets.find(ws => ws.url === ic.websocketurl);
|
||||
|
||||
let serverTime = props.data[state.icID].output.timestamp;
|
||||
let localTime = Date.now();
|
||||
let absoluteOffset = Math.abs(serverTime - localTime);
|
||||
|
||||
if(typeof websocket === 'undefined'){
|
||||
return {timeOffset: Number.parseFloat(absoluteOffset/1000).toPrecision(5)}
|
||||
}
|
||||
return {timeOffset: Number.parseFloat(absoluteOffset/1000).toPrecision(5), websocketOpen: websocket.connected, icName: ic.name};
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
let icSelected = " ";
|
||||
if(!this.state.websocketOpen){
|
||||
icSelected = "no connection";
|
||||
} else if (this.props.widget.customProperties.showOffset){
|
||||
icSelected = this.state.timeOffset + 's';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="time-offset">
|
||||
{this.props.widget.customProperties.icID !== -1 ?
|
||||
(<span></span>) : (<span>no IC</span>)
|
||||
}
|
||||
<OverlayTrigger key={0} placement={'left'} overlay={<Tooltip id={`tooltip-${"traffic-light"}`}>
|
||||
{this.props.widget.customProperties.icID !== -1 ?
|
||||
(<span>{this.state.icName}<br></br>Offset: {this.state.timeOffset + "s"}</span>)
|
||||
:
|
||||
(<span>Please select Infrastructure Component</span>)}
|
||||
</Tooltip>}>
|
||||
<TrafficLight Horizontal={this.props.widget.customProperties.horizontal} width={this.props.widget.width} height={this.props.widget.height}
|
||||
RedOn={(this.props.widget.customProperties.threshold_red <= this.state.timeOffset) || !this.state.websocketOpen}
|
||||
YellowOn={(this.props.widget.customProperties.threshold_yellow <= this.state.timeOffset) && (this.state.timeOffset < this.props.widget.customProperties.threshold_red) && this.state.websocketOpen}
|
||||
GreenOn={(this.state.timeOffset < this.props.widget.customProperties.threshold_yellow) && this.state.websocketOpen}
|
||||
/>
|
||||
</OverlayTrigger>
|
||||
{this.props.widget.customProperties.icID !== -1 ?
|
||||
(
|
||||
<span>{icSelected}</span>)
|
||||
:
|
||||
(<span>selected</span>)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default WidgetTimeOffset;
|
Loading…
Add table
Reference in a new issue