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/CHANGELOG.md b/extensions/cornerstone/CHANGELOG.md index 2cd27731b..a6a6f7122 100644 --- a/extensions/cornerstone/CHANGELOG.md +++ b/extensions/cornerstone/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.5.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.5.0...@ohif/extension-cornerstone@1.5.1) (2019-11-15) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + # [1.5.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.4.1...@ohif/extension-cornerstone@1.5.0) (2019-11-13) diff --git a/extensions/cornerstone/package.json b/extensions/cornerstone/package.json index 574d23e3c..917cd9cad 100644 --- a/extensions/cornerstone/package.json +++ b/extensions/cornerstone/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-cornerstone", - "version": "1.5.0", + "version": "1.5.1", "description": "OHIF extension for Cornerstone", "author": "OHIF", "license": "MIT", 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/extensions/vtk/CHANGELOG.md b/extensions/vtk/CHANGELOG.md index 9d5f63aae..9a9acf56f 100644 --- a/extensions/vtk/CHANGELOG.md +++ b/extensions/vtk/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.53.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.53.7...@ohif/extension-vtk@0.53.8) (2019-11-15) + +**Note:** Version bump only for package @ohif/extension-vtk + + + + + +## [0.53.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.53.6...@ohif/extension-vtk@0.53.7) (2019-11-15) + +**Note:** Version bump only for package @ohif/extension-vtk + + + + + ## [0.53.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.53.5...@ohif/extension-vtk@0.53.6) (2019-11-14) **Note:** Version bump only for package @ohif/extension-vtk diff --git a/extensions/vtk/package.json b/extensions/vtk/package.json index 8df509ec4..edc9f72be 100644 --- a/extensions/vtk/package.json +++ b/extensions/vtk/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-vtk", - "version": "0.53.6", + "version": "0.53.8", "description": "OHIF extension for VTK.js", "author": "OHIF", "license": "MIT", @@ -52,8 +52,8 @@ "react-vtkjs-viewport": "^0.3.9" }, "devDependencies": { - "@ohif/core": "^1.9.0", - "@ohif/ui": "^0.62.1", + "@ohif/core": "^1.10.0", + "@ohif/ui": "^0.62.3", "cornerstone-tools": "^4.0.9", "cornerstone-wado-image-loader": "^3.0.0", "dcmjs": "^0.6.1", diff --git a/platform/core/CHANGELOG.md b/platform/core/CHANGELOG.md index 4b491a779..8805dc754 100644 --- a/platform/core/CHANGELOG.md +++ b/platform/core/CHANGELOG.md @@ -3,6 +3,25 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.10.0](https://github.com/OHIF/Viewers/compare/@ohif/core@1.9.1...@ohif/core@1.10.0) (2019-11-15) + + +### Features + +* Inject into Extension Modules / improve tests ([f63d8a7](https://github.com/OHIF/Viewers/commit/f63d8a73d867ad9dfd8ee0cad74edce180eb34f0)) + + + + + +## [1.9.1](https://github.com/OHIF/Viewers/compare/@ohif/core@1.9.0...@ohif/core@1.9.1) (2019-11-15) + +**Note:** Version bump only for package @ohif/core + + + + + # [1.9.0](https://github.com/OHIF/Viewers/compare/@ohif/core@1.8.0...@ohif/core@1.9.0) (2019-11-13) diff --git a/platform/core/package.json b/platform/core/package.json index 2723604f6..4f9720456 100644 --- a/platform/core/package.json +++ b/platform/core/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/core", - "version": "1.9.0", + "version": "1.10.0", "description": "Generic business logic for web-based medical imaging applications", "author": "OHIF Core Team", "license": "MIT", diff --git a/platform/core/src/extensions/ExtensionManager.js b/platform/core/src/extensions/ExtensionManager.js index ddb702485..e5adbb85f 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, }); } @@ -110,7 +110,9 @@ export default class ExtensionManager { } try { - const extensionModule = getModuleFn(); + const extensionModule = getModuleFn({ + servicesManager: this._servicesManager, + }); if (!extensionModule) { log.warn( diff --git a/platform/core/src/extensions/ExtensionManager.test.js b/platform/core/src/extensions/ExtensionManager.test.js index cfe892ef0..37b396e52 100644 --- a/platform/core/src/extensions/ExtensionManager.test.js +++ b/platform/core/src/extensions/ExtensionManager.test.js @@ -40,6 +40,30 @@ describe('ExtensionManager.js', () => { }); describe('registerExtension()', () => { + it('calls preRegistration() for extension', () => { + // SUT + const fakeExtension = { one: '1', preRegistration: jest.fn() }; + extensionManager.registerExtension(fakeExtension); + + // Assert + expect(fakeExtension.preRegistration.mock.calls.length).toBe(1); + }); + + it('calls preRegistration() passing configuration and servicesManager instance for extension', () => { + const configuration = { config: 'Some configuration' }; + extensionManager._servicesManager = { services: { TestService: {} } }; + + // SUT + const fakeExtension = { one: '1', preRegistration: jest.fn() }; + extensionManager.registerExtension(fakeExtension, configuration); + + // Assert + expect(fakeExtension.preRegistration.mock.calls[0][0]).toEqual({ + servicesManager: extensionManager._servicesManager, + configuration, + }); + }); + it('logs a warning if the extension is null or undefined', () => { const undefinedExtension = undefined; const nullExtension = null; @@ -110,6 +134,25 @@ describe('ExtensionManager.js', () => { ); }); + it('successfully passes a servicesManager instance to each module', () => { + extensionManager._servicesManager = { services: { TestService: {} } }; + + const extension = { + id: 'hello-world', + getViewportModule: jest.fn(), + getSopClassHandlerModule: jest.fn(), + getPanelModule: jest.fn(), + getToolbarModule: jest.fn(), + getCommandsModule: jest.fn(), + }; + + extensionManager.registerExtension(extension); + + expect(extension.getViewportModule.mock.calls[0][0]).toEqual({ + servicesManager: extensionManager._servicesManager, + }); + }); + it('successfully registers a module for each module type', () => { const extension = { id: 'hello-world', 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/ServicesManager.js b/platform/core/src/services/ServicesManager.js index 0e38e5695..4b71a580a 100644 --- a/platform/core/src/services/ServicesManager.js +++ b/platform/core/src/services/ServicesManager.js @@ -1,9 +1,49 @@ +import log from './../log.js'; + export default class ServicesManager { constructor() { this.services = {}; + this.registeredServiceNames = []; } - register(service) { + /** + * + * @param {Object} service + */ + registerService(service) { + if (!service) { + log.warn( + 'Attempting to register a null/undefined service. Exiting early.' + ); + return; + } + + let serviceName = service.name; + + if (!serviceName) { + log.warn(`Service name not set. Exiting early.`); + return; + } + + if (this.registeredServiceNames.includes(serviceName)) { + log.warn( + `Extension name ${serviceName} has already been registered. Exiting before duplicating services.` + ); + return; + } + this.services[service.name] = service; + + // Track service registration + this.registeredServiceNames.push(serviceName); + } + + /** + * An array of services. + * + * @param {Object[]} services - Array of services + */ + registerServices(services) { + services.forEach(service => this.registerService(service)); } } diff --git a/platform/core/src/services/ServicesManager.test.js b/platform/core/src/services/ServicesManager.test.js new file mode 100644 index 000000000..71ec25432 --- /dev/null +++ b/platform/core/src/services/ServicesManager.test.js @@ -0,0 +1,70 @@ +import ServicesManager from './ServicesManager.js'; +import log from '../log.js'; + +jest.mock('./../log.js'); + +describe('ServicesManager.js', () => { + let servicesManager; + + beforeEach(() => { + servicesManager = new ServicesManager(); + log.warn.mockClear(); + jest.clearAllMocks(); + }); + + describe('registerServices()', () => { + it('calls registerService() for each service', () => { + servicesManager.registerService = jest.fn(); + + const fakeServices = [ + { name: 'UINotificationTestService', hide: jest.fn() }, + { name: 'UIModalTestService', hide: jest.fn() }, + ]; + + servicesManager.registerServices(fakeServices); + + expect(servicesManager.registerService.mock.calls.length).toBe(2); + }); + }); + + describe('registerService()', () => { + it('logs a warning if the service is null or undefined', () => { + const undefinedService = undefined; + const nullService = null; + + servicesManager.registerService(undefinedService); + servicesManager.registerService(nullService); + + expect(log.warn.mock.calls.length).toBe(2); + }); + + it('logs a warning if the service does not have a name', () => { + const serviceWithEmptyName = { name: '', hide: jest.fn() }; + const serviceWithoutName = { hide: jest.fn() }; + + servicesManager.registerService(serviceWithEmptyName); + servicesManager.registerService(serviceWithoutName); + + expect(log.warn.mock.calls.length).toBe(2); + }); + + it('tracks which services have been registered', () => { + const service = { + name: 'UINotificationService', + }; + + servicesManager.registerService(service); + + expect(servicesManager.registeredServiceNames).toContain(service.name); + }); + + it('logs a warning if the service has an name that has already been registered', () => { + const service = { name: 'UINotificationService' }; + + servicesManager.registerService(service); + servicesManager.registerService(service); + + expect(log.warn.mock.calls.length).toBe(1); + }); + }); +}); 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/CHANGELOG.md b/platform/ui/CHANGELOG.md index 88e3da997..e9b03b77e 100644 --- a/platform/ui/CHANGELOG.md +++ b/platform/ui/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.62.3](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.62.2...@ohif/ui@0.62.3) (2019-11-15) + +**Note:** Version bump only for package @ohif/ui + + + + + +## [0.62.2](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.62.1...@ohif/ui@0.62.2) (2019-11-15) + +**Note:** Version bump only for package @ohif/ui + + + + + ## [0.62.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.62.0...@ohif/ui@0.62.1) (2019-11-14) **Note:** Version bump only for package @ohif/ui diff --git a/platform/ui/package.json b/platform/ui/package.json index 9b900bf70..9126bfc4a 100644 --- a/platform/ui/package.json +++ b/platform/ui/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/ui", - "version": "0.62.1", + "version": "0.62.3", "description": "A set of React components for Medical Imaging Viewers", "author": "OHIF Contributors", "license": "MIT", 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 && ( - {' '}