1
0
Fork 0
mirror of https://git.rwth-aachen.de/acs/public/villas/web/ synced 2025-03-30 00:00:13 +01:00
VILLASweb/src/dashboard/dashboard.js

438 lines
12 KiB
JavaScript

/**
* 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 { Container } from 'flux/utils';
import Fullscreenable from 'react-fullscreenable';
import classNames from 'classnames';
import Widget from '../widget/widget';
import EditWidget from '../widget/edit-widget/edit-widget';
import WidgetContextMenu from '../widget/widget-context-menu';
import WidgetToolbox from '../widget/widget-toolbox';
import WidgetArea from '../widget/widget-area';
import DashboardButtonGroup from './dashboard-button-group';
import LoginStore from '../user/login-store';
import DashboardStore from './dashboard-store';
import SignalStore from '../signal/signal-store'
import FileStore from '../file/file-store';
import WidgetStore from '../widget/widget-store';
import AppDispatcher from '../common/app-dispatcher';
import 'react-contexify/dist/ReactContexify.min.css';
class Dashboard extends Component {
static lastWidgetKey = 0;
static getStores() {
return [ DashboardStore, FileStore, LoginStore, WidgetStore, SignalStore ];
}
static calculateState(prevState, props) {
if (prevState == null) {
prevState = {};
}
const sessionToken = LoginStore.getState().token;
let dashboard = DashboardStore.getState().find(d => d.id === parseInt(props.match.params.dashboard, 10));
if (dashboard == null){
AppDispatcher.dispatch({
type: 'dashboards/start-load',
data: props.match.params.dashboard,
token: sessionToken
});
}
// obtain all widgets of a dashboard
let widgets = WidgetStore.getState().filter(w => w.dashboardID === parseInt(props.match.params.dashboard, 10));
// compute max y coordinate
let maxHeight = null;
maxHeight = Object.keys(widgets).reduce( (maxHeightSoFar, widgetKey) => {
let thisWidget = widgets[widgetKey];
let thisWidgetHeight = thisWidget.y + thisWidget.height;
return thisWidgetHeight > maxHeightSoFar? thisWidgetHeight : maxHeightSoFar;
}, 0);
// TODO filter signals to the ones belonging to the scenario at hand!
let signals = SignalStore.getState();
// get files of all widgets
let allFiles = FileStore.getState();
let files = [];
let file, widget;
for (file of allFiles){
for (widget of widgets){
if (file.widgetID === widget.id){
files.push(file);
}
}
}
// TODO create list of infrastructure components in use
return {
dashboard,
widgets,
signals,
sessionToken: sessionToken,
files: files,
editing: prevState.editing || false,
paused: prevState.paused || false,
editModal: false,
modalData: null,
modalIndex: null,
widgetChangeData: [],
widgetAddData:prevState.widgetAddData || [],
maxWidgetHeight: maxHeight || null,
dropZoneHeight: maxHeight +80 || null,
};
}
static getNewWidgetKey() {
const widgetKey = this.lastWidgetKey;
this.lastWidgetKey++;
return widgetKey;
}
//!!!won't work anymore
componentDidMount() {
// load widgets of dashboard
AppDispatcher.dispatch({
type: 'widgets/start-load',
token: this.state.sessionToken,
param: '?dashboardID=' + this.state.dashboard.id
});
// TODO open websockets in componentDidMount
// TODO close websockets in componentWillUnmount
}
handleKeydown(e) {
switch (e.key) {
case ' ':
case 'p':
this.setState({ paused: !this.state.paused });
break;
case 'e':
this.setState({ editing: !this.state.editing });
break;
case 'f':
this.props.toggleFullscreen();
break;
default:
}
}
/*
* Adapt the area's height with the position of the new widget.
* Return true if the height increased, otherwise false.
*/
increaseHeightWithWidget(widget) {
let increased = false;
let thisWidgetHeight = widget.y + widget.height;
if (thisWidgetHeight > this.state.maxWidgetHeight) {
increased = true;
this.setState({
maxWidgetHeight: thisWidgetHeight,
dropZoneHeight: thisWidgetHeight + 40
});
}
return increased;
}
transformToWidgetsList(widgets) {
return Object.keys(widgets).map( (key) => widgets[key]);
}
handleDrop(widget) {
widget.dashboardID = this.state.dashboard.id;
let tempChanges = this.state.widgetAddData;
tempChanges.push(widget);
this.setState({ widgetAddData: tempChanges})
AppDispatcher.dispatch({
type: 'widgets/start-add',
token: this.state.sessionToken,
data: widget
});
};
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);
this.setState({widgetChangeData: temp});
}
/*
* Set the initial height state based on the existing widgets
*/
computeHeightWithWidgets(widgets) {
// Compute max height from widgets
let maxHeight = Object.keys(widgets).reduce( (maxHeightSoFar, widgetKey) => {
let thisWidget = widgets[widgetKey];
let thisWidgetHeight = thisWidget.y + thisWidget.height;
return thisWidgetHeight > maxHeightSoFar? thisWidgetHeight : maxHeightSoFar;
}, 0);
this.setState({
maxWidgetHeight: maxHeight,
dropZoneHeight: maxHeight + 80
});
}
editWidget(widget, index){
this.setState({ editModal: true, modalData: widget, modalIndex: index });
};
closeEdit(data){
if (data == null) {
AppDispatcher.dispatch({
type: 'widgets/start-load',
token: this.state.sessionToken,
param: '?dashboardID=1'
});
this.setState({ editModal: false });
return;
}
AppDispatcher.dispatch({
type: 'widgets/start-edit',
token: this.state.sessionToken,
data: data
});
this.setState({ editModal: false });
};
deleteWidget(widget, index) {
AppDispatcher.dispatch({
type: 'widgets/start-remove',
data: widget,
token: this.state.sessionToken
});
};
startEditing(){
this.state.widgets.forEach( widget => {
if(widget.type === 'Slider' || widget.type === 'NumberInput' || widget.type === 'Button'){
console.log("we should move in here");
AppDispatcher.dispatch({
type: 'widgets/start-edit',
token: this.state.sessionToken,
data: widget
});
}
});
this.setState({ editing: true });
};
saveEditing() {
// Provide the callback so it can be called when state change is applied
// TODO: Check if callback is needed
AppDispatcher.dispatch({
type: 'dashboards/start-edit',
data: this.state.dashboard,
token: this.state.sessionToken
});
this.state.widgetChangeData.forEach( widget => {
AppDispatcher.dispatch({
type: 'widgets/start-edit',
token: this.state.sessionToken,
data: widget
});
});
this.setState({ editing: false, widgetChangeData: [], widgetAddData: [] });
};
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
let temp = [];
this.state.widgetAddData.forEach(rawWidget => {
this.state.widgets.forEach(widget => {
if(widget.y === rawWidget.y && widget.x === rawWidget.x && widget.type === rawWidget.type){
temp.push(widget);
}
})
})
temp.forEach( widget => {
AppDispatcher.dispatch({
type: 'widgets/start-remove',
data: widget,
token: this.state.sessionToken
});
});
AppDispatcher.dispatch({
type: 'widgets/start-load',
token: this.state.sessionToken,
param: '?dashboardID=1'
});
this.setState({ editing: false, widgetChangeData: [], widgetAddData: []});
};
setGrid(value) {
let dashboard = this.state.dashboard;
dashboard.grid = value;
this.setState({ dashboard });
this.forceUpdate();
};
pauseData(){
this.setState({ paused: true });
};
unpauseData() {
this.setState({ paused: false });
};
render() {
const grid = this.state.dashboard.grid;
const boxClasses = classNames('section', 'box', { 'fullscreen-padding': this.props.isFullscreen });
let draggable = this.state.editing;
return <div className={boxClasses} >
<div className='section-header box-header'>
<div className="section-title">
<span>{this.state.dashboard.name}</span>
</div>
<DashboardButtonGroup
editing={this.state.editing}
onEdit={this.startEditing.bind(this)}
fullscreen={this.props.isFullscreen}
paused={this.state.paused}
onSave={this.saveEditing.bind(this)}
onCancel={this.cancelEditing.bind(this)}
onFullscreen={this.props.toggleFullscreen}
onPause={this.pauseData.bind(this)}
onUnpause={this.unpauseData.bind(this)}
/>
</div>
<div className="box box-content" onContextMenu={ (e) => e.preventDefault() }>
{this.state.editing &&
<WidgetToolbox grid={grid} onGridChange={this.setGrid.bind(this)} widgets={this.state.widgets} />
}
{!draggable?(
<WidgetArea widgets={this.state.widgets} editing={this.state.editing} grid={grid} onWidgetAdded={this.handleDrop.bind(this)}>
{this.state.widgets != null && Object.keys(this.state.widgets).map(widgetKey => (
<WidgetContextMenu
key={widgetKey}
index={parseInt(widgetKey,10)}
widget={this.state.widgets[widgetKey]}
onEdit={this.editWidget.bind(this)}
onDelete={this.deleteWidget.bind(this)}
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}
/>
))}
</WidgetArea>
) : (
<WidgetArea widgets={this.state.widgets} editing={this.state.editing} grid={grid} onWidgetAdded={this.handleDrop.bind(this)}>
{this.state.widgets != null && Object.keys(this.state.widgets).map(widgetKey => (
<Widget
key={widgetKey}
data={this.state.widgets[widgetKey]}
onWidgetChange={this.widgetChange.bind(this)}
onWidgetStatusChange={this.widgetStatusChange.bind(this)}
editing={this.state.editing}
index={parseInt(widgetKey,10)}
grid={grid}
paused={this.state.paused}
/>
))}
</WidgetArea>
)}
<EditWidget
sessionToken={this.state.sessionToken}
show={this.state.editModal}
onClose={this.closeEdit.bind(this)}
widget={this.state.modalData}
signals={this.state.signals}
files={this.state.files}
/>
</div>
</div>;
}
}
let fluxContainerConverter = require('../common/FluxContainerConverter');
export default Fullscreenable()(Container.create(fluxContainerConverter.convert(Dashboard), { withProps: true }));