diff --git a/extensions/default/src/ViewerLayout/index.jsx b/extensions/default/src/ViewerLayout/index.jsx
index cf2c11826..cbf414efc 100644
--- a/extensions/default/src/ViewerLayout/index.jsx
+++ b/extensions/default/src/ViewerLayout/index.jsx
@@ -1,7 +1,6 @@
import React, { useEffect, useState } from 'react';
-import classnames from 'classnames';
import PropTypes from 'prop-types';
-import { SidePanel } from '@ohif/ui';
+import { SidePanel, ErrorBoundary } from '@ohif/ui';
import Header from './Header.jsx';
import NestedMenu from './ToolbarButtonNestedMenu.jsx';
@@ -93,27 +92,28 @@ function ViewerLayout({
return (
{/* LEFT SIDEPANELS */}
{leftPanelComponents.length && (
-
+
+
+
)}
{/* TOOLBAR + GRID */}
-
- {toolbars.secondary.map(toolDef => {
- const { id, Component, componentProps } = toolDef;
-
- return ;
- })}
-
+
+
+ {toolbars.secondary.map(toolDef => {
+ const { id, Component, componentProps } = toolDef;
+ return ;
+ })}
+
+
-
+
+
+
{rightPanelComponents.length && (
-
+
+
+
)}
diff --git a/platform/ui/index.js b/platform/ui/index.js
index e3c8a8f25..5aa3402d6 100644
--- a/platform/ui/index.js
+++ b/platform/ui/index.js
@@ -33,6 +33,7 @@ export {
Dialog,
Dropdown,
EmptyStudies,
+ ErrorBoundary,
ExpandableToolbarButton,
ListMenu,
Icon,
diff --git a/platform/ui/package.json b/platform/ui/package.json
index 17619f2cf..601b7862d 100644
--- a/platform/ui/package.json
+++ b/platform/ui/package.json
@@ -42,6 +42,7 @@
"react-dnd": "^10.0.2",
"react-dnd-html5-backend": "^10.0.2",
"react-dnd-touch-backend": "^10.0.2",
+ "react-error-boundary": "2.2.x",
"react-dom": "16.11.0",
"react-modal": "^3.11.2",
"react-powerplug": "1.0.0",
diff --git a/platform/ui/src/components/ErrorBoundary/ErrorBoundary.jsx b/platform/ui/src/components/ErrorBoundary/ErrorBoundary.jsx
new file mode 100644
index 000000000..f6915592c
--- /dev/null
+++ b/platform/ui/src/components/ErrorBoundary/ErrorBoundary.jsx
@@ -0,0 +1,109 @@
+import React, { useState } from 'react';
+import { ErrorBoundary as ReactErrorBoundary } from 'react-error-boundary';
+import PropTypes from 'prop-types';
+import Modal from '../Modal';
+
+const isProduction = process.env.NODE_ENV === 'production';
+
+const DefaultFallback = ({ error, componentStack, context, resetErrorBoundary, fallbackRoute }) => {
+ const title = `Something went wrong${!isProduction && ` in ${context}`}.`;
+ const subtitle = `Sorry, something went wrong there. Try again.`;
+ return (
+
+
{title}
+
{subtitle}
+ {!isProduction && (
+
+
Context: {context}
+
Error Message: {error.message}
+
Stack: {componentStack}
+
+ )}
+
+ );
+};
+
+const noop = () => { };
+
+DefaultFallback.propTypes = {
+ error: PropTypes.object.isRequired,
+ resetErrorBoundary: PropTypes.func,
+ componentStack: PropTypes.string,
+};
+
+DefaultFallback.defaultProps = {
+ resetErrorBoundary: noop
+};
+
+const ErrorBoundary = ({
+ context,
+ onReset,
+ onError,
+ fallbackComponent: FallbackComponent,
+ children,
+ fallbackRoute,
+ isPage
+}) => {
+ const [isOpen, setIsOpen] = useState(true);
+
+ const onErrorHandler = (error, componentStack) => {
+ console.error(`${context} Error Boundary`, error, componentStack, context);
+ onError(error, componentStack, context);
+ };
+
+ const onResetHandler = (...args) => onReset(...args);
+
+ const withModal = (Component) => props => (
+ {
+ setIsOpen(false);
+ if (fallbackRoute) {
+ window.location = fallbackRoute;
+ }
+ }}
+ >
+
+
+ );
+
+ const Fallback = isPage ? FallbackComponent : withModal(FallbackComponent);
+
+ return (
+ (
+
+ )}
+ onReset={onResetHandler}
+ onError={onErrorHandler}
+ >
+ {children}
+
+ );
+};
+
+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
+};
+
+ErrorBoundary.defaultProps = {
+ context: 'OHIF',
+ onReset: noop,
+ onError: noop,
+ fallbackComponent: DefaultFallback,
+ fallbackRoute: null
+};
+
+export default ErrorBoundary;
diff --git a/platform/ui/src/components/ErrorBoundary/ErrorBoundary.mdx b/platform/ui/src/components/ErrorBoundary/ErrorBoundary.mdx
new file mode 100644
index 000000000..bb65df025
--- /dev/null
+++ b/platform/ui/src/components/ErrorBoundary/ErrorBoundary.mdx
@@ -0,0 +1,59 @@
+---
+name: Error Boundary
+menu: Data Display
+route: components/errorBoundary
+---
+
+import { Playground, Props } from 'docz';
+import { ErrorBoundary } from '@ohif/ui';
+
+# Error Boundary
+
+This component can be used to display a message or handle unhandled errors.
+
+## Import
+
+```javascript
+import { ErrorBoundary } from '@ohif/ui';
+```
+
+## Basic usage
+
+
+
+
+ {() => {
+ throw new Error('Error!');
+ return ;
+ }}
+
+
+
+
+## Custom error fallback
+
+
+ {() => {
+ const CustomFallback = ({ error, componentStack, resetErrorBoundary }) => {
+ return (
+
+
This is a custom fallback!
+
+ );
+ };
+ return (
+
+
+ {() => {
+ throw new Error('Error!');
+ return ;
+ }}
+
+
+ );
+ }}
+
+
+## Properties
+
+
diff --git a/platform/ui/src/components/ErrorBoundary/index.js b/platform/ui/src/components/ErrorBoundary/index.js
new file mode 100644
index 000000000..b54419282
--- /dev/null
+++ b/platform/ui/src/components/ErrorBoundary/index.js
@@ -0,0 +1,2 @@
+import ErrorBoundary from './ErrorBoundary';
+export default ErrorBoundary;
diff --git a/platform/ui/src/components/ViewportActionBar/ViewportActionBar.jsx b/platform/ui/src/components/ViewportActionBar/ViewportActionBar.jsx
index 9129d871c..12f052e76 100644
--- a/platform/ui/src/components/ViewportActionBar/ViewportActionBar.jsx
+++ b/platform/ui/src/components/ViewportActionBar/ViewportActionBar.jsx
@@ -136,7 +136,7 @@ const ViewportActionBar = ({
)}
-
+
)}
>
-
+
{allRoutes.map((route, i) => {
+ console.log(route);
return (
(
// eslint-disable-next-line react/jsx-props-no-spreading
-
+
+
+
)}
/>
);
diff --git a/yarn.lock b/yarn.lock
index 1b0b1ec12..7cb2da6e7 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -18075,6 +18075,13 @@ react-dropzone@^10.1.7:
file-selector "^0.1.12"
prop-types "^15.7.2"
+react-error-boundary@2.2.x:
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-2.2.3.tgz#34c8238012d3b4148cec47a1b3cec669d5206578"
+ integrity sha512-Jiaiu6CJ4ho3sMCVI7gg+O/JB5vlFFZGwlnpFBTCOSyheYRTzz+FhBMo7tfnCTB/ZR0LaMzAPGbZGrEzAOd0eg==
+ dependencies:
+ "@babel/runtime" "^7.9.6"
+
react-error-overlay@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-3.0.0.tgz#c2bc8f4d91f1375b3dad6d75265d51cd5eeaf655"