diff --git a/extensions/_example/src/index.js b/extensions/_example/src/index.js
index 6d1e82bac..c061a44af 100644
--- a/extensions/_example/src/index.js
+++ b/extensions/_example/src/index.js
@@ -11,7 +11,10 @@ export default {
* LIFECYCLE HOOKS
*/
- preRegistration({ serviceManager, configuration: extensionConfiguration }) {},
+ preRegistration({
+ servicesManager,
+ configuration: extensionConfiguration,
+ }) {},
/**
* MODULE GETTERS
diff --git a/extensions/cornerstone/src/index.js b/extensions/cornerstone/src/index.js
index 5451f4041..6470382c9 100644
--- a/extensions/cornerstone/src/index.js
+++ b/extensions/cornerstone/src/index.js
@@ -24,8 +24,8 @@ export default {
* @param {object} [configuration={}]
* @param {object|array} [configuration.csToolsConfig] - Passed directly to `initCornerstoneTools`
*/
- preRegistration({ serviceManager, configuration = {} }) {
- init({ serviceManager, configuration });
+ preRegistration({ servicesManager, configuration = {} }) {
+ init({ servicesManager, configuration });
},
getViewportModule() {
return OHIFCornerstoneViewport;
diff --git a/extensions/cornerstone/src/init.js b/extensions/cornerstone/src/init.js
index 0795e2034..aee850564 100644
--- a/extensions/cornerstone/src/init.js
+++ b/extensions/cornerstone/src/init.js
@@ -28,7 +28,7 @@ cornerstone.metaData.addProvider(fallbackMetaDataProvider, -1);
* @param {object} configuration
* @param {Object|Array} configuration.csToolsConfig
*/
-export default function init({ serviceManager, configuration = {} }) {
+export default function init({ servicesManager, configuration = {} }) {
const { csToolsConfig } = configuration;
const { StackManager } = OHIF.utils;
const metadataProvider = new OHIF.cornerstone.MetadataProvider();
diff --git a/platform/core/src/extensions/ExtensionManager.js b/platform/core/src/extensions/ExtensionManager.js
index ddb702485..a6ceb7c36 100644
--- a/platform/core/src/extensions/ExtensionManager.js
+++ b/platform/core/src/extensions/ExtensionManager.js
@@ -68,7 +68,7 @@ export default class ExtensionManager {
// preRegistrationHook
if (extension.preRegistration) {
extension.preRegistration({
- serviceManager: this._servicesManager,
+ servicesManager: this._servicesManager,
configuration,
});
}
diff --git a/platform/core/src/index.js b/platform/core/src/index.js
index 6cec02db8..cb482e505 100644
--- a/platform/core/src/index.js
+++ b/platform/core/src/index.js
@@ -19,7 +19,7 @@ import ui from './ui';
import user from './user.js';
import utils from './utils/';
-import { createUiNotificationService } from './services';
+import { createUINotificationService, createUIModalService } from './services';
const OHIF = {
MODULE_TYPES,
@@ -46,7 +46,8 @@ const OHIF = {
measurements,
hangingProtocols,
//
- createUiNotificationService,
+ createUINotificationService,
+ createUIModalService,
};
export {
@@ -73,7 +74,8 @@ export {
measurements,
hangingProtocols,
//
- createUiNotificationService,
+ createUINotificationService,
+ createUIModalService,
};
export { OHIF };
diff --git a/platform/core/src/index.test.js b/platform/core/src/index.test.js
index a017a8a39..b06073e12 100644
--- a/platform/core/src/index.test.js
+++ b/platform/core/src/index.test.js
@@ -10,7 +10,8 @@ describe('Top level exports', () => {
'HotkeysManager',
'ServicesManager',
//
- 'createUiNotificationService',
+ 'createUINotificationService',
+ 'createUIModalService',
//
'utils',
'studies',
diff --git a/platform/core/src/services/UIModalService/index.js b/platform/core/src/services/UIModalService/index.js
new file mode 100644
index 000000000..b93bccdb8
--- /dev/null
+++ b/platform/core/src/services/UIModalService/index.js
@@ -0,0 +1,88 @@
+/**
+ * A UI Element
+ *
+ * @typedef {ReactElement|HTMLElement} Modal
+ */
+
+/**
+ * UI Modal
+ *
+ * @typedef {Object} ModalProps
+ * @property {string} [header=null] -
+ * @property {string} [footer=null] -
+ * @property {string} [backdrop=false] -
+ * @property {string} [keyboard=false] -
+ * @property {number} [show=true] -
+ * @property {string} [closeButton=true] -
+ * @property {string} [title=null] - 'Modal Title'
+ * @property {boolean} [customClassName=null] - '.ModalClass'
+ */
+
+const uiModalServicePublicAPI = {
+ name: 'UIModalService',
+ hide,
+ show,
+ setServiceImplementation,
+};
+
+const uiModalServiceImplementation = {
+ _hide: () => console.warn('hide() NOT IMPLEMENTED'),
+ _show: () => console.warn('show() NOT IMPLEMENTED'),
+};
+
+function createUIModalService() {
+ return uiModalServicePublicAPI;
+}
+
+/**
+ * Show a new UI modal;
+ *
+ * @param {Modal} component React component
+ * @param {ModalProps} props { header, footer, backdrop, keyboard, show, closeButton, title, customClassName }
+ */
+function show(
+ component,
+ props = {
+ header: null,
+ footer: null,
+ backdrop: false,
+ keyboard: false,
+ show: true,
+ closeButton: true,
+ title: null,
+ customClassName: null,
+ }
+) {
+ return uiModalServiceImplementation._show(component, props);
+}
+
+/**
+ * Hides/dismisses the modal, if currently shown
+ *
+ * @returns void
+ */
+function hide() {
+ return uiModalServiceImplementation._hide();
+}
+
+/**
+ *
+ *
+ * @param {*} {
+ * hide: hideImplementation,
+ * show: showImplementation,
+ * }
+ */
+function setServiceImplementation({
+ hide: hideImplementation,
+ show: showImplementation,
+}) {
+ if (hideImplementation) {
+ uiModalServiceImplementation._hide = hideImplementation;
+ }
+ if (showImplementation) {
+ uiModalServiceImplementation._show = showImplementation;
+ }
+}
+
+export default createUIModalService;
diff --git a/platform/core/src/services/UINotificationService/index.js b/platform/core/src/services/UINotificationService/index.js
index 97db61040..7538a6280 100644
--- a/platform/core/src/services/UINotificationService/index.js
+++ b/platform/core/src/services/UINotificationService/index.js
@@ -10,7 +10,7 @@
* @property {boolean} [autoClose=true]
*/
-const uiNotificationServicePublicApi = {
+const uiNotificationServicePublicAPI = {
name: 'UINotificationService',
hide,
show,
@@ -22,8 +22,8 @@ const uiNotificationServiceImplementation = {
_show: () => console.warn('show() NOT IMPLEMENTED'),
};
-function createUiNotificationService() {
- return uiNotificationServicePublicApi;
+function createUINotificationService() {
+ return uiNotificationServicePublicAPI;
}
/**
@@ -81,4 +81,4 @@ function setServiceImplementation({
}
}
-export default createUiNotificationService;
+export default createUINotificationService;
diff --git a/platform/core/src/services/index.js b/platform/core/src/services/index.js
index ded69e822..033dee202 100644
--- a/platform/core/src/services/index.js
+++ b/platform/core/src/services/index.js
@@ -1,4 +1,5 @@
import ServicesManager from './ServicesManager.js';
-import createUiNotificationService from './UINotificationService';
+import createUINotificationService from './UINotificationService';
+import createUIModalService from './UIModalService';
-export { createUiNotificationService, ServicesManager };
+export { createUINotificationService, createUIModalService, ServicesManager };
diff --git a/platform/ui/src/components/ohifModal/OHIFModal.js b/platform/ui/src/components/ohifModal/OHIFModal.js
index f69c9376e..483c650f9 100644
--- a/platform/ui/src/components/ohifModal/OHIFModal.js
+++ b/platform/ui/src/components/ohifModal/OHIFModal.js
@@ -13,7 +13,7 @@ const OHIFModal = ({
onHide,
footer: Footer,
header: Header,
- children: Component,
+ children,
}) => (
}
)}
-
- {Component && }
-
+ {children}
{Footer && (
- {' '}
)}
@@ -52,9 +49,20 @@ OHIFModal.propTypes = {
show: PropTypes.bool,
title: PropTypes.string,
onHide: PropTypes.func,
- footer: PropTypes.node,
- header: PropTypes.node,
- children: PropTypes.node,
+ footer: PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.node),
+ PropTypes.node,
+ PropTypes.func,
+ ]),
+ header: PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.node),
+ PropTypes.node,
+ PropTypes.func,
+ ]),
+ children: PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.node),
+ PropTypes.node,
+ ]).isRequired,
};
export default OHIFModal;
diff --git a/platform/ui/src/utils/ModalProvider.js b/platform/ui/src/utils/ModalProvider.js
index c1734cfb1..0b752cfaf 100644
--- a/platform/ui/src/utils/ModalProvider.js
+++ b/platform/ui/src/utils/ModalProvider.js
@@ -1,15 +1,23 @@
-import React, { useState, createContext, useContext } from 'react';
+import React, {
+ useState,
+ createContext,
+ useContext,
+ useEffect,
+ useCallback,
+} from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
const ModalContext = createContext(null);
-const { Provider, Consumer } = ModalContext;
+const { Provider } = ModalContext;
export const useModal = () => useContext(ModalContext);
-const ModalProvider = ({ children, modal: Modal }) => {
+const ModalProvider = ({ children, modal: Modal, service }) => {
const DEFAULT_OPTIONS = {
component: null /* The component instance inside the modal. */,
+ header: null /* The content inside the modal header. */,
+ footer: null /* The content inside the modal footer. */,
backdrop: false /* Should the modal render a backdrop overlay. */,
keyboard: false /* Modal is dismissible via the esc key. */,
show: true /* Make the Modal visible or hidden. */,
@@ -20,51 +28,81 @@ const ModalProvider = ({ children, modal: Modal }) => {
const [options, setOptions] = useState(DEFAULT_OPTIONS);
+ /**
+ * Sets the implementation of a modal service that can be used by extensions.
+ *
+ * @returns void
+ */
+ useEffect(() => {
+ if (service) {
+ service.setServiceImplementation({ hide, show });
+ }
+ }, [hide, service, show]);
+
/**
* Show the modal and override its configuration props.
*
* @returns void
*/
- const show = (component, props = {}) =>
- setOptions(Object.assign({}, options, props, { component }));
+ const show = useCallback(
+ (component, props = {}) =>
+ setOptions(Object.assign({}, options, props, { component })),
+ [options]
+ );
/**
* Hide the modal and set its properties to default.
*
* @returns void
*/
- const hide = () => setOptions(DEFAULT_OPTIONS);
+ const hide = useCallback(() => setOptions(DEFAULT_OPTIONS), [
+ DEFAULT_OPTIONS,
+ ]);
+
+ const { component: Component } = options;
return (
-
-
- {props => {
- const { component, footer, header, customClassName } = props;
- return component ? (
-
- {component}
-
- ) : null;
- }}
-
+
+ {options.component && (
+
+
+
+ )}
{children}
);
};
+ModalProvider.defaultProps = {
+ service: null,
+};
+
ModalProvider.propTypes = {
- children: PropTypes.node,
- modal: PropTypes.node,
+ children: PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.node),
+ PropTypes.node,
+ ]).isRequired,
+ modal: PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.node),
+ PropTypes.node,
+ PropTypes.func,
+ ]).isRequired,
+ service: PropTypes.shape({
+ setServiceImplementation: PropTypes.func,
+ }),
};
/**
@@ -74,7 +112,8 @@ ModalProvider.propTypes = {
*/
export const withModal = Component => {
return function WrappedComponent(props) {
- return ;
+ const { show, hide } = useModal();
+ return ;
};
};
diff --git a/platform/ui/src/utils/SnackbarProvider.js b/platform/ui/src/utils/SnackbarProvider.js
index 4151aec53..cd8f3b1e0 100644
--- a/platform/ui/src/utils/SnackbarProvider.js
+++ b/platform/ui/src/utils/SnackbarProvider.js
@@ -5,6 +5,8 @@ import React, {
useCallback,
useEffect,
} from 'react';
+import PropTypes from 'prop-types';
+
import SnackbarContainer from '../components/snackbar/SnackbarContainer';
import SnackbarTypes from '../components/snackbar/SnackbarTypes';
@@ -25,49 +27,62 @@ const SnackbarProvider = ({ children, service }) => {
const [count, setCount] = useState(1);
const [snackbarItems, setSnackbarItems] = useState([]);
+ /**
+ * Sets the implementation of a notification service that can be used by extensions.
+ *
+ * @returns void
+ */
useEffect(() => {
- service.setServiceImplementation({ hide, show });
+ if (service) {
+ service.setServiceImplementation({ hide, show });
+ }
}, [service, hide, show]);
- const show = useCallback(options => {
- if (!options || (!options.title && !options.message)) {
- console.warn(
- 'Snackbar cannot be rendered without required parameters: title | message'
- );
+ const show = useCallback(
+ options => {
+ if (!options || (!options.title && !options.message)) {
+ console.warn(
+ 'Snackbar cannot be rendered without required parameters: title | message'
+ );
- return null;
- }
+ return null;
+ }
- const newItem = {
- ...DEFAULT_OPTIONS,
- ...options,
- id: count,
- visible: true,
- };
+ const newItem = {
+ ...DEFAULT_OPTIONS,
+ ...options,
+ id: count,
+ visible: true,
+ };
- setSnackbarItems(state => [...state, newItem]);
- setCount(count + 1);
- });
+ setSnackbarItems(state => [...state, newItem]);
+ setCount(count + 1);
+ },
+ [count, DEFAULT_OPTIONS]
+ );
- const hide = useCallback(id => {
- const hideItem = items => {
- const newItems = items.map(item => {
- if (item.id === id) {
- item.visible = false;
- }
+ const hide = useCallback(
+ id => {
+ const hideItem = items => {
+ const newItems = items.map(item => {
+ if (item.id === id) {
+ item.visible = false;
+ }
- return item;
- });
+ return item;
+ });
- return newItems;
- };
+ return newItems;
+ };
- setSnackbarItems(state => hideItem(state));
+ setSnackbarItems(state => hideItem(state));
- setTimeout(() => {
- setSnackbarItems(state => [...state.filter(item => item.id !== id)]);
- }, 1000);
- });
+ setTimeout(() => {
+ setSnackbarItems(state => [...state.filter(item => item.id !== id)]);
+ }, 1000);
+ },
+ [setSnackbarItems]
+ );
const hideAll = () => {
// reset count
@@ -95,6 +110,21 @@ const SnackbarProvider = ({ children, service }) => {
);
};
+SnackbarProvider.defaultProps = {
+ service: null,
+};
+
+SnackbarProvider.propTypes = {
+ children: PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.node),
+ PropTypes.node,
+ PropTypes.func,
+ ]).isRequired,
+ service: PropTypes.shape({
+ setServiceImplementation: PropTypes.func,
+ }),
+};
+
/**
*
* High Order Component to use the snackbar methods through a Class Component
diff --git a/platform/viewer/src/App.js b/platform/viewer/src/App.js
index 862a2c2ba..c496b5428 100644
--- a/platform/viewer/src/App.js
+++ b/platform/viewer/src/App.js
@@ -8,7 +8,8 @@ import {
ExtensionManager,
ServicesManager,
HotkeysManager,
- createUiNotificationService,
+ createUINotificationService,
+ createUIModalService,
utils,
} from '@ohif/core';
import React, { Component } from 'react';
@@ -44,7 +45,8 @@ const commandsManagerConfig = {
};
// Services
-const UINotificationService = createUiNotificationService();
+const UINotificationService = createUINotificationService();
+const UIModalService = createUIModalService();
const commandsManager = new CommandsManager(commandsManagerConfig);
const hotkeysManager = new HotkeysManager(commandsManager);
@@ -89,7 +91,7 @@ class App extends Component {
const { servers, extensions, hotkeys, oidc } = props;
this.initUserManager(oidc);
- _initServices([UINotificationService]);
+ _initServices([UINotificationService, UIModalService]);
_initExtensions(extensions, hotkeys);
_initServers(servers);
initWebWorkers();
@@ -112,7 +114,10 @@ class App extends Component {
-
+
@@ -133,7 +138,7 @@ class App extends Component {
-
+
diff --git a/platform/viewer/src/appExtensions/MeasurementsPanel/index.js b/platform/viewer/src/appExtensions/MeasurementsPanel/index.js
index 52a89b961..3ce151015 100644
--- a/platform/viewer/src/appExtensions/MeasurementsPanel/index.js
+++ b/platform/viewer/src/appExtensions/MeasurementsPanel/index.js
@@ -7,8 +7,8 @@ export default {
*/
id: 'measurements-table',
- preRegistration({ serviceManager, configuration = {} }) {
- init({ serviceManager, configuration });
+ preRegistration({ servicesManager, configuration = {} }) {
+ init({ servicesManager, configuration });
},
getPanelModule() {
return {
diff --git a/platform/viewer/src/appExtensions/MeasurementsPanel/init.js b/platform/viewer/src/appExtensions/MeasurementsPanel/init.js
index e957c6d28..0b1067ee0 100644
--- a/platform/viewer/src/appExtensions/MeasurementsPanel/init.js
+++ b/platform/viewer/src/appExtensions/MeasurementsPanel/init.js
@@ -34,7 +34,7 @@ const MEASUREMENT_ACTION_MAP = {
* @export
* @param {*} configuration
*/
-export default function init({ serviceManager, configuration = {} }) {
+export default function init({ servicesManager, configuration = {} }) {
// If these tools were already added by a different extension, we want to replace
// them with the same tools that have an alternative configuration. By passing in
// our custom `getMeasurementLocationCallback`, we can...
diff --git a/platform/viewer/src/components/Header/Header.js b/platform/viewer/src/components/Header/Header.js
index 882c065d5..9da9f401f 100644
--- a/platform/viewer/src/components/Header/Header.js
+++ b/platform/viewer/src/components/Header/Header.js
@@ -20,7 +20,7 @@ class Header extends Component {
t: PropTypes.func.isRequired,
userManager: PropTypes.object,
user: PropTypes.object,
- modalContext: PropTypes.object,
+ modal: PropTypes.object,
};
static defaultProps = {
@@ -40,7 +40,7 @@ class Header extends Component {
t,
user,
userManager,
- modalContext: { show },
+ modal: { show },
} = this.props;
this.options = [
{
diff --git a/platform/viewer/src/connectedComponents/ToolbarRow.js b/platform/viewer/src/connectedComponents/ToolbarRow.js
index fe13d3919..007da4335 100644
--- a/platform/viewer/src/connectedComponents/ToolbarRow.js
+++ b/platform/viewer/src/connectedComponents/ToolbarRow.js
@@ -287,7 +287,7 @@ function _handleBuiltIn({ behavior } = {}) {
}
if (behavior === 'DOWNLOAD_SCREEN_SHOT') {
- this.props.modalContext.show(ConnectedViewportDownloadForm, {
+ this.props.modal.show(ConnectedViewportDownloadForm, {
title: this.props.t('Download High Quality Image'),
customClassName: 'ViewportDownloadForm',
});