feat: expose UiNotifications service (#1172)

* playing around

* Change extension init and preRegistration signature

* Clear test code

* Update core index test

* Fix test

* Tricking the bug

* Renaming file back to trick some weird bug
This commit is contained in:
Igor Octaviano 2019-11-13 17:02:14 -03:00 committed by Danny Brown
parent c59c5b3f14
commit 5c04e34c8f
13 changed files with 165 additions and 25 deletions

View File

@ -11,7 +11,7 @@ export default {
* LIFECYCLE HOOKS
*/
preRegistration(extensionConfiguration) {},
preRegistration({ serviceManager, configuration: extensionConfiguration }) {},
/**
* MODULE GETTERS

View File

@ -24,8 +24,8 @@ export default {
* @param {object} [configuration={}]
* @param {object|array} [configuration.csToolsConfig] - Passed directly to `initCornerstoneTools`
*/
preRegistration(configuration = {}) {
init(configuration);
preRegistration({ serviceManager, configuration = {} }) {
init({ serviceManager, configuration });
},
getViewportModule() {
return OHIFCornerstoneViewport;

View File

@ -4,16 +4,18 @@ import csTools from 'cornerstone-tools';
import initCornerstoneTools from './initCornerstoneTools.js';
import queryString from 'query-string';
function fallbackMetaDataProvider (type, imageId) {
function fallbackMetaDataProvider(type, imageId) {
if (!imageId.includes('wado?requestType=WADO')) {
return
return;
}
// If you call for an WADO-URI imageId and get no
// If you call for an WADO-URI imageId and get no
// metadata, try reformatting to WADO-RS imageId
const qs = queryString.parse(imageId);
const wadoRoot = window.store.getState().servers.servers[0].wadoRoot
const wadoRsImageId = `wadors:${wadoRoot}/studies/${qs.studyUID}/series/${qs.seriesUID}/instances/${qs.objectUID}/frames/${qs.frame || 1}`;
const wadoRoot = window.store.getState().servers.servers[0].wadoRoot;
const wadoRsImageId = `wadors:${wadoRoot}/studies/${qs.studyUID}/series/${
qs.seriesUID
}/instances/${qs.objectUID}/frames/${qs.frame || 1}`;
return cornerstone.metaData.get(type, wadoRsImageId);
}
@ -21,13 +23,12 @@ function fallbackMetaDataProvider (type, imageId) {
// Add this fallback provider with a low priority so it is handled last
cornerstone.metaData.addProvider(fallbackMetaDataProvider, -1);
/**
*
* @param {object} configuration
* @param {Object|Array} configuration.csToolsConfig
*/
export default function init(configuration = {}) {
export default function init({ serviceManager, configuration = {} }) {
const { csToolsConfig } = configuration;
const { StackManager } = OHIF.utils;
const metadataProvider = new OHIF.cornerstone.MetadataProvider();
@ -97,6 +98,7 @@ export default function init(configuration = {}) {
];
tools.forEach(tool => csTools.addTool(tool));
csTools.setToolActive('Pan', { mouseButtonMask: 4 });
csTools.setToolActive('Zoom', { mouseButtonMask: 2 });
csTools.setToolActive('Wwwc', { mouseButtonMask: 1 });

View File

@ -2,12 +2,13 @@ import MODULE_TYPES from './MODULE_TYPES.js';
import log from './../log.js';
export default class ExtensionManager {
constructor({ commandsManager }) {
constructor({ commandsManager, servicesManager }) {
this.modules = {};
this.registeredExtensionIds = [];
this.moduleTypeNames = Object.values(MODULE_TYPES);
//
this._commandsManager = commandsManager;
this._servicesManager = servicesManager;
this.moduleTypeNames.forEach(moduleType => {
this.modules[moduleType] = [];
@ -66,7 +67,10 @@ export default class ExtensionManager {
// preRegistrationHook
if (extension.preRegistration) {
extension.preRegistration(configuration);
extension.preRegistration({
serviceManager: this._servicesManager,
configuration,
});
}
// Register Modules

View File

@ -1,6 +1,7 @@
import './lib';
import { ExtensionManager, MODULE_TYPES } from './extensions';
import { ServicesManager } from './services';
import classes, { CommandsManager, HotkeysManager } from './classes/';
import DICOMWeb from './DICOMWeb';
@ -18,12 +19,15 @@ import ui from './ui';
import user from './user.js';
import utils from './utils/';
import { createUiNotificationService } from './services';
const OHIF = {
MODULE_TYPES,
//
CommandsManager,
ExtensionManager,
HotkeysManager,
ServicesManager,
//
utils,
studies,
@ -41,6 +45,8 @@ const OHIF = {
viewer: {},
measurements,
hangingProtocols,
//
createUiNotificationService,
};
export {
@ -49,6 +55,7 @@ export {
CommandsManager,
ExtensionManager,
HotkeysManager,
ServicesManager,
//
utils,
studies,
@ -65,6 +72,8 @@ export {
DICOMWeb,
measurements,
hangingProtocols,
//
createUiNotificationService,
};
export { OHIF };

View File

@ -8,6 +8,9 @@ describe('Top level exports', () => {
'CommandsManager',
'ExtensionManager',
'HotkeysManager',
'ServicesManager',
//
'createUiNotificationService',
//
'utils',
'studies',

View File

@ -0,0 +1,9 @@
export default class ServicesManager {
constructor() {
this.services = {};
}
register(service) {
this.services[service.name] = service;
}
}

View File

@ -0,0 +1,84 @@
/**
* A UI Notification
*
* @typedef {Object} Notification
* @property {string} title -
* @property {string} message -
* @property {number} [duration=5000] - in ms
* @property {string} [position="bottomRight"] -"topLeft" | "topCenter | "topRight" | "bottomLeft" | "bottomCenter" | "bottomRight"
* @property {string} [type="info"] - "info" | "error" | "warning" | "success"
* @property {boolean} [autoClose=true]
*/
const uiNotificationServicePublicApi = {
name: 'UINotificationService',
hide,
show,
setServiceImplementation,
};
const uiNotificationServiceImplementation = {
_hide: () => console.warn('hide() NOT IMPLEMENTED'),
_show: () => console.warn('show() NOT IMPLEMENTED'),
};
function createUiNotificationService() {
return uiNotificationServicePublicApi;
}
/**
* Create and show a new UI notification; returns the
* ID of the created notification.
*
* @param {Notification} notification { title, message, duration, position, type, autoClose}
* @returns {number} id
*/
function show({
title,
message,
duration = 5000,
position = 'bottomRight',
type = 'info',
autoClose = true,
}) {
return uiNotificationServiceImplementation._show({
title,
message,
duration,
position,
type,
autoClose,
});
}
/**
* Hides/dismisses the notification, if currently shown
*
* @param {number} id - id of the notification to hide/dismiss
* @returns undefined
*/
function hide(id) {
return uiNotificationServiceImplementation._hide({ id });
}
/**
*
*
* @param {*} {
* hide: hideImplementation,
* show: showImplementation,
* }
*/
function setServiceImplementation({
hide: hideImplementation,
show: showImplementation,
}) {
if (hideImplementation) {
uiNotificationServiceImplementation._hide = hideImplementation;
}
if (showImplementation) {
uiNotificationServiceImplementation._show = showImplementation;
}
}
export default createUiNotificationService;

View File

@ -0,0 +1,4 @@
import ServicesManager from './ServicesManager.js';
import createUiNotificationService from './UINotificationService';
export { createUiNotificationService, ServicesManager };

View File

@ -1,4 +1,10 @@
import React, { useState, createContext, useContext } from 'react';
import React, {
useState,
createContext,
useContext,
useCallback,
useEffect,
} from 'react';
import SnackbarContainer from '../components/snackbar/SnackbarContainer';
import SnackbarTypes from '../components/snackbar/SnackbarTypes';
@ -6,7 +12,7 @@ const SnackbarContext = createContext(null);
export const useSnackbarContext = () => useContext(SnackbarContext);
const SnackbarProvider = ({ children }) => {
const SnackbarProvider = ({ children, service }) => {
const DEFAULT_OPTIONS = {
title: '',
message: '',
@ -19,7 +25,11 @@ const SnackbarProvider = ({ children }) => {
const [count, setCount] = useState(1);
const [snackbarItems, setSnackbarItems] = useState([]);
const show = options => {
useEffect(() => {
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'
@ -37,9 +47,9 @@ const SnackbarProvider = ({ children }) => {
setSnackbarItems(state => [...state, newItem]);
setCount(count + 1);
};
});
const hide = id => {
const hide = useCallback(id => {
const hideItem = items => {
const newItems = items.map(item => {
if (item.id === id) {
@ -57,7 +67,7 @@ const SnackbarProvider = ({ children }) => {
setTimeout(() => {
setSnackbarItems(state => [...state.filter(item => item.id !== id)]);
}, 1000);
};
});
const hideAll = () => {
// reset count

View File

@ -6,7 +6,9 @@ import './config';
import {
CommandsManager,
ExtensionManager,
ServicesManager,
HotkeysManager,
createUiNotificationService,
utils,
} from '@ohif/core';
import React, { Component } from 'react';
@ -41,9 +43,16 @@ const commandsManagerConfig = {
getActiveContexts: () => getActiveContexts(store.getState()),
};
// Services
const UINotificationService = createUiNotificationService();
const commandsManager = new CommandsManager(commandsManagerConfig);
const hotkeysManager = new HotkeysManager(commandsManager);
const extensionManager = new ExtensionManager({ commandsManager });
const servicesManager = new ServicesManager();
const extensionManager = new ExtensionManager({
commandsManager,
servicesManager,
});
// ~~~~ END APP SETUP
// TODO[react] Use a provider when the whole tree is React
@ -76,9 +85,11 @@ class App extends Component {
super(props);
this._appConfig = props;
const { servers, extensions, hotkeys, oidc } = props;
this.initUserManager(oidc);
_initServices([UINotificationService]);
_initExtensions(extensions, hotkeys);
_initServers(servers);
initWebWorkers();
@ -100,7 +111,7 @@ class App extends Component {
<UserManagerContext.Provider value={userManager}>
<Router basename={routerBasename}>
<WhiteLabellingContext.Provider value={whiteLabelling}>
<SnackbarProvider>
<SnackbarProvider service={UINotificationService}>
<ModalProvider modal={OHIFModal}>
<OHIFStandaloneViewer userManager={userManager} />
</ModalProvider>
@ -121,7 +132,7 @@ class App extends Component {
<I18nextProvider i18n={i18n}>
<Router basename={routerBasename}>
<WhiteLabellingContext.Provider value={whiteLabelling}>
<SnackbarProvider>
<SnackbarProvider service={UINotificationService}>
<ModalProvider modal={OHIFModal}>
<OHIFStandaloneViewer />
</ModalProvider>
@ -168,6 +179,10 @@ class App extends Component {
}
}
function _initServices(services) {
services.forEach(service => servicesManager.register(service));
}
/**
* @param
*/
@ -214,4 +229,4 @@ function _makeAbsoluteIfNecessary(url, base_url) {
const ExportedApp = process.env.NODE_ENV === 'development' ? hot(App) : App;
export default ExportedApp;
export { commandsManager, extensionManager, hotkeysManager };
export { commandsManager, extensionManager, hotkeysManager, servicesManager };

View File

@ -7,8 +7,8 @@ export default {
*/
id: 'measurements-table',
preRegistration(configuration = {}) {
init(configuration);
preRegistration({ serviceManager, configuration = {} }) {
init({ serviceManager, configuration });
},
getPanelModule() {
return {

View File

@ -34,7 +34,7 @@ const MEASUREMENT_ACTION_MAP = {
* @export
* @param {*} configuration
*/
export default function init(configuration) {
export default function init({ serviceManager, 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...