ohif-viewer/platform/core/src/extensions/ExtensionManager.js
Igor Octaviano 55a580659e 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.
2019-12-09 12:47:23 -05:00

185 lines
4.9 KiB
JavaScript

import MODULE_TYPES from './MODULE_TYPES.js';
import log from './../log.js';
export default class ExtensionManager {
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] = [];
});
}
/**
* An array of extensions, or an array of arrays that contains extension
* configuration pairs.
*
* @param {Object[]} extensions - Array of extensions
*/
registerExtensions(extensions) {
extensions.forEach(extension => {
const hasConfiguration = Array.isArray(extension);
if (hasConfiguration) {
const [ohifExtension, configuration] = extension;
this.registerExtension(ohifExtension, configuration);
} else {
this.registerExtension(extension);
}
});
}
/**
*
* TODO: Id Management: SopClassHandlers currently refer to viewport module by id; setting the extension id as viewport module id is a workaround for now
* @param {Object} extension
* @param {Object} configuration
*/
registerExtension(extension, configuration = {}) {
if (!extension) {
log.warn(
'Attempting to register a null/undefined extension. Exiting early.'
);
return;
}
let extensionId = extension.id;
if (!extensionId) {
extensionId = Math.random()
.toString(36)
.substr(2, 5);
log.warn(`Extension ID not set. Using random string ID: ${extensionId}`);
}
if (this.registeredExtensionIds.includes(extensionId)) {
log.warn(
`Extension ID ${extensionId} has already been registered. Exiting before duplicating modules.`
);
return;
}
// preRegistrationHook
if (extension.preRegistration) {
extension.preRegistration({
servicesManager: this._servicesManager,
commandsManager: this._commandsManager,
configuration,
});
}
// Register Modules
this.moduleTypeNames.forEach(moduleType => {
const extensionModule = this._getExtensionModule(
moduleType,
extension,
extensionId
);
if (extensionModule) {
this._initSpecialModuleTypes(moduleType, extensionModule);
this.modules[moduleType].push({
extensionId,
module: extensionModule,
});
}
});
// Track extension registration
this.registeredExtensionIds.push(extensionId);
}
/**
* @private
* @param {string} moduleType
* @param {Object} extension
* @param {string} extensionId - Used for logging warnings
*/
_getExtensionModule(moduleType, extension, extensionId) {
const getModuleFnName = 'get' + _capitalizeFirstCharacter(moduleType);
const getModuleFn = extension[getModuleFnName];
if (!getModuleFn) {
return;
}
try {
const extensionModule = getModuleFn({
servicesManager: this._servicesManager,
commandsManager: this._commandsManager,
});
if (!extensionModule) {
log.warn(
`Null or undefined returned when registering the ${getModuleFnName} module for the ${extensionId} extension`
);
}
return extensionModule;
} catch (ex) {
log.error(
`Exception thrown while trying to call ${getModuleFnName} for the ${extensionId} extension`
);
}
}
_initSpecialModuleTypes(moduleType, extensionModule) {
switch (moduleType) {
case 'commandsModule': {
const { definitions, defaultContext } = extensionModule;
if (!definitions || Object.keys(definitions).length === 0) {
log.warn('Commands Module contains no command definitions');
return;
}
this._initCommandsModule(definitions, defaultContext);
break;
}
default:
// code block
}
}
/**
*
* @private
* @param {Object[]} commandDefinitions
*/
_initCommandsModule(commandDefinitions, defaultContext = 'VIEWER') {
if (!this._commandsManager.getContext(defaultContext)) {
this._commandsManager.createContext(defaultContext);
}
Object.keys(commandDefinitions).forEach(commandName => {
const commandDefinition = commandDefinitions[commandName];
const commandHasContextThatDoesNotExist =
commandDefinition.context &&
!this._commandsManager.getContext(commandDefinition.context);
if (commandHasContextThatDoesNotExist) {
this._commandsManager.createContext(commandDefinition.context);
}
this._commandsManager.registerCommand(
commandDefinition.context || defaultContext,
commandName,
commandDefinition
);
});
}
}
/**
* @private
* @param {string} lower
*/
function _capitalizeFirstCharacter(lower) {
return lower.charAt(0).toUpperCase() + lower.substr(1);
}