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:
commit
7dbd000534
16 changed files with 2254 additions and 3468 deletions
|
@ -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
5207
package-lock.json
generated
File diff suppressed because it is too large
Load diff
72
package.json
72
package.json
|
@ -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",
|
||||
|
|
|
@ -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))
|
||||
});
|
||||
});
|
||||
});
|
17
src/app.js
17
src/app.js
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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}/>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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" ?
|
||||
<>
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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 || '' }
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue