Add error boundary to nake frontend more resiliant
Allows users to recover from errors in userStorage Is related to #23
This commit is contained in:
@@ -12,6 +12,7 @@ import TitleBar from './components/TitleBar'
|
|||||||
import Tree from './components/Tree/Tree'
|
import Tree from './components/Tree/Tree'
|
||||||
import UpdateNotifier from './UpdateNotifier'
|
import UpdateNotifier from './UpdateNotifier'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
|
import ErrorBoundary from './ErrorBoundary'
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
selectedNode?: q.TreeNode,
|
selectedNode?: q.TreeNode,
|
||||||
@@ -82,25 +83,28 @@ class App extends React.Component<Props, State> {
|
|||||||
public render() {
|
public render() {
|
||||||
const { settingsVisible } = this.props
|
const { settingsVisible } = this.props
|
||||||
const { content, contentShift, centerContent } = this.getStyles()
|
const { content, contentShift, centerContent } = this.getStyles()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={centerContent}>
|
<div style={centerContent}>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<Settings />
|
<ErrorBoundary>
|
||||||
<div style={settingsVisible ? contentShift : content}>
|
<Settings />
|
||||||
<TitleBar />
|
<div style={settingsVisible ? contentShift : content}>
|
||||||
<div style={centerContent}>
|
<TitleBar />
|
||||||
<div style={this.getStyles().left}>
|
<div style={centerContent}>
|
||||||
<Tree connectionId={this.state.connectionId} didSelectNode={(node: q.TreeNode) => {
|
<div style={this.getStyles().left}>
|
||||||
this.setState({ selectedNode: node })
|
<Tree connectionId={this.state.connectionId} didSelectNode={(node: q.TreeNode) => {
|
||||||
}} />
|
this.setState({ selectedNode: node })
|
||||||
|
}} />
|
||||||
|
</div>
|
||||||
|
<div style={this.getStyles().right}>
|
||||||
|
<Sidebar connectionId={this.state.connectionId} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={this.getStyles().right}>
|
</div>
|
||||||
<Sidebar connectionId={this.state.connectionId} />
|
<UpdateNotifier />
|
||||||
</div>
|
<Connection onConnection={(connectionId: string) => this.setState({ connectionId })}/>
|
||||||
</div>
|
</ErrorBoundary>
|
||||||
</div>
|
|
||||||
<UpdateNotifier />
|
|
||||||
<Connection onConnection={(connectionId: string) => this.setState({ connectionId })}/>
|
|
||||||
</div >
|
</div >
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
103
app/src/ErrorBoundary.tsx
Normal file
103
app/src/ErrorBoundary.tsx
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import * as React from 'react'
|
||||||
|
import * as q from '../../backend/src/Model'
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Modal,
|
||||||
|
Paper,
|
||||||
|
Toolbar,
|
||||||
|
Typography,
|
||||||
|
} from '@material-ui/core'
|
||||||
|
|
||||||
|
import Warning from '@material-ui/icons/Warning'
|
||||||
|
import SentimentDissatisfied from '@material-ui/icons/SentimentDissatisfied'
|
||||||
|
|
||||||
|
import { Theme, withStyles } from '@material-ui/core/styles'
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
error?: Error
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
classes: any
|
||||||
|
}
|
||||||
|
|
||||||
|
class ErrorBoundary extends React.Component<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props)
|
||||||
|
this.state = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentDidCatch(error: Error, errorInfo: any) {
|
||||||
|
console.log('did catch', error)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getDerivedStateFromError(error: Error) {
|
||||||
|
return { error }
|
||||||
|
}
|
||||||
|
|
||||||
|
private restart = () => {
|
||||||
|
window.location = window.location
|
||||||
|
}
|
||||||
|
|
||||||
|
private clearStorage = () => {
|
||||||
|
localStorage.clear()
|
||||||
|
window.location = window.location
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
if (!this.state.error) {
|
||||||
|
return this.props.children
|
||||||
|
}
|
||||||
|
|
||||||
|
const { classes } = this.props
|
||||||
|
return (
|
||||||
|
<Modal open={true} disableAutoFocus={true}>
|
||||||
|
<Paper className={classes.root}>
|
||||||
|
<Toolbar style={{ padding: '0' }}>
|
||||||
|
<Typography className={classes.title} variant="h6" color="inherit"><Warning /> Oooooops!</Typography>
|
||||||
|
</Toolbar>
|
||||||
|
<Typography>I hoped that you would never see this window, but MQTT-Explorer had an unexpected error.</Typography>
|
||||||
|
<Typography style={{ textAlign: 'center' }}><SentimentDissatisfied /></Typography>
|
||||||
|
<pre className={classes.textColor} style={{ maxHeight: '40vh', overflow: 'scroll' }}>
|
||||||
|
<code className={classes.textColor}>
|
||||||
|
{this.state.error.stack}
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
<Typography>
|
||||||
|
Please report this issue with a short description of what happened to
|
||||||
|
<span> <a className={classes.textColor} href="https://github.com/thomasnordquist/MQTT-Explorer/issues">https://github.com/thomasnordquist/MQTT-Explorer/issues</a></span>
|
||||||
|
</Typography>
|
||||||
|
<div>
|
||||||
|
<div className={classes.buttonPositioning}><Button onClick={this.restart}>Restart</Button></div>
|
||||||
|
<div className={classes.buttonPositioning}><Button onClick={this.clearStorage}>Start Fresh</Button></div>
|
||||||
|
</div>
|
||||||
|
</Paper>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = (theme: Theme) => ({
|
||||||
|
root: {
|
||||||
|
minWidth: 550,
|
||||||
|
maxWidth: 650,
|
||||||
|
backgroundColor: theme.palette.background.default,
|
||||||
|
margin: '14vh auto auto auto',
|
||||||
|
padding: `${2 * theme.spacing.unit}px`,
|
||||||
|
outline: 'none',
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
margin: '0',
|
||||||
|
textAlign: 'center' as 'center',
|
||||||
|
},
|
||||||
|
textColor: {
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
},
|
||||||
|
buttonPositioning: {
|
||||||
|
textAlign: 'center' as 'center',
|
||||||
|
marginTop: `${theme.spacing.unit * 2}px`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default withStyles(styles)(ErrorBoundary)
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { rendererEvents, trackError, trackUserInteraction } from '../../events'
|
||||||
let userId = window.localStorage.getItem('userId')
|
let userId = window.localStorage.getItem('userId')
|
||||||
const sha1 = require('sha1')
|
const sha1 = require('sha1')
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user