diff --git a/package.json b/package.json index 1830bf3..d794de0 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/components/widget.js b/src/components/widget.js index fa37ce3..424d799 100644 --- a/src/components/widget.js +++ b/src/components/widget.js @@ -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)} > - {widget.name} + +
{widget.name}
+
); } else { return ( -
{widget.name}
+
+ {widget.name} +
); } } diff --git a/src/containers/visualization.js b/src/containers/visualization.js index dc260f0..5257c9b 100644 --- a/src/containers/visualization.js +++ b/src/containers/visualization.js @@ -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 (
-

{this.state.visualization.name}

-
- {this.state.editing ? ( -
- - -
- ) : ( - - )} +

+ {this.state.visualization.name} +

+ +
+ {this.state.editing ? ( +
+ + +
+ ) : ( + + )} +
- {this.state.editing && -
- -
- } +
+ {this.state.editing && +
+ +
+ } + + this.handleDrop(item)} editing={this.state.editing}> + {this.state.visualization.widgets != null && + this.state.visualization.widgets.map((widget, index) => ( + this.widgetChange(w, i)} editing={this.state.editing} index={index} /> + ))} + - this.handleDrop(item)} editing={this.state.editing}> {this.state.visualization.widgets != null && this.state.visualization.widgets.map((widget, index) => ( - + + this.editWidget(e, data)}>Edit + this.deleteWidget(e, data)}>Delete + ))} - +
); } diff --git a/src/styles/widgets.css b/src/styles/widgets.css index 29f86ca..7fe801b 100644 --- a/src/styles/widgets.css +++ b/src/styles/widgets.css @@ -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; +}