ohif-viewer/platform/ui/src/components/ErrorBoundary/ErrorBoundary.tsx

131 lines
3.4 KiB
TypeScript
Raw Normal View History

2020-06-26 19:08:29 +02:00
import React, { useState } from 'react';
2020-06-26 15:26:50 +02:00
import { ErrorBoundary as ReactErrorBoundary } from 'react-error-boundary';
2020-06-26 17:38:10 +02:00
import PropTypes from 'prop-types';
2020-06-26 19:08:29 +02:00
import Modal from '../Modal';
import Icon from '../Icon';
import IconButton from '../IconButton';
2020-06-26 15:26:50 +02:00
const isProduction = process.env.NODE_ENV === 'production';
const DefaultFallback = ({
error,
context,
resetErrorBoundary,
fallbackRoute,
}) => {
const [showDetails, setShowDetails] = useState(false);
2020-06-26 19:08:29 +02:00
const title = `Something went wrong${!isProduction && ` in ${context}`}.`;
const subtitle = `Sorry, something went wrong there. Try again.`;
2020-06-26 15:26:50 +02:00
return (
2020-06-26 19:08:29 +02:00
<div className="ErrorFallback bg-primary-dark w-full h-full" role="alert">
<p className="text-primary-light text-xl">{title}</p>
<p className="text-primary-light text-base">{subtitle}</p>
{!isProduction && (
<div className="rounded-md bg-secondary-dark p-5 mt-5 font-mono space-y-2">
<p className="text-primary-light">Context: {context}</p>
<p className="text-primary-light">Error Message: {error.message}</p>
<IconButton
variant="contained"
color="inherit"
size="initial"
className="text-primary-active"
onClick={() => setShowDetails(!showDetails)}
>
<React.Fragment>
<div>{'Stack Trace'}</div>
<Icon width="15px" height="15px" name="chevron-down" />
</React.Fragment>
</IconButton>
{showDetails && (
<p className="px-4 text-primary-light">Stack: {error.stack}</p>
)}
2020-06-26 19:08:29 +02:00
</div>
2020-06-26 15:26:50 +02:00
)}
</div>
);
};
const noop = () => {};
2020-06-26 19:08:29 +02:00
DefaultFallback.propTypes = {
error: PropTypes.object.isRequired,
resetErrorBoundary: PropTypes.func,
componentStack: PropTypes.string,
};
DefaultFallback.defaultProps = {
resetErrorBoundary: noop,
2020-06-26 15:26:50 +02:00
};
const ErrorBoundary = ({
2020-06-26 19:08:29 +02:00
context,
onReset,
onError,
fallbackComponent: FallbackComponent,
children,
fallbackRoute,
isPage,
2020-06-26 15:26:50 +02:00
}) => {
2020-06-26 19:08:29 +02:00
const [isOpen, setIsOpen] = useState(true);
2020-06-26 15:26:50 +02:00
const onErrorHandler = (error, componentStack) => {
2020-06-26 19:08:29 +02:00
console.error(`${context} Error Boundary`, error, componentStack, context);
onError(error, componentStack, context);
2020-06-26 15:26:50 +02:00
};
2020-06-26 19:08:29 +02:00
const onResetHandler = (...args) => onReset(...args);
const withModal = Component => props => (
2020-06-26 19:08:29 +02:00
<Modal
closeButton
shouldCloseOnEsc
isOpen={isOpen}
title={'Something went wrong'}
onClose={() => {
setIsOpen(false);
if (fallbackRoute && typeof window !== 'undefined') {
2020-06-26 19:08:29 +02:00
window.location = fallbackRoute;
}
}}
>
<Component {...props} />
</Modal>
);
const Fallback = isPage ? FallbackComponent : withModal(FallbackComponent);
2020-06-26 15:26:50 +02:00
return (
<ReactErrorBoundary
2020-06-26 19:08:29 +02:00
fallbackRender={props => (
<Fallback {...props} context={context} fallbackRoute={fallbackRoute} />
2020-06-26 19:08:29 +02:00
)}
2020-06-26 15:26:50 +02:00
onReset={onResetHandler}
onError={onErrorHandler}
>
{children}
</ReactErrorBoundary>
);
};
2020-06-26 19:08:29 +02:00
ErrorBoundary.propTypes = {
context: PropTypes.string,
onReset: PropTypes.func,
onError: PropTypes.func,
fallbackComponent: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
children: PropTypes.node.isRequired,
fallbackRoute: PropTypes.string,
2020-06-26 19:08:29 +02:00
};
ErrorBoundary.defaultProps = {
context: 'OHIF',
onReset: noop,
onError: noop,
fallbackComponent: DefaultFallback,
fallbackRoute: null,
2020-06-26 19:08:29 +02:00
};
2020-06-26 15:26:50 +02:00
export default ErrorBoundary;