/** * File: visualization.js * Author: Markus Grigull * Date: 02.03.2017 * * This file is part of VILLASweb. * * VILLASweb is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * VILLASweb is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with VILLASweb. If not, see . ******************************************************************************/ import React, { Component } from 'react'; import { Container } from 'flux/utils'; import { Button } from 'react-bootstrap'; import { ContextMenu, MenuItem } from 'react-contextmenu'; import WidgetFactory from '../components/widget-factory'; import ToolboxItem from '../components/toolbox-item'; import Dropzone from '../components/dropzone'; import Widget from './widget'; import EditWidget from '../components/dialog/edit-widget'; import VisualizationStore from '../stores/visualization-store'; import ProjectStore from '../stores/project-store'; import SimulationStore from '../stores/simulation-store'; import FileStore from '../stores/file-store'; import AppDispatcher from '../app-dispatcher'; import NotificationsDataManager from '../data-managers/notifications-data-manager'; import NotificationsFactory from '../data-managers/notifications-factory'; class Visualization extends Component { static getStores() { return [ VisualizationStore, ProjectStore, SimulationStore, FileStore ]; } static calculateState(prevState) { if (prevState == null) { prevState = {}; } return { visualizations: VisualizationStore.getState(), projects: ProjectStore.getState(), simulations: SimulationStore.getState(), files: FileStore.getState(), visualization: prevState.visualization || {}, project: prevState.project || null, simulation: prevState.simulation || null, editing: prevState.editing || false, grid: prevState.grid || false, editModal: prevState.editModal || false, modalData: prevState.modalData || null, modalIndex: prevState.modalIndex || null, maxWidgetHeight: prevState.maxWidgetHeight || 0, dropZoneHeight: prevState.dropZoneHeight || 0, last_widget_key: prevState.last_widget_key || 0 }; } componentWillMount() { AppDispatcher.dispatch({ type: 'visualizations/start-load' }); } componentDidUpdate() { if (this.state.visualization._id !== this.props.params.visualization) { this.reloadVisualization(); } // load depending project if (this.state.project == null && this.state.projects) { this.state.projects.forEach((project) => { if (project._id === this.state.visualization.project) { this.setState({ project: project, simulation: null }); AppDispatcher.dispatch({ type: 'simulations/start-load', data: project.simulation }); } }); } // load depending simulation if (this.state.simulation == null && this.state.simulations && this.state.project) { this.state.simulations.forEach((simulation) => { if (simulation._id === this.state.project.simulation) { this.setState({ simulation: simulation }); } }); } } getNewWidgetKey() { // Increase the counter and update the state return this.state.last_widget_key++; } transformToWidgetsDict(widgets) { var widgetsDict = {}; // Create a new key and make a copy of the widget object widgets.forEach( (widget) => widgetsDict[this.getNewWidgetKey()] = Object.assign({}, widget) ); return widgetsDict; } transformToWidgetsList(widgets) { return Object.keys(widgets).map( (key) => widgets[key]); } reloadVisualization() { // select visualization by param id this.state.visualizations.forEach((tempVisualization) => { if (tempVisualization._id === this.props.params.visualization) { // convert widgets list to a dictionary var visualization = Object.assign({}, tempVisualization, { widgets: tempVisualization.widgets? this.transformToWidgetsDict(tempVisualization.widgets) : {} }); this.computeHeightWithWidgets(visualization.widgets); this.setState({ visualization: visualization, project: null }); AppDispatcher.dispatch({ type: 'projects/start-load', data: visualization.project }); } }); } handleDrop(item, position) { let widget = null; let defaultSimulator = null; if (this.state.simulation.models && this.state.simulation.models.length === 0) { NotificationsDataManager.addNotification(NotificationsFactory.NO_SIM_MODEL_AVAILABLE); } else { defaultSimulator = this.state.simulation.models[0].simulator; } // create new widget widget = WidgetFactory.createWidgetOfType(item.name, position, defaultSimulator); var new_widgets = this.state.visualization.widgets; var widget_key = this.getNewWidgetKey(); new_widgets[widget_key] = widget; var visualization = Object.assign({}, this.state.visualization, { widgets: new_widgets }); this.increaseHeightWithWidget(widget); this.setState({ visualization: visualization }); } widgetStatusChange(updated_widget, key) { // Widget changed internally, make changes effective then save them this.widgetChange(updated_widget, key, this.saveChanges); } widgetChange(updated_widget, key, callback = null) { var widgets_update = {}; widgets_update[key] = updated_widget; var new_widgets = Object.assign({}, this.state.visualization.widgets, widgets_update); var visualization = Object.assign({}, this.state.visualization, { widgets: new_widgets }); // Check if the height needs to be increased, the section may have shrunk if not if (!this.increaseHeightWithWidget(updated_widget)) { this.computeHeightWithWidgets(visualization.widgets); } this.setState({ visualization: visualization }, callback); } /* * Set the initial height state based on the existing widgets */ computeHeightWithWidgets(widgets) { // Compute max height from widgets let maxHeight = Object.keys(widgets).reduce( (maxHeightSoFar, widgetKey) => { let thisWidget = widgets[widgetKey]; let thisWidgetHeight = thisWidget.y + thisWidget.height; return thisWidgetHeight > maxHeightSoFar? thisWidgetHeight : maxHeightSoFar; }, 0); this.setState({ maxWidgetHeight: maxHeight, dropZoneHeight: maxHeight + 40 }); } /* * Adapt the area's height with the position of the new widget. * Return true if the height increased, otherwise false. */ increaseHeightWithWidget(widget) { let increased = false; let thisWidgetHeight = widget.y + widget.height; if (thisWidgetHeight > this.state.maxWidgetHeight) { increased = true; this.setState({ maxWidgetHeight: thisWidgetHeight, dropZoneHeight: thisWidgetHeight + 40 }); } return increased; } editWidget(e, data) { this.setState({ editModal: true, modalData: this.state.visualization.widgets[data.key], modalIndex: data.key }); } closeEdit(data) { if (data) { // save changes temporarily var widgets_update = {}; widgets_update[this.state.modalIndex] = data; var new_widgets = Object.assign({}, this.state.visualization.widgets, widgets_update); var visualization = Object.assign({}, this.state.visualization, { widgets: new_widgets }); this.setState({ editModal: false, visualization: visualization }); } else { this.setState({ editModal: false }); } } deleteWidget(e, data) { delete this.state.visualization.widgets[data.key]; var visualization = Object.assign({}, this.state.visualization, { widgets: this.state.visualization.widgets }); this.setState({ visualization: visualization }); } stopEditing() { // Provide the callback so it can be called when state change is applied this.setState({ editing: false }, this.saveChanges ); } saveChanges() { // Transform to a list var visualization = Object.assign({}, this.state.visualization, { widgets: this.transformToWidgetsList(this.state.visualization.widgets) }); AppDispatcher.dispatch({ type: 'visualizations/start-edit', data: visualization }); } discardChanges() { this.setState({ editing: false, visualization: {} }); this.reloadVisualization(); } moveWidget(e, data, applyDirection) { var widget = this.state.visualization.widgets[data.key]; var updated_widgets = {}; updated_widgets[data.key] = applyDirection(widget); var new_widgets = Object.assign({}, this.state.visualization.widgets, updated_widgets); var visualization = Object.assign({}, this.state.visualization, { widgets: new_widgets }); this.setState({ visualization: visualization }); } moveAbove(widget) { // increase z-Order widget.z++; return widget; } moveToFront(widget) { // increase z-Order widget.z = 100; return widget; } moveUnderneath(widget) { // decrease z-Order widget.z--; if (widget.z < 0) { widget.z = 0; } return widget; } moveToBack(widget) { // increase z-Order widget.z = 0; return widget; } render() { var current_widgets = this.state.visualization.widgets; return (
{this.state.visualization.name}
{this.state.editing ? (
) : (
)}
e.preventDefault() }> {this.state.editing &&
} this.handleDrop(item, position)} editing={this.state.editing}> {current_widgets != null && Object.keys(current_widgets).map( (widget_key) => ( this.widgetChange(w, k)} onWidgetStatusChange={(w, k) => this.widgetStatusChange(w, k)} editing={this.state.editing} index={widget_key} grid={this.state.grid} /> ))} {current_widgets != null && Object.keys(current_widgets).map( (widget_key) => ( this.editWidget(e, data)}>Edit this.deleteWidget(e, data)}>Delete this.moveWidget(e, data, this.moveAbove)}>Move above this.moveWidget(e, data, this.moveToFront)}>Move to front this.moveWidget(e, data, this.moveUnderneath)}>Move underneath this.moveWidget(e, data, this.moveToBack)}>Move to back ))} this.closeEdit(data)} widget={this.state.modalData} simulation={this.state.simulation} files={this.state.files} />
); } } export default Container.create(Visualization);