1
0
Fork 0
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:
Sonja Happ 2019-11-04 15:20:54 +01:00
parent 5ecb254e13
commit 7331bf517b
10 changed files with 823 additions and 490 deletions

37
package-lock.json generated
View file

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

View file

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

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

View file

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

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

View 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

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

View 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

View 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

View file

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