feat: enhanced error handling ui (#3021)
* feat: better error handling * better error handling * try to fix netlify build * remove new line
This commit is contained in:
parent
83ea228d86
commit
eddd510a35
@ -8,14 +8,6 @@ import { utils } from '@ohif/core';
|
|||||||
|
|
||||||
const { downloadCSVReport } = utils;
|
const { downloadCSVReport } = utils;
|
||||||
|
|
||||||
// tools with measurements to display inside the panel
|
|
||||||
const MEASUREMENT_TOOLS = [
|
|
||||||
'EllipticalROI',
|
|
||||||
'RectangleROI',
|
|
||||||
'Length',
|
|
||||||
'Bidirectional',
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function PanelMeasurementTable({
|
export default function PanelMeasurementTable({
|
||||||
servicesManager,
|
servicesManager,
|
||||||
commandsManager,
|
commandsManager,
|
||||||
@ -185,12 +177,8 @@ PanelMeasurementTable.propTypes = {
|
|||||||
|
|
||||||
function _getMappedMeasurements(MeasurementService) {
|
function _getMappedMeasurements(MeasurementService) {
|
||||||
const measurements = MeasurementService.getMeasurements();
|
const measurements = MeasurementService.getMeasurements();
|
||||||
// filter out measurements whose toolName is not in MEASUREMENT_TOOLS
|
|
||||||
const measurementTools = measurements.filter(measurement =>
|
|
||||||
MEASUREMENT_TOOLS.includes(measurement.toolName)
|
|
||||||
);
|
|
||||||
|
|
||||||
const mappedMeasurements = measurementTools.map((m, index) =>
|
const mappedMeasurements = measurements.map((m, index) =>
|
||||||
_mapMeasurementToDisplay(m, index, MeasurementService.VALUE_TYPES)
|
_mapMeasurementToDisplay(m, index, MeasurementService.VALUE_TYPES)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -122,10 +122,29 @@ function ViewerLayout({
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const getPanelData = id => {
|
const getComponent = id => {
|
||||||
const entry = extensionManager.getModuleEntry(id);
|
const entry = extensionManager.getModuleEntry(id);
|
||||||
// TODO, not sure why sidepanel content has to be JSX, and not a children prop?
|
|
||||||
const content = entry.component;
|
if (!entry) {
|
||||||
|
throw new Error(
|
||||||
|
`${id} is not a valid entry for an extension module, please check your configuration or make sure the extension is registered.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let content;
|
||||||
|
if (entry && entry.component) {
|
||||||
|
content = entry.component;
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
`No component found from extension ${id}. Check the reference string to the extension in your Mode configuration`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { entry, content };
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPanelData = id => {
|
||||||
|
const { content, entry } = getComponent(id);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
iconName: entry.iconName,
|
iconName: entry.iconName,
|
||||||
@ -156,7 +175,7 @@ function ViewerLayout({
|
|||||||
}, [HangingProtocolService]);
|
}, [HangingProtocolService]);
|
||||||
|
|
||||||
const getViewportComponentData = viewportComponent => {
|
const getViewportComponentData = viewportComponent => {
|
||||||
const entry = extensionManager.getModuleEntry(viewportComponent.namespace);
|
const { entry } = getComponent(viewportComponent.namespace);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
component: entry.component,
|
component: entry.component,
|
||||||
|
|||||||
@ -498,8 +498,7 @@ class MeasurementService {
|
|||||||
measurement.source = source;
|
measurement.source = source;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Failed to map '${sourceInfo}' measurement for annotationType ${annotationType}:`,
|
`Failed to map '${sourceInfo}' measurement for annotationType ${annotationType}: ${error.message}`
|
||||||
error.message
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,18 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { ErrorBoundary as ReactErrorBoundary } from 'react-error-boundary';
|
import { ErrorBoundary as ReactErrorBoundary } from 'react-error-boundary';
|
||||||
|
import { Icon, IconButton } from '@ohif/ui';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Modal from '../Modal';
|
import Modal from '../Modal';
|
||||||
|
|
||||||
const isProduction = process.env.NODE_ENV === 'production';
|
const isProduction = process.env.NODE_ENV === 'production';
|
||||||
|
|
||||||
const DefaultFallback = ({ error, componentStack, context, resetErrorBoundary, fallbackRoute }) => {
|
const DefaultFallback = ({
|
||||||
|
error,
|
||||||
|
context,
|
||||||
|
resetErrorBoundary,
|
||||||
|
fallbackRoute,
|
||||||
|
}) => {
|
||||||
|
const [showDetails, setShowDetails] = useState(false);
|
||||||
const title = `Something went wrong${!isProduction && ` in ${context}`}.`;
|
const title = `Something went wrong${!isProduction && ` in ${context}`}.`;
|
||||||
const subtitle = `Sorry, something went wrong there. Try again.`;
|
const subtitle = `Sorry, something went wrong there. Try again.`;
|
||||||
return (
|
return (
|
||||||
@ -13,17 +20,33 @@ const DefaultFallback = ({ error, componentStack, context, resetErrorBoundary, f
|
|||||||
<p className="text-primary-light text-xl">{title}</p>
|
<p className="text-primary-light text-xl">{title}</p>
|
||||||
<p className="text-primary-light text-base">{subtitle}</p>
|
<p className="text-primary-light text-base">{subtitle}</p>
|
||||||
{!isProduction && (
|
{!isProduction && (
|
||||||
<div className="rounded-md bg-secondary-dark p-5 mt-5">
|
<div className="rounded-md bg-secondary-dark p-5 mt-5 font-mono space-y-2">
|
||||||
<pre className="text-primary-light">Context: {context}</pre>
|
<p className="text-primary-light">Context: {context}</p>
|
||||||
<pre className="text-primary-light">Error Message: {error.message}</pre>
|
<p className="text-primary-light">Error Message: {error.message}</p>
|
||||||
<pre className="text-primary-light">Stack: {componentStack}</pre>
|
|
||||||
|
<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>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const noop = () => { };
|
const noop = () => {};
|
||||||
|
|
||||||
DefaultFallback.propTypes = {
|
DefaultFallback.propTypes = {
|
||||||
error: PropTypes.object.isRequired,
|
error: PropTypes.object.isRequired,
|
||||||
@ -32,7 +55,7 @@ DefaultFallback.propTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
DefaultFallback.defaultProps = {
|
DefaultFallback.defaultProps = {
|
||||||
resetErrorBoundary: noop
|
resetErrorBoundary: noop,
|
||||||
};
|
};
|
||||||
|
|
||||||
const ErrorBoundary = ({
|
const ErrorBoundary = ({
|
||||||
@ -42,7 +65,7 @@ const ErrorBoundary = ({
|
|||||||
fallbackComponent: FallbackComponent,
|
fallbackComponent: FallbackComponent,
|
||||||
children,
|
children,
|
||||||
fallbackRoute,
|
fallbackRoute,
|
||||||
isPage
|
isPage,
|
||||||
}) => {
|
}) => {
|
||||||
const [isOpen, setIsOpen] = useState(true);
|
const [isOpen, setIsOpen] = useState(true);
|
||||||
|
|
||||||
@ -53,7 +76,7 @@ const ErrorBoundary = ({
|
|||||||
|
|
||||||
const onResetHandler = (...args) => onReset(...args);
|
const onResetHandler = (...args) => onReset(...args);
|
||||||
|
|
||||||
const withModal = (Component) => props => (
|
const withModal = Component => props => (
|
||||||
<Modal
|
<Modal
|
||||||
closeButton
|
closeButton
|
||||||
shouldCloseOnEsc
|
shouldCloseOnEsc
|
||||||
@ -75,11 +98,7 @@ const ErrorBoundary = ({
|
|||||||
return (
|
return (
|
||||||
<ReactErrorBoundary
|
<ReactErrorBoundary
|
||||||
fallbackRender={props => (
|
fallbackRender={props => (
|
||||||
<Fallback
|
<Fallback {...props} context={context} fallbackRoute={fallbackRoute} />
|
||||||
{...props}
|
|
||||||
context={context}
|
|
||||||
fallbackRoute={fallbackRoute}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
onReset={onResetHandler}
|
onReset={onResetHandler}
|
||||||
onError={onErrorHandler}
|
onError={onErrorHandler}
|
||||||
@ -95,7 +114,7 @@ ErrorBoundary.propTypes = {
|
|||||||
onError: PropTypes.func,
|
onError: PropTypes.func,
|
||||||
fallbackComponent: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
|
fallbackComponent: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
|
||||||
children: PropTypes.node.isRequired,
|
children: PropTypes.node.isRequired,
|
||||||
fallbackRoute: PropTypes.string
|
fallbackRoute: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
ErrorBoundary.defaultProps = {
|
ErrorBoundary.defaultProps = {
|
||||||
@ -103,7 +122,7 @@ ErrorBoundary.defaultProps = {
|
|||||||
onReset: noop,
|
onReset: noop,
|
||||||
onError: noop,
|
onError: noop,
|
||||||
fallbackComponent: DefaultFallback,
|
fallbackComponent: DefaultFallback,
|
||||||
fallbackRoute: null
|
fallbackRoute: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ErrorBoundary;
|
export default ErrorBoundary;
|
||||||
|
|||||||
@ -201,6 +201,14 @@ const ICONS = {
|
|||||||
'old-stop': oldStop,
|
'old-stop': oldStop,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function addIcon(iconName, iconSVG) {
|
||||||
|
if (ICONS[iconName]) {
|
||||||
|
console.warn(`Icon ${iconName} already exists.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
ICONS[iconName] = iconSVG;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the matching SVG Icon as a React Component.
|
* Return the matching SVG Icon as a React Component.
|
||||||
* Results in an inlined SVG Element. If there's no match,
|
* Results in an inlined SVG Element. If there's no match,
|
||||||
@ -214,4 +222,4 @@ export default function getIcon(key, props) {
|
|||||||
return React.createElement(ICONS[key], props);
|
return React.createElement(ICONS[key], props);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { getIcon, ICONS };
|
export { getIcon, ICONS, addIcon };
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import Dialog from './Dialog';
|
|||||||
import Dropdown from './Dropdown';
|
import Dropdown from './Dropdown';
|
||||||
import EmptyStudies from './EmptyStudies';
|
import EmptyStudies from './EmptyStudies';
|
||||||
import ErrorBoundary from './ErrorBoundary';
|
import ErrorBoundary from './ErrorBoundary';
|
||||||
import Icon from './Icon';
|
import Icon, { addIcon } from './Icon';
|
||||||
import IconButton from './IconButton';
|
import IconButton from './IconButton';
|
||||||
import Input from './Input';
|
import Input from './Input';
|
||||||
import InputDateRange from './InputDateRange';
|
import InputDateRange from './InputDateRange';
|
||||||
@ -89,6 +89,7 @@ export {
|
|||||||
ExpandableToolbarButton,
|
ExpandableToolbarButton,
|
||||||
ListMenu,
|
ListMenu,
|
||||||
Icon,
|
Icon,
|
||||||
|
addIcon,
|
||||||
IconButton,
|
IconButton,
|
||||||
Input,
|
Input,
|
||||||
InputDateRange,
|
InputDateRange,
|
||||||
|
|||||||
@ -107,7 +107,7 @@ export {
|
|||||||
} from './components';
|
} from './components';
|
||||||
|
|
||||||
/** These are mostly used in the docs */
|
/** These are mostly used in the docs */
|
||||||
export { getIcon, ICONS } from './components/Icon/getIcon';
|
export { getIcon, ICONS, addIcon } from './components/Icon/getIcon';
|
||||||
export { BackgroundColor } from './pages/Colors/BackgroundColor';
|
export { BackgroundColor } from './pages/Colors/BackgroundColor';
|
||||||
export { ModalComponent } from './contextProviders/ModalComponent';
|
export { ModalComponent } from './contextProviders/ModalComponent';
|
||||||
export { Types };
|
export { Types };
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
3.7
|
3.8
|
||||||
Loading…
Reference in New Issue
Block a user