Commit 8066cbb1 authored by Sonja Happ's avatar Sonja Happ
Browse files

merge example-dashboard

parents cfb6cae6 95c7a865
This diff is collapsed.
......@@ -3,51 +3,54 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.19",
"@fortawesome/free-solid-svg-icons": "^5.9.0",
"@fortawesome/react-fontawesome": "^0.1.4",
"@fortawesome/fontawesome-svg-core": "^1.2.26",
"@fortawesome/free-solid-svg-icons": "^5.12.0",
"@fortawesome/react-fontawesome": "^0.1.8",
"babel-runtime": "^6.26.0",
"bootstrap": "^4.3.1",
"bootstrap": "^4.4.1",
"classnames": "^2.2.6",
"d3-array": "^2.2.0",
"d3-array": "^2.4.0",
"d3-axis": "^1.0.12",
"d3-scale": "^3.0.0",
"d3-scale-chromatic": "^1.3.3",
"d3-selection": "^1.4.0",
"d3-shape": "^1.3.5",
"d3-time-format": "^2.1.3",
"d3-scale": "^3.2.1",
"d3-scale-chromatic": "^1.5.0",
"d3-selection": "^1.4.1",
"d3-shape": "^1.3.7",
"d3-time-format": "^2.2.3",
"es6-promise": "^4.2.8",
"fibers": "^4.0.2",
"file-saver": "^2.0.2",
"flux": "^3.1.3",
"frontend-collective-react-dnd-scrollzone": "^1.0.2",
"gaugeJS": "^1.3.7",
"handlebars": "^4.5.1",
"handlebars": "^4.7.1",
"immutable": "^4.0.0-rc.12",
"jquery": "^3.4.1",
"jszip": "^3.2.2",
"libcimsvg": "git+https://git.rwth-aachen.de/acs/public/cim/pintura-npm-package.git",
"lodash": "^4.17.15",
"node-sass": "^4.13.0",
"prop-types": "^15.7.2",
"rc-slider": "^8.6.13",
"react": "^16.8.6",
"react-bootstrap": "^1.0.0-beta.9",
"rc-slider": "^8.7.1",
"react": "^16.12.0",
"react-bootstrap": "^1.0.0-beta.16",
"react-contexify": "^4.1.1",
"react-d3": "^0.4.0",
"react-dnd": "^9.3.2",
"react-dnd-html5-backend": "^9.3.2",
"react-dom": "^16.8.6",
"react-dnd": "^9.5.1",
"react-dnd-html5-backend": "^9.5.1",
"react-dom": "^16.12.0",
"react-fullscreenable": "^2.5.1-0",
"react-grid-system": "^4.4.10",
"react-grid-system": "^4.4.11",
"react-json-view": "^1.19.1",
"react-notification-system": "^0.2.17",
"react-rnd": "^10.0.0",
"react-router": "^5.0.1",
"react-router-dom": "^5.0.1",
"react-scripts": "^3.0.1",
"react-sortable-tree": "^2.6.2",
"react-svg-pan-zoom": "^3.1.0",
"superagent": "^5.1.0",
"typescript": "^3.5.3",
"react-rnd": "^10.1.4",
"react-router": "^5.1.2",
"react-router-dom": "^5.1.2",
"react-scripts": "^3.3.0",
"react-sortable-tree": "^2.7.1",
"react-svg-pan-zoom": "^3.8.0",
"sass": "^1.24.4",
"superagent": "^5.2.1",
"typescript": "^3.7.4",
"validator": "^11.1.0"
},
"devDependencies": {
......
......@@ -31,7 +31,7 @@ import { Hidden } from 'react-grid-system'
import AppDispatcher from './common/app-dispatcher';
import ScenarioStore from './scenario/scenario-store';
import SimulatorStore from './simulator/simulator-store';
import UserStore from './user/user-store';
import LoginStore from './user/login-store';
import NotificationsDataManager from './common/data-managers/notifications-data-manager';
import Home from './common/home';
......@@ -40,73 +40,60 @@ import Footer from './common/footer';
import SidebarMenu from './common/menu-sidebar';
import HeaderMenu from './common/header-menu';
//import Projects from './project/projects';
//import Project from './project/project';
import Simulators from './simulator/simulators';
import Dashboard from './dashboard/dashboard';
//import Simulations from './simulation/simulations';
//import Simulation from './simulation/simulation';
import Scenarios from './scenario/scenarios';
import Scenario from './scenario/scenario';
import SimulationModel from './simulationmodel/simulation-model';
import Users from './user/users';
import User from './user/user';
import FluxContainerConverter from "./common/FluxContainerConverter";
import './styles/app.css';
class App extends React.Component {
static getStores() {
return [ SimulatorStore, UserStore, ScenarioStore];
return [ SimulatorStore, LoginStore, ScenarioStore];
}
static calculateState(prevState) {
let currentUser = UserStore.getState().currentUser;
return {
simulators: SimulatorStore.getState(),
scenarios: ScenarioStore.getState(),
currentRole: currentUser ? currentUser.role : '',
currentUsername: currentUser ? currentUser.username: '',
currentUserID: UserStore.getState().userid,
token: UserStore.getState().token,
currentUser: LoginStore.getState().currentUser,
token: LoginStore.getState().token,
showSidebarMenu: false,
};
}
componentWillMount() {
// if token stored locally, request user
const token = localStorage.getItem('token');
const userid = localStorage.getItem('userid');
componentDidMount() {
NotificationsDataManager.setSystem(this.refs.notificationSystem);
// if token stored locally, request user
let token = localStorage.getItem("token");
let currentUser = JSON.parse(localStorage.getItem("currentUser"));
if (token != null && token !== '') {
// save token so we dont logout
this.setState({ token });
AppDispatcher.dispatch({
type: 'users/logged-in',
token: token,
userid: userid
currentUser: currentUser
});
}
}
componentDidMount() {
// load all simulators and scenarios to fetch data
AppDispatcher.dispatch({
type: 'simulators/start-load',
token: this.state.token
});
// AppDispatcher.dispatch({
// type: 'simulators/start-load',
// token: this.state.token
// });
//
// AppDispatcher.dispatch({
// type: 'scenarios/start-load',
// token: this.state.token
// });
AppDispatcher.dispatch({
type: 'scenarios/start-load',
token: this.state.token
});
NotificationsDataManager.setSystem(this.refs.notificationSystem);
}
showSidebarMenu = () => {
......@@ -130,7 +117,7 @@ class App extends React.Component {
*/}
<Hidden sm md lg xl>
<Col style={{ width: this.state.showSidebarMenu ? '280px' : '0px' }} className="sidenav">
<HeaderMenu onClose={this.hideSidebarMenu} currentRole={this.state.currentRole} />
<HeaderMenu onClose={this.hideSidebarMenu} currentRole={this.state.currentUser.role} />
</Col>
</Hidden>
......@@ -141,7 +128,7 @@ class App extends React.Component {
<div className={`app-body app-body-spacing`} >
<Col xs={false}>
<SidebarMenu currentRole={this.state.currentRole} />
<SidebarMenu currentRole={this.state.currentUser.role} />
</Col>
<div className={`app-content app-content-margin-left`}>
......@@ -171,5 +158,6 @@ class App extends React.Component {
//<Route exact path="/simulations" component={Simulations} />
//<Route path="/simulations/:simulation" component={Simulation} />
export default Container.create(FluxContainerConverter.convert(App));
let fluxContainerConverter = require('./common/FluxContainerConverter');
export default Container.create(fluxContainerConverter.convert(App));
//DragDropContext(HTML5Backend)(Container.create(App));
/// FluxContainerConverter.js
/// This is an ugly workaround found here https://github.com/facebook/flux/issues/351 to make Flux Containers work with ES6
export default {
module.exports = {
convert: function(containerClass) {
const tmp = containerClass;
containerClass = function(...args) {
......
......@@ -37,7 +37,7 @@ const REQUEST_TIMEOUT_NOTIFICATION = {
level: 'error'
};
// Check if the error was due to network failure, timeouts, etc.
// Check if the error was due to network failure, timeouts, etc.
// Can be used for the rest of requests
function isNetworkError(err) {
let result = false;
......@@ -45,7 +45,7 @@ function isNetworkError(err) {
// If not status nor response fields, it is a network error. TODO: Handle timeouts
if (err.status == null || err.response == null) {
result = true;
let notification = err.timeout? REQUEST_TIMEOUT_NOTIFICATION : SERVER_NOT_REACHABLE_NOTIFICATION;
NotificationsDataManager.addNotification(notification);
}
......@@ -79,7 +79,7 @@ class RestAPI {
if (token != null) {
req.set('Authorization', "Bearer " + token);
}
req.end(function (error, res) {
if (res == null || res.status !== 200) {
......
......@@ -68,7 +68,6 @@ class ArrayStore extends ReduceStore {
reduce(state, action) {
switch (action.type) {
case this.type + '/start-load':
if (Array.isArray(action.data)) {
action.data.forEach((id) => {
this.dataManager.load(id, action.token,action.param);
......@@ -87,17 +86,17 @@ class ArrayStore extends ReduceStore {
case this.type + '/load-error':
if (action.error && !action.error.handled && action.error.response) {
const USER_LOAD_ERROR_NOTIFICATION = {
title: 'Failed to load',
message: action.error.response.body.message,
level: 'error'
};
NotificationsDataManager.addNotification(USER_LOAD_ERROR_NOTIFICATION);
}
return super.reduce(state, action);
case this.type + '/start-add':
this.dataManager.add(action.data, action.token,action.param);
return state;
......@@ -106,7 +105,7 @@ class ArrayStore extends ReduceStore {
return this.updateElements(state, [action.data]);
case this.type + '/add-error':
return state;
......@@ -128,30 +127,17 @@ class ArrayStore extends ReduceStore {
level: 'error'
};
NotificationsDataManager.addNotification(USER_REMOVE_ERROR_NOTIFICATION);
}
return super.reduce(state, action);
case this.type + '/start-edit':
this.dataManager.update(action.data, action.token,action.param);
return state;
case this.type + '/start-own-edit':
case this.type + '/start-edit':
this.dataManager.update(action.data, action.token,action.param);
return state;
case this.type + '/edited':
return this.updateElements(state, [action.data]);
case this.type + '/confirm-pw-doesnt-match':
const USER_PW_ERROR_NOTIFICATION = {
title: 'The new password does not match',
message: 'Try again',
level: 'error'
};
NotificationsDataManager.addNotification(USER_PW_ERROR_NOTIFICATION);
return state;
case this.type + '/edit-error':
return state;
......
......@@ -65,10 +65,10 @@ class RestDataManager {
}
else{
if(id != null){
return this.makeURL(this.url + '/' + id + '?' + param);
return this.makeURL(this.url + '/' + id + param);
}
else {
return this.makeURL(this.url + '?' + param);
return this.makeURL(this.url + param)
}
}
case 'remove/update':
......@@ -76,7 +76,7 @@ class RestDataManager {
return this.makeURL(this.url + '/' + object.id);
}
else{
return this.makeURL(this.url + '/' + object.id + '?' + param);
return this.makeURL(this.url + '/' + object.id + param);
}
default:
console.log("something went wrong");
......
......@@ -36,8 +36,10 @@ class EditableHeader extends React.Component {
};
}
componentWillReceiveProps(nextProps) {
this.setState({ title: nextProps.title });
static getDerivedStateFromProps(props, state){
return {
title: props.title
};
}
edit = () => {
......
......@@ -26,18 +26,15 @@ import React from 'react';
//import RestAPI from '../api/rest-api';
import config from '../config';
import UserStore from "../user/user-store";
import LoginStore from "../user/login-store";
class Home extends React.Component {
constructor(props) {
super(props);
let currentUser = UserStore.getState().currentUser;
this.state = {
currentRole: currentUser ? currentUser.role : '',
currentUsername: currentUser ? currentUser.username: '',
currentUserID: currentUser ? currentUser.id: 0,
token: UserStore.getState().token
currentUser: LoginStore.getState().currentUser,
token: LoginStore.getState().token
};
}
......@@ -48,12 +45,6 @@ class Home extends React.Component {
return '?';
}
componentDidMount() {
//RestAPI.get('/api/v1/counts').then(response => {
// this.setState({ counts: response });
//});
}
render() {
return (
<div className="home-container">
......@@ -64,7 +55,7 @@ class Home extends React.Component {
VILLASweb is a frontend for distributed real-time simulation hosted by <a href={"mailto:" + config.admin.mail}>{config.admin.name}</a>.
</p>
<p>
You are logged in as user <b>{this.state.currentUsername}</b> with <b>ID {this.state.currentUserID}</b> and role <b>{this.state.currentRole}</b>.
You are logged in as user <b>{this.state.currentUser.username}</b> with <b>ID {this.state.currentUser.id}</b> and role <b>{this.state.currentUser.role}</b>.
</p>
{/*
<p>
......
......@@ -32,7 +32,7 @@ class CustomTable extends Component {
this.activeInput = null;
this.state = {
rows: this.getRows(props),
rows: CustomTable.getRows(props),
editCell: [ -1, -1 ]
};
}
......@@ -45,7 +45,7 @@ class CustomTable extends Component {
this.setState({ editCell: [ column, row ]}); // x, y
}
addCell(data, index, child) {
static addCell(data, index, child) {
// add data to cell
let content = null;
......@@ -112,7 +112,7 @@ class CustomTable extends Component {
}
if (child.props.checkbox) {
const checkboxKey = this.props.checkboxKey;
const checkboxKey = child.props.checkboxKey;
cell.push(<FormCheck className="table-control-checkbox" inline checked={checkboxKey ? data[checkboxKey] : null} onChange={e => child.props.onChecked(index, e)} />);
}
......@@ -122,12 +122,12 @@ class CustomTable extends Component {
}
return cell;
}
} // addCell
componentWillReceiveProps(nextProps) {
const rows = this.getRows(nextProps);
static getDerivedStateFromProps(props, state){
const rows = CustomTable.getRows(props);
this.setState({ rows });
return { rows };
}
componentDidUpdate() {
......@@ -147,7 +147,7 @@ class CustomTable extends Component {
this.setState({ editCell: [ -1, -1 ] });
}
getRows(props) {
static getRows(props) {
if (props.data == null) {
return [];
}
......@@ -156,13 +156,13 @@ class CustomTable extends Component {
// check if multiple columns
if (Array.isArray(props.children) === false) {
// table only has a single column
return [ this.addCell(data, index, props.children) ];
return [ CustomTable.addCell(data, index, props.children) ];
}
const row = [];
for (let child of props.children) {
row.push(this.addCell(data, index, child));
row.push(CustomTable.addCell(data, index, child));
}
return row;
......
......@@ -22,6 +22,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button } from 'react-bootstrap';
import Icon from "../common/icon";
class DashboardButtonGroup extends React.Component {
render() {
......@@ -38,39 +39,39 @@ class DashboardButtonGroup extends React.Component {
if (this.props.editing) {
buttons.push(
<Button key={key++} bsStyle="info" onClick={this.props.onSave} style={buttonStyle}>
<span class="glyphicon glyphicon-floppy-disk"></span> Save
<Button key={key++} onClick={this.props.onSave} style={buttonStyle}>
<Icon icon="save" /> Save
</Button>,
<Button key={key++} bsStyle="info" onClick={this.props.onCancel} style={buttonStyle}>
<span class="glyphicon glyphicon-remove" ></span> Cancel
<Button key={key++} onClick={this.props.onCancel} style={buttonStyle}>
<Icon icon="times" /> Cancel
</Button>
);
} else {
if (this.props.fullscreen !== true) {
buttons.push(
<Button key={key++} bsStyle="info" onClick={this.props.onFullscreen} style={buttonStyle}>
<span className="glyphicon glyphicon-resize-full"></span> Fullscreen
<Button key={key++} onClick={this.props.onFullscreen} style={buttonStyle}>
<Icon icon="expand" /> Fullscreen
</Button>
);
}
if (this.props.paused) {
buttons.push(
<Button key={key++} bsStyle="info" onClick={this.props.onUnpause} style={buttonStyle}>
<span className="glyphicon glyphicon-play"></span> Live
<Button key={key++} onClick={this.props.onUnpause} style={buttonStyle}>
<Icon icon="play" /> Live
</Button>
);
} else {
buttons.push(
<Button key={key++} bsStyle="info" onClick={this.props.onPause} style={buttonStyle}>
<span className="glyphicon glyphicon-pause"></span> Pause
<Button key={key++} onClick={this.props.onPause} style={buttonStyle}>
<Icon icon="pause" /> Pause
</Button>
);
}
buttons.push(
<Button key={key++} bsStyle="info" onClick={this.props.onEdit} style={buttonStyle}>
<span className="glyphicon glyphicon-pencil"></span> Pause
<Button key={key++} onClick={this.props.onEdit} style={buttonStyle}>
<Icon icon="pen" /> Edit
</Button>
);
}
......
......@@ -19,7 +19,7 @@
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
import React from 'react';
import React, {Component} from 'react';
import { Container } from 'flux/utils';
import Fullscreenable from 'react-fullscreenable';
import classNames from 'classnames';
......@@ -34,82 +34,139 @@ import WidgetToolbox from './widget-toolbox';
import WidgetArea from './widget-area';
import DashboardButtonGroup from './dashboard-button-group';
import UserStore from '../user/user-store';
import LoginStore from '../user/login-store';
import DashboardStore from './dashboard-store';
import ProjectStore from '../project/project-store';
import SimulationStore from '../simulation/simulation-store';
import SimulationModelStore from '../simulationmodel/simulation-model-store';
import FileStore from '../file/file-store';
import WidgetStore from '../widget/widget-store';
import AppDispatcher from '../common/app-dispatcher';
import FluxContainerConverter from "../common/FluxContainerConverter";
import 'react-contexify/dist/ReactContexify.min.css';
class Dashboard extends React.Component {
class Dashboard extends Component {
static lastWidgetKey = 0;
static getStores() {
return [ DashboardStore, ProjectStore, SimulationStore, SimulationModelStore, FileStore, UserStore ];
return [ DashboardStore, ProjectStore, SimulationStore, SimulationModelStore, FileStore, LoginStore, WidgetStore ];
}
static calculateState(prevState, props) {
if (prevState == null) {
prevState = {};
}
const sessionToken = LoginStore.getState().token;
let maxHeight = null;
let dashboard = Map();
let rawDashboard = DashboardStore.getState().find(v => v._id === props.match.params.dashboard);
let dashboards = DashboardStore.getState()
let rawDashboard = dashboards[props.match.params.dashboard - 1];
if (rawDashboard != null) {
if (rawDashboard) {
dashboard = Map(rawDashboard);
// convert widgets list to a dictionary to be able to reference widgets
const widgets = {};
//let widgets = {};
for (let widget of dashboard.get('widgets')) {
widgets[this.getNewWidgetKey()] = widget;
let rawWidgets = WidgetStore.getState();
if(rawWidgets.length === 0){
AppDispatcher.dispatch({
type: 'widgets/start-load',
token: sessionToken,
param: '?dashboardID=1'
});
}