1
0
Fork 0
mirror of https://git.rwth-aachen.de/acs/public/villas/web/ synced 2025-03-16 00:00:03 +01:00

Merge branch 'react-update' into 'master'

update to React 17, Flux 4.0.0

See merge request acs/public/villas/web!76
This commit is contained in:
Sonja Happ 2021-03-25 13:53:34 +00:00
commit 7dbd000534
16 changed files with 2254 additions and 3468 deletions

View file

@ -15,7 +15,7 @@
# along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
# ******************************************************************************
FROM node:12.2 AS builder
FROM node:14.16 AS builder
# Create app directory
RUN mkdir -p /usr/src/app

5207
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -3,74 +3,58 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.32",
"@fortawesome/free-solid-svg-icons": "^5.15.1",
"@createnl/grouped-checkboxes": "^1.1.2",
"@fortawesome/react-fontawesome": "^0.1.13",
"@fortawesome/fontawesome-svg-core": "^1.2.34",
"@fortawesome/free-solid-svg-icons": "^5.15.2",
"@fortawesome/react-fontawesome": "^0.1.14",
"babel-runtime": "^6.26.0",
"bootstrap": "^4.5.3",
"bufferutil": "^4.0.2",
"canvas": "^2.6.1",
"bootstrap": "^4.6.0",
"classnames": "^2.2.6",
"d3-array": "^2.8.0",
"d3-axis": "^2.0.0",
"d3": "^6.6.0",
"d3-array": "^2.12.0",
"d3-axis": "^2.1.0",
"d3-scale": "^3.2.3",
"d3-scale-chromatic": "^2.0.0",
"d3-selection": "^2.0.0",
"d3-shape": "^2.0.0",
"d3-shape": "^2.1.0",
"d3-time-format": "^3.0.0",
"es6-promise": "^4.2.8",
"fibers": "^5.0.0",
"file-saver": "^2.0.5",
"flux": "^3.1.3",
"flux": "^4.0.1",
"gaugeJS": "^1.3.7",
"handlebars": "^4.7.6",
"jquery": "^3.5.1",
"handlebars": "^4.7.7",
"jquery": "^3.6.0",
"jszip": "^3.6.0",
"jsonwebtoken": "^8.5.1",
"jszip": "^3.5.0",
"libcimsvg": "git+https://git.rwth-aachen.de/acs/public/cim/pintura-npm-package.git",
"lodash": "^4.17.20",
"lodash": "^4.17.21",
"moment": "^2.29.1",
"moment-duration-format": "^2.3.2",
"multiselect-react-dropdown": "^1.6.2",
"node-sass": "^4.14.1",
"multiselect-react-dropdown": "^1.6.11",
"popper.js": "^1.16.1",
"prop-types": "^15.7.2",
"rc-slider": "^9.6.4",
"react": "^16.14.0",
"react-bootstrap": "^1.4.0",
"react-bootstrap-time-picker": "^2.0.1",
"rc-slider": "^9.7.1",
"react": "^17.0.1",
"react-bootstrap": "^1.5.2",
"react-collapse": "^5.1.0",
"react-color": "^2.19.3",
"react-contexify": "^4.1.1",
"react-d3": "^0.4.0",
"react-dnd": "^10.0.2",
"react-dnd-html5-backend": "^10.0.2",
"react-dom": "^16.14.0",
"react-contexify": "^5.0.0",
"react-dnd": "^13.1.1",
"react-dnd-html5-backend": "^12.1.1",
"react-dom": "^17.0.1",
"react-fullscreenable": "^2.5.1-0",
"react-grid-system": "^7.1.1",
"react-grid-system": "^7.1.2",
"react-json-view": "^1.21.3",
"react-notification-system": "^0.4.0",
"react-rnd": "^10.2.3",
"react-router": "^5.2.0",
"react-rnd": "^10.2.4",
"react-router-dom": "^5.2.0",
"react-scripts": "^4.0.1",
"react-svg-pan-zoom": "^3.8.1",
"react-scripts": "^4.0.3",
"react-svg-pan-zoom": "^3.10.0",
"react-trafficlight": "^5.2.1",
"sass": "^1.29.0",
"superagent": "^6.1.0",
"swagger-ui-react": "^3.42.0",
"ts-node": "^9.0.0",
"type-fest": "^0.13.1",
"typescript": "^4.1.2",
"utf-8-validate": "^5.0.3",
"validator": "^13.1.17",
"webpack-hot-middleware": "^2.25.0",
"webpack-plugin-serve": "^1.2.1"
},
"devDependencies": {
"chai": "^4.2.0"
"swagger-ui-react": "^3.45.0",
"typescript": "^4.2.3"
},
"devDependencies": {},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",

View file

@ -1,51 +0,0 @@
import { expect } from 'chai';
import createControls from '../../widget/edit-widget/edit-widget-control-creator';
import EditWidgetTextControl from '../../widget/edit-widget/edit-widget-text-control';
import EditWidgetColorControl from '../../widget/edit-widget/edit-widget-color-control';
import EditWidgetTimeControl from '../../widget/edit-widget/edit-widget-time-control';
import EditFileWidgetControl from '../../widget/edit-widget/edit-widget-file-control';
import EditWidgetSignalControl from '../../widget/edit-widget/edit-widget-signal-control';
import EditWidgetSignalsControl from '../../widget/edit-widget/edit-widget-signals-control';
import EditWidgetOrientation from '../../widget/edit-widget/edit-widget-orientation';
import EditWidgetTextSizeControl from '../../widget/edit-widget/edit-widget-text-size-control';
import EditWidgetAspectControl from '../../widget/edit-widget/edit-widget-aspect-control';
import EditWidgetCheckboxControl from '../../widget/edit-widget/edit-widget-checkbox-control';
import EditWidgetMinMaxControl from '../../widget/edit-widget/edit-widget-min-max-control';
import EditWidgetColorZonesControl from '../../widget/edit-widget/edit-widget-color-zones-control';
import EditWidgetHTMLContent from '../../widget/edit-widget/edit-widget-html-content';
import EditWidgetNumberControl from '../../widget/edit-widget/edit-widget-number-control';
describe('edit widget control creator', () => {
it('should not return null', () => {
let controls = createControls('Value', null, null, null, null, null, null);
expect(controls).to.be.not.undefined;
});
var runs = [
{ args: { widgetType: 'Lamp' }, result: { controlNumber: 5, controlTypes: [EditWidgetSignalControl, EditWidgetTextControl, EditWidgetColorControl, EditWidgetColorControl] } },
{ args: { widgetType: 'Value' }, result: { controlNumber: 5, controlTypes: [EditWidgetTextControl, EditWidgetSignalControl, EditWidgetTextSizeControl, EditWidgetCheckboxControl] } },
{ args: { widgetType: 'Plot' }, result: { controlNumber: 5, controlTypes: [EditWidgetTimeControl, EditWidgetSignalsControl, EditWidgetTextControl, EditWidgetMinMaxControl] } },
{ args: { widgetType: 'Table' }, result: { controlNumber: 2, controlTypes: [EditWidgetCheckboxControl] } },
{ args: { widgetType: 'Image' }, result: { controlNumber: 2, controlTypes: [EditFileWidgetControl, EditWidgetAspectControl] } },
{ args: { widgetType: 'Gauge' }, result: { controlNumber: 6, controlTypes: [EditWidgetTextControl, EditWidgetSignalControl, EditWidgetCheckboxControl, EditWidgetColorZonesControl, EditWidgetMinMaxControl] } },
{ args: { widgetType: 'Slider' }, result: { controlNumber: 9, controlTypes: [EditWidgetTextControl, EditWidgetOrientation, EditWidgetSignalControl, EditWidgetCheckboxControl, EditWidgetCheckboxControl, EditWidgetMinMaxControl, EditWidgetNumberControl, EditWidgetNumberControl] } },
{ args: { widgetType: 'Button' }, result: { controlNumber: 6, controlTypes: [EditWidgetTextControl, EditWidgetSignalControl, EditWidgetCheckboxControl, EditWidgetNumberControl, EditWidgetNumberControl] } },
{ args: { widgetType: 'Box' }, result: { controlNumber: 2, controlTypes: [EditWidgetColorControl, EditWidgetColorControl] } },
{ args: { widgetType: 'Label' }, result: { controlNumber: 3, controlTypes: [EditWidgetTextControl, EditWidgetTextSizeControl, EditWidgetColorControl] } },
{ args: { widgetType: 'HTML' }, result: { controlNumber: 1, controlTypes: [EditWidgetHTMLContent] } },
{ args: { widgetType: 'Input'}, result: { controlNumber: 3, controlTypes: [EditWidgetTextControl, EditWidgetSignalControl] } }
];
runs.forEach( (run) => {
let itMsg = run.args.widgetType + ' widget edit should have correct controls';
it(itMsg, () => {
let controls = createControls(run.args.widgetType, null, null, null, null, null, null);
expect(controls).to.have.lengthOf(run.result.controlNumber);
controls.forEach( (control) => expect(control.type).to.be.oneOf(run.result.controlTypes))
});
});
});

View file

@ -17,7 +17,7 @@
import React from 'react';
import { DndProvider } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
import { HTML5Backend }from 'react-dnd-html5-backend';
import NotificationSystem from 'react-notification-system';
import { Redirect, Route } from 'react-router-dom';
import jwt from 'jsonwebtoken'
@ -37,6 +37,7 @@ import Scenario from './scenario/scenario';
import Users from './user/users';
import User from './user/user';
import APIBrowser from './common/api-browser';
import LoginStore from './user/login-store'
import './styles/app.css';
@ -46,16 +47,20 @@ class App extends React.Component {
constructor(props) {
super(props);
AppDispatcher.dispatch({
type: 'config/load',
});
this.state = {}
}
static getStores() {
return [LoginStore]
}
componentDidMount() {
NotificationsDataManager.setSystem(this.refs.notificationSystem);
AppDispatcher.dispatch({
type: 'config/load',
});
// if token stored locally, we are already logged-in
let token = localStorage.getItem("token");
if (token != null && token !== '') {
@ -68,7 +73,7 @@ class App extends React.Component {
});
} else {
let currentUser = JSON.parse(localStorage.getItem("currentUser"));
console.log("Already logged-in")
console.log("Logged-in as user ", currentUser.username)
AppDispatcher.dispatch({
type: 'users/logged-in',
token: token,

View file

@ -39,7 +39,7 @@ class APIBrowser extends React.Component {
return spec;
}
componentWillMount() {
componentDidMount() {
this._asyncRequest = RestAPI.get('/api/v2/openapi')
.then((spec) => {
this._asyncRequest = null;

View file

@ -58,11 +58,7 @@ class SidebarMenu extends React.Component {
AppDispatcher.dispatch({
type: 'users/logout'
});
// The Login Store is deleted automatically
// discard login token and current User
localStorage.setItem('token', '');
localStorage.setItem('currentUser', '');
// The Login Store and local storage are deleted automatically
}
render() {

View file

@ -56,7 +56,6 @@ class DashboardButtonGroup extends React.Component {
} else {
if (this.props.fullscreen !== true) {
buttons.push(
<OverlayTrigger key={key++} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"expand"}`}> Change to fullscreen view </Tooltip>} >
<Button key={key} variant= 'light' size="lg" onClick={this.props.onFullscreen} style={buttonStyle}>
<Icon icon="expand" classname='icon-color' style={iconStyle}/>

View file

@ -475,7 +475,7 @@ class Dashboard extends Component {
const boxClasses = classNames('section', 'box', { 'fullscreen-padding': this.props.isFullscreen });
let draggable = this.state.editing;
let dropZoneHeight = this.state.dashboard.height;
return <div className={boxClasses} >
return (<div className={boxClasses} >
<div className='section-header box-header'>
<div className="section-title">
<h2>{this.state.dashboard.name}</h2>
@ -587,8 +587,11 @@ class Dashboard extends Component {
configs={this.state.configs}
sessionToken={this.state.sessionToken}
/>
</div>
</div>;
</div>
);
}
}

View file

@ -46,6 +46,23 @@ class ICDialog extends React.Component {
FileSaver.saveAs(blob, this.props.ic.name + ".svg");
}
isJSON(data){
if (data === undefined || data === null){
return false;
}
let str = JSON.stringify(data);
try
{
JSON.parse(str)
}
catch(ex){
return false
}
return true
}
render() {
let graphURL = ""
@ -123,14 +140,19 @@ class ICDialog extends React.Component {
<Col>
<h5>Raw Status:</h5>
{this.isJSON(this.props.ic.statusupdateraw) ?
<ReactJson
src={this.props.ic.statusupdateraw}
name={false}
displayDataTypes={false}
displayObjectSize={false}
enableClipboard={false}
collapsed={1}
collapsed={2}
/>
:
<div>No valid JSON raw data available.</div>
}
{this.props.ic.type === "villas-node" ?
<>

View file

@ -15,7 +15,7 @@
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
import React, { Component } from 'react';
import React from 'react';
import { Container } from 'flux/utils';
import { NavbarBrand } from 'react-bootstrap';
import NotificationSystem from 'react-notification-system';
@ -28,15 +28,10 @@ import NotificationsDataManager from '../common/data-managers/notifications-data
import LoginStore from './login-store'
import AppDispatcher from '../common/app-dispatcher';
class Login extends Component {
class Login extends React.Component {
constructor(props) {
super(props);
// Load config in case the user goes directly to /login
// otherwise it will be loaded in app constructor
AppDispatcher.dispatch({
type: 'config/load',
});
}
static getStores() {
@ -56,6 +51,12 @@ class Login extends Component {
componentDidMount() {
NotificationsDataManager.setSystem(this.refs.notificationSystem);
// load config in case the user goes directly to /login
// otherwise it will be loaded in app constructor
AppDispatcher.dispatch({
type: 'config/load',
});
}
render() {

View file

@ -26,11 +26,7 @@ class Logout extends React.Component {
type: 'users/logout'
});
// The Login Store is deleted automatically
// discard login token and current User
localStorage.setItem('token', '');
localStorage.setItem('currentUser', '');
// The Login Store and local storage are deleted automatically
}
render() {

View file

@ -16,7 +16,7 @@
******************************************************************************/
import React, { Component } from 'react';
import { Form, OverlayTrigger, Tooltip, Button } from 'react-bootstrap';
import { Form, OverlayTrigger, Tooltip, Button, Col } from 'react-bootstrap';
import ColorPicker from './color-picker'
import Icon from "../../common/icon";
@ -77,8 +77,8 @@ class EditWidgetColorControl extends Component {
let style = {
backgroundColor: color,
opacity: opacity,
width: '260px',
height: '40px'
width: '80px',
height: '40px',
}
let tooltipText = "Change color and opacity";
@ -87,19 +87,23 @@ class EditWidgetColorControl extends Component {
}
return <Form.Group>
return ( <Form.Row>
<Form.Group as={Col}>
<Form.Label>{this.props.label}</Form.Label>
</Form.Group>
<Form.Group as={Col}>
<div className='section-buttons-group-right'>
<OverlayTrigger key={0} placement={'right'} overlay={<Tooltip id={`tooltip-${"color"}`}> {tooltipText} </Tooltip>} >
<Button key={2} style={style} onClick={this.openColorPicker.bind(this)} >
<Button style={style} onClick={this.openColorPicker.bind(this)} >
<Icon icon="paint-brush"/>
</Button>
</OverlayTrigger>
</div>
<ColorPicker show={this.state.showColorPicker} onClose={(data) => this.closeEditModal(data)} widget={this.state.widget} controlId={this.props.controlId} disableOpacity={this.props.disableOpacity}/>
</Form.Group>;
</Form.Group>
</Form.Row>
);
}
}

View file

@ -16,7 +16,7 @@
******************************************************************************/
import React, { Component } from 'react';
import { OverlayTrigger, Tooltip , Button } from 'react-bootstrap';
import { OverlayTrigger, Tooltip , Button, Form } from 'react-bootstrap';
import ColorPicker from './color-picker'
import Icon from "../../common/icon";
import { scaleOrdinal } from "d3-scale";

View file

@ -174,6 +174,7 @@ class EditWidgetDialog extends React.Component {
onClose={(c) => this.onClose(c)}
onReset={() => this.resetState()}
valid={this.valid}
size={'sm'}
>
<Form encType='multipart/form-data'>
{ controls || '' }

View file

@ -17,7 +17,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Menu, Item, Separator, MenuProvider } from 'react-contexify';
import { Menu, Item, Separator, contextMenu } from 'react-contexify';
import Widget from './widget';
class WidgetContextMenu extends React.Component {
@ -93,10 +93,46 @@ class WidgetContextMenu extends React.Component {
}
};
showMenu = e => {
let index = this.props.index
if (this.props.editing){
contextMenu.show({
event: e,
id: 'widgetMenu' + index,
position: {
x: 'inherit',
y: 'inherit',
}
})
}
else {
contextMenu.show({
event: e,
id: 'widgetMenu' + index,
})
}
}
render() {
const isLocked = this.props.widget.locked;
const ContextMenu = () => (
<Menu id={'widgetMenu'+ this.props.index} style={{zIndex: 1000, display: 'inline-block'}}>
let dim = {
width: '100%',
height: '100%'
};
return (
<div style={dim} onContextMenu={this.showMenu}>
<Widget
data={this.props.widget}
onWidgetChange={this.props.onWidgetChange}
editing={this.props.editing}
index={this.props.index}
paused={this.props.paused}
/>
<Menu id={'widgetMenu' + this.props.index}>
<Item disabled={isLocked} onClick={this.editWidget}>Edit</Item>
<Item disabled={isLocked} onClick={this.duplicateWidget}>Duplicate</Item>
<Item disabled={isLocked} onClick={this.deleteWidget}>Delete</Item>
@ -113,25 +149,8 @@ class WidgetContextMenu extends React.Component {
<Item disabled={isLocked} onClick={this.lockWidget}>Lock</Item>
<Item disabled={isLocked === false} onClick={this.unlockWidget}>Unlock</Item>
</Menu>
);
let dim = {
width: '100%',
height: '100%'
};
return <div style={dim}>
<MenuProvider id={'widgetMenu'+ this.props.index} style={dim}>
<Widget
data={this.props.widget}
onWidgetChange={this.props.onWidgetChange}
editing={this.props.editing}
index={this.props.index}
paused={this.props.paused}
/>
</MenuProvider>
<ContextMenu />
</div>
);
}
}