mirror of
https://git.rwth-aachen.de/acs/public/villas/web/
synced 2025-03-09 00:00:01 +01:00
cherry picked all changes from the cleanup-widgets branch and merged them to the new file/ folder structure and package versions; most likely, further fixes are required to make things work. Improves #209 and #205
This commit is contained in:
parent
5ecb254e13
commit
7331bf517b
10 changed files with 823 additions and 490 deletions
37
package-lock.json
generated
37
package-lock.json
generated
|
@ -5153,11 +5153,18 @@
|
|||
}
|
||||
},
|
||||
"eslint-utils": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.0.tgz",
|
||||
"integrity": "sha512-7ehnzPaP5IIEh1r1tkjuIrxqhNkzUJa9z3R92tLJdZIVdWaczEhr3EbhGtsMrVxi1KeR8qA7Off6SWc5WNQqyQ==",
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz",
|
||||
"integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==",
|
||||
"requires": {
|
||||
"eslint-visitor-keys": "^1.0.0"
|
||||
"eslint-visitor-keys": "^1.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"eslint-visitor-keys": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz",
|
||||
"integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"eslint-visitor-keys": {
|
||||
|
@ -6048,9 +6055,9 @@
|
|||
"integrity": "sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ=="
|
||||
},
|
||||
"handlebars": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz",
|
||||
"integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==",
|
||||
"version": "4.5.1",
|
||||
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.1.tgz",
|
||||
"integrity": "sha512-C29UoFzHe9yM61lOsIlCE5/mQVGrnIOrOq7maQl76L7tYPCgC1og0Ajt6uWnX4ZTxBPnjw+CUvawphwCfJgUnA==",
|
||||
"requires": {
|
||||
"neo-async": "^2.6.0",
|
||||
"optimist": "^0.6.1",
|
||||
|
@ -12841,13 +12848,21 @@
|
|||
"integrity": "sha512-8OaIKfzL5cpx8eCMAhhvTlft8GYF8b2eQr6JkCyVdrgjcytyOmPCXrqXFcUnhonRpLlh5yxEZVohm6mzaowUOw=="
|
||||
},
|
||||
"uglify-js": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.0.tgz",
|
||||
"integrity": "sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==",
|
||||
"version": "3.6.7",
|
||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.7.tgz",
|
||||
"integrity": "sha512-4sXQDzmdnoXiO+xvmTzQsfIiwrjUCSA95rSP4SEd8tDb51W2TiDOlL76Hl+Kw0Ie42PSItCW8/t6pBNCF2R48A==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"commander": "~2.20.0",
|
||||
"commander": "~2.20.3",
|
||||
"source-map": "~0.6.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": {
|
||||
"version": "2.20.3",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"uncontrollable": {
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
"flux": "^3.1.3",
|
||||
"frontend-collective-react-dnd-scrollzone": "^1.0.2",
|
||||
"gaugeJS": "^1.3.7",
|
||||
"handlebars": "^4.1.2",
|
||||
"handlebars": "^4.5.1",
|
||||
"immutable": "^4.0.0-rc.12",
|
||||
"jquery": "^3.4.1",
|
||||
"jszip": "^3.2.2",
|
||||
|
|
96
src/dashboard/dashboard-button-group.js
Normal file
96
src/dashboard/dashboard-button-group.js
Normal file
|
@ -0,0 +1,96 @@
|
|||
/**
|
||||
* File: dashboard-button-group.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 31.05.2018
|
||||
*
|
||||
* 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 PropTypes from 'prop-types';
|
||||
import { Button } from 'react-bootstrap';
|
||||
|
||||
class DashboardButtonGroup extends React.Component {
|
||||
render() {
|
||||
const buttonStyle = {
|
||||
marginLeft: '8px'
|
||||
};
|
||||
|
||||
const buttons = [];
|
||||
let key = 0;
|
||||
|
||||
if (this.props.fullscreen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.props.editing) {
|
||||
buttons.push(
|
||||
<Button key={key++} bsStyle="info" onClick={this.props.onSave} style={buttonStyle}>
|
||||
<span class="glyphicon glyphicon-floppy-disk"></span> Save
|
||||
</Button>,
|
||||
<Button key={key++} bsStyle="info" onClick={this.props.onCancel} style={buttonStyle}>
|
||||
<span class="glyphicon glyphicon-remove" ></span> Cancel
|
||||
</Button>
|
||||
);
|
||||
} else {
|
||||
if (this.props.fullscreen !== true) {
|
||||
buttons.push(
|
||||
<Button key={key++} bsStyle="info" onClick={this.props.onFullscreen} style={buttonStyle}>
|
||||
<span className="glyphicon glyphicon-resize-full"></span> Fullscreen
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.paused) {
|
||||
buttons.push(
|
||||
<Button key={key++} bsStyle="info" onClick={this.props.onUnpause} style={buttonStyle}>
|
||||
<span className="glyphicon glyphicon-play"></span> Live
|
||||
</Button>
|
||||
);
|
||||
} else {
|
||||
buttons.push(
|
||||
<Button key={key++} bsStyle="info" onClick={this.props.onPause} style={buttonStyle}>
|
||||
<span className="glyphicon glyphicon-pause"></span> Pause
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
buttons.push(
|
||||
<Button key={key++} bsStyle="info" onClick={this.props.onEdit} style={buttonStyle}>
|
||||
<span className="glyphicon glyphicon-pencil"></span> Pause
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return <div className='section-buttons-group-right'>
|
||||
{buttons}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
DashboardButtonGroup.propTypes = {
|
||||
editing: PropTypes.bool,
|
||||
fullscreen: PropTypes.bool,
|
||||
paused: PropTypes.bool,
|
||||
onEdit: PropTypes.func,
|
||||
onSave: PropTypes.func,
|
||||
onCancel: PropTypes.func,
|
||||
onFullscreen: PropTypes.func,
|
||||
onPause: PropTypes.func,
|
||||
onUnpause: PropTypes.func
|
||||
};
|
||||
|
||||
export default DashboardButtonGroup;
|
|
@ -21,19 +21,18 @@
|
|||
|
||||
import React from 'react';
|
||||
import { Container } from 'flux/utils';
|
||||
import { Button, ButtonToolbar } from 'react-bootstrap';
|
||||
import { ContextMenu, Item, Separator } from 'react-contexify';
|
||||
import Fullscreenable from 'react-fullscreenable';
|
||||
import Slider from 'rc-slider';
|
||||
import classNames from 'classnames';
|
||||
import { Map } from 'immutable'
|
||||
|
||||
import Icon from '../common/icon';
|
||||
import WidgetFactory from '../widget/widget-factory';
|
||||
import ToolboxItem from './toolbox-item';
|
||||
import Dropzone from './dropzone';
|
||||
//import Icon from '../common/icon';
|
||||
import Widget from '../widget/widget';
|
||||
import EditWidget from '../widget/edit-widget';
|
||||
import Grid from './grid';
|
||||
|
||||
import WidgetContextMenu from './widget-context-menu';
|
||||
import WidgetToolbox from './widget-toolbox';
|
||||
import WidgetArea from './widget-area';
|
||||
import DashboardButtonGroup from './dashboard-button-group';
|
||||
|
||||
import UserStore from '../user/user-store';
|
||||
import DashboardStore from './dashboard-store';
|
||||
|
@ -42,12 +41,12 @@ import SimulationStore from '../simulation/simulation-store';
|
|||
import SimulationModelStore from '../simulationmodel/simulation-model-store';
|
||||
import FileStore from '../file/file-store';
|
||||
import AppDispatcher from '../common/app-dispatcher';
|
||||
import NotificationsDataManager from '../common/data-managers/notifications-data-manager';
|
||||
import NotificationsFactory from '../common/data-managers/notifications-factory';
|
||||
|
||||
import 'react-contexify/dist/ReactContexify.min.css';
|
||||
|
||||
class Dashboard extends React.Component {
|
||||
|
||||
static lastWidgetKey = 0;
|
||||
static getStores() {
|
||||
return [ DashboardStore, ProjectStore, SimulationStore, SimulationModelStore, FileStore, UserStore ];
|
||||
}
|
||||
|
@ -57,19 +56,46 @@ class Dashboard extends React.Component {
|
|||
prevState = {};
|
||||
}
|
||||
|
||||
let dashboard = Map();
|
||||
let rawDashboard = DashboardStore.getState().find(v => v._id === props.match.params.dashboard);
|
||||
|
||||
if (rawDashboard != null) {
|
||||
dashboard = Map(rawDashboard);
|
||||
|
||||
// convert widgets list to a dictionary to be able to reference widgets
|
||||
const widgets = {};
|
||||
|
||||
for (let widget of dashboard.get('widgets')) {
|
||||
widgets[this.getNewWidgetKey()] = widget;
|
||||
}
|
||||
|
||||
dashboard = dashboard.set('widgets', widgets);
|
||||
|
||||
// this.computeHeightWithWidgets(widgets);
|
||||
|
||||
// this.setState({ dashboard: selectedDashboards, project: null });
|
||||
|
||||
// AppDispatcher.dispatch({
|
||||
// type: 'projects/start-load',
|
||||
// data: selectedDashboard.get('project'),
|
||||
// token: this.state.sessionToken
|
||||
// });
|
||||
|
||||
}
|
||||
|
||||
let simulationModels = [];
|
||||
if (prevState.simulation != null) {
|
||||
simulationModels = SimulationModelStore.getState().filter(m => prevState.simulation.models.includes(m._id));
|
||||
}
|
||||
|
||||
return {
|
||||
dashboard,
|
||||
|
||||
sessionToken: UserStore.getState().token,
|
||||
dashboards: DashboardStore.getState(),
|
||||
projects: ProjectStore.getState(),
|
||||
simulations: SimulationStore.getState(),
|
||||
files: FileStore.getState(),
|
||||
|
||||
dashboard: prevState.dashboard || {},
|
||||
project: prevState.project || null,
|
||||
simulation: prevState.simulation || null,
|
||||
simulationModels,
|
||||
|
@ -81,20 +107,30 @@ class Dashboard extends React.Component {
|
|||
modalIndex: prevState.modalIndex || null,
|
||||
|
||||
maxWidgetHeight: prevState.maxWidgetHeight || 0,
|
||||
dropZoneHeight: prevState.dropZoneHeight || 0
|
||||
dropZoneHeight: prevState.dropZoneHeight || 0,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
// TODO: Don't fetch token from local, use user-store!
|
||||
const token = localStorage.getItem('token');
|
||||
static getNewWidgetKey() {
|
||||
const widgetKey = this.lastWidgetKey;
|
||||
this.lastWidgetKey++;
|
||||
|
||||
return widgetKey;
|
||||
}
|
||||
|
||||
|
||||
componentWillMount() {
|
||||
//document.addEventListener('keydown', this.handleKeydown.bind(this));
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'dashboards/start-load',
|
||||
token
|
||||
});
|
||||
if (this.state.dashboard.has('id') === false) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'dashboards/start-load',
|
||||
data: this.props.match.params.dashboard,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -149,12 +185,24 @@ class Dashboard extends React.Component {
|
|||
}
|
||||
}*/
|
||||
|
||||
transformToWidgetsDict(widgets) {
|
||||
var widgetsDict = {};
|
||||
// Create a new key and make a copy of the widget object
|
||||
var key = 0;
|
||||
widgets.forEach( (widget) => widgetsDict[key++] = Object.assign({}, widget) );
|
||||
return widgetsDict;
|
||||
/*
|
||||
* 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) {
|
||||
|
@ -186,64 +234,40 @@ class Dashboard extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
snapToGrid(value) {
|
||||
if (this.state.dashboard.grid === 1) return value;
|
||||
handleDrop = widget => {
|
||||
const widgets = this.state.dashboard.get('widgets') || [];
|
||||
|
||||
return Math.round(value / this.state.dashboard.grid) * this.state.dashboard.grid;
|
||||
}
|
||||
const widgetKey = this.getNewWidgetKey();
|
||||
widgets[widgetKey] = widget;
|
||||
|
||||
handleDrop(item, position) {
|
||||
const dashboard = this.state.dashboard.set('widgets');
|
||||
|
||||
let widget = null;
|
||||
let defaultSimulationModel = null;
|
||||
// this.increaseHeightWithWidget(widget);
|
||||
|
||||
if (this.state.simulation.models && this.state.simulation.models.length === 0) {
|
||||
NotificationsDataManager.addNotification(NotificationsFactory.NO_SIM_MODEL_AVAILABLE);
|
||||
} else {
|
||||
defaultSimulationModel = this.state.simulation.models[0];
|
||||
}
|
||||
this.setState({ dashboard });
|
||||
};
|
||||
|
||||
// snap position to grid
|
||||
position.x = this.snapToGrid(position.x);
|
||||
position.y = this.snapToGrid(position.y);
|
||||
|
||||
// create new widget
|
||||
widget = WidgetFactory.createWidgetOfType(item.name, position, defaultSimulationModel);
|
||||
|
||||
var new_widgets = this.state.dashboard.widgets;
|
||||
var new_key = Object.keys(new_widgets).length;
|
||||
|
||||
new_widgets[new_key] = widget;
|
||||
|
||||
var dashboard = Object.assign({}, this.state.dashboard, {
|
||||
widgets: new_widgets
|
||||
});
|
||||
|
||||
this.increaseHeightWithWidget(widget);
|
||||
this.setState({ dashboard: dashboard });
|
||||
}
|
||||
|
||||
widgetStatusChange(updated_widget, key) {
|
||||
// Widget changed internally, make changes effective then save them
|
||||
this.widgetChange(updated_widget, key, this.saveChanges);
|
||||
}
|
||||
|
||||
widgetChange(updated_widget, key, callback = null) {
|
||||
var widgets_update = {};
|
||||
widgets_update[key] = updated_widget;
|
||||
var new_widgets = Object.assign({}, this.state.dashboard.widgets, widgets_update);
|
||||
widgetChange = (widget, index, callback = null) => {
|
||||
const widgets = this.state.dashboard.get('widgets');
|
||||
widgets[index] = widget;
|
||||
|
||||
var dashboard = Object.assign({}, this.state.dashboard, {
|
||||
widgets: new_widgets
|
||||
});
|
||||
const dashboard = this.state.dashboard.set('widgets');
|
||||
|
||||
// Check if the height needs to be increased, the section may have shrunk if not
|
||||
if (!this.increaseHeightWithWidget(updated_widget)) {
|
||||
if (!this.increaseHeightWithWidget(widget)) {
|
||||
this.computeHeightWithWidgets(dashboard.widgets);
|
||||
}
|
||||
this.setState({ dashboard: dashboard }, callback);
|
||||
|
||||
this.setState({ dashboard }, callback);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Set the initial height state based on the existing widgets
|
||||
*/
|
||||
|
@ -261,289 +285,137 @@ class Dashboard extends React.Component {
|
|||
dropZoneHeight: maxHeight + 80
|
||||
});
|
||||
}
|
||||
/*
|
||||
* 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;
|
||||
|
||||
|
||||
editWidget = (widget, index) => {
|
||||
this.setState({ editModal: true, modalData: widget, modalIndex: index });
|
||||
}
|
||||
|
||||
editWidget(e, data) {
|
||||
this.setState({ editModal: true, modalData: this.state.dashboard.widgets[data.key], modalIndex: data.key });
|
||||
}
|
||||
|
||||
closeEdit(data) {
|
||||
if (data) {
|
||||
// save changes temporarily
|
||||
var widgets_update = {};
|
||||
widgets_update[this.state.modalIndex] = data;
|
||||
|
||||
var new_widgets = Object.assign({}, this.state.dashboard.widgets, widgets_update);
|
||||
|
||||
var dashboard = Object.assign({}, this.state.dashboard, {
|
||||
widgets: new_widgets
|
||||
});
|
||||
|
||||
this.setState({ editModal: false, dashboard: dashboard });
|
||||
} else {
|
||||
closeEdit = data => {
|
||||
if (data == null) {
|
||||
this.setState({ editModal: false });
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
deleteWidget(e, data) {
|
||||
delete this.state.dashboard.widgets[data.key];
|
||||
var dashboard = Object.assign({}, this.state.dashboard, {
|
||||
widgets: this.state.dashboard.widgets
|
||||
});
|
||||
this.setState({ dashboard: dashboard });
|
||||
}
|
||||
const widgets = this.state.dashboard.get('widgets');
|
||||
widgets[this.state.modalIndex] = data;
|
||||
|
||||
stopEditing() {
|
||||
const dashboard = this.state.dashboard.set('widgets', widgets);
|
||||
|
||||
this.setState({ editModal: false, dashboard });
|
||||
};
|
||||
|
||||
|
||||
deleteWidget = (widget, index) => {
|
||||
const widgets = this.state.dashboard.get('widgets');
|
||||
delete widgets[index];
|
||||
|
||||
const dashboard = this.state.dashboard.set('widgets');
|
||||
|
||||
this.setState({ dashboard });
|
||||
};
|
||||
|
||||
|
||||
startEditing = () => {
|
||||
this.setState({ editing: true });
|
||||
};
|
||||
|
||||
saveEditing = () => {
|
||||
// Provide the callback so it can be called when state change is applied
|
||||
// TODO: Check if callback is needed
|
||||
this.setState({ editing: false }, this.saveChanges );
|
||||
}
|
||||
};
|
||||
|
||||
saveChanges() {
|
||||
// Transform to a list
|
||||
var dashboard = Object.assign({}, this.state.dashboard, {
|
||||
widgets: this.transformToWidgetsList(this.state.dashboard.widgets)
|
||||
const dashboard = Object.assign({}, this.state.dashboard.toJS(), {
|
||||
widgets: this.transformToWidgetsList(this.state.dashboard.get('widgets'))
|
||||
});
|
||||
|
||||
const token = localStorage.getItem('token');
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'dashboards/start-edit',
|
||||
data: dashboard,
|
||||
token
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
|
||||
discardChanges() {
|
||||
this.setState({ editing: false, dashboard: {} });
|
||||
cancelEditing = () => {
|
||||
this.setState({ editing: false, dasboard: {} });
|
||||
|
||||
this.reloadDashboard();
|
||||
}
|
||||
};
|
||||
|
||||
moveWidget(e, data, applyDirection) {
|
||||
var widget = this.state.dashboard.widgets[data.key];
|
||||
var updated_widgets = {};
|
||||
updated_widgets[data.key] = applyDirection(widget);
|
||||
var new_widgets = Object.assign({}, this.state.dashboard.widgets, updated_widgets);
|
||||
|
||||
var dashboard = Object.assign({}, this.state.dashboard, {
|
||||
widgets: new_widgets
|
||||
});
|
||||
|
||||
this.setState({ dashboard: dashboard });
|
||||
}
|
||||
|
||||
moveAbove(widget) {
|
||||
// increase z-Order
|
||||
widget.z++;
|
||||
return widget;
|
||||
}
|
||||
|
||||
moveToFront(widget) {
|
||||
// increase z-Order
|
||||
widget.z = 100;
|
||||
return widget;
|
||||
}
|
||||
|
||||
moveUnderneath(widget) {
|
||||
// decrease z-Order
|
||||
widget.z--;
|
||||
if (widget.z < 0) {
|
||||
widget.z = 0;
|
||||
}
|
||||
return widget;
|
||||
}
|
||||
|
||||
moveToBack(widget) {
|
||||
// increase z-Order
|
||||
widget.z = 0;
|
||||
return widget;
|
||||
}
|
||||
|
||||
setGrid(value) {
|
||||
// value 0 would block all widgets, set 1 as 'grid disabled'
|
||||
if (value === 0) {
|
||||
value = 1;
|
||||
}
|
||||
|
||||
let dashboard = Object.assign({}, this.state.dashboard, {
|
||||
grid: value
|
||||
});
|
||||
setGrid = value => {
|
||||
const dashboard = this.state.dashboard.set('grid', value);
|
||||
|
||||
this.setState({ dashboard });
|
||||
}
|
||||
|
||||
lockWidget(data) {
|
||||
// lock the widget
|
||||
let widget = this.state.dashboard.widgets[data.key];
|
||||
widget.locked = true;
|
||||
|
||||
// update dashboard
|
||||
let widgets = {};
|
||||
widgets[data.key] = widget;
|
||||
widgets = Object.assign({}, this.state.dashboard.widgets, widgets);
|
||||
|
||||
const dashboard = Object.assign({}, this.state.dashboard, { widgets });
|
||||
this.setState({ dashboard });
|
||||
}
|
||||
|
||||
unlockWidget(data) {
|
||||
// lock the widget
|
||||
let widget = this.state.dashboard.widgets[data.key];
|
||||
widget.locked = false;
|
||||
|
||||
// update dashboard
|
||||
let widgets = {};
|
||||
widgets[data.key] = widget;
|
||||
widgets = Object.assign({}, this.state.dashboard.widgets, widgets);
|
||||
|
||||
const dashboard = Object.assign({}, this.state.dashboard, { widgets });
|
||||
this.setState({ dashboard });
|
||||
}
|
||||
};
|
||||
|
||||
pauseData = () => {
|
||||
this.setState({ paused: true });
|
||||
}
|
||||
};
|
||||
|
||||
unpauseData = () => {
|
||||
this.setState({ paused: false });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
render() {
|
||||
const current_widgets = this.state.dashboard.widgets;
|
||||
const widgets = this.state.dashboard.get('widgets');
|
||||
const grid = this.state.dashboard.get('grid');
|
||||
|
||||
let boxClasses = classNames('section', 'box', { 'fullscreen-container': this.props.isFullscreen });
|
||||
const boxClasses = classNames('section', 'box', { 'fullscreen-padding': this.props.isFullscreen });
|
||||
|
||||
let buttons = []
|
||||
let editingControls = [];
|
||||
let gridControl = {};
|
||||
|
||||
if (this.state.editing) {
|
||||
buttons.push({ click: () => this.stopEditing(), icon: 'save', text: 'Save' });
|
||||
buttons.push({ click: () => this.discardChanges(), icon: 'ban', text: 'Cancel' });
|
||||
|
||||
gridControl = <div key={editingControls.length}>
|
||||
<span>Grid: {this.state.dashboard.grid > 1 ? this.state.dashboard.grid : 'Disabled'}</span>
|
||||
<Slider value={this.state.dashboard.grid} style={{ width: '80px' }} step={5} onChange={value => this.setGrid(value)} />
|
||||
</div>
|
||||
}
|
||||
|
||||
if (!this.props.isFullscreen) {
|
||||
buttons.push({ click: this.props.toggleFullscreen, icon: 'expand', text: 'Fullscreen' });
|
||||
buttons.push({ click: this.state.paused ? this.unpauseData : this.pauseData, icon: this.state.paused ? 'play' : 'pause', text: this.state.paused ? 'Live' : 'Pause' });
|
||||
|
||||
if (!this.state.editing)
|
||||
buttons.push({ click: () => this.setState({ editing: true }), icon: 'edit', text: 'Edit' });
|
||||
}
|
||||
|
||||
const buttonList = buttons.map((btn, idx) =>
|
||||
<Button key={idx} bsStyle="info" onClick={btn.click} style={{ marginLeft: '8px' }}>
|
||||
<Icon icon={btn.icon} /> {btn.text}
|
||||
</Button>
|
||||
);
|
||||
|
||||
// Only one topology widget at the time is supported
|
||||
let thereIsTopologyWidget = current_widgets && Object.values(current_widgets).filter( widget => widget.type === 'Topology').length > 0;
|
||||
let topologyItemMsg = !thereIsTopologyWidget? '' : 'Currently only one is supported';
|
||||
|
||||
return (
|
||||
<div className={boxClasses} >
|
||||
<div className='section-header box-header'>
|
||||
<div className="section-title">
|
||||
<span>{this.state.dashboard.name}</span>
|
||||
</div>
|
||||
|
||||
<div className="section-buttons-group-right">
|
||||
{ this.state.editing && gridControl }
|
||||
{ buttonList }
|
||||
</div>
|
||||
return <div className={boxClasses} >
|
||||
<div className='section-header box-header'>
|
||||
<div className="section-title">
|
||||
<span>{this.state.dashboard.get('name')}</span>
|
||||
</div>
|
||||
|
||||
<div className="box box-content" onContextMenu={ (e) => e.preventDefault() }>
|
||||
{this.state.editing &&
|
||||
<div className="toolbar">
|
||||
<ButtonToolbar className="section-buttons-group-right">
|
||||
{ editingControls }
|
||||
</ButtonToolbar>
|
||||
<ButtonToolbar className="toolbox box-header">
|
||||
<ToolboxItem icon="star" name="CustomAction" type="widget" />
|
||||
<ToolboxItem icon="play" name="Action" type="widget" disabled={true} />
|
||||
<ToolboxItem icon="lightbulb" name="Lamp" type="widget" />
|
||||
<ToolboxItem icon="font" name="Value" type="widget" />
|
||||
<ToolboxItem icon="chart-area" name="Plot" type="widget" />
|
||||
<ToolboxItem icon="table" name="Table" type="widget" />
|
||||
<ToolboxItem icon="tag" name="Label" type="widget" />
|
||||
<ToolboxItem icon="image" name="Image" type="widget" />
|
||||
<ToolboxItem icon="table" name="PlotTable" type="widget" />
|
||||
<ToolboxItem icon="dot-circle" name="Button" type="widget" />
|
||||
<ToolboxItem icon="i-cursor" name="Input" type="widget" />
|
||||
<ToolboxItem icon="sliders-h" name="Slider" type="widget" />
|
||||
<ToolboxItem icon="tachometer-alt" name="Gauge" type="widget" />
|
||||
<ToolboxItem icon="square" name="Box" type="widget" />
|
||||
<ToolboxItem icon="code" name="HTML" type="html" />
|
||||
<ToolboxItem icon="project-diagram" name="Topology" type="widget" disabled={thereIsTopologyWidget} title={topologyItemMsg}/>
|
||||
</ButtonToolbar>
|
||||
</div>
|
||||
}
|
||||
|
||||
<Dropzone height={this.state.dropZoneHeight} onDrop={(item, position) => this.handleDrop(item, position)} editing={this.state.editing}>
|
||||
{current_widgets != null &&
|
||||
Object.keys(current_widgets).map(widget_key => (
|
||||
<Widget
|
||||
key={widget_key}
|
||||
data={current_widgets[widget_key]}
|
||||
simulation={this.state.simulation}
|
||||
onWidgetChange={(w, k) => this.widgetChange(w, k)}
|
||||
onWidgetStatusChange={(w, k) => this.widgetStatusChange(w, k)}
|
||||
editing={this.state.editing}
|
||||
index={widget_key}
|
||||
grid={this.state.dashboard.grid}
|
||||
paused={this.state.paused}
|
||||
/>
|
||||
))}
|
||||
|
||||
<Grid size={this.state.dashboard.grid} disabled={this.state.dashboard.grid === 1 || !this.state.editing} />
|
||||
</Dropzone>
|
||||
|
||||
{current_widgets != null &&
|
||||
Object.keys(current_widgets).map(widget_key => {
|
||||
const data = { key: widget_key };
|
||||
|
||||
const locked = this.state.dashboard.widgets[widget_key].locked;
|
||||
const disabledMove = locked || this.state.dashboard.widgets[widget_key].type === 'Box';
|
||||
|
||||
return <ContextMenu style={{zIndex: 100}} id={'widgetMenu'+ widget_key} key={widget_key}>
|
||||
<Item disabled={locked} onClick={e => this.editWidget(e, data)}>Edit</Item>
|
||||
<Item disabled={locked} onClick={e => this.deleteWidget(e, data)}>Delete</Item>
|
||||
<Separator />
|
||||
<Item disabled={disabledMove} onClick={e => this.moveWidget(e, data, this.moveAbove)}>Move above</Item>
|
||||
<Item disabled={disabledMove} onClick={e => this.moveWidget(e, data, this.moveToFront)}>Move to front</Item>
|
||||
<Item disabled={disabledMove} onClick={e => this.moveWidget(e, data, this.moveUnderneath)}>Move underneath</Item>
|
||||
<Item disabled={disabledMove} onClick={e => this.moveWidget(e, data, this.moveToBack)}>Move to back</Item>
|
||||
<Separator />
|
||||
<Item disabled={locked} onClick={e => this.lockWidget(data)}>Lock</Item>
|
||||
<Item disabled={!locked} onClick={e => this.unlockWidget(data)}>Unlock</Item>
|
||||
</ContextMenu>
|
||||
})}
|
||||
|
||||
<EditWidget sessionToken={this.state.sessionToken} show={this.state.editModal} onClose={(data) => this.closeEdit(data)} widget={this.state.modalData} simulationModels={this.state.simulationModels} files={this.state.files} />
|
||||
</div>
|
||||
<DashboardButtonGroup
|
||||
editing={this.state.editing}
|
||||
fullscreen={this.props.isFullscreen}
|
||||
paused={this.state.paused}
|
||||
onEdit={this.startEditing}
|
||||
onSave={this.saveEditing}
|
||||
onCancel={this.cancelEditing}
|
||||
onFullscreen={this.props.toggleFullscreen}
|
||||
onPause={this.pauseData}
|
||||
onUnpause={this.unpauseData}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
<div className="box box-content" onContextMenu={ (e) => e.preventDefault() }>
|
||||
{this.state.editing &&
|
||||
<WidgetToolbox grid={grid} onGridChange={this.setGrid} widgets={widgets} />
|
||||
}
|
||||
|
||||
<WidgetArea widgets={widgets} editing={this.state.editing} grid={grid} onWidgetAdded={this.handleDrop}>
|
||||
{widgets != null && Object.keys(widgets).map(widgetKey => (
|
||||
<Widget
|
||||
key={widgetKey}
|
||||
data={widgets[widgetKey]}
|
||||
simulation={this.state.simulation}
|
||||
onWidgetChange={(w, k) => this.widgetChange(w, k)}
|
||||
onWidgetStatusChange={(w, k) => this.widgetStatusChange(w, k)}
|
||||
editing={this.state.editing}
|
||||
index={widgetKey}
|
||||
grid={grid}
|
||||
paused={this.state.paused}
|
||||
/>
|
||||
))}
|
||||
</WidgetArea>
|
||||
|
||||
{/* TODO: Create only one context menu for all widgets */}
|
||||
{widgets != null && Object.keys(widgets).map(widgetKey => (
|
||||
<WidgetContextMenu key={widgetKey} index={widgetKey} widget={widgets[widgetKey]} onEdit={this.editWidget} onDelete={this.deleteWidget} onChange={this.widgetChange} />
|
||||
))}
|
||||
|
||||
<EditWidget sessionToken={this.state.sessionToken} show={this.state.editModal} onClose={this.closeEdit} widget={this.state.modalData} simulationModels={this.state.simulationModels} files={this.state.files} />
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
78
src/dashboard/widget-area.js
Normal file
78
src/dashboard/widget-area.js
Normal file
|
@ -0,0 +1,78 @@
|
|||
/**
|
||||
* File: widget-area.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 31.05.2018
|
||||
*
|
||||
* 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 PropTypes from 'prop-types';
|
||||
|
||||
import Dropzone from './dropzone';
|
||||
import Grid from './grid';
|
||||
|
||||
import WidgetFactory from '../widget/widget-factory';
|
||||
|
||||
class WidgetArea extends React.Component {
|
||||
snapToGrid(value) {
|
||||
if (this.props.grid === 1) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return Math.round(value / this.props.grid) * this.props.grid;
|
||||
}
|
||||
|
||||
handleDrop = (item, position) => {
|
||||
position.x = this.snapToGrid(position.x);
|
||||
position.y = this.snapToGrid(position.y);
|
||||
|
||||
const widget = WidgetFactory.createWidgetOfType(item.name, position, this.props.defaultSimulationModel);
|
||||
|
||||
if (this.props.onWidgetAdded != null) {
|
||||
this.props.onWidgetAdded(widget);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const maxHeight = Object.values(this.props.widgets).reduce((currentHeight, widget) => {
|
||||
const absolutHeight = widget.y + widget.height;
|
||||
|
||||
return absolutHeight > currentHeight ? absolutHeight : currentHeight;
|
||||
}, 0);
|
||||
|
||||
return <Dropzone height={maxHeight + 80} onDrop={this.handleDrop} editing={this.props.editing}>
|
||||
{this.props.children}
|
||||
|
||||
<Grid size={this.props.grid} disabled={this.props.grid === 1 || this.props.editing !== true} />
|
||||
</Dropzone>;
|
||||
}
|
||||
}
|
||||
|
||||
WidgetArea.propTypes = {
|
||||
children: PropTypes.node, //TODO is .node correct here? Was .children before leading to compile error
|
||||
editing: PropTypes.bool,
|
||||
grid: PropTypes.number,
|
||||
defaultSimulationModel: PropTypes.string,
|
||||
widgets: PropTypes.object,
|
||||
onWidgetAdded: PropTypes.func
|
||||
};
|
||||
|
||||
WidgetArea.defaultProps = {
|
||||
widgets: {}
|
||||
};
|
||||
|
||||
export default WidgetArea;
|
123
src/dashboard/widget-context-menu.js
Normal file
123
src/dashboard/widget-context-menu.js
Normal file
|
@ -0,0 +1,123 @@
|
|||
/**
|
||||
* File: widget-context-menu.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 31.05.2018
|
||||
*
|
||||
* 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 PropTypes from 'prop-types';
|
||||
import { contextMenu, Item, Separator } from 'react-contexify';
|
||||
|
||||
class WidgetContextMenu extends React.Component {
|
||||
editWidget = event => {
|
||||
if (this.props.onEdit != null) {
|
||||
this.props.onEdit(this.props.widget, this.props.index);
|
||||
}
|
||||
};
|
||||
|
||||
deleteWidget = event => {
|
||||
if (this.props.onDelete != null) {
|
||||
this.props.onDelete(this.props.widget, this.props.index);
|
||||
}
|
||||
};
|
||||
|
||||
moveAbove = event => {
|
||||
this.props.widget.z++;
|
||||
if (this.props.widget.z > 100) {
|
||||
this.props.widget.z = 100;
|
||||
}
|
||||
|
||||
if (this.props.onChange != null) {
|
||||
this.props.onChange(this.props.widget, this.props.index);
|
||||
}
|
||||
};
|
||||
|
||||
moveToFront = event => {
|
||||
this.props.widget.z = 100;
|
||||
|
||||
if (this.props.onChange != null) {
|
||||
this.props.onChange(this.props.widget, this.props.index);
|
||||
}
|
||||
};
|
||||
|
||||
moveUnderneath = event => {
|
||||
this.props.widget.z--;
|
||||
if (this.props.widget.z < 0) {
|
||||
this.props.widget.z = 0;
|
||||
}
|
||||
|
||||
if (this.props.onChange != null) {
|
||||
this.props.onChange(this.props.widget, this.props.index);
|
||||
}
|
||||
};
|
||||
|
||||
moveToBack = event => {
|
||||
this.props.widget.z = 0;
|
||||
|
||||
if (this.props.onChange != null) {
|
||||
this.props.onChange(this.props.widget, this.props.index);
|
||||
}
|
||||
};
|
||||
|
||||
lockWidget = event => {
|
||||
this.props.widget.locked = true;
|
||||
|
||||
if (this.props.onChange != null) {
|
||||
this.props.onChange(this.props.widget, this.props.index);
|
||||
}
|
||||
};
|
||||
|
||||
unlockWidget = event => {
|
||||
this.props.widget.locked = false;
|
||||
|
||||
if (this.props.onChange != null) {
|
||||
this.props.onChange(this.props.widget, this.props.index);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const isLocked = this.props.widget.locked;
|
||||
|
||||
return <contextMenu id={'widgetMenu'+ this.props.index}>
|
||||
<Item disabled={isLocked} onClick={this.editWidget}>Edit</Item>
|
||||
<Item disabled={isLocked} onClick={this.deleteWidget}>Delete</Item>
|
||||
|
||||
<Separator />
|
||||
|
||||
<Item disabled={isLocked} onClick={this.moveAbove}>Move above</Item>
|
||||
<Item disabled={isLocked} onClick={this.moveToFront}>Move to front</Item>
|
||||
<Item disabled={isLocked} onClick={this.moveUnderneath}>Move underneath</Item>
|
||||
<Item disabled={isLocked} onClick={this.moveToBack}>Move to back</Item>
|
||||
|
||||
<Separator />
|
||||
|
||||
<Item disabled={isLocked} onClick={this.lockWidget}>Lock</Item>
|
||||
<Item disabled={isLocked === false} onClick={this.unlockWidget}>Unlock</Item>
|
||||
</contextMenu>;
|
||||
}
|
||||
}
|
||||
|
||||
WidgetContextMenu.propTypes = {
|
||||
index: PropTypes.number.isRequired,
|
||||
widget: PropTypes.object.isRequired,
|
||||
onEdit: PropTypes.func,
|
||||
onDelete: PropTypes.func,
|
||||
onChange: PropTypes.func
|
||||
};
|
||||
|
||||
export default WidgetContextMenu
|
77
src/dashboard/widget-toolbox.js
Normal file
77
src/dashboard/widget-toolbox.js
Normal file
|
@ -0,0 +1,77 @@
|
|||
/**
|
||||
* File: widget-toolbox.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 31.05.2018
|
||||
*
|
||||
* 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 PropTypes from 'prop-types';
|
||||
import Slider from 'rc-slider';
|
||||
|
||||
import ToolboxItem from './toolbox-item';
|
||||
|
||||
class WidgetToolbox extends React.Component {
|
||||
onGridChange = value => {
|
||||
// value 0 would block all widgets, set 1 as 'grid disabled'
|
||||
if (value === 0) {
|
||||
value = 1;
|
||||
}
|
||||
|
||||
if (this.props.onGridChange != null) {
|
||||
this.props.onGridChange(value);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
// Only one topology widget at the time is supported
|
||||
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' : '';
|
||||
|
||||
return <div className='toolbox box-header'>
|
||||
<ToolboxItem name='Lamp' type='widget' />
|
||||
<ToolboxItem name='Value' type='widget' />
|
||||
<ToolboxItem name='Plot' type='widget' />
|
||||
<ToolboxItem name='Table' type='widget' />
|
||||
<ToolboxItem name='Label' type='widget' />
|
||||
<ToolboxItem name='Image' type='widget' />
|
||||
<ToolboxItem name='PlotTable' type='widget' />
|
||||
<ToolboxItem name='Button' type='widget' />
|
||||
<ToolboxItem name='NumberInput' type='widget' />
|
||||
<ToolboxItem name='Slider' type='widget' />
|
||||
<ToolboxItem name='Gauge' type='widget' />
|
||||
<ToolboxItem name='Box' type='widget' />
|
||||
<ToolboxItem name='HTML' type='html' />
|
||||
<ToolboxItem name='Topology' type='widget' disabled={thereIsTopologyWidget} title={topologyItemMsg}/>
|
||||
|
||||
<div className='section-buttons-group-right'>
|
||||
<div>
|
||||
<span>Grid: { this.props.grid > 1 ? this.props.grid : 'Disabled' }</span>
|
||||
<Slider value={this.props.grid} style={{ width: '80px' }} step={5} onChange={this.onGridChange} />
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
};
|
||||
}
|
||||
|
||||
WidgetToolbox.propTypes = {
|
||||
widgets: PropTypes.array,
|
||||
grid: PropTypes.number,
|
||||
onGridChange: PropTypes.func
|
||||
};
|
||||
|
||||
export default WidgetToolbox;
|
142
src/widget/editable-widget-container.js
Normal file
142
src/widget/editable-widget-container.js
Normal file
|
@ -0,0 +1,142 @@
|
|||
/**
|
||||
* File: editable-widget-container.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 31.05.2018
|
||||
*
|
||||
* 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 PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { Rnd } from 'react-rnd';
|
||||
import { contextMenu } from 'react-contexify';
|
||||
|
||||
class EditableWidgetContainer extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.rnd = null;
|
||||
}
|
||||
|
||||
snapToGrid(value) {
|
||||
if (this.props.grid === 1) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return Math.round(value / this.props.grid) * this.props.grid;
|
||||
}
|
||||
|
||||
borderWasClicked = event => {
|
||||
if (event.button !== 2) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
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 (this.props.onWidgetChange != null) {
|
||||
this.props.onWidgetChange(widget, this.props.index);
|
||||
}
|
||||
};
|
||||
|
||||
resizeStop = (direction, delta, ref, event) => {
|
||||
const widget = this.props.widget;
|
||||
|
||||
// resize depends on direction
|
||||
if (direction === 'left' || direction === 'topLeft' || direction === 'bottomLeft') {
|
||||
widget.x -= delta.width;
|
||||
}
|
||||
|
||||
if (direction === 'top' || direction === 'topLeft' || direction === 'topRight') {
|
||||
widget.y -= delta.height;
|
||||
}
|
||||
|
||||
widget.width += delta.width;
|
||||
widget.height += delta.height;
|
||||
|
||||
if (this.props.onWidgetChange != null) {
|
||||
this.props.onWidgetChange(widget, this.props.index);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const widget = this.props.widget;
|
||||
|
||||
const resizing = {
|
||||
bottom: !widget.locked,
|
||||
bottomLeft: !widget.locked,
|
||||
bottomRight: !widget.locked,
|
||||
left: !widget.locked,
|
||||
right: !widget.locked,
|
||||
top: !widget.locked,
|
||||
topLeft: !widget.locked,
|
||||
topRight: !widget.locked
|
||||
};
|
||||
|
||||
const gridArray = [ this.props.grid, this.props.grid ];
|
||||
|
||||
const widgetClasses = classNames({
|
||||
'editing-widget': true,
|
||||
'locked': widget.locked
|
||||
});
|
||||
|
||||
return <Rnd
|
||||
ref={c => { this.rnd = c; }}
|
||||
default={{ x: Number(widget.x), y: Number(widget.y), width: widget.width, height: widget.height }}
|
||||
minWidth={widget.minWidth}
|
||||
minHeight={widget.minHeight}
|
||||
lockAspectRatio={Boolean(widget.lockAspect)}
|
||||
bounds={'parent'}
|
||||
className={widgetClasses}
|
||||
onResizeStart={this.borderWasClicked}
|
||||
onResizeStop={this.resizeStop}
|
||||
onDrag={this.drag}
|
||||
onDragStop={this.dragStop}
|
||||
dragGrid={gridArray}
|
||||
resizeGrid={gridArray}
|
||||
zIndex={widget.z}
|
||||
enableResizing={resizing}
|
||||
disableDragging={widget.locked}
|
||||
>
|
||||
<contextMenu id={'widgetMenu' + this.props.index}>
|
||||
{this.props.children}
|
||||
</contextMenu>
|
||||
</Rnd>;
|
||||
}
|
||||
}
|
||||
|
||||
EditableWidgetContainer.propTypes = {
|
||||
widget: PropTypes.object.isRequired,
|
||||
index: PropTypes.number.isRequired,
|
||||
grid: PropTypes.number,
|
||||
onWidgetChange: PropTypes.func
|
||||
};
|
||||
|
||||
export default EditableWidgetContainer
|
47
src/widget/widget-container.js
Normal file
47
src/widget/widget-container.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* File: widget-container.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 31.05.2018
|
||||
*
|
||||
* 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 PropTypes from 'prop-types';
|
||||
|
||||
class WidgetContainer extends React.Component {
|
||||
render() {
|
||||
const containerStyle = {
|
||||
width: Number(this.props.widget.width),
|
||||
height: Number(this.props.widget.height),
|
||||
left: Number(this.props.widget.x),
|
||||
top: Number(this.props.widget.y),
|
||||
zIndex: Number(this.props.widget.z),
|
||||
position: 'absolute'
|
||||
};
|
||||
|
||||
return <div className='widget' style={containerStyle}>
|
||||
{this.props.children}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
WidgetContainer.propTypes = {
|
||||
widget: PropTypes.object.isRequired,
|
||||
children: PropTypes.node, //TODO is .node correct here? Was .children before leading to compile error
|
||||
};
|
||||
|
||||
export default WidgetContainer
|
|
@ -21,9 +21,6 @@
|
|||
|
||||
import React from 'react';
|
||||
import { Container } from 'flux/utils';
|
||||
import { ContextMenuProvider } from 'react-contexify';
|
||||
import { Rnd } from 'react-rnd';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import AppDispatcher from '../common/app-dispatcher';
|
||||
import UserStore from '../user/user-store';
|
||||
|
@ -31,6 +28,9 @@ import SimulatorDataStore from '../simulator/simulator-data-store';
|
|||
import SimulationModelStore from '../simulationmodel/simulation-model-store';
|
||||
import FileStore from '../file/file-store';
|
||||
|
||||
import EditableWidgetContainer from './editable-widget-container';
|
||||
import WidgetContainer from './widget-container';
|
||||
|
||||
import WidgetCustomAction from './widgets/custom-action';
|
||||
import WidgetAction from './widgets/action';
|
||||
import WidgetLamp from './widgets/lamp';
|
||||
|
@ -56,8 +56,6 @@ class Widget extends React.Component {
|
|||
}
|
||||
|
||||
static calculateState(prevState, props) {
|
||||
const sessionToken = UserStore.getState().token;
|
||||
|
||||
let simulatorData = {};
|
||||
|
||||
if (props.paused) {
|
||||
|
@ -68,101 +66,32 @@ class Widget extends React.Component {
|
|||
simulatorData = SimulatorDataStore.getState();
|
||||
}
|
||||
|
||||
if (prevState) {
|
||||
return {
|
||||
sessionToken,
|
||||
simulatorData,
|
||||
files: FileStore.getState(),
|
||||
sequence: prevState.sequence + 1,
|
||||
return {
|
||||
simulatorData,
|
||||
files: FileStore.getState(),
|
||||
simulationModels: SimulationModelStore.getState(),
|
||||
|
||||
simulationModels: SimulationModelStore.getState()
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
sessionToken,
|
||||
simulatorData,
|
||||
files: FileStore.getState(),
|
||||
sequence: 0,
|
||||
sequence: prevState != null ? prevState.sequence + 1 : 0,
|
||||
|
||||
simulationModels: SimulationModelStore.getState()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
// Reference to the context menu element
|
||||
this.contextMenuTriggerViaDraggable = null;
|
||||
sessionToken: UserStore.getState().token
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
// If loading for the first time
|
||||
if (this.state.sessionToken) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'files/start-load',
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulationModels/start-load',
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
snapToGrid(value) {
|
||||
if (this.props.grid === 1)
|
||||
return value;
|
||||
|
||||
return Math.round(value / this.props.grid) * this.props.grid;
|
||||
}
|
||||
|
||||
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) {
|
||||
// update widget
|
||||
let widget = this.props.data;
|
||||
widget.x = this.snapToGrid(data.x);
|
||||
widget.y = this.snapToGrid(data.y);
|
||||
|
||||
this.props.onWidgetChange(widget, this.props.index);
|
||||
}
|
||||
|
||||
resizeStop(direction, delta, event) {
|
||||
// update widget
|
||||
let widget = Object.assign({}, this.props.data);
|
||||
|
||||
// resize depends on direction
|
||||
if (direction === 'left' || direction === 'topLeft' || direction === 'bottomLeft') {
|
||||
widget.x -= delta.width;
|
||||
if (this.state.sessionToken == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (direction === 'top' || direction === 'topLeft' || direction === 'topRight') {
|
||||
widget.y -= delta.height;
|
||||
}
|
||||
AppDispatcher.dispatch({
|
||||
type: 'files/start-load',
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
|
||||
widget.width += delta.width;
|
||||
widget.height += delta.height;
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulationModels/start-load',
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
|
||||
this.props.onWidgetChange(widget, this.props.index);
|
||||
}
|
||||
|
||||
borderWasClicked(e) {
|
||||
// check if it was triggered by the right button
|
||||
if (e.button === 2) {
|
||||
// launch the context menu using the reference
|
||||
if(this.contextMenuTriggerViaDraggable) {
|
||||
this.contextMenuTriggerViaDraggable.handleContextClick(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inputDataChanged(widget, data) {
|
||||
|
@ -184,16 +113,7 @@ class Widget extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
// configure grid
|
||||
const grid = [this.props.grid, this.props.grid];
|
||||
|
||||
// get widget element
|
||||
const widget = this.props.data;
|
||||
let borderedWidget = false;
|
||||
let element = null;
|
||||
let zIndex = Number(widget.z);
|
||||
|
||||
createWidget(widget) {
|
||||
let simulationModel = null;
|
||||
|
||||
for (let model of this.state.simulationModels) {
|
||||
|
@ -204,93 +124,56 @@ class Widget extends React.Component {
|
|||
simulationModel = model;
|
||||
}
|
||||
|
||||
// dummy is passed to widgets to keep updating them while in edit mode
|
||||
if (widget.type === 'CustomAction') {
|
||||
element = <WidgetCustomAction widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel} />
|
||||
return <WidgetCustomAction widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel} />
|
||||
} else if (widget.type === 'Action') {
|
||||
element = <WidgetAction widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel} />
|
||||
return <WidgetAction widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel}/>
|
||||
} else if (widget.type === 'Lamp') {
|
||||
element = <WidgetLamp widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel} />
|
||||
return <WidgetLamp widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel} />
|
||||
} else if (widget.type === 'Value') {
|
||||
element = <WidgetValue widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel} />
|
||||
return <WidgetValue widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel} />
|
||||
} else if (widget.type === 'Plot') {
|
||||
element = <WidgetPlot widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel} paused={this.props.paused} />
|
||||
return <WidgetPlot widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel} paused={this.props.paused} />
|
||||
} else if (widget.type === 'Table') {
|
||||
element = <WidgetTable widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel} />
|
||||
return <WidgetTable widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel} />
|
||||
} else if (widget.type === 'Label') {
|
||||
element = <WidgetLabel widget={widget} />
|
||||
return <WidgetLabel widget={widget} />
|
||||
} else if (widget.type === 'PlotTable') {
|
||||
element = <WidgetPlotTable widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel} editing={this.props.editing} onWidgetChange={(w) => this.props.onWidgetStatusChange(w, this.props.index)} paused={this.props.paused} />
|
||||
return <WidgetPlotTable widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel} editing={this.props.editing} onWidgetChange={(w) => this.props.onWidgetStatusChange(w, this.props.index)} paused={this.props.paused} />
|
||||
} else if (widget.type === 'Image') {
|
||||
element = <WidgetImage widget={widget} files={this.state.files} token={this.state.sessionToken} />
|
||||
return <WidgetImage widget={widget} files={this.state.files} token={this.state.sessionToken} />
|
||||
} else if (widget.type === 'Button') {
|
||||
element = <WidgetButton widget={widget} editing={this.props.editing} simulationModel={simulationModel} onInputChanged={(value) => this.inputDataChanged(widget, value)} />
|
||||
} else if (widget.type === 'Input') {
|
||||
element = <WidgetInput widget={widget} editing={this.props.editing} simulationModel={simulationModel} onInputChanged={(value) => this.inputDataChanged(widget, value)} />
|
||||
return <WidgetButton widget={widget} editing={this.props.editing} simulationModel={simulationModel} onInputChanged={(value) => this.inputDataChanged(widget, value)} />
|
||||
} else if (widget.type === 'NumberInput') {
|
||||
return <WidgetInput widget={widget} editing={this.props.editing} simulationModel={simulationModel} onInputChanged={(value) => this.inputDataChanged(widget, value)} />
|
||||
} else if (widget.type === 'Slider') {
|
||||
element = <WidgetSlider widget={widget} editing={this.props.editing} simulationModel={simulationModel} onInputChanged={(value) => this.inputDataChanged(widget, value)} onWidgetChange={(w) => this.props.onWidgetStatusChange(w, this.props.index) } />
|
||||
return <WidgetSlider widget={widget} editing={this.props.editing} simulationModel={simulationModel} onWidgetChange={(w) => this.props.onWidgetStatusChange(w, this.props.index) } onInputChanged={value => this.inputDataChanged(widget, value)} />
|
||||
} else if (widget.type === 'Gauge') {
|
||||
element = <WidgetGauge widget={widget} data={this.state.simulatorData} editing={this.props.editing} simulationModel={simulationModel} />
|
||||
return <WidgetGauge widget={widget} data={this.state.simulatorData} editing={this.props.editing} simulationModel={simulationModel} />
|
||||
} else if (widget.type === 'Box') {
|
||||
element = <WidgetBox widget={widget} editing={this.props.editing} />
|
||||
return <WidgetBox widget={widget} editing={this.props.editing} />
|
||||
} else if (widget.type === 'HTML') {
|
||||
element = <WidgetHTML widget={widget} editing={this.props.editing} />
|
||||
return <WidgetHTML widget={widget} editing={this.props.editing} />
|
||||
} else if (widget.type === 'Topology') {
|
||||
element = <WidgetTopology widget={widget} files={this.state.files} />
|
||||
return <WidgetTopology widget={widget} files={this.state.files} />
|
||||
}
|
||||
|
||||
if (widget.type === 'Box')
|
||||
zIndex = 0;
|
||||
return null;
|
||||
}
|
||||
|
||||
const widgetClasses = classNames({
|
||||
'widget': !this.props.editing,
|
||||
'editing-widget': this.props.editing,
|
||||
'border': borderedWidget,
|
||||
'unselectable': false,
|
||||
'locked': widget.locked && this.props.editing
|
||||
});
|
||||
|
||||
render() {
|
||||
const element = this.createWidget(this.props.data);
|
||||
|
||||
if (this.props.editing) {
|
||||
const resizing = { bottom: !widget.locked, bottomLeft: !widget.locked, bottomRight: !widget.locked, left: !widget.locked, right: !widget.locked, top: !widget.locked, topLeft: !widget.locked, topRight: !widget.locked};
|
||||
|
||||
return (
|
||||
<Rnd
|
||||
ref={c => { this.rnd = c; }}
|
||||
default={{ x: Number(widget.x), y: Number(widget.y), width: widget.width, height: widget.height }}
|
||||
minWidth={widget.minWidth}
|
||||
minHeight={widget.minHeight}
|
||||
lockAspectRatio={Boolean(widget.lockAspect)}
|
||||
bounds={'parent'}
|
||||
className={ widgetClasses }
|
||||
onResizeStart={(event, direction, ref) => this.borderWasClicked(event)}
|
||||
onResizeStop={(event, direction, ref, delta) => this.resizeStop(direction, delta, event)}
|
||||
onDrag={(event, data) => this.drag(event, data)}
|
||||
onDragStop={(event, data) => this.dragStop(event, data)}
|
||||
dragGrid={grid}
|
||||
resizeGrid={grid}
|
||||
z={zIndex}
|
||||
enableResizing={resizing}
|
||||
disableDragging={widget.locked}
|
||||
>
|
||||
<ContextMenuProvider className={'full'} id={'widgetMenu' + this.props.index}>
|
||||
{element}
|
||||
</ContextMenuProvider>
|
||||
</Rnd>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div
|
||||
className={ widgetClasses }
|
||||
style={{
|
||||
width: Number(widget.width), height: Number(widget.height),
|
||||
left: Number(widget.x), top: Number(widget.y),
|
||||
zIndex: zIndex,
|
||||
position: 'absolute'
|
||||
}}>
|
||||
{element}
|
||||
</div>
|
||||
);
|
||||
return <EditableWidgetContainer widget={this.props.data} grid={this.props.grid} index={this.props.index}>
|
||||
{element}
|
||||
</EditableWidgetContainer>;
|
||||
}
|
||||
|
||||
return <WidgetContainer widget={this.props.data}>
|
||||
{element}
|
||||
</WidgetContainer>;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue