diff --git a/extensions/vtk/src/OHIFVTKViewport.js b/extensions/vtk/src/OHIFVTKViewport.js index 19e557143..bac284a4e 100644 --- a/extensions/vtk/src/OHIFVTKViewport.js +++ b/extensions/vtk/src/OHIFVTKViewport.js @@ -72,7 +72,7 @@ class OHIFVTKViewport extends Component { }; static defaultProps = { - onScroll: () => { }, + onScroll: () => {}, }; static id = 'OHIFVTKViewport'; @@ -138,6 +138,8 @@ class OHIFVTKViewport extends Component { SOPInstanceUID, frameIndex ) => { + const { UINotificationService } = this.props.servicesManager.services; + const stack = OHIFVTKViewport.getCornerstoneStack( studies, StudyInstanceUID, @@ -159,11 +161,14 @@ class OHIFVTKViewport extends Component { const { activeLabelmapIndex } = brushStackState; const labelmap3D = brushStackState.labelmaps3D[activeLabelmapIndex]; - if (brushStackState.labelmaps3D.length > 1 && this.props.viewportIndex === 0) { - const { UINotificationService } = this.props.servicesManager.services; + if ( + brushStackState.labelmaps3D.length > 1 && + this.props.viewportIndex === 0 + ) { UINotificationService.show({ title: 'Overlapping Segmentation Found', - message: 'Overlapping segmentations cannot be displayed when in MPR mode', + message: + 'Overlapping segmentations cannot be displayed when in MPR mode', type: 'info', }); } @@ -305,52 +310,79 @@ class OHIFVTKViewport extends Component { seriesDescription: displaySet.seriesDescription, }; - const { - imageDataObject, - labelmapDataObject, - labelmapColorLUT, - } = this.getViewportData( - studies, - StudyInstanceUID, - displaySetInstanceUID, - SOPInstanceUID, - frameIndex - ); + try { + const { + imageDataObject, + labelmapDataObject, + labelmapColorLUT, + } = this.getViewportData( + studies, + StudyInstanceUID, + displaySetInstanceUID, + SOPInstanceUID, + frameIndex + ); - this.imageDataObject = imageDataObject; + this.imageDataObject = imageDataObject; - /* TODO: Not currently used until we have drawing tools in vtkjs. - if (!labelmap) { - labelmap = createLabelMapImageData(data); - } */ + /* TODO: Not currently used until we have drawing tools in vtkjs. + if (!labelmap) { + labelmap = createLabelMapImageData(data); + } */ - const volumeActor = this.getOrCreateVolume( - imageDataObject, - displaySetInstanceUID - ); + const volumeActor = this.getOrCreateVolume( + imageDataObject, + displaySetInstanceUID + ); - this.setState( - { - percentComplete: 0, - dataDetails, - }, - () => { - this.loadProgressively(imageDataObject); + this.setState( + { + percentComplete: 0, + dataDetails, + }, + () => { + this.loadProgressively(imageDataObject); - // TODO: There must be a better way to do this. - // We do this so that if all the data is available the react-vtkjs-viewport - // Will render _something_ before the volumes are set and the volume - // Construction that happens in react-vtkjs-viewport locks up the CPU. - setTimeout(() => { - this.setState({ - volumes: [volumeActor], - paintFilterLabelMapImageData: labelmapDataObject, - paintFilterBackgroundImageData: imageDataObject.vtkImageData, - labelmapColorLUT, - }); - }, 200); + // TODO: There must be a better way to do this. + // We do this so that if all the data is available the react-vtkjs-viewport + // Will render _something_ before the volumes are set and the volume + // Construction that happens in react-vtkjs-viewport locks up the CPU. + setTimeout(() => { + this.setState({ + volumes: [volumeActor], + paintFilterLabelMapImageData: labelmapDataObject, + paintFilterBackgroundImageData: imageDataObject.vtkImageData, + labelmapColorLUT, + }); + }, 200); + } + ); + } catch (error) { + const errorTitle = 'Failed to load 2D MPR'; + console.error(errorTitle, error); + const { UINotificationService } = this.props.servicesManager.services; + if (this.props.viewportIndex === 0) { + const message = error.message.includes('buffer') + ? 'Dataset is too big to display in MPR' + : error.message; + console.error(errorTitle, error); + UINotificationService.show({ + title: errorTitle, + message, + type: 'error', + autoClose: false, + action: { + label: 'Exit 2D MPR', + onClick: ({ close }) => { + // context: 'ACTIVE_VIEWPORT::VTK', + close(); + this.props.commandsManager.runCommand('setCornerstoneLayout'); + }, + }, + }); } - ); + this.setState({ isLoaded: true }); + } } componentDidMount() { @@ -363,7 +395,7 @@ class OHIFVTKViewport extends Component { if ( displaySet.displaySetInstanceUID !== - prevDisplaySet.displaySetInstanceUID || + prevDisplaySet.displaySetInstanceUID || displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID || displaySet.frameIndex !== prevDisplaySet.frameIndex ) { diff --git a/extensions/vtk/src/index.js b/extensions/vtk/src/index.js index dde0d6ce0..18e69c22c 100644 --- a/extensions/vtk/src/index.js +++ b/extensions/vtk/src/index.js @@ -9,7 +9,9 @@ import { version } from '../package.json'; // import loadLocales from './loadLocales'; const OHIFVTKViewport = asyncComponent(() => - retryImport(() => import(/* webpackChunkName: "OHIFVTKViewport" */ './OHIFVTKViewport.js')) + retryImport(() => + import(/* webpackChunkName: "OHIFVTKViewport" */ './OHIFVTKViewport.js') + ) ); const vtkExtension = { @@ -21,7 +23,11 @@ const vtkExtension = { getViewportModule({ commandsManager, servicesManager }) { const ExtendedVTKViewport = props => ( - + ); return withCommandsManager(ExtendedVTKViewport, commandsManager); }, diff --git a/platform/core/src/services/UINotificationService/index.js b/platform/core/src/services/UINotificationService/index.js index 0cdb8ea95..42e4b0512 100644 --- a/platform/core/src/services/UINotificationService/index.js +++ b/platform/core/src/services/UINotificationService/index.js @@ -8,6 +8,7 @@ * @property {string} [position="bottomRight"] -"topLeft" | "topCenter | "topRight" | "bottomLeft" | "bottomCenter" | "bottomRight" * @property {string} [type="info"] - "info" | "error" | "warning" | "success" * @property {boolean} [autoClose=true] + * @property {object} [action=null] */ const name = 'UINotificationService'; @@ -34,7 +35,7 @@ const serviceImplementation = { * Create and show a new UI notification; returns the * ID of the created notification. * - * @param {Notification} notification { title, message, duration, position, type, autoClose} + * @param {Notification} notification { title, message, duration, position, type, autoClose, action} * @returns {number} id */ function _show({ @@ -44,6 +45,7 @@ function _show({ position = 'bottomRight', type = 'info', autoClose = true, + action = null, }) { return serviceImplementation._show({ title, @@ -52,6 +54,7 @@ function _show({ position, type, autoClose, + action, }); } diff --git a/platform/ui/src/components/snackbar/Snackbar.css b/platform/ui/src/components/snackbar/Snackbar.css index bb99cefe6..8d7399165 100644 --- a/platform/ui/src/components/snackbar/Snackbar.css +++ b/platform/ui/src/components/snackbar/Snackbar.css @@ -156,19 +156,50 @@ transition: all 300ms ease; } -.sb-success { +.sb-item .sb-action, +.sb-error .sb-action, +.sb-warning .sb-action, +.sb-info .sb-action, +.sb-success .sb-action { + margin-top: 10px; + display: inline-block; + padding: 6px 12px; + margin-bottom: 0; + font-size: 14px; + font-weight: 400; + line-height: 1.42857143; + text-align: center; + white-space: nowrap; + vertical-align: middle; + -ms-touch-action: manipulation; + touch-action: manipulation; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-image: none; + border: none; + color: white; +} + +.sb-success, +.sb-success .sb-action { background-color: var(--snackbar-success); } -.sb-error { +.sb-error, +.sb-error .sb-action { background-color: var(--snackbar-error); } -.sb-warning { +.sb-warning, +.sb-warning .sb-action { background-color: var(--snackbar-warning); } -.sb-info { +.sb-info, +.sb-info .sb-action { background-color: var(--snackbar-info); } diff --git a/platform/ui/src/components/snackbar/SnackbarItem.js b/platform/ui/src/components/snackbar/SnackbarItem.js index 40c43697e..2e1d62ccf 100644 --- a/platform/ui/src/components/snackbar/SnackbarItem.js +++ b/platform/ui/src/components/snackbar/SnackbarItem.js @@ -1,10 +1,14 @@ -import React, { useState, useEffect } from 'react'; +import React, { useEffect } from 'react'; const SnackbarItem = ({ options, onClose }) => { const handleClose = () => { onClose(options.id); }; + const handleClick = () => { + options.action.onClick({ ...options, close: handleClose }); + }; + useEffect(() => { if (options.autoClose) { setTimeout(() => { @@ -24,6 +28,11 @@ const SnackbarItem = ({ options, onClose }) => { {options.title &&
{options.title}
} {options.message &&
{options.message}
} + {options.action && ( + + )} ); }; diff --git a/platform/ui/src/contextProviders/SnackbarProvider.js b/platform/ui/src/contextProviders/SnackbarProvider.js index 4b4c6bcbd..86709fedc 100644 --- a/platform/ui/src/contextProviders/SnackbarProvider.js +++ b/platform/ui/src/contextProviders/SnackbarProvider.js @@ -22,6 +22,7 @@ const SnackbarProvider = ({ children, service }) => { autoClose: true, position: 'bottomRight', type: SnackbarTypes.INFO, + action: null, }; const [count, setCount] = useState(1);