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 visualization list and detailed view

Placeholder widgets are displayed, modifable but not updated yet
This commit is contained in:
Markus Grigull 2017-03-03 13:21:25 +01:00
parent 2fa75f0e58
commit c6677e4553
25 changed files with 1036 additions and 264 deletions

View file

@ -14,6 +14,7 @@
"react-dnd": "^2.2.4",
"react-dnd-html5-backend": "^2.2.4",
"react-dom": "^15.4.2",
"react-rnd": "^4.2.2",
"react-router": "^3.0.2",
"superagent": "^3.5.0"
},

View file

@ -7,10 +7,20 @@
* Unauthorized copying of this file, via any medium is strictly prohibited.
**********************************************************************************/
import React, { Component } from 'react';
import { Modal, Button, FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
import React, { Component, PropTypes } from 'react';
import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
import Dialog from './dialog';
class EditSimulatorDialog extends Component {
static propTypes = {
show: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
simulator: PropTypes.object.isRequired
};
valid: false;
constructor(props) {
super(props);
@ -19,23 +29,15 @@ class EditSimulatorDialog extends Component {
simulatorid: '1',
endpoint: '',
_id: ''
};
}
onClose(canceled) {
if (canceled === false) {
this.props.onClose(this.state);
} else {
this.props.onClose();
}
this.closeModal = this.closeModal.bind(this);
this.cancelModal = this.cancelModal.bind(this);
this.handleChange = this.handleChange.bind(this);
this.validateForm = this.validateForm.bind(this);
this.resetState = this.resetState.bind(this);
}
valid: false
closeModal() {
this.props.onClose(this.state);
}
cancelModal() {
this.props.onClose(null);
}
handleChange(e) {
@ -80,33 +82,22 @@ class EditSimulatorDialog extends Component {
render() {
return (
<Modal show={this.props.show} onEnter={this.resetState}>
<Modal.Header>
<Modal.Title>Edit Simulator</Modal.Title>
</Modal.Header>
<Modal.Body>
<form>
<FormGroup controlId="name" validationState={this.validateForm('name')}>
<ControlLabel>Name</ControlLabel>
<FormControl type="text" placeholder="Enter name" value={this.state.name} onChange={this.handleChange} />
</FormGroup>
<FormGroup controlId="simulatorid" validationState={this.validateForm('simulatorid')}>
<ControlLabel>Simulator ID</ControlLabel>
<FormControl type="number" placeholder="Enter simulator ID" value={this.state.simulatorid} onChange={this.handleChange} />
</FormGroup>
<FormGroup controlId="endpoint" validationState={this.validateForm('endpoint')}>
<ControlLabel>Endpoint</ControlLabel>
<FormControl type="text" placeholder="Enter endpoint" value={this.state.endpoint} onChange={this.handleChange} />
</FormGroup>
</form>
</Modal.Body>
<Modal.Footer>
<Button onClick={this.cancelModal}>Cancel</Button>
<Button bsStyle="primary" onClick={this.closeModal} disabled={!this.valid}>Save</Button>
</Modal.Footer>
</Modal>
<Dialog show={this.props.show} title="Edit Simulator" buttonTitle="save" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
<form>
<FormGroup controlId="name" validationState={this.validateForm('name')}>
<ControlLabel>Name</ControlLabel>
<FormControl type="text" placeholder="Enter name" value={this.state.name} onChange={(e) => this.handleChange(e)} />
</FormGroup>
<FormGroup controlId="simulatorid" validationState={this.validateForm('simulatorid')}>
<ControlLabel>Simulator ID</ControlLabel>
<FormControl type="number" placeholder="Enter simulator ID" value={this.state.simulatorid} onChange={(e) => this.handleChange(e)} />
</FormGroup>
<FormGroup controlId="endpoint" validationState={this.validateForm('endpoint')}>
<ControlLabel>Endpoint</ControlLabel>
<FormControl type="text" placeholder="Enter endpoint" value={this.state.endpoint} onChange={(e) => this.handleChange(e)} />
</FormGroup>
</form>
</Dialog>
);
}
}

View file

@ -0,0 +1,82 @@
/**
* File: dialog-new-visualization.js
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
* Date: 03.03.2017
* Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC
* This file is part of VILLASweb. All Rights Reserved. Proprietary and confidential.
* Unauthorized copying of this file, via any medium is strictly prohibited.
**********************************************************************************/
import React, { Component, PropTypes } from 'react';
import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
import Dialog from './dialog';
class EditVisualizationDialog extends Component {
static propTypes = {
show: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
visualization: PropTypes.object.isRequired
};
valid: false;
constructor(props) {
super(props);
this.state = {
name: '',
_id: ''
}
}
onClose(canceled) {
if (canceled === false) {
this.props.onClose(this.state);
} else {
this.props.onClose();
}
}
handleChange(e) {
this.setState({ [e.target.id]: e.target.value });
}
resetState() {
this.setState({
name: this.props.visualization.name,
_id: this.props.visualization._id
});
}
validateForm(target) {
// check all controls
var name = true;
if (this.state.name === '') {
name = false;
}
this.valid = name;
// return state to control
if (target === 'name') return name ? "success" : "error";
return "success";
}
render() {
return (
<Dialog show={this.props.show} title="Edit Visualization" buttonTitle="save" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
<form>
<FormGroup controlId="name" validationState={this.validateForm('name')}>
<ControlLabel>Name</ControlLabel>
<FormControl type="text" placeholder="Enter name" value={this.state.name} onChange={(e) => this.handleChange(e)} />
</FormGroup>
</form>
</Dialog>
);
}
}
export default EditVisualizationDialog;

View file

@ -7,34 +7,35 @@
* Unauthorized copying of this file, via any medium is strictly prohibited.
**********************************************************************************/
import React, { Component } from 'react';
import { Modal, Button, FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
import React, { Component, PropTypes } from 'react';
import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
import Dialog from './dialog';
class NewSimulatorDialog extends Component {
static propTypes = {
show: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired
};
valid: false;
constructor(props) {
super(props);
this.state = {
this.state = {
name: '',
simulatorid: '1',
endpoint: ''
};
}
onClose(canceled) {
if (canceled === false) {
this.props.onClose(this.state);
} else {
this.props.onClose();
}
this.closeModal = this.closeModal.bind(this);
this.cancelModal = this.cancelModal.bind(this);
this.handleChange = this.handleChange.bind(this);
this.validateForm = this.validateForm.bind(this);
this.resetState = this.resetState.bind(this);
}
valid: false
closeModal() {
this.props.onClose(this.state);
}
cancelModal() {
this.props.onClose(null);
}
handleChange(e) {
@ -74,33 +75,22 @@ class NewSimulatorDialog extends Component {
render() {
return (
<Modal show={this.props.show} onEnter={this.resetState}>
<Modal.Header>
<Modal.Title>New Simulator</Modal.Title>
</Modal.Header>
<Modal.Body>
<form>
<FormGroup controlId="name" validationState={this.validateForm('name')}>
<ControlLabel>Name</ControlLabel>
<FormControl type="text" placeholder="Enter name" value={this.state.name} onChange={this.handleChange} />
</FormGroup>
<FormGroup controlId="simulatorid" validationState={this.validateForm('simulatorid')}>
<ControlLabel>Simulator ID</ControlLabel>
<FormControl type="number" placeholder="Enter simulator ID" value={this.state.simulatorid} onChange={this.handleChange} />
</FormGroup>
<FormGroup controlId="endpoint" validationState={this.validateForm('endpoint')}>
<ControlLabel>Endpoint</ControlLabel>
<FormControl type="text" placeholder="Enter endpoint" value={this.state.endpoint} onChange={this.handleChange} />
</FormGroup>
</form>
</Modal.Body>
<Modal.Footer>
<Button onClick={this.cancelModal}>Cancel</Button>
<Button bsStyle="primary" onClick={this.closeModal} disabled={!this.valid}>Add</Button>
</Modal.Footer>
</Modal>
<Dialog show={this.props.show} title="New Simulator" buttonTitle="Add" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
<form>
<FormGroup controlId="name" validationState={this.validateForm('name')}>
<ControlLabel>Name</ControlLabel>
<FormControl type="text" placeholder="Enter name" value={this.state.name} onChange={(e) => this.handleChange(e)} />
</FormGroup>
<FormGroup controlId="simulatorid" validationState={this.validateForm('simulatorid')}>
<ControlLabel>Simulator ID</ControlLabel>
<FormControl type="number" placeholder="Enter simulator ID" value={this.state.simulatorid} onChange={(e) => this.handleChange(e)} />
</FormGroup>
<FormGroup controlId="endpoint" validationState={this.validateForm('endpoint')}>
<ControlLabel>Endpoint</ControlLabel>
<FormControl type="text" placeholder="Enter endpoint" value={this.state.endpoint} onChange={(e) => this.handleChange(e)} />
</FormGroup>
</form>
</Dialog>
);
}
}

View file

@ -0,0 +1,77 @@
/**
* File: dialog-new-visualization.js
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
* Date: 03.03.2017
* Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC
* This file is part of VILLASweb. All Rights Reserved. Proprietary and confidential.
* Unauthorized copying of this file, via any medium is strictly prohibited.
**********************************************************************************/
import React, { Component, PropTypes } from 'react';
import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
import Dialog from './dialog';
class NewVisualzationDialog extends Component {
static propTypes = {
show: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired
};
valid: false;
constructor(props) {
super(props);
this.state = {
name: ''
}
}
onClose(canceled) {
if (canceled === false) {
this.props.onClose(this.state);
} else {
this.props.onClose();
}
}
handleChange(e) {
this.setState({ [e.target.id]: e.target.value });
}
resetState() {
this.setState({ name: '' });
}
validateForm(target) {
// check all controls
var name = true;
if (this.state.name === '') {
name = false;
}
this.valid = name;
// return state to control
if (target === 'name') return name ? "success" : "error";
return "success";
}
render() {
return (
<Dialog show={this.props.show} title="New Visualization" buttonTitle="add" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
<form>
<FormGroup controlId="name" validationState={this.validateForm('name')}>
<ControlLabel>Name</ControlLabel>
<FormControl type="text" placeholder="Enter name" value={this.state.name} onChange={(e) => this.handleChange(e)} />
</FormGroup>
</form>
</Dialog>
);
}
}
export default NewVisualzationDialog;

49
src/components/dialog.js Normal file
View file

@ -0,0 +1,49 @@
/**
* File: dialog.js
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
* Date: 03.03.2017
* Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC
* This file is part of VILLASweb. All Rights Reserved. Proprietary and confidential.
* Unauthorized copying of this file, via any medium is strictly prohibited.
**********************************************************************************/
import React, { Component, PropTypes } from 'react';
import { Modal, Button } from 'react-bootstrap';
class Dialog extends Component {
static propTypes = {
title: PropTypes.string.isRequired,
show: PropTypes.bool.isRequired,
buttonTitle: PropTypes.string.isRequired,
onClose: PropTypes.func.isRequired
};
closeModal() {
this.props.onClose(false);
}
cancelModal() {
this.props.onClose(true);
}
render() {
return (
<Modal show={this.props.show} onEnter={this.props.onReset}>
<Modal.Header>
<Modal.Title>{this.props.title}</Modal.Title>
</Modal.Header>
<Modal.Body>
{this.props.children}
</Modal.Body>
<Modal.Footer>
<Button onClick={() => this.cancelModal()}>Cancel</Button>
<Button bsStyle="primary" onClick={() => this.closeModal()} disabled={!this.props.valid}>{this.props.buttonTitle}</Button>
</Modal.Footer>
</Modal>
);
}
}
export default Dialog;

View file

@ -0,0 +1,51 @@
/**
* File: dropzone.js
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
* Date: 02.03.2017
* Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC
* This file is part of VILLASweb. All Rights Reserved. Proprietary and confidential.
* Unauthorized copying of this file, via any medium is strictly prohibited.
**********************************************************************************/
import React, { Component, PropTypes } from 'react';
import { DropTarget } from 'react-dnd';
import classNames from 'classnames';
const dropzoneTarget = {
drop(props, monitor) {
props.onDrop(monitor.getItem());
}
};
function collect(connect, monitor) {
return {
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver(),
canDrop: monitor.canDrop()
};
}
class Dropzone extends Component {
static propTypes = {
connectDropTarget: PropTypes.func.isRequired,
isOver: PropTypes.bool.isRequired,
canDrop: PropTypes.bool.isRequired,
onDrop: PropTypes.func.isRequired
};
render() {
var toolboxClass = classNames({
'toolbox-dropzone': true,
'toolbox-dropzone-active': this.props.isOver && this.props.canDrop && this.props.editing,
'toolbox-dropzone-editing': this.props.editing
});
return this.props.connectDropTarget(
<div className={toolboxClass}>
{this.props.children}
</div>
);
}
}
export default DropTarget('widget', dropzoneTarget, collect)(Dropzone);

View file

@ -21,6 +21,7 @@ class SidebarMenu extends Component {
<li><Link to="/projects" activeClassName="active">Projects</Link></li>
<li><Link to="/simulation" activeClassName="active">Simulations</Link></li>
<li><Link to="/simulators" activeClassName="active">Simulators</Link></li>
<li><Link to="/visualizations" activeClassName="active">Visualizations</Link></li>
</ul>
</div>
);

View file

@ -0,0 +1,75 @@
/**
* File: table-control-link.js
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
* Date: 03.03.2017
* Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC
* This file is part of VILLASweb. All Rights Reserved. Proprietary and confidential.
* Unauthorized copying of this file, via any medium is strictly prohibited.
**********************************************************************************/
import React, { Component, PropTypes } from 'react';
import { Button, Glyphicon } from 'react-bootstrap';
import { Link } from 'react-router';
class ControlLinkTable extends Component {
static propTypes = {
columns: PropTypes.array.isRequired,
onEdit: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
linkRoot: PropTypes.string.isRequired
};
render() {
// create sorted rows
var rows = this.props.data.map(row => {
// add cells by column keys
var rowArray = [ row._id ];
for (var i = 0; i < this.props.columns.length; i++) {
if (row[this.props.columns[i].key] != null) {
rowArray.push(row[this.props.columns[i].key].toString());
} else {
rowArray.push("");
}
}
return rowArray;
});
return (
<table width={this.props.width} className="table">
<thead>
<tr>
{this.props.columns.map(column => (
<th key={column.key} width={column.width}>{column.title}</th>
))}
<th width="70px"></th>
</tr>
</thead>
<tbody>
{rows.map((row) => (
<tr key={row[0]}>
{row.filter((element, index) => {
return index !== 0;
}).map((cell, index) => (
<td key={index}>
{index === 0 ? (
<Link to={this.props.linkRoot + '/' + row[0]}>{cell}</Link>
) : (
{cell}
)}
</td>
))}
<td>
<Button bsClass="table-control-button" onClick={() => this.props.onEdit(row[0])}><Glyphicon glyph="pencil" /></Button>
<Button bsClass="table-control-button" onClick={() => this.props.onDelete(row[0])}><Glyphicon glyph="remove" /></Button>
</td>
</tr>
))}
</tbody>
</table>
);
}
}
export default ControlLinkTable;

View file

@ -1,5 +1,5 @@
/**
* File: table.js
* File: table-control.js
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
* Date: 02.03.2017
* Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC

View file

@ -0,0 +1,52 @@
/**
* File: toolbox-item.js
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
* Date: 02.03.2017
* Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC
* This file is part of VILLASweb. All Rights Reserved. Proprietary and confidential.
* Unauthorized copying of this file, via any medium is strictly prohibited.
**********************************************************************************/
import React, { Component, PropTypes } from 'react';
import { DragSource } from 'react-dnd';
import classNames from 'classnames';
const toolboxItemSource = {
beginDrag(props) {
return {
name: props.name
};
}
};
function collect(connect, monitor) {
return {
connectDragSource: connect.dragSource(),
isDragging: monitor.isDragging()
}
}
class ToolboxItem extends Component {
static propTypes = {
connectDragSource: PropTypes.func.isRequired,
isDragging: PropTypes.bool.isRequired,
name: PropTypes.string.isRequired,
type: PropTypes.string.isRequired
};
render() {
var itemClass = classNames({
'toolbox-item': true,
'toolbox-item-dragging': this.props.isDragging
});
var dropEffect = 'copy';
return this.props.connectDragSource(
<span className={itemClass}>
{this.props.name}
</span>
, {dropEffect});
}
}
export default DragSource('widget', toolboxItemSource, collect)(ToolboxItem);

65
src/components/widget.js Normal file
View file

@ -0,0 +1,65 @@
/**
* File: widget.js
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
* Date: 02.03.2017
* Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC
* This file is part of VILLASweb. All Rights Reserved. Proprietary and confidential.
* Unauthorized copying of this file, via any medium is strictly prohibited.
**********************************************************************************/
import React, { Component } from 'react';
import Rnd from 'react-rnd';
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);
}
dragStop(event, ui) {
// update widget
var widget = this.props.data;
widget.x = ui.position.left;
widget.y = ui.position.top;
this.props.onWidgetChange(widget);
}
render() {
const widget = this.props.data;
if (this.props.editing) {
return (
<Rnd
ref={c => { this.rnd = c; }}
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}
>
<span>{widget.name}</span>
</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>
);
}
}
}
export default Widget;

View file

@ -9,6 +9,8 @@
import React, { Component } from 'react';
import { Container } from 'flux/utils';
import { DragDropContext } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
// import AppDispatcher from '../app-dispatcher';
import VillasStore from '../stores/villas-store';
@ -52,4 +54,4 @@ class App extends Component {
}
}
export default Container.create(App);
export default DragDropContext(HTML5Backend)(Container.create(App));

View file

@ -19,18 +19,6 @@ import NewSimulatorDialog from '../components/dialog-new-simulator';
import EditSimulatorDialog from '../components/dialog-edit-simulator';
class Simulators extends Component {
constructor(props) {
super(props);
this.showNewModal = this.showNewModal.bind(this);
this.closeNewModal = this.closeNewModal.bind(this);
this.showDeleteModal = this.showDeleteModal.bind(this);
this.confirmDeleteModal = this.confirmDeleteModal.bind(this);
this.cancelDeleteModal = this.cancelDeleteModal.bind(this);
this.showEditModal = this.showEditModal.bind(this);
this.closeEditModal = this.closeEditModal.bind(this);
}
static getStores() {
return [ SimulatorStore ];
}
@ -52,17 +40,13 @@ class Simulators extends Component {
});
}
showNewModal() {
this.setState({ newModal: true });
}
closeNewModal(data) {
this.setState({ newModal : false });
if (data) {
AppDispatcher.dispatch({
type: 'simulators/start-add',
simulator: data
data: data
});
}
}
@ -80,16 +64,12 @@ class Simulators extends Component {
this.setState({ deleteModal: true, modalSimulator: deleteSimulator });
}
cancelDeleteModal() {
this.setState({ deleteModal: false });
}
confirmDeleteModal() {
this.setState({ deleteModal: false });
AppDispatcher.dispatch({
type: 'simulators/start-remove',
simulator: this.state.modalSimulator
data: this.state.modalSimulator
});
}
@ -112,7 +92,7 @@ class Simulators extends Component {
if (data) {
AppDispatcher.dispatch({
type: 'simulators/start-edit',
simulator: data
data: data
});
}
}
@ -129,13 +109,13 @@ class Simulators extends Component {
<div>
<h1>Simulators</h1>
<ControlTable columns={columns} data={this.state.simulators} width='100%' onEdit={this.showEditModal} onDelete={this.showDeleteModal} />
<ControlTable columns={columns} data={this.state.simulators} width='100%' onEdit={(id) => this.showEditModal(id)} onDelete={(id) => this.showDeleteModal(id)} />
<Button onClick={this.showNewModal}>New Simulator</Button>
<Button onClick={() => this.setState({ newModal: true })}>New Simulator</Button>
<NewSimulatorDialog show={this.state.newModal} onClose={this.closeNewModal} />
<NewSimulatorDialog show={this.state.newModal} onClose={(data) => this.closeNewModal(data)} />
<EditSimulatorDialog show={this.state.editModal} onClose={this.closeEditModal} simulator={this.state.modalSimulator} />
<EditSimulatorDialog show={this.state.editModal} onClose={(data) => this.closeEditModal(data)} simulator={this.state.modalSimulator} />
<Modal show={this.state.deleteModal}>
<Modal.Header>
@ -147,8 +127,8 @@ class Simulators extends Component {
</Modal.Body>
<Modal.Footer>
<Button onClick={this.cancelDeleteModal}>Cancel</Button>
<Button bsStyle="danger" onClick={this.confirmDeleteModal}>Delete</Button>
<Button onClick={() => this.setState({ deleteModal: false })}>Cancel</Button>
<Button bsStyle="danger" onClick={() => this.confirmDeleteModal()}>Delete</Button>
</Modal.Footer>
</Modal>
</div>

View file

@ -0,0 +1,96 @@
/**
* File: visualization.js
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
* Date: 02.03.2017
* Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC
* This file is part of VILLASweb. All Rights Reserved. Proprietary and confidential.
* Unauthorized copying of this file, via any medium is strictly prohibited.
**********************************************************************************/
import React, { Component } from 'react';
import { Container } from 'flux/utils';
import { Button } from 'react-bootstrap';
import ToolboxItem from '../components/toolbox-item';
import Dropzone from '../components/dropzone';
import Widget from '../components/widget';
import VisualizationStore from '../stores/visualization-store';
import AppDispatcher from '../app-dispatcher';
class Visualization extends Component {
static getStores() {
return [ VisualizationStore ];
}
static calculateState() {
return {
visualizations: VisualizationStore.getState(),
visualization: {},
editing: false
}
}
handleDrop(item) {
console.log(item);
}
widgetChange(widget) {
console.log(widget);
}
componentWillMount() {
AppDispatcher.dispatch({
type: 'visualizations/start-load'
});
}
componentDidUpdate() {
if (this.state.visualization._id !== this.props.params.visualization) {
this.reloadVisualization();
}
}
reloadVisualization() {
// select visualization by param id
this.state.visualizations.forEach((visualization) => {
if (visualization._id === this.props.params.visualization) {
this.setState({ visualization: visualization });
}
});
}
render() {
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>
)}
</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={this.widgetChange} editing={this.state.editing} />
))}
</Dropzone>
</div>
);
}
}
export default Container.create(Visualization);

View file

@ -0,0 +1,136 @@
/**
* File: visualizations.js
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
* Date: 03.03.2017
* Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC
* This file is part of VILLASweb. All Rights Reserved. Proprietary and confidential.
* Unauthorized copying of this file, via any medium is strictly prohibited.
**********************************************************************************/
import React, { Component } from 'react';
import { Container } from 'flux/utils';
import { Button, Modal } from 'react-bootstrap';
import AppDispatcher from '../app-dispatcher';
import VisualizationStore from '../stores/visualization-store';
import ControlLinkTable from '../components/table-control-link';
import NewVisualzationDialog from '../components/dialog-new-visualization';
import EditVisualizationDialog from '../components/dialog-edit-visualization';
class Visualizations extends Component {
static getStores() {
return [ VisualizationStore ];
}
static calculateState() {
return {
visualizations: VisualizationStore.getState(),
newModal: false,
deleteModal: false,
editModal: false,
modalVisualization: {}
};
}
componentWillMount() {
AppDispatcher.dispatch({
type: 'visualizations/start-load'
});
}
closeNewModal(data) {
this.setState({ newModal : false });
if (data) {
AppDispatcher.dispatch({
type: 'visualizations/start-add',
data: data
});
}
}
showDeleteModal(id) {
// get visualization by id
var deleteVisualization;
this.state.visualizations.forEach((visualization) => {
if (visualization._id === id) {
deleteVisualization = visualization;
}
});
this.setState({ deleteModal: true, modalVisualization: deleteVisualization });
}
confirmDeleteModal() {
this.setState({ deleteModal: false });
AppDispatcher.dispatch({
type: 'visualizations/start-remove',
data: this.state.modalVisualization
});
}
showEditModal(id) {
// get visualization by id
var editVisualization;
this.state.visualizations.forEach((visualization) => {
if (visualization._id === id) {
editVisualization = visualization;
}
});
this.setState({ editModal: true, modalVisualization: editVisualization });
}
closeEditModal(data) {
this.setState({ editModal : false });
if (data) {
AppDispatcher.dispatch({
type: 'visualizations/start-edit',
data: data
});
}
}
render() {
var columns = [
{ title: 'Name', key: 'name' }
];
return (
<div>
<h1>Visualizations</h1>
<ControlLinkTable columns={columns} data={this.state.visualizations} width='100%' onEdit={(id) => this.showEditModal(id)} onDelete={(id) => this.showDeleteModal(id)} linkRoot="/visualizations"/>
<Button onClick={() => this.setState({ newModal: true })}>New Visualization</Button>
<NewVisualzationDialog show={this.state.newModal} onClose={(data) => this.closeNewModal(data)} />
<EditVisualizationDialog show={this.state.editModal} onClose={(data) => this.closeEditModal(data)} visualization={this.state.modalVisualization} />
<Modal show={this.state.deleteModal}>
<Modal.Header>
<Modal.Title>Delete Visualization</Modal.Title>
</Modal.Header>
<Modal.Body>
Are you sure you want to delete the visualization <strong>'{this.state.modalVisualization.name}'</strong>?
</Modal.Body>
<Modal.Footer>
<Button onClick={() => this.setState({ deleteModal: false })}>Cancel</Button>
<Button bsStyle="danger" onClick={() => this.confirmDeleteModal()}>Delete</Button>
</Modal.Footer>
</Modal>
</div>
);
}
}
export default Container.create(Visualizations);

View file

@ -0,0 +1,82 @@
/**
* File: data-manager.js
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
* Date: 03.03.2017
* Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC
* This file is part of VILLASweb. All Rights Reserved. Proprietary and confidential.
* Unauthorized copying of this file, via any medium is strictly prohibited.
**********************************************************************************/
import RestAPI from '../api/rest-api';
import AppDispatcher from '../app-dispatcher';
class RestDataManager {
constructor(type, url) {
this.url = url;
this.type = type;
}
load() {
RestAPI.get(this.url).then(response => {
AppDispatcher.dispatch({
type: this.type + 's/loaded',
data: response[this.type + 's']
});
}).catch(error => {
AppDispatcher.dispatch({
type: this.type + 's/load-error',
error: error
});
});
}
add(object) {
var obj = {};
obj[this.type] = object;
RestAPI.post(this.url, obj).then(response => {
AppDispatcher.dispatch({
type: this.type + 's/added',
data: response[this.type]
});
}).catch(error => {
AppDispatcher.dispatch({
type: this.type + 's/add-error',
error: error
});
});
}
remove(object) {
RestAPI.delete(this.url + '/' + object._id).then(response => {
AppDispatcher.dispatch({
type: this.type + 's/removed',
data: response[this.type]
});
}).catch(error => {
AppDispatcher.dispatch({
type: this.type + 's/remove-error',
error: error
});
});
}
update(object) {
var obj = {};
obj[this.type] = object;
RestAPI.put(this.url + '/' + object._id, obj).then(response => {
AppDispatcher.dispatch({
type: this.type + 's/edited',
data: response[this.type]
});
}).catch(error => {
AppDispatcher.dispatch({
type: this.type + 's/edit-error',
error: error
});
});
}
};
export default RestDataManager;

View file

@ -7,65 +7,6 @@
* Unauthorized copying of this file, via any medium is strictly prohibited.
**********************************************************************************/
import RestAPI from '../api/rest-api';
import AppDispatcher from '../app-dispatcher';
import RestDataManager from './rest-data-manager';
const SimulatorsDataManager = {
loadSimulators() {
RestAPI.get('/simulators').then(response => {
AppDispatcher.dispatch({
type: 'simulators/loaded',
simulators: response.simulators
});
}).catch(error => {
AppDispatcher.dispatch({
type: 'simulators/load-error',
error: error
});
});
},
addSimulator(simulator) {
RestAPI.post('/simulators', { simulator: simulator }).then(response => {
AppDispatcher.dispatch({
type: 'simulators/added',
simulator: response.simulator
});
}).catch(error => {
AppDispatcher.dispatch({
type: 'simulators/add-error',
error: error
});
});
},
removeSimulator(simulator) {
RestAPI.delete('/simulators/' + simulator._id).then(response => {
AppDispatcher.dispatch({
type: 'simulators/removed',
simulator: simulator
});
}).catch(error => {
AppDispatcher.dispatch({
type: 'simulators/remove-error',
error: error
});
});
},
editSimulator(simulator) {
RestAPI.put('/simulators/' + simulator._id, { simulator: simulator }).then(response => {
AppDispatcher.dispatch({
type: 'simulators/edited',
simulator: response.simulator
});
}).catch(error => {
AppDispatcher.dispatch({
type: 'simulators/edit-error',
error: error
});
});
}
}
export default SimulatorsDataManager;
export default new RestDataManager('simulator', '/simulators');

View file

@ -0,0 +1,12 @@
/**
* File: visualizations-data-manager.js
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
* Date: 03.03.2017
* Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC
* This file is part of VILLASweb. All Rights Reserved. Proprietary and confidential.
* Unauthorized copying of this file, via any medium is strictly prohibited.
**********************************************************************************/
import RestDataManager from './rest-data-manager';
export default new RestDataManager('visualization', '/visualizations');

View file

@ -14,6 +14,8 @@ import App from './containers/app';
import Home from './containers/home';
import Projects from './containers/projects';
import Simulators from './containers/simulators';
import Visualization from './containers/visualization';
import Visualizations from './containers/visualizations';
class Root extends Component {
render() {
@ -23,6 +25,9 @@ class Root extends Component {
<Route path='/home' component={Home} />
<Route path='/projects' component={Projects} />
<Route path='/simulators' component={Simulators} />
<Route path='/visualizations' component={Visualizations} />
<Route path='/visualizations/:visualization' component={Visualization} />
</Route>
</Router>
);

93
src/stores/array-store.js Normal file
View file

@ -0,0 +1,93 @@
/**
* File: array-store.js
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
* Date: 03.03.2017
* Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC
* This file is part of VILLASweb. All Rights Reserved. Proprietary and confidential.
* Unauthorized copying of this file, via any medium is strictly prohibited.
**********************************************************************************/
import { ReduceStore } from 'flux/utils';
import AppDispatcher from '../app-dispatcher';
class ArrayStore extends ReduceStore {
constructor(type, dataManager) {
super(AppDispatcher);
this.type = type;
this.dataManager = dataManager;
}
getInitialState() {
return [];
}
reduce(state, action) {
var array;
switch (action.type) {
case this.type + '/start-load':
this.dataManager.load();
return state;
case this.type + '/loaded':
return action.data;
case this.type + '/load-error':
// TODO: Add error message
return state;
case this.type + '/start-add':
this.dataManager.add(action.data);
return state;
case this.type + '/added':
// state should always be immutable, thus make new copy
array = state.slice();
array.push(action.data);
return array;
case this.type + '/add-error':
// TODO: Add error message
return state;
case this.type + '/start-remove':
this.dataManager.remove(action.data);
return state;
case this.type + '/removed':
return state.filter((item) => {
return (item !== action.data);
});
case this.type + '/remove-error':
// TODO: Add error message
return state;
case this.type + '/start-edit':
this.dataManager.update(action.data);
return state;
case this.type + '/edited':
array = state.slice();
for (var i = 0; i < array.length; i++) {
if (array[i]._id === action.data._id) {
array[i] = action.data;
}
}
return array;
case this.type + '/edit-error':
// TODO: Add error message
return state;
default:
return state;
}
}
}
export default ArrayStore;

View file

@ -7,85 +7,7 @@
* Unauthorized copying of this file, via any medium is strictly prohibited.
**********************************************************************************/
import { ReduceStore } from 'flux/utils';
import AppDispatcher from '../app-dispatcher';
import ArrayStore from './array-store';
import SimulatorsDataManager from '../data-managers/simulators-data-manager';
class SimulatorStore extends ReduceStore {
constructor() {
super(AppDispatcher);
}
getInitialState() {
return [];
}
reduce(state, action) {
var simulators;
switch (action.type) {
case 'simulators/start-load':
SimulatorsDataManager.loadSimulators();
return state;
case 'simulators/loaded':
return action.simulators;
case 'simulators/load-error':
// TODO: Add error message
return state;
case 'simulators/start-add':
SimulatorsDataManager.addSimulator(action.simulator);
return state;
case 'simulators/added':
// state should always be immutable, thus make new copy
simulators = state.slice();
simulators.push(action.simulator);
return simulators;
case 'simulators/add-error':
// TODO: Add error message
return state;
case 'simulators/start-remove':
SimulatorsDataManager.removeSimulator(action.simulator);
return state;
case 'simulators/removed':
return state.filter((simulator) => {
return (simulator !== action.simulator)
});
case 'simulators/remove-error':
// TODO: Add error message
return state;
case 'simulators/start-edit':
SimulatorsDataManager.editSimulator(action.simulator);
return state;
case 'simulators/edited':
simulators = state.slice();
for (var i = 0; i < simulators.length; i++) {
if (simulators[i]._id === action.simulator._id) {
simulators[i] = action.simulator;
}
}
return simulators;
case 'simulators/edit-error':
// TODO: Add error message
return state;
default:
return state;
}
}
}
export default new SimulatorStore();
export default new ArrayStore('simulators', SimulatorsDataManager);

View file

@ -0,0 +1,13 @@
/**
* File: visualization-store.js
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
* Date: 02.03.2017
* Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC
* This file is part of VILLASweb. All Rights Reserved. Proprietary and confidential.
* Unauthorized copying of this file, via any medium is strictly prohibited.
**********************************************************************************/
import ArrayStore from './array-store';
import VisualizationsDataManager from '../data-managers/visualizations-data-manager';
export default new ArrayStore('visualizations', VisualizationsDataManager);

View file

@ -53,7 +53,7 @@ body {
.app-content {
min-height: 200px;
margin: 20px 20px 20px 170px;
margin: 20px 20px 20px 200px;
padding: 15px 20px;
background-color: #fff;
@ -67,6 +67,8 @@ body {
.menu-sidebar {
float: left;
width: 160px;
margin: 20px 0 0 20px;
padding: 20px 25px 20px 25px;
@ -132,3 +134,40 @@ body {
.table-control-button:hover {
color: #888;
}
/**
* Toolbox
*/
.toolbox-dropzone {
width: 100%;
min-height: 400px;
position: relative;
}
.toolbox-dropzone-editing {
border: 3px dashed #e1e1e1;
}
.toolbox-dropzone-active {
border-color: #aaa;
}
.toolbox {
margin-top: 10px;
margin-bottom: 10px;
}
.toolbox-item {
display: inline-block;
padding: 5px 10px;
border: 1px solid gray;
cursor: move;
}
.toolbox-item-dragging {
opacity: 0.4;
}

17
src/styles/widgets.css Normal file
View file

@ -0,0 +1,17 @@
/**
* File: widgets.css
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
* Date: 02.03.2017
* Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC
* This file is part of VILLASweb. All Rights Reserved. Proprietary and confidential.
* Unauthorized copying of this file, via any medium is strictly prohibited.
**********************************************************************************/
.widget {
width: 100%;
height: 100%;
padding: 5px 10px;
border: 1px solid lightgray;
}