69 lines
1.7 KiB
TypeScript
69 lines
1.7 KiB
TypeScript
import { AlertTriangle, RefreshCw } from 'lucide-react';
|
|
import { Component, type ErrorInfo, type ReactNode } from 'react';
|
|
import log from 'electron-log/renderer';
|
|
|
|
import TextButton from './styled/TextButton';
|
|
|
|
type Props = {
|
|
tabName: string;
|
|
children: ReactNode;
|
|
};
|
|
|
|
type State = {
|
|
error?: Error;
|
|
componentStack?: string;
|
|
};
|
|
|
|
class TabErrorBoundary extends Component<Props, State> {
|
|
state: State = {};
|
|
|
|
static getDerivedStateFromError(error: Error): State {
|
|
return { error };
|
|
}
|
|
|
|
componentDidCatch(error: Error, info: ErrorInfo) {
|
|
log.error(`Tab "${this.props.tabName}" crashed:`, error, info);
|
|
this.setState({ error, componentStack: info.componentStack ?? undefined });
|
|
}
|
|
|
|
componentDidUpdate(prevProps: Props) {
|
|
if (prevProps.tabName !== this.props.tabName) {
|
|
this.setState({ error: undefined, componentStack: undefined });
|
|
}
|
|
}
|
|
|
|
#reset = () => this.setState({ error: undefined, componentStack: undefined });
|
|
|
|
render() {
|
|
if (!this.state.error) return this.props.children;
|
|
const { error, componentStack } = this.state;
|
|
return (
|
|
<div className="tw-surface flex min-h-0 flex-grow flex-col gap-3">
|
|
<div className="flex items-center gap-2">
|
|
<AlertTriangle size={22} className="text-red" />
|
|
<h4 className="text-red">{this.props.tabName} crashed</h4>
|
|
</div>
|
|
<hr />
|
|
<p className="text-white">
|
|
{error.name}: {error.message}
|
|
</p>
|
|
{componentStack && (
|
|
<pre className="s1 max-h-[200px] overflow-auto whitespace-pre-wrap text-blueGray">
|
|
{componentStack.trim()}
|
|
</pre>
|
|
)}
|
|
<hr />
|
|
<TextButton
|
|
icon={RefreshCw}
|
|
onClick={this.#reset}
|
|
className="self-end text-warmGreen"
|
|
>
|
|
Try again
|
|
</TextButton>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
export default TabErrorBoundary;
|