feat!: Ability to configure cornerstone tools via extension configuration (#1229)

* Fix ExtensionManager bug and add test to bandaid

* Add tools configuration to extension manager preinit

* Fix reducing of configs

* Merge internal with external configs

* Merge internal with external configs

* Remove dialog from init in measurementstable

* Testing injected configuration

* New way to set config

* Add new prop to dialog provider to allow disabling last position

* Remove code from preinit in cornerstone

* Add new prop to dialog provider to allow disabling last position

* Add centralize to dialogs

* Reorder dialogs when adding them

* Fix draggable styles (cursor)

* Remove repositioning methods from labelling flow and remove overlay from labelling manager

* Fix empty array being set in bringToFront

* Add new command to update table and pass commands manager to modules/preinit hook

* Ad UIContextMenu service / factory

* Use new contextmenu service in measurementspanel extension

* Use dialogs for arrow annotate in default

* Remove positioning funcionality from tool context menu

* Add context menu service

* Pass commandsModule to extension

* Update edit description dialog and simple dialog to position relative

* Remove style code from labelling flow and manager

* Remove eventdata from labelling

* Remove labelling code from measurement init

* Add commandsmanager to provider

* Update contextmenu provider and service

* Add touchstart and mouseclick to hide contextmenu

* Hide labelling if click/touch

* Remove labelling and context menu dead code

* Fix undefined bug if ViewerMain grid has no children

* Fix broken prop on context menu

* Update commandsmodule based on master

* Fix broken configuration

* Update script tag config

* Remove cornerstone from toolcontextmenu

* Remove cornerstone from toolcontextmenu

* Split labelling and context menu providers

* Split labelling and context menu providers

* Update test

* Destructure extensions into new array

* CR Update: Move default arrow config to cornerstone instead of default

* CR Update: Fix app configuration props structure

* CR Update: Fix app configuration prop in script tag and extract commands manager from providers

* CR Update: Create custom providers to use commandsManager

* CR Update: Use services directly in measurementspanel

* CR Update: Pass components to providers

* CR Update: Remove position from dialog

* CR Update: fix dialog prop check

* CR Update: Fix comments

* CR Update: Update documentation

* CR Update: Add test default configuration

* CR Update: Add default empy array to extensions

* CR Update: Update i18n configuration all ot match current function configuration

* CR Update: Add defaults to injected dependencies in configuration and extension configuration

* CR Update: Add defaults to configuration with no args

* Update documentation

* CR Update: Add default for tools

* CR Update: Update config object to i18n

* CR Update: spread defaults

* CR Update: Add tool configuration example to cornerstone extension

* CR Update: Add tool configuration to netlify (testing)

* CR Update: Remove netlify config for tools

* CR Update: Rollback changes to i18n to be fixed later

* CR Update: Update documentation and pass whole cornerstone config object instead of tools key

SEE: https://www.conventionalcommits.org/en/v1.0.0/#commit-message-with-both-and-breaking-change-footer

BREAKING CHANGE: modifies the exposed react <App /> components props. The contract for providing configuration for the app has changed. Please reference updated documentation for guidance.
This commit is contained in:
Igor Octaviano authored and Danny Brown committed 2019-12-09 12:47:23 -05:00
1 parent fd7620ced9
commit 55a580659e
66 files changed
+1464 -1223

No files matched your search

+119 -45
View File
@@ -4,9 +4,13 @@ import { I18nextProvider } from 'react-i18next';
import PropTypes from 'prop-types';
import { Provider } from 'react-redux';
import { BrowserRouter as Router } from 'react-router-dom';
import OHIFCornerstoneExtension from '@ohif/extension-cornerstone';
import { hot } from 'react-hot-loader/root';
import OHIFCornerstoneExtension from '@ohif/extension-cornerstone';
import ToolContextMenu from './connectedComponents/ToolContextMenu';
import LabellingManager from './components/Labelling/LabellingManager';
import {
SnackbarProvider,
ModalProvider,
@@ -14,6 +18,11 @@ import {
OHIFModal,
} from '@ohif/ui';
import {
LabellingFlowProvider,
ContextMenuProvider,
} from './appCustomProviders';
import {
CommandsManager,
ExtensionManager,
@@ -22,8 +31,10 @@ import {
createUINotificationService,
createUIModalService,
createUIDialogService,
createUIContextMenuService,
createUILabellingFlowService,
utils,
redux as reduxOHIF
redux as reduxOHIF,
} from '@ohif/core';
import i18n from '@ohif/i18n';
@@ -46,12 +57,12 @@ import OHIFStandaloneViewer from './OHIFStandaloneViewer';
/** Store */
import { getActiveContexts } from './store/layout/selectors.js';
import store from './store';
const { setUserPreferences } = reduxOHIF.actions;
/** Contexts */
import WhiteLabellingContext from './context/WhiteLabellingContext';
import UserManagerContext from './context/UserManagerContext';
import AppContext from './context/AppContext';
const { setUserPreferences } = reduxOHIF.actions;
/** ~~~~~~~~~~~~~ Application Setup */
const commandsManagerConfig = {
@@ -63,6 +74,8 @@ const commandsManagerConfig = {
const UINotificationService = createUINotificationService();
const UIModalService = createUIModalService();
const UIDialogService = createUIDialogService();
const UIContextMenuService = createUIContextMenuService();
const UILabellingFlowService = createUILabellingFlowService();
/** Managers */
const commandsManager = new CommandsManager(commandsManagerConfig);
@@ -79,23 +92,25 @@ window.store = store;
class App extends Component {
static propTypes = {
routerBasename: PropTypes.string.isRequired,
servers: PropTypes.object.isRequired,
//
oidc: PropTypes.array,
whiteLabelling: PropTypes.object,
extensions: PropTypes.arrayOf(
config: PropTypes.oneOfType([
PropTypes.func,
PropTypes.shape({
id: PropTypes.string.isRequired,
})
),
hotkeys: PropTypes.array,
routerBasename: PropTypes.string.isRequired,
oidc: PropTypes.array,
whiteLabelling: PropTypes.object,
extensions: PropTypes.array,
}),
]).isRequired,
defaultExtensions: PropTypes.array,
};
static defaultProps = {
whiteLabelling: {},
oidc: [],
extensions: [],
config: {
whiteLabelling: {},
oidc: [],
extensions: [],
},
defaultExtensions: [],
};
_appConfig;
@@ -104,31 +119,59 @@ class App extends Component {
constructor(props) {
super(props);
this._appConfig = props;
const { config, defaultExtensions } = props;
const { servers, extensions, hotkeys, oidc } = props;
const appDefaultConfig = {
cornerstoneExtensionConfig: {},
extensions: [],
routerBasename: '/',
whiteLabelling: {},
};
this._appConfig = {
...appDefaultConfig,
...(typeof config === 'function' ? config({ servicesManager }) : config),
};
const {
servers,
hotkeys,
cornerstoneExtensionConfig,
extensions,
oidc,
} = this._appConfig;
this.initUserManager(oidc);
_initServices([UINotificationService, UIModalService, UIDialogService]);
_initExtensions(extensions, hotkeys);
_initServices([
UINotificationService,
UIModalService,
UIDialogService,
UIContextMenuService,
UILabellingFlowService,
]);
_initExtensions(
[...defaultExtensions, ...extensions],
cornerstoneExtensionConfig
);
/*
* Must run after extension commands are registered
* if there is no hotkeys from localStorage set up from config.
*/
_initHotkeys(hotkeys);
_initServers(servers);
initWebWorkers();
}
render() {
const { whiteLabelling, routerBasename } = this.props;
const userManager = this._userManager;
const config = {
appConfig: this._appConfig,
};
if (userManager) {
const { whiteLabelling, routerBasename } = this._appConfig;
if (this._userManager) {
return (
<AppContext.Provider value={config}>
<AppContext.Provider value={{ appConfig: this._appConfig }}>
<Provider store={store}>
<I18nextProvider i18n={i18n}>
<OidcProvider store={store} userManager={userManager}>
<UserManagerContext.Provider value={userManager}>
<OidcProvider store={store} userManager={this._userManager}>
<UserManagerContext.Provider value={this._userManager}>
<Router basename={routerBasename}>
<WhiteLabellingContext.Provider value={whiteLabelling}>
<SnackbarProvider service={UINotificationService}>
@@ -137,7 +180,21 @@ class App extends Component {
modal={OHIFModal}
service={UIModalService}
>
<OHIFStandaloneViewer userManager={userManager} />
<LabellingFlowProvider
service={UILabellingFlowService}
labellingComponent={LabellingManager}
commandsManager={commandsManager}
>
<ContextMenuProvider
service={UIContextMenuService}
contextMenuComponent={ToolContextMenu}
commandsManager={commandsManager}
>
<OHIFStandaloneViewer
userManager={this._userManager}
/>
</ContextMenuProvider>
</LabellingFlowProvider>
</ModalProvider>
</DialogProvider>
</SnackbarProvider>
@@ -152,7 +209,7 @@ class App extends Component {
}
return (
<AppContext.Provider value={config}>
<AppContext.Provider value={{ appConfig: this._appConfig }}>
<Provider store={store}>
<I18nextProvider i18n={i18n}>
<Router basename={routerBasename}>
@@ -160,7 +217,19 @@ class App extends Component {
<SnackbarProvider service={UINotificationService}>
<DialogProvider service={UIDialogService}>
<ModalProvider modal={OHIFModal} service={UIModalService}>
<OHIFStandaloneViewer />
<LabellingFlowProvider
service={UILabellingFlowService}
labellingComponent={LabellingManager}
commandsManager={commandsManager}
>
<ContextMenuProvider
service={UIContextMenuService}
contextMenuComponent={ToolContextMenu}
commandsManager={commandsManager}
>
<OHIFStandaloneViewer />
</ContextMenuProvider>
</LabellingFlowProvider>
</ModalProvider>
</DialogProvider>
</SnackbarProvider>
@@ -174,10 +243,10 @@ class App extends Component {
initUserManager(oidc) {
if (oidc && !!oidc.length) {
const firstOpenIdClient = this.props.oidc[0];
const firstOpenIdClient = this._appConfig.oidc[0];
const { protocol, host } = window.location;
const { routerBasename } = this.props;
const { routerBasename } = this._appConfig;
const baseUri = `${protocol}//${host}${routerBasename}`;
const redirect_uri = firstOpenIdClient.redirect_uri || '/callback';
@@ -213,22 +282,22 @@ function _initServices(services) {
/**
* @param
*/
function _initExtensions(extensions, hotkeys) {
const defaultExtensions = [
function _initExtensions(extensions, cornerstoneExtensionConfig) {
const requiredExtensions = [
GenericViewerCommands,
OHIFCornerstoneExtension,
// WARNING: MUST BE REGISTERED _AFTER_ OHIFCORNERSTONEEXTENSION
[OHIFCornerstoneExtension, cornerstoneExtensionConfig],
/* WARNING: MUST BE REGISTERED _AFTER_ OHIFCornerstoneExtension */
MeasurementsPanel,
];
const mergedExtensions = defaultExtensions.concat(extensions);
const mergedExtensions = requiredExtensions.concat(extensions);
extensionManager.registerExtensions(mergedExtensions);
}
function _initHotkeys(hotkeys) {
const { hotkeyDefinitions = {} } = store.getState().preferences || {};
let updateStore = false;
let hotkeysToUse = hotkeyDefinitions;
// Must run after extension commands are registered
// if there is no hotkeys from localStorate set up from config
if (!Object.keys(hotkeyDefinitions).length) {
hotkeysToUse = hotkeys;
updateStore = true;
@@ -236,7 +305,8 @@ function _initExtensions(extensions, hotkeys) {
if (hotkeysToUse) {
hotkeysManager.setHotkeys(hotkeysToUse);
// set default based on app config
/* Set hotkeys default based on app config. */
hotkeysManager.setDefaultHotKeys(hotkeys);
if (updateStore) {
@@ -264,7 +334,9 @@ function _makeAbsoluteIfNecessary(url, base_url) {
return url;
}
// Make sure base_url and url are not duplicating slashes
/*
* Make sure base_url and url are not duplicating slashes.
*/
if (base_url[base_url.length - 1] === '/') {
base_url = base_url.slice(0, base_url.length - 1);
}
@@ -272,7 +344,9 @@ function _makeAbsoluteIfNecessary(url, base_url) {
return base_url + url;
}
// Only wrap/use hot if in dev
/*
* Only wrap/use hot if in dev.
*/
const ExportedApp = process.env.NODE_ENV === 'development' ? hot(App) : App;
export default ExportedApp;