2019-08-13 21:36:46 +02:00
|
|
|
import MODULE_TYPES from './MODULE_TYPES.js';
|
|
|
|
|
import log from './../log.js';
|
|
|
|
|
|
|
|
|
|
export default class ExtensionManager {
|
2020-05-06 19:17:44 +02:00
|
|
|
constructor({ commandsManager, servicesManager, api, appConfig = {} }) {
|
2019-08-13 21:36:46 +02:00
|
|
|
this.modules = {};
|
|
|
|
|
this.registeredExtensionIds = [];
|
|
|
|
|
this.moduleTypeNames = Object.values(MODULE_TYPES);
|
|
|
|
|
//
|
|
|
|
|
this._commandsManager = commandsManager;
|
2019-11-13 21:02:14 +01:00
|
|
|
this._servicesManager = servicesManager;
|
2019-12-16 20:07:53 +01:00
|
|
|
this._appConfig = appConfig;
|
2020-05-06 19:17:44 +02:00
|
|
|
this._api = api;
|
2019-08-13 21:36:46 +02:00
|
|
|
|
2020-05-07 20:04:33 +02:00
|
|
|
this.modulesMap = {};
|
2019-08-13 21:36:46 +02:00
|
|
|
this.moduleTypeNames.forEach(moduleType => {
|
|
|
|
|
this.modules[moduleType] = [];
|
|
|
|
|
});
|
2020-05-07 20:04:33 +02:00
|
|
|
this.dataSourceMap = {};
|
2020-05-13 12:06:13 +02:00
|
|
|
|
|
|
|
|
console.log('modules map');
|
|
|
|
|
console.log(this.modulesMap);
|
2019-08-13 21:36:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* An array of extensions, or an array of arrays that contains extension
|
|
|
|
|
* configuration pairs.
|
|
|
|
|
*
|
|
|
|
|
* @param {Object[]} extensions - Array of extensions
|
|
|
|
|
*/
|
2020-05-11 18:56:46 +02:00
|
|
|
registerExtensions = (extensions, dataSources) => {
|
2019-08-13 21:36:46 +02:00
|
|
|
extensions.forEach(extension => {
|
|
|
|
|
const hasConfiguration = Array.isArray(extension);
|
|
|
|
|
|
|
|
|
|
if (hasConfiguration) {
|
2019-12-09 18:47:23 +01:00
|
|
|
const [ohifExtension, configuration] = extension;
|
2020-05-11 18:56:46 +02:00
|
|
|
this.registerExtension(ohifExtension, dataSources, configuration);
|
2019-08-13 21:36:46 +02:00
|
|
|
} else {
|
2020-05-11 18:56:46 +02:00
|
|
|
this.registerExtension(extension, dataSources);
|
2019-08-13 21:36:46 +02:00
|
|
|
}
|
|
|
|
|
});
|
2020-05-09 20:23:43 +02:00
|
|
|
};
|
2019-08-13 21:36:46 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* 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
|
|
|
|
|
*/
|
2020-05-11 18:56:46 +02:00
|
|
|
registerExtension = (extension, dataSources, configuration = {}) => {
|
2019-08-13 21:36:46 +02:00
|
|
|
if (!extension) {
|
|
|
|
|
log.warn(
|
|
|
|
|
'Attempting to register a null/undefined extension. Exiting early.'
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let extensionId = extension.id;
|
|
|
|
|
|
|
|
|
|
if (!extensionId) {
|
2020-05-13 12:06:13 +02:00
|
|
|
// Note: Mode framework cannot function without IDs.
|
|
|
|
|
log.warn(extension);
|
|
|
|
|
throw new Error(`Extension ID not set`);
|
2019-08-13 21:36:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.registeredExtensionIds.includes(extensionId)) {
|
|
|
|
|
log.warn(
|
|
|
|
|
`Extension ID ${extensionId} has already been registered. Exiting before duplicating modules.`
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// preRegistrationHook
|
|
|
|
|
if (extension.preRegistration) {
|
2019-11-13 21:02:14 +01:00
|
|
|
extension.preRegistration({
|
2019-11-14 15:50:37 +01:00
|
|
|
servicesManager: this._servicesManager,
|
2019-12-02 17:29:04 +01:00
|
|
|
commandsManager: this._commandsManager,
|
2019-12-16 20:07:53 +01:00
|
|
|
appConfig: this._appConfig,
|
2019-11-13 21:02:14 +01:00
|
|
|
configuration,
|
|
|
|
|
});
|
2019-08-13 21:36:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Register Modules
|
|
|
|
|
this.moduleTypeNames.forEach(moduleType => {
|
|
|
|
|
const extensionModule = this._getExtensionModule(
|
|
|
|
|
moduleType,
|
|
|
|
|
extension,
|
2019-12-16 20:07:53 +01:00
|
|
|
extensionId,
|
|
|
|
|
configuration
|
2019-08-13 21:36:46 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (extensionModule) {
|
2020-05-13 12:06:13 +02:00
|
|
|
switch (moduleType) {
|
|
|
|
|
case MODULE_TYPES.COMMANDS:
|
|
|
|
|
this._initCommandsModule(extensionModule);
|
|
|
|
|
break;
|
|
|
|
|
case MODULE_TYPES.DATA_SOURCE:
|
|
|
|
|
this._initDataSourcesModule(
|
|
|
|
|
extensionModule,
|
|
|
|
|
extensionId,
|
|
|
|
|
dataSources
|
|
|
|
|
);
|
|
|
|
|
break;
|
|
|
|
|
case MODULE_TYPES.TOOLBAR:
|
|
|
|
|
this._initToolBarModule(extensionModule, extensionId);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case MODULE_TYPES.PANEL:
|
|
|
|
|
case MODULE_TYPES.SOP_CLASS_HANDLER:
|
|
|
|
|
case MODULE_TYPES.VIEWPORT:
|
|
|
|
|
case MODULE_TYPES.CONTEXT:
|
|
|
|
|
case MODULE_TYPES.LAYOUT_TEMPLATE:
|
|
|
|
|
// Default for most extension points,
|
|
|
|
|
// Just adds each entry ready for consumption by mode.
|
|
|
|
|
|
|
|
|
|
extensionModule.forEach(element => {
|
|
|
|
|
this.modulesMap[
|
|
|
|
|
`${extensionId}.${moduleType}.${element.name}`
|
|
|
|
|
] = element;
|
|
|
|
|
});
|
|
|
|
|
}
|
2019-08-13 21:36:46 +02:00
|
|
|
|
|
|
|
|
this.modules[moduleType].push({
|
|
|
|
|
extensionId,
|
|
|
|
|
module: extensionModule,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Track extension registration
|
|
|
|
|
this.registeredExtensionIds.push(extensionId);
|
2020-05-09 20:23:43 +02:00
|
|
|
};
|
2019-08-13 21:36:46 +02:00
|
|
|
|
2020-05-09 20:23:43 +02:00
|
|
|
getModuleEntry = stringEntry => {
|
2020-05-07 20:04:33 +02:00
|
|
|
return this.modulesMap[stringEntry];
|
2020-05-09 20:23:43 +02:00
|
|
|
};
|
2020-05-07 20:04:33 +02:00
|
|
|
|
2020-05-11 18:56:46 +02:00
|
|
|
getDataSources = dataSourceName => {
|
2020-05-09 10:57:52 +02:00
|
|
|
// Note: this currently uses the data source name, which feels weird...
|
2020-05-11 18:56:46 +02:00
|
|
|
return this.dataSourceMap[dataSourceName];
|
2020-05-09 20:23:43 +02:00
|
|
|
};
|
2020-05-07 20:04:33 +02:00
|
|
|
|
2020-05-13 12:06:13 +02:00
|
|
|
_initToolBarModule = (extensionModule, extensionId) => {
|
|
|
|
|
let { definitions, defaultContext } = extensionModule;
|
|
|
|
|
if (!definitions || Object.keys(definitions).length === 0) {
|
|
|
|
|
log.warn('Commands Module contains no command definitions');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
defaultContext = defaultContext || 'VIEWER';
|
|
|
|
|
|
|
|
|
|
definitions.forEach(definition => {
|
|
|
|
|
console.log(`${extensionId}.${MODULE_TYPES.TOOLBAR}.${definition.id}`);
|
|
|
|
|
|
|
|
|
|
// TODO -> Deep copy instead of mutation? We only do this once, but would be better.
|
|
|
|
|
definition.context = definition.context || defaultContext;
|
|
|
|
|
|
|
|
|
|
this.modulesMap[
|
|
|
|
|
`${extensionId}.${MODULE_TYPES.TOOLBAR}.${definition.id}`
|
|
|
|
|
] = definition;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2019-08-13 21:36:46 +02:00
|
|
|
/**
|
|
|
|
|
* @private
|
|
|
|
|
* @param {string} moduleType
|
|
|
|
|
* @param {Object} extension
|
|
|
|
|
* @param {string} extensionId - Used for logging warnings
|
|
|
|
|
*/
|
2020-05-09 20:23:43 +02:00
|
|
|
_getExtensionModule = (moduleType, extension, extensionId, configuration) => {
|
2019-08-13 21:36:46 +02:00
|
|
|
const getModuleFnName = 'get' + _capitalizeFirstCharacter(moduleType);
|
|
|
|
|
const getModuleFn = extension[getModuleFnName];
|
|
|
|
|
|
|
|
|
|
if (!getModuleFn) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
2019-11-14 23:29:40 +01:00
|
|
|
const extensionModule = getModuleFn({
|
2020-05-15 12:48:00 +02:00
|
|
|
getDataSources: this.getDataSources,
|
2019-11-14 23:29:40 +01:00
|
|
|
servicesManager: this._servicesManager,
|
2019-12-02 17:29:04 +01:00
|
|
|
commandsManager: this._commandsManager,
|
2019-12-16 20:07:53 +01:00
|
|
|
appConfig: this._appConfig,
|
|
|
|
|
configuration,
|
2020-05-06 19:17:44 +02:00
|
|
|
api: this._api
|
2019-11-14 23:29:40 +01:00
|
|
|
});
|
2019-08-13 21:36:46 +02:00
|
|
|
|
|
|
|
|
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`
|
|
|
|
|
);
|
2020-05-10 16:33:04 +02:00
|
|
|
log.error(ex);
|
2019-08-13 21:36:46 +02:00
|
|
|
}
|
2020-05-09 20:23:43 +02:00
|
|
|
};
|
2019-08-13 21:36:46 +02:00
|
|
|
|
2020-05-13 12:06:13 +02:00
|
|
|
_initDataSourcesModule(extensionModule, extensionId, dataSources) {
|
|
|
|
|
extensionModule.forEach(element => {
|
|
|
|
|
const namespace = `${extensionId}.${MODULE_TYPES.DATA_SOURCE}.${element.name}`;
|
|
|
|
|
|
|
|
|
|
dataSources.forEach(dataSource => {
|
|
|
|
|
if (dataSource.namespace === namespace) {
|
|
|
|
|
const dataSourceInstance = element.createDataSource(
|
|
|
|
|
dataSource.configuration
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (this.dataSourceMap[dataSource.sourceName]) {
|
|
|
|
|
this.dataSourceMap[dataSource.sourceName].push(dataSourceInstance);
|
|
|
|
|
} else {
|
|
|
|
|
this.dataSourceMap[dataSource.sourceName] = [dataSourceInstance];
|
|
|
|
|
}
|
2019-08-13 21:36:46 +02:00
|
|
|
}
|
2020-05-13 12:06:13 +02:00
|
|
|
});
|
|
|
|
|
});
|
2020-05-09 10:57:52 +02:00
|
|
|
|
2020-05-13 12:06:13 +02:00
|
|
|
extensionModule.forEach(element => {
|
|
|
|
|
this.modulesMap[
|
|
|
|
|
`${extensionId}.${MODULE_TYPES.DATA_SOURCE}.${element.name}`
|
|
|
|
|
] = element;
|
|
|
|
|
});
|
|
|
|
|
}
|
2019-08-13 21:36:46 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* @private
|
|
|
|
|
* @param {Object[]} commandDefinitions
|
|
|
|
|
*/
|
2020-05-13 12:06:13 +02:00
|
|
|
_initCommandsModule = extensionModule => {
|
|
|
|
|
let { definitions, defaultContext } = extensionModule;
|
|
|
|
|
if (!definitions || Object.keys(definitions).length === 0) {
|
|
|
|
|
log.warn('Commands Module contains no command definitions');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
defaultContext = defaultContext || 'VIEWER';
|
|
|
|
|
|
2019-08-13 21:36:46 +02:00
|
|
|
if (!this._commandsManager.getContext(defaultContext)) {
|
|
|
|
|
this._commandsManager.createContext(defaultContext);
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-13 12:06:13 +02:00
|
|
|
Object.keys(definitions).forEach(commandName => {
|
|
|
|
|
const commandDefinition = definitions[commandName];
|
2019-08-13 21:36:46 +02:00
|
|
|
const commandHasContextThatDoesNotExist =
|
|
|
|
|
commandDefinition.context &&
|
|
|
|
|
!this._commandsManager.getContext(commandDefinition.context);
|
|
|
|
|
|
|
|
|
|
if (commandHasContextThatDoesNotExist) {
|
|
|
|
|
this._commandsManager.createContext(commandDefinition.context);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this._commandsManager.registerCommand(
|
|
|
|
|
commandDefinition.context || defaultContext,
|
|
|
|
|
commandName,
|
|
|
|
|
commandDefinition
|
|
|
|
|
);
|
|
|
|
|
});
|
2020-05-09 20:23:43 +02:00
|
|
|
};
|
2019-08-13 21:36:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @private
|
|
|
|
|
* @param {string} lower
|
|
|
|
|
*/
|
|
|
|
|
function _capitalizeFirstCharacter(lower) {
|
|
|
|
|
return lower.charAt(0).toUpperCase() + lower.substr(1);
|
|
|
|
|
}
|