diff --git a/src/api/websocket-api.js b/src/api/websocket-api.js index 33f96b8..c5cad2c 100644 --- a/src/api/websocket-api.js +++ b/src/api/websocket-api.js @@ -8,13 +8,9 @@ **********************************************************************************/ class WebsocketAPI { - constructor() { - this.sockets = {}; - } - - addSocket(endpoint, identifier, callbacks) { + addSocket(endpoint, callbacks) { // create web socket client - var socket = new WebSocket('ws://' + endpoint, 'live'); + var socket = new WebSocket(this.getURL(endpoint), 'live'); socket.binaryType = 'arraybuffer'; // register callbacks @@ -23,13 +19,11 @@ class WebsocketAPI { if (callbacks.onMessage) socket.addEventListener('message', event => callbacks.onMessage(event)); if (callbacks.onError) socket.addEventListener('error', event => callbacks.onError(event)); - // save socket - this.sockets[identifier] = socket; + return socket; } - removeSocket(identifier) { - this.sockets[identifier].close(); - delete this.sockets[identifier]; + getURL(endpoint) { + return 'ws://' + endpoint; } } diff --git a/src/app-dispatcher.js b/src/app-dispatcher.js index 689348d..52ba2af 100644 --- a/src/app-dispatcher.js +++ b/src/app-dispatcher.js @@ -9,6 +9,20 @@ import { Dispatcher } from 'flux'; -const dispatcher: Dispatcher = new Dispatcher(); +class AppDispatcher extends Dispatcher { + dispatch(payload) { + if (this.isDispatching()) { + // try again later + var self = this; -export default dispatcher; + setTimeout(function() { + self.dispatch(payload); + }, 1); + } else { + // do actual dispatch + super.dispatch(payload); + } + } +} + +export default new AppDispatcher(); diff --git a/src/components/widget-value.js b/src/components/widget-value.js new file mode 100644 index 0000000..e016d0a --- /dev/null +++ b/src/components/widget-value.js @@ -0,0 +1,34 @@ +/** + * File: widget-value.js + * Author: Markus Grigull + * Date: 04.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 EditWidgetValueDialog from './dialog-edit-widget-value'; + +class WidgetValue extends Component { + render() { + // calculate value + var value = null; + const identifier = '58bfd9facd76830327c8b6d4'; + const signal = 2; + + if (this.props.data && this.props.data[identifier] && this.props.data[identifier].values) { + const signalArray = this.props.data[identifier].values[signal]; + value = signalArray[signalArray.length - 1].y; + } + + return ( +
+ {this.props.widget.name}: {value} +
+ ); + } +} + +export default WidgetValue; diff --git a/src/containers/app.js b/src/containers/app.js index 37acdb1..d5283ac 100644 --- a/src/containers/app.js +++ b/src/containers/app.js @@ -12,8 +12,9 @@ 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'; +import AppDispatcher from '../app-dispatcher'; +import SimulationStore from '../stores/simulation-store'; +import SimulatorStore from '../stores/simulator-store'; import Header from '../components/header'; import Footer from '../components/footer'; @@ -23,15 +24,68 @@ import '../styles/app.css'; class App extends Component { static getStores() { - return [ VillasStore ]; + return [ SimulationStore, SimulatorStore ]; } static calculateState() { return { - villas: VillasStore.getState() + simulators: SimulatorStore.getState(), + simulations: SimulationStore.getState() }; } + componentWillMount() { + // load all simulators and simulations to fetch simulation data + AppDispatcher.dispatch({ + type: 'simulators/start-load' + }); + + AppDispatcher.dispatch({ + type: 'simulations/start-load' + }); + } + + componentDidUpdate() { + if (this.state.simulators && this.state.simulations && this.state.simulations.length > 0) { + // get list of used simulators + var simulators = []; + + this.state.simulations.forEach((simulation) => { + // open connection to each simulator running a simulation model + simulation.models.forEach((simulationModel) => { + // add simulator to list if not already part of + const index = simulators.findIndex((element) => { + return element.simulator === simulationModel.simulator; + }); + + if (index === -1) { + simulators.push({ simulator: simulationModel.simulator, signals: simulationModel.length }); + } else { + if (simulators[index].length < simulationModel.length) { + simulators[index].length = simulationModel.length; + } + } + }); + }); + + // open connection to each simulator + this.state.simulators.forEach((simulator) => { + const index = simulators.findIndex((element) => { + return element.simulator === simulator._id; + }); + + if (index !== -1) { + AppDispatcher.dispatch({ + type: 'simulatorData/open', + identifier: simulator._id, + endpoint: simulator.endpoint, + signals: simulators[index].signals + }); + } + }); + } + } + render() { // get children var children = this.props.children; diff --git a/src/containers/project.js b/src/containers/project.js index 34804d7..910eebe 100644 --- a/src/containers/project.js +++ b/src/containers/project.js @@ -71,21 +71,6 @@ class Visualizations extends Component { } } - 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) => { @@ -94,7 +79,10 @@ class Visualizations extends Component { this.setState({ project: JSON.parse(JSON.stringify(project)) }); // load visualizations - this.loadVisualizations(project.visualizations); + AppDispatcher.dispatch({ + type: 'visualizations/start-load', + data: project.visualizations + }); } }); } diff --git a/src/containers/visualization.js b/src/containers/visualization.js index 9fd585f..31bf209 100644 --- a/src/containers/visualization.js +++ b/src/containers/visualization.js @@ -15,12 +15,15 @@ import { ContextMenu, MenuItem } from 'react-contextmenu'; import ToolboxItem from '../components/toolbox-item'; import Dropzone from '../components/dropzone'; import Widget from './widget'; + import VisualizationStore from '../stores/visualization-store'; +import ProjectStore from '../stores/project-store'; +import SimulationStore from '../stores/simulation-store'; import AppDispatcher from '../app-dispatcher'; class Visualization extends Component { static getStores() { - return [ VisualizationStore ]; + return [ VisualizationStore, ProjectStore, SimulationStore ]; } static calculateState(prevState) { @@ -38,11 +41,34 @@ class Visualization extends Component { visualizations: VisualizationStore.getState(), visualization: {}, + simulation: null, editing: false, grid: false } } + 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) { + // JSON.parse(JSON.stringify(obj)) = deep clone to make also copy of widget objects inside + this.setState({ visualization: JSON.parse(JSON.stringify(visualization)) }); + } + }); + } + handleDrop(item) { // add new widget var widget = { @@ -99,34 +125,6 @@ class Visualization extends Component { this.forceUpdate(); } - componentWillMount() { - AppDispatcher.dispatch({ - type: 'visualizations/start-load' - }); - - AppDispatcher.dispatch({ - type: 'simulatorData/open', - endpoint: 'localhost:5000', - identifier: 'RTDS' - }); - } - - 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) { - // JSON.parse(JSON.stringify(obj)) = deep clone to make also copy of widget objects inside - this.setState({ visualization: JSON.parse(JSON.stringify(visualization)) }); - } - }); - } - render() { return (
diff --git a/src/containers/widget.js b/src/containers/widget.js index b436075..c24a0a0 100644 --- a/src/containers/widget.js +++ b/src/containers/widget.js @@ -84,7 +84,7 @@ class Widget extends Component { } else { return (
- +
); } diff --git a/src/data-managers/simulator-data-manager.js b/src/data-managers/simulator-data-data-manager.js similarity index 56% rename from src/data-managers/simulator-data-manager.js rename to src/data-managers/simulator-data-data-manager.js index 049eda2..2dffa9a 100644 --- a/src/data-managers/simulator-data-manager.js +++ b/src/data-managers/simulator-data-data-manager.js @@ -1,5 +1,5 @@ /** - * File: simulator-data-manager.js + * File: simulator-data-data-manager.js * Author: Markus Grigull * Date: 03.03.2017 * Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC @@ -10,34 +10,47 @@ import WebsocketAPI from '../api/websocket-api'; import AppDispatcher from '../app-dispatcher'; -class SimulationDataManager { - open(endpoint, identifier) { - WebsocketAPI.addSocket(endpoint, identifier, { onOpen: event => this.onOpen(event), onClose: event => this.onClose(event), onMessage: event => this.onMessage(event) }); +class SimulatorDataDataManager { + constructor() { + this._sockets = {}; } - onOpen(event) { - // TODO: Add identifiers to callbacks + open(endpoint, identifier, signals) { + // pass signals to onOpen callback + if (this._sockets[identifier] != null) { + if (this._sockets[identifier].url !== WebsocketAPI.getURL(endpoint)) { + // replace connection, since endpoint changed + this._sockets.close(); + this._sockets[identifier] = WebsocketAPI.addSocket(endpoint, { onOpen: (event) => this.onOpen(event, identifier, signals), onClose: (event) => this.onClose(event, identifier), onMessage: (event) => this.onMessage(event, identifier) }); + } + } else { + this._sockets[identifier] = WebsocketAPI.addSocket(endpoint, { onOpen: (event) => this.onOpen(event, identifier, signals), onClose: (event) => this.onClose(event, identifier), onMessage: (event) => this.onMessage(event, identifier) }); + } + } + + onOpen(event, identifier, signals) { AppDispatcher.dispatch({ type: 'simulatorData/opened', - identifier: 'RTDS', - signals: 8 + identifier: identifier, + signals: signals }); } - onClose(event) { + onClose(event, identifier) { AppDispatcher.dispatch({ - type: 'simulatorData/closed' + type: 'simulatorData/closed', + identifier: identifier }); } - onMessage(event) { + onMessage(event, identifier) { var message = this.bufferToMessage(event.data); AppDispatcher.dispatch({ type: 'simulatorData/data-changed', data: message, - identifier: 'RTDS' + identifier: identifier }); } @@ -69,4 +82,4 @@ class SimulationDataManager { } } -export default new SimulationDataManager(); +export default new SimulatorDataDataManager(); diff --git a/src/stores/simulator-data-store.js b/src/stores/simulator-data-store.js index 38b69a3..c14644f 100644 --- a/src/stores/simulator-data-store.js +++ b/src/stores/simulator-data-store.js @@ -10,7 +10,7 @@ import { ReduceStore } from 'flux/utils'; import AppDispatcher from '../app-dispatcher'; -import SimulatorDataManager from '../data-managers/simulator-data-manager'; +import SimulatorDataDataManager from '../data-managers/simulator-data-data-manager'; const MAX_VALUES = 10000; @@ -28,7 +28,7 @@ class SimulationDataStore extends ReduceStore { switch (action.type) { case 'simulatorData/open': - SimulatorDataManager.open(action.endpoint, action.identifier); + SimulatorDataDataManager.open(action.endpoint, action.identifier, action.signals); return state; case 'simulatorData/opened': @@ -63,6 +63,11 @@ class SimulationDataStore extends ReduceStore { return state; case 'simulatorData/closed': + // close and delete socket + state[action.identifier].close(); + state[action.identifier] = null; + + this.__emitChange(); return state; default: