diff --git a/src/components/dialog/dialog.js b/src/components/dialog/dialog.js index a317a02..e3c774d 100644 --- a/src/components/dialog/dialog.js +++ b/src/components/dialog/dialog.js @@ -39,7 +39,7 @@ class Dialog extends Component { - + ); diff --git a/src/components/dialog/edit-project.js b/src/components/dialog/edit-project.js new file mode 100644 index 0000000..b4facfb --- /dev/null +++ b/src/components/dialog/edit-project.js @@ -0,0 +1,94 @@ +/** + * File: edit-project.js + * Author: Markus Grigull + * Date: 07.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 EditProjectDialog extends Component { + static propTypes = { + show: PropTypes.bool.isRequired, + onClose: PropTypes.func.isRequired, + project: PropTypes.object.isRequired, + simulations: PropTypes.array.isRequired + }; + + valid: true; + + constructor(props) { + super(props); + + this.state = { + name: '', + simulation: '', + _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.project.name, + simulation: this.props.project.simulation, + _id: this.props.project._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)} /> + + + + Simulation + this.handleChange(e)}> + {this.props.simulations.map(simulation => ( + + ))} + + +
+
+ ); + } +} + +export default EditProjectDialog; diff --git a/src/components/dialog/new-project.js b/src/components/dialog/new-project.js new file mode 100644 index 0000000..6abc0c8 --- /dev/null +++ b/src/components/dialog/new-project.js @@ -0,0 +1,86 @@ +/** + * File: new-project.js + * Author: Markus Grigull + * Date: 07.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 NewProjectDialog extends Component { + static propTypes = { + show: PropTypes.bool.isRequired, + onClose: PropTypes.func.isRequired, + simulations: PropTypes.array.isRequired + }; + + valid: false; + + constructor(props) { + super(props); + + this.state = { + name: '', + simulation: '' + }; + } + + 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: '', simulation: this.props.simulations[0]._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"; + } + + render() { + return ( + this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}> +
+ + Name + this.handleChange(e)} /> + + + + Simulation + this.handleChange(e)}> + {this.props.simulations.map(simulation => ( + + ))} + + +
+
+ ); + } +} + +export default NewProjectDialog; diff --git a/src/components/dialog/new-simulation-model.js b/src/components/dialog/new-simulation-model.js index 005c928..cb3ca63 100644 --- a/src/components/dialog/new-simulation-model.js +++ b/src/components/dialog/new-simulation-model.js @@ -72,7 +72,7 @@ class NewSimulationModelDialog extends Component { } resetState() { - this.setState({ name: '', simulator: '', length: '1', mapping: [ { name: 'Signal', type: 'Type' } ] }); + this.setState({ name: '', simulator: this.props.simulators[0]._id, length: '1', mapping: [ { name: 'Signal', type: 'Type' } ] }); } validateForm(target) { diff --git a/src/components/menu-sidebar.js b/src/components/menu-sidebar.js index 509f33d..afe51a6 100644 --- a/src/components/menu-sidebar.js +++ b/src/components/menu-sidebar.js @@ -21,7 +21,6 @@ class SidebarMenu extends Component {
  • Projects
  • Simulations
  • Simulators
  • -
  • Visualizations
  • ); diff --git a/src/containers/visualizations.js b/src/containers/project.js similarity index 55% rename from src/containers/visualizations.js rename to src/containers/project.js index 782fdd1..34804d7 100644 --- a/src/containers/visualizations.js +++ b/src/containers/project.js @@ -1,5 +1,5 @@ /** - * File: visualizations.js + * File: project.js * Author: Markus Grigull * Date: 03.03.2017 * Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC @@ -12,6 +12,7 @@ import { Container } from 'flux/utils'; import { Button, Modal, Glyphicon } from 'react-bootstrap'; import AppDispatcher from '../app-dispatcher'; +import ProjectStore from '../stores/project-store'; import VisualizationStore from '../stores/visualization-store'; import Table from '../components/table'; @@ -21,35 +22,95 @@ import EditVisualizationDialog from '../components/dialog/edit-visualization'; class Visualizations extends Component { static getStores() { - return [ VisualizationStore ]; + return [ ProjectStore, VisualizationStore ]; } - static calculateState() { - return { - visualizations: VisualizationStore.getState(), + static calculateState(prevState) { + if (prevState) { + return { + projects: ProjectStore.getState(), + visualizations: VisualizationStore.getState(), - newModal: false, - deleteModal: false, - editModal: false, - modalData: {} - }; + newModal: prevState.newModal, + deleteModal: prevState.deleteModal, + editModal: prevState.editModal, + modalData: prevState.modalData, + + project: prevState.project, + reload: prevState.reload + }; + } else { + return { + projects: ProjectStore.getState(), + visualizations: VisualizationStore.getState(), + + newModal: false, + deleteModal: false, + editModal: false, + modalData: {}, + + project: {}, + reload: false + }; + } } componentWillMount() { AppDispatcher.dispatch({ - type: 'visualizations/start-load' + type: 'projects/start-load' + }); + } + + componentDidUpdate() { + if (this.state.project._id !== this.props.params.project /*|| this.state.reload*/) { + this.reloadProject(); + + if (this.state.reload) { + this.setState({ reload: false }); + } + } + } + + loadVisualizations(ids) { + if (AppDispatcher.isDispatching()) { + // try again later + var self = this; + setTimeout(function() { + self.loadVisualizations(ids); + }, 1); + } else { + AppDispatcher.dispatch({ + type: 'visualizations/start-load', + data: ids + }); + } + } + + reloadProject() { + // select project by param id + this.state.projects.forEach((project) => { + if (project._id === this.props.params.project) { + // JSON.parse(JSON.stringify(obj)) = deep clone to make also copy of widget objects inside + this.setState({ project: JSON.parse(JSON.stringify(project)) }); + + // load visualizations + this.loadVisualizations(project.visualizations); + } }); } closeNewModal(data) { - this.setState({ newModal : false }); - if (data) { + // add project to visualization + data.project = this.state.project._id; + AppDispatcher.dispatch({ type: 'visualizations/start-add', data: data }); } + + this.setState({ newModal: false, reload: data != null }); } confirmDeleteModal() { @@ -73,11 +134,24 @@ class Visualizations extends Component { } render() { + // get visualizations for this project + var visualizations = []; + + if (this.state.visualizations && this.state.project.visualizations) { + this.state.visualizations.forEach((visualization) => { + this.state.project.visualizations.forEach((id) => { + if (visualization._id === id) { + visualizations.push(visualization); + } + }); + }); + } + return (
    -

    Visualizations

    +

    {this.state.project.name}

    - +
    this.setState({ editModal: true, modalData: this.state.visualizations[index] })} onDelete={index => this.setState({ deleteModal: true, modalData: this.state.visualizations[index] })} />
    diff --git a/src/containers/projects.js b/src/containers/projects.js index 825ca7f..4e6a4b6 100644 --- a/src/containers/projects.js +++ b/src/containers/projects.js @@ -9,26 +9,116 @@ import React, { Component } from 'react'; import { Container } from 'flux/utils'; +import { Button, Modal, Glyphicon } from 'react-bootstrap'; -// import AppDispatcher from '../app-dispatcher'; -import VillasStore from '../stores/villas-store'; +import AppDispatcher from '../app-dispatcher'; +import ProjectStore from '../stores/project-store'; +import SimulationStore from '../stores/simulation-store'; + +import Table from '../components/table'; +import TableColumn from '../components/table-column'; +import NewProjectDialog from '../components/dialog/new-project'; +import EditProjectDialog from '../components/dialog/edit-project'; class Projects extends Component { static getStores() { - return [ VillasStore ]; + return [ ProjectStore, SimulationStore ]; } static calculateState() { return { - villas: VillasStore.getState() + projects: ProjectStore.getState(), + simulations: SimulationStore.getState(), + + newModal: false, + editModal: false, + deleteModal: false, + modalData: {} }; } + componentWillMount() { + AppDispatcher.dispatch({ + type: 'projects/start-load' + }); + + AppDispatcher.dispatch({ + type: 'simulations/start-load' + }); + } + + closeNewModal(data) { + this.setState({ newModal: false }); + + if (data) { + AppDispatcher.dispatch({ + type: 'projects/start-add', + data: data + }); + } + } + + confirmDeleteModal() { + this.setState({ deleteModal: false }); + + AppDispatcher.dispatch({ + type: 'projects/start-remove', + data: this.state.modalData + }); + } + + closeEditModal(data) { + this.setState({ editModal: false }); + + if (data) { + AppDispatcher.dispatch({ + type: 'projects/start-edit', + data: data + }); + } + } + + getSimulationName(id) { + for (var i = 0; i < this.state.simulations.length; i++) { + if (this.state.simulations[i]._id === id) { + return this.state.simulations[i].name; + } + } + + return id; + } + render() { return (

    Projects

    + + + this.getSimulationName(id)} /> + this.setState({ editModal: true, modalData: this.state.projects[index] })} onDelete={index => this.setState({ deleteModal: true, modalData: this.state.projects[index] })} /> +
    + + + + this.closeNewModal(data)} simulations={this.state.simulations} /> + + this.closeEditModal(data)} project={this.state.modalData} simulations={this.state.simulations} /> + + + + Delete Project + + + + Are you sure you want to delete the project '{this.state.modalData.name}'? + + + + + + +
    ); } diff --git a/src/data-managers/projects-data-manager.js b/src/data-managers/projects-data-manager.js new file mode 100644 index 0000000..a85801d --- /dev/null +++ b/src/data-managers/projects-data-manager.js @@ -0,0 +1,12 @@ +/** + * File: projects-data-manager.js + * Author: Markus Grigull + * Date: 07.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('project', '/projects'); diff --git a/src/data-managers/rest-data-manager.js b/src/data-managers/rest-data-manager.js index db92935..0b5d178 100644 --- a/src/data-managers/rest-data-manager.js +++ b/src/data-managers/rest-data-manager.js @@ -16,18 +16,34 @@ class RestDataManager { this.type = type; } - load() { - RestAPI.get(this.url).then(response => { - AppDispatcher.dispatch({ - type: this.type + 's/loaded', - data: response[this.type + 's'] + load(id) { + if (id != null) { + // load single object + RestAPI.get(this.url + '/' + id).then(response => { + AppDispatcher.dispatch({ + type: this.type + 's/loaded', + data: response[this.type] + }); + }).catch(error => { + AppDispatcher.dispatch({ + type: this.type + 's/load-error', + error: error + }); }); - }).catch(error => { - AppDispatcher.dispatch({ - type: this.type + 's/load-error', - error: error + } else { + // load all objects + 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) { diff --git a/src/data-managers/simulations-data-manager.js b/src/data-managers/simulations-data-manager.js index 4a9d0bf..d5a606e 100644 --- a/src/data-managers/simulations-data-manager.js +++ b/src/data-managers/simulations-data-manager.js @@ -7,6 +7,6 @@ * Unauthorized copying of this file, via any medium is strictly prohibited. **********************************************************************************/ - import RestDataManager from './rest-data-manager'; +import RestDataManager from './rest-data-manager'; - export default new RestDataManager('simulation', '/simulations'); +export default new RestDataManager('simulation', '/simulations'); diff --git a/src/router.js b/src/router.js index 5689e6e..49ed410 100644 --- a/src/router.js +++ b/src/router.js @@ -13,9 +13,9 @@ import { Router, Route, hashHistory } from 'react-router'; import App from './containers/app'; import Home from './containers/home'; import Projects from './containers/projects'; +import Project from './containers/project'; import Simulators from './containers/simulators'; import Visualization from './containers/visualization'; -import Visualizations from './containers/visualizations'; import Simulations from './containers/simulations'; import Simulation from './containers/simulation'; @@ -25,10 +25,12 @@ class Root extends Component { + + + - diff --git a/src/stores/array-store.js b/src/stores/array-store.js index 8f2179d..b7c166a 100644 --- a/src/stores/array-store.js +++ b/src/stores/array-store.js @@ -23,16 +23,46 @@ class ArrayStore extends ReduceStore { return []; } - reduce(state, action) { - var array; + updateElements(state, newElements) { + // search for existing element to update + state.forEach((element, index, array) => { + newElements.forEach((updateElement, index) => { + if (element._id === updateElement._id) { + array[index] = element; + // remove updated element from update list + newElements.splice(index, 1); + } + }); + }); + + // all elements still in the list will just be added + state = state.concat(newElements); + + // announce change to listeners + this.__emitChange(); + + return state; + } + + reduce(state, action) { switch (action.type) { case this.type + '/start-load': - this.dataManager.load(); + if (Array.isArray(action.data)) { + action.data.forEach((id) => { + this.dataManager.load(id); + }); + } else { + this.dataManager.load(action.data); + } return state; case this.type + '/loaded': - return action.data; + if (Array.isArray(action.data)) { + return this.updateElements(state, action.data); + } else { + return this.updateElements(state, [action.data]); + } case this.type + '/load-error': // TODO: Add error message @@ -43,11 +73,7 @@ class ArrayStore extends ReduceStore { return state; case this.type + '/added': - // signal array change since its not automatically detected - state.push(action.data); - this.__emitChange(); - - return state; + return this.updateElements(state, [action.data]); case this.type + '/add-error': // TODO: Add error message @@ -71,14 +97,7 @@ class ArrayStore extends ReduceStore { 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; + return this.updateElements(state, [action.data]); case this.type + '/edit-error': // TODO: Add error message diff --git a/src/stores/project-store.js b/src/stores/project-store.js new file mode 100644 index 0000000..405820f --- /dev/null +++ b/src/stores/project-store.js @@ -0,0 +1,13 @@ +/** + * File: project-store.js + * Author: Markus Grigull + * Date: 07.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 ProjectsDataManager from '../data-managers/projects-data-manager'; + +export default new ArrayStore('projects', ProjectsDataManager);