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

Add widget context menu

Widget deletion render is kinda broken, but deletion works
This commit is contained in:
Markus Grigull 2017-03-03 16:58:35 +01:00
parent c6677e4553
commit d9a2ae55a9
4 changed files with 168 additions and 37 deletions

View file

@ -10,6 +10,7 @@
"immutable": "^3.8.1",
"react": "^15.4.2",
"react-bootstrap": "^0.30.7",
"react-contextmenu": "^2.3.0",
"react-d3": "^0.4.0",
"react-dnd": "^2.2.4",
"react-dnd-html5-backend": "^2.2.4",

View file

@ -9,24 +9,18 @@
import React, { Component } from 'react';
import Rnd from 'react-rnd';
import { ContextMenuTrigger } from 'react-contextmenu';
import '../styles/widgets.css';
class Widget extends Component {
constructor(props) {
super(props);
this.resizeStop = this.resizeStop.bind(this);
this.dragStop = this.dragStop.bind(this);
}
resizeStop(direction, styleSize, clientSize, delta) {
// update widget
var widget = this.props.data;
widget.width = styleSize.width;
widget.height = styleSize.height;
this.props.onWidgetChange(widget);
this.props.onWidgetChange(widget, this.props.index);
}
dragStop(event, ui) {
@ -35,7 +29,7 @@ class Widget extends Component {
widget.x = ui.position.left;
widget.y = ui.position.top;
this.props.onWidgetChange(widget);
this.props.onWidgetChange(widget, this.props.index);
}
render() {
@ -48,15 +42,19 @@ class Widget extends Component {
initial={{ x: Number(widget.x), y: Number(widget.y), width: widget.width, height: widget.height }}
bounds={'parent'}
className="widget"
onResizeStop={this.resizeStop}
onDragStop={this.dragStop}
onResizeStop={(direction, styleSize, clientSize, delta) => this.resizeStop(direction, styleSize, clientSize, delta)}
onDragStop={(event, ui) => this.dragStop(event, ui)}
>
<span>{widget.name}</span>
<ContextMenuTrigger id={'widgetMenu' + this.props.index} attributes={{ style: { width: '100%', height: '100%' } }}>
<div>{widget.name}</div>
</ContextMenuTrigger>
</Rnd>
);
} else {
return (
<div className="widget" style={{ width: Number(widget.width), height: Number(widget.height), left: Number(widget.x), top: Number(widget.y), position: 'relative' }}>{widget.name}</div>
<div className="widget" style={{ width: Number(widget.width), height: Number(widget.height), left: Number(widget.x), top: Number(widget.y), position: 'absolute' }}>
{widget.name}
</div>
);
}
}

View file

@ -10,6 +10,7 @@
import React, { Component } from 'react';
import { Container } from 'flux/utils';
import { Button } from 'react-bootstrap';
import { ContextMenu, MenuItem } from 'react-contextmenu';
import ToolboxItem from '../components/toolbox-item';
import Dropzone from '../components/dropzone';
@ -32,11 +33,59 @@ class Visualization extends Component {
}
handleDrop(item) {
console.log(item);
// add new widget
var widget = {
name: 'Name',
type: item.name,
width: 100,
height: 100,
x: 0,
y: 0,
z: 0
};
var visualization = this.state.visualization;
visualization.widgets.push(widget);
this.setState({ visualization: visualization });
this.forceUpdate();
}
widgetChange(widget) {
console.log(widget);
widgetChange(widget, index) {
// save changes temporarily
var visualization = this.state.visualization;
visualization.widgets[index] = widget;
this.setState({ visualization: visualization });
}
editWidget(e, data) {
}
deleteWidget(e, data) {
// delete widget temporarily
var visualization = this.state.visualization;
visualization.widgets.splice(data.index, 1);
this.setState({ visualization: visualization });
this.forceUpdate();
}
saveChanges() {
AppDispatcher.dispatch({
type: 'visualizations/start-edit',
data: this.state.visualization
});
this.setState({ editing: false });
}
discardChanges() {
this.setState({ editing: false, visualization: {} });
this.reloadVisualization();
this.forceUpdate();
}
componentWillMount() {
@ -55,39 +104,56 @@ class Visualization extends Component {
// select visualization by param id
this.state.visualizations.forEach((visualization) => {
if (visualization._id === this.props.params.visualization) {
this.setState({ visualization: visualization });
// JSON.parse(JSON.stringify(obj)) = deep clone to make also copy of widget objects inside
this.setState({ visualization: JSON.parse(JSON.stringify(visualization)) });
}
});
}
render() {
console.log(this.state.visualization.widgets);
return (
<div>
<h1>{this.state.visualization.name}</h1>
<div>
{this.state.editing ? (
<div>
<Button bsStyle="link" onClick={() => this.setState({ editing: false })}>Save</Button>
<Button bsStyle="link" onClick={() => this.setState({ editing: false })}>Cancel</Button>
</div>
) : (
<Button bsStyle="link" onClick={() => this.setState({ editing: true })}>Edit</Button>
)}
<h1>
{this.state.visualization.name}
</h1>
<div>
{this.state.editing ? (
<div>
<Button bsStyle="link" onClick={() => this.saveChanges()}>Save</Button>
<Button bsStyle="link" onClick={() => this.discardChanges()}>Cancel</Button>
</div>
) : (
<Button bsStyle="link" onClick={() => this.setState({ editing: true })}>Edit</Button>
)}
</div>
</div>
{this.state.editing &&
<div className="toolbox">
<ToolboxItem name="Value" type="widget" />
</div>
}
<div>
{this.state.editing &&
<div className="toolbox">
<ToolboxItem name="Value" type="widget" />
</div>
}
<Dropzone onDrop={item => this.handleDrop(item)} editing={this.state.editing}>
{this.state.visualization.widgets != null &&
this.state.visualization.widgets.map((widget, index) => (
<Widget key={index} data={widget} onWidgetChange={(w, i) => this.widgetChange(w, i)} editing={this.state.editing} index={index} />
))}
</Dropzone>
<Dropzone onDrop={item => this.handleDrop(item)} editing={this.state.editing}>
{this.state.visualization.widgets != null &&
this.state.visualization.widgets.map((widget, index) => (
<Widget key={index} data={widget} onWidgetChange={this.widgetChange} editing={this.state.editing} />
<ContextMenu id={'widgetMenu' + index} key={index}>
<MenuItem data={{index: index}} onClick={(e, data) => this.editWidget(e, data)}>Edit</MenuItem>
<MenuItem data={{index: index}} onClick={(e, data) => this.deleteWidget(e, data)}>Delete</MenuItem>
</ContextMenu>
))}
</Dropzone>
</div>
</div>
);
}

View file

@ -11,7 +11,73 @@
width: 100%;
height: 100%;
padding: 5px 10px;
border: 1px solid lightgray;
}
.react-contextmenu {
min-width: 160px;
padding: 5px 0;
margin: 2px 0 0;
font-size: 16px;
color: #373a3c;
text-align: left;
background-color: #fff;
background-clip: padding-box;
border: 1px solid rgba(0,0,0,.15);
border-radius: .25rem;
outline: none;
opacity: 0;
pointer-events: none;
z-index: 100;
}
.react-contextmenu.react-contextmenu--visible {
opacity: 1;
pointer-events: auto;
}
.react-contextmenu-item {
width: 200px;
padding: 3px 20px;
font-weight: 400;
line-height: 1.5;
color: #373a3c;
text-align: inherit;
white-space: nowrap;
background: 0 0;
border: 0;
cursor: pointer;
}
.react-contextmenu-item.react-contextmenu-item--active,
.react-contextmenu-item:hover {
color: #fff;
background-color: #0275d8;
border-color: #0275d8;
text-decoration: none;
}
.react-contextmenu-item--divider {
margin-bottom: 3px;
padding: 2px 0;
border-bottom: 1px solid rgba(0,0,0,.15);
cursor: inherit;
}
.react-contextmenu-item--divider:hover {
background-color: transparent;
border-color: rgba(0,0,0,.15);
}
.react-contextmenu-item.react-contextmenu-submenu {
padding: 0;
}
.react-contextmenu-item.react-contextmenu-submenu > .react-contextmenu-item {
}
.react-contextmenu-item.react-contextmenu-submenu > .react-contextmenu-item:after {
content: "▶";
display: inline-block;
position: absolute;
right: 7px;
}