diff --git a/package.json b/package.json index e8b8119..1830bf3 100644 --- a/package.json +++ b/package.json @@ -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" }, diff --git a/src/components/dialog-edit-simulator.js b/src/components/dialog-edit-simulator.js index a281522..bddec15 100644 --- a/src/components/dialog-edit-simulator.js +++ b/src/components/dialog-edit-simulator.js @@ -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 ( - - - Edit Simulator - - - -
- - Name - - - - Simulator ID - - - - Endpoint - - -
-
- - - - - -
+ this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}> +
+ + Name + this.handleChange(e)} /> + + + Simulator ID + this.handleChange(e)} /> + + + Endpoint + this.handleChange(e)} /> + +
+
); } } diff --git a/src/components/dialog-edit-visualization.js b/src/components/dialog-edit-visualization.js new file mode 100644 index 0000000..5284c7a --- /dev/null +++ b/src/components/dialog-edit-visualization.js @@ -0,0 +1,82 @@ +/** + * File: dialog-new-visualization.js + * Author: Markus Grigull + * 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 ( + this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}> +
+ + Name + this.handleChange(e)} /> + +
+
+ ); + } +} + +export default EditVisualizationDialog; diff --git a/src/components/dialog-new-simulator.js b/src/components/dialog-new-simulator.js index 95a3aa0..93a074e 100644 --- a/src/components/dialog-new-simulator.js +++ b/src/components/dialog-new-simulator.js @@ -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 ( - - - New Simulator - - - -
- - Name - - - - Simulator ID - - - - Endpoint - - -
-
- - - - - -
+ this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}> +
+ + Name + this.handleChange(e)} /> + + + Simulator ID + this.handleChange(e)} /> + + + Endpoint + this.handleChange(e)} /> + +
+
); } } diff --git a/src/components/dialog-new-visualization.js b/src/components/dialog-new-visualization.js new file mode 100644 index 0000000..d24aa6e --- /dev/null +++ b/src/components/dialog-new-visualization.js @@ -0,0 +1,77 @@ +/** + * File: dialog-new-visualization.js + * Author: Markus Grigull + * 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 ( + this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}> +
+ + Name + this.handleChange(e)} /> + +
+
+ ); + } +} + +export default NewVisualzationDialog; diff --git a/src/components/dialog.js b/src/components/dialog.js new file mode 100644 index 0000000..a317a02 --- /dev/null +++ b/src/components/dialog.js @@ -0,0 +1,49 @@ +/** + * File: dialog.js + * Author: Markus Grigull + * 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 ( + + + {this.props.title} + + + + {this.props.children} + + + + + + + + ); + } +} + +export default Dialog; diff --git a/src/components/dropzone.js b/src/components/dropzone.js new file mode 100644 index 0000000..de2986d --- /dev/null +++ b/src/components/dropzone.js @@ -0,0 +1,51 @@ +/** + * File: dropzone.js + * Author: Markus Grigull + * 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( +
+ {this.props.children} +
+ ); + } +} + +export default DropTarget('widget', dropzoneTarget, collect)(Dropzone); diff --git a/src/components/menu-sidebar.js b/src/components/menu-sidebar.js index c6df652..b396169 100644 --- a/src/components/menu-sidebar.js +++ b/src/components/menu-sidebar.js @@ -21,6 +21,7 @@ class SidebarMenu extends Component {
  • Projects
  • Simulations
  • Simulators
  • +
  • Visualizations
  • ); diff --git a/src/components/table-control-link.js b/src/components/table-control-link.js new file mode 100644 index 0000000..4e9c87a --- /dev/null +++ b/src/components/table-control-link.js @@ -0,0 +1,75 @@ +/** + * File: table-control-link.js + * Author: Markus Grigull + * 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 ( + + + + {this.props.columns.map(column => ( + + ))} + + + + + {rows.map((row) => ( + + {row.filter((element, index) => { + return index !== 0; + }).map((cell, index) => ( + + ))} + + + ))} + +
    {column.title}
    + {index === 0 ? ( + {cell} + ) : ( + {cell} + )} + + + +
    + ); + } +} + +export default ControlLinkTable; diff --git a/src/components/table-control.js b/src/components/table-control.js index 9256a36..ea12ca8 100644 --- a/src/components/table-control.js +++ b/src/components/table-control.js @@ -1,5 +1,5 @@ /** - * File: table.js + * File: table-control.js * Author: Markus Grigull * Date: 02.03.2017 * Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC diff --git a/src/components/toolbox-item.js b/src/components/toolbox-item.js new file mode 100644 index 0000000..bedb46a --- /dev/null +++ b/src/components/toolbox-item.js @@ -0,0 +1,52 @@ +/** + * File: toolbox-item.js + * Author: Markus Grigull + * 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( + + {this.props.name} + + , {dropEffect}); + } +} + +export default DragSource('widget', toolboxItemSource, collect)(ToolboxItem); diff --git a/src/components/widget.js b/src/components/widget.js new file mode 100644 index 0000000..fa37ce3 --- /dev/null +++ b/src/components/widget.js @@ -0,0 +1,65 @@ +/** + * File: widget.js + * Author: Markus Grigull + * 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 ( + { 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} + > + {widget.name} + + ); + } else { + return ( +
    {widget.name}
    + ); + } + } +} + +export default Widget; diff --git a/src/containers/app.js b/src/containers/app.js index b1b8a57..37acdb1 100644 --- a/src/containers/app.js +++ b/src/containers/app.js @@ -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)); diff --git a/src/containers/simulators.js b/src/containers/simulators.js index d01ebf2..e3be874 100644 --- a/src/containers/simulators.js +++ b/src/containers/simulators.js @@ -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 {

    Simulators

    - + this.showEditModal(id)} onDelete={(id) => this.showDeleteModal(id)} /> - + - + this.closeNewModal(data)} /> - + this.closeEditModal(data)} simulator={this.state.modalSimulator} /> @@ -147,8 +127,8 @@ class Simulators extends Component { - - + +
    diff --git a/src/containers/visualization.js b/src/containers/visualization.js new file mode 100644 index 0000000..dc260f0 --- /dev/null +++ b/src/containers/visualization.js @@ -0,0 +1,96 @@ +/** + * File: visualization.js + * Author: Markus Grigull + * 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 ( +
    +

    {this.state.visualization.name}

    + +
    + {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) => ( + + ))} + +
    + ); + } +} + +export default Container.create(Visualization); diff --git a/src/containers/visualizations.js b/src/containers/visualizations.js new file mode 100644 index 0000000..1de8d9b --- /dev/null +++ b/src/containers/visualizations.js @@ -0,0 +1,136 @@ +/** + * File: visualizations.js + * Author: Markus Grigull + * 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 ( +
    +

    Visualizations

    + + this.showEditModal(id)} onDelete={(id) => this.showDeleteModal(id)} linkRoot="/visualizations"/> + + + + this.closeNewModal(data)} /> + + this.closeEditModal(data)} visualization={this.state.modalVisualization} /> + + + + Delete Visualization + + + + Are you sure you want to delete the visualization '{this.state.modalVisualization.name}'? + + + + + + + +
    + ); + } +} + +export default Container.create(Visualizations); diff --git a/src/data-managers/rest-data-manager.js b/src/data-managers/rest-data-manager.js new file mode 100644 index 0000000..87ef406 --- /dev/null +++ b/src/data-managers/rest-data-manager.js @@ -0,0 +1,82 @@ +/** + * File: data-manager.js + * Author: Markus Grigull + * 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; diff --git a/src/data-managers/simulators-data-manager.js b/src/data-managers/simulators-data-manager.js index d10a333..ee6e25c 100644 --- a/src/data-managers/simulators-data-manager.js +++ b/src/data-managers/simulators-data-manager.js @@ -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'); diff --git a/src/data-managers/visualizations-data-manager.js b/src/data-managers/visualizations-data-manager.js new file mode 100644 index 0000000..10210bc --- /dev/null +++ b/src/data-managers/visualizations-data-manager.js @@ -0,0 +1,12 @@ +/** + * File: visualizations-data-manager.js + * Author: Markus Grigull + * 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'); diff --git a/src/router.js b/src/router.js index fca3355..83230d4 100644 --- a/src/router.js +++ b/src/router.js @@ -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 { + + + ); diff --git a/src/stores/array-store.js b/src/stores/array-store.js new file mode 100644 index 0000000..41123e8 --- /dev/null +++ b/src/stores/array-store.js @@ -0,0 +1,93 @@ +/** + * File: array-store.js + * Author: Markus Grigull + * 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; diff --git a/src/stores/simulator-store.js b/src/stores/simulator-store.js index 83a4533..9413db7 100644 --- a/src/stores/simulator-store.js +++ b/src/stores/simulator-store.js @@ -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); diff --git a/src/stores/visualization-store.js b/src/stores/visualization-store.js new file mode 100644 index 0000000..5b5c83f --- /dev/null +++ b/src/stores/visualization-store.js @@ -0,0 +1,13 @@ +/** + * File: visualization-store.js + * Author: Markus Grigull + * 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); diff --git a/src/styles/app.css b/src/styles/app.css index 1230010..aa73469 100644 --- a/src/styles/app.css +++ b/src/styles/app.css @@ -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; +} diff --git a/src/styles/widgets.css b/src/styles/widgets.css new file mode 100644 index 0000000..29f86ca --- /dev/null +++ b/src/styles/widgets.css @@ -0,0 +1,17 @@ +/** + * File: widgets.css + * Author: Markus Grigull + * 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; +}