2023-02-10 20:30:56 +01:00
|
|
|
import MODULE_TYPES from './MODULE_TYPES';
|
|
|
|
|
import log from '../log';
|
|
|
|
|
import { AppConfig } from '../types/AppConfig';
|
2023-07-20 17:43:05 +02:00
|
|
|
import { PubSubService, ServicesManager } from '../services';
|
2023-02-10 20:30:56 +01:00
|
|
|
import { HotkeysManager, CommandsManager } from '../classes';
|
2023-07-20 17:43:05 +02:00
|
|
|
import { DataSourceDefinition } from '../types';
|
2023-02-10 20:30:56 +01:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* This is the arguments given to create the extension.
|
|
|
|
|
*/
|
|
|
|
|
export interface ExtensionConstructor {
|
|
|
|
|
servicesManager: ServicesManager;
|
|
|
|
|
commandsManager: CommandsManager;
|
|
|
|
|
hotkeysManager: HotkeysManager;
|
|
|
|
|
appConfig: AppConfig;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The configuration of an extension.
|
|
|
|
|
* This uses type as the extension manager only knows that the configuration
|
|
|
|
|
* is an object of some sort, and doesn't know anything else about it.
|
|
|
|
|
*/
|
|
|
|
|
export type ExtensionConfiguration = Record<string, unknown>;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The parameters passed to the extension.
|
|
|
|
|
*/
|
|
|
|
|
export interface ExtensionParams extends ExtensionConstructor {
|
|
|
|
|
extensionManager: ExtensionManager;
|
2023-08-08 20:39:31 +02:00
|
|
|
servicesManager: ServicesManager;
|
2023-02-10 20:30:56 +01:00
|
|
|
configuration?: ExtensionConfiguration;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The type of an actual extension instance.
|
|
|
|
|
* This is an interface as it declares possible calls, but extensions can
|
|
|
|
|
* have more values than this.
|
|
|
|
|
*/
|
|
|
|
|
export interface Extension {
|
2023-02-17 21:04:07 +01:00
|
|
|
id: string;
|
2023-03-22 22:45:28 +01:00
|
|
|
preRegistration?: (p: ExtensionParams) => Promise<void> | void;
|
|
|
|
|
getHangingProtocolModule?: (p: ExtensionParams) => unknown;
|
|
|
|
|
getCommandsModule?: (p: ExtensionParams) => CommandsModule;
|
2023-04-18 17:17:50 +02:00
|
|
|
getViewportModule?: (p: ExtensionParams) => unknown;
|
|
|
|
|
getUtilityModule?: (p: ExtensionParams) => unknown;
|
feat(RT): add dicom RT support via volume viewports (#3310)
* feat: initial RT support
* make the segmentation service work with representation data
* feat: make segmentation service work with representations
* fix rtss vis
* fix: rt hydration
* fix the rendering of rt names
* fix imports
* refactor: Modify status and click handling for hydration of RTStructures
Modify status and click handling for hydration of RTStructures by renaming `onPillClick` to `onStatusClick` in `OHIFCornerstoneRTViewport.tsx` and `_getStatusComponent.tsx` files. Also, update initial segmentation configurations in `PanelSegmentation.tsx` and simplify configuration changes and values for segmentation service in `SegmentationService.ts`. Finally, remove console debug in `CornerstoneViewportService.ts`.
* wip for highlighting contours
* refactor rt displayset code
* review code update
* update cornerstone dependencies
* refactor: Update license year, version number, and minor code cleanup
This commit updates the license year in several files, updates the version number in package.json, and contains minor code cleanup in two files.
* add bulkdataURI retrieve for RT
* fix package version
* apply review comments
* apply review comments
* apply review comments
* feat(panels): refactor and streamline segmentation configuration and inputs
Rewrote state hooks and streamlined the configuration input for `PanelSegmentation` to be more verbose and reusable. Included several new input types, including the `InputRange` component which now shows a fixed floating value based on the step provided. The `SegmentationConfig` component now works with dynamic values controlled by `initialConfig`. These changes should improve function usability and make the code more maintainable going forward.
* fix various bugs
* fix contour delete by upgrade cs3d version
* feat(viewport, inputNumber, segmentationConfig, orthanc): Implement minimum and maximum values for input number components, and useBulkDataURI for Orthanc configuration. Compare measurement view planes with absolute viewport view planes in Cornerstone viewport.
* update yarn lock
2023-04-26 18:21:08 +02:00
|
|
|
getCustomizationModule?: (p: ExtensionParams) => unknown;
|
2023-08-08 20:39:31 +02:00
|
|
|
getSopClassHandlerModule?: (p: ExtensionParams) => unknown;
|
2023-04-18 17:17:50 +02:00
|
|
|
onModeEnter?: () => void;
|
|
|
|
|
onModeExit?: () => void;
|
2023-02-10 20:30:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export type ExtensionRegister = {
|
|
|
|
|
id: string;
|
|
|
|
|
create: (p: ExtensionParams) => Extension;
|
|
|
|
|
};
|
2019-08-13 21:36:46 +02:00
|
|
|
|
2023-03-22 22:45:28 +01:00
|
|
|
export type CommandsModule = {
|
|
|
|
|
actions: Record<string, unknown>;
|
|
|
|
|
definitions: Record<string, unknown>;
|
|
|
|
|
defaultContext?: string;
|
|
|
|
|
};
|
|
|
|
|
|
2023-07-20 17:43:05 +02:00
|
|
|
export default class ExtensionManager extends PubSubService {
|
|
|
|
|
public static readonly EVENTS = {
|
|
|
|
|
ACTIVE_DATA_SOURCE_CHANGED: 'event::activedatasourcechanged',
|
|
|
|
|
};
|
|
|
|
|
|
2023-02-10 20:30:56 +01:00
|
|
|
private _commandsManager: CommandsManager;
|
|
|
|
|
private _servicesManager: ServicesManager;
|
|
|
|
|
private _hotkeysManager: HotkeysManager;
|
|
|
|
|
|
2021-05-28 20:32:48 +02:00
|
|
|
constructor({
|
|
|
|
|
commandsManager,
|
|
|
|
|
servicesManager,
|
|
|
|
|
hotkeysManager,
|
|
|
|
|
appConfig = {},
|
2023-02-10 20:30:56 +01:00
|
|
|
}: ExtensionConstructor) {
|
2023-07-20 17:43:05 +02:00
|
|
|
super(ExtensionManager.EVENTS);
|
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;
|
2020-08-19 21:02:59 +02:00
|
|
|
this._hotkeysManager = hotkeysManager;
|
2019-12-16 20:07:53 +01:00
|
|
|
this._appConfig = appConfig;
|
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-08-04 11:11:23 +02:00
|
|
|
this._extensionLifeCycleHooks = { onModeEnter: {}, onModeExit: {} };
|
2020-05-07 20:04:33 +02:00
|
|
|
this.dataSourceMap = {};
|
2022-12-29 16:13:06 +01:00
|
|
|
this.dataSourceDefs = {};
|
2020-06-05 17:36:11 +02:00
|
|
|
this.defaultDataSourceName = appConfig.defaultDataSourceName;
|
2023-07-20 17:43:05 +02:00
|
|
|
this.activeDataSource = appConfig.defaultDataSourceName;
|
2020-06-05 17:36:11 +02:00
|
|
|
}
|
2020-05-13 12:06:13 +02:00
|
|
|
|
2023-07-20 17:43:05 +02:00
|
|
|
public setActiveDataSource(dataSource: string): void {
|
|
|
|
|
if (this.activeDataSource === dataSource) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.activeDataSource = dataSource;
|
|
|
|
|
|
|
|
|
|
this._broadcastEvent(
|
|
|
|
|
ExtensionManager.EVENTS.ACTIVE_DATA_SOURCE_CHANGED,
|
|
|
|
|
this.dataSourceDefs[this.activeDataSource]
|
|
|
|
|
);
|
2019-08-13 21:36:46 +02:00
|
|
|
}
|
|
|
|
|
|
2023-02-10 20:30:56 +01:00
|
|
|
/**
|
|
|
|
|
* Calls all the services and extension on mode enters.
|
|
|
|
|
* The service onModeEnter is called first
|
|
|
|
|
* Then registered extensions onModeEnter is called
|
|
|
|
|
* This is supposed to setup the extension for a standard entry.
|
|
|
|
|
*/
|
|
|
|
|
public onModeEnter(): void {
|
2020-07-03 19:18:59 +02:00
|
|
|
const {
|
|
|
|
|
registeredExtensionIds,
|
|
|
|
|
_servicesManager,
|
|
|
|
|
_commandsManager,
|
2020-08-19 21:02:59 +02:00
|
|
|
_hotkeysManager,
|
2020-07-03 19:18:59 +02:00
|
|
|
_extensionLifeCycleHooks,
|
|
|
|
|
} = this;
|
|
|
|
|
|
2022-12-09 16:58:01 +01:00
|
|
|
// The onModeEnter of the service must occur BEFORE the extension
|
|
|
|
|
// onModeEnter in order to reset the state to a standard state
|
|
|
|
|
// before the extension restores and cached data.
|
2022-11-24 22:28:10 +01:00
|
|
|
for (const service of Object.values(_servicesManager.services)) {
|
|
|
|
|
service?.onModeEnter?.();
|
|
|
|
|
}
|
2020-08-04 11:11:23 +02:00
|
|
|
|
2020-07-03 19:18:59 +02:00
|
|
|
registeredExtensionIds.forEach(extensionId => {
|
|
|
|
|
const onModeEnter = _extensionLifeCycleHooks.onModeEnter[extensionId];
|
|
|
|
|
|
|
|
|
|
if (typeof onModeEnter === 'function') {
|
|
|
|
|
onModeEnter({
|
|
|
|
|
servicesManager: _servicesManager,
|
|
|
|
|
commandsManager: _commandsManager,
|
2021-05-28 20:32:48 +02:00
|
|
|
hotkeysManager: _hotkeysManager,
|
2020-07-03 19:18:59 +02:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-10 20:30:56 +01:00
|
|
|
public onModeExit(): void {
|
2020-08-04 11:11:23 +02:00
|
|
|
const {
|
|
|
|
|
registeredExtensionIds,
|
|
|
|
|
_servicesManager,
|
|
|
|
|
_commandsManager,
|
|
|
|
|
_extensionLifeCycleHooks,
|
|
|
|
|
} = this;
|
|
|
|
|
|
|
|
|
|
registeredExtensionIds.forEach(extensionId => {
|
|
|
|
|
const onModeExit = _extensionLifeCycleHooks.onModeExit[extensionId];
|
|
|
|
|
|
|
|
|
|
if (typeof onModeExit === 'function') {
|
|
|
|
|
onModeExit({
|
|
|
|
|
servicesManager: _servicesManager,
|
|
|
|
|
commandsManager: _commandsManager,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
2022-12-09 16:58:01 +01:00
|
|
|
|
|
|
|
|
// The service onModeExit calls must occur after the extension ones
|
|
|
|
|
// so that extension ones can store/restore data.
|
|
|
|
|
for (const service of Object.values(_servicesManager.services)) {
|
|
|
|
|
try {
|
|
|
|
|
service?.onModeExit?.();
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.warn('onModeExit caught', e);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-08-04 11:11:23 +02:00
|
|
|
}
|
|
|
|
|
|
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
|
|
|
|
|
*/
|
2023-02-10 20:30:56 +01:00
|
|
|
public registerExtensions = async (
|
|
|
|
|
extensions: (
|
|
|
|
|
| ExtensionRegister
|
|
|
|
|
| [ExtensionRegister, ExtensionConfiguration]
|
|
|
|
|
)[],
|
|
|
|
|
dataSources: unknown[] = []
|
|
|
|
|
): Promise<void> => {
|
2022-07-27 18:39:04 +02:00
|
|
|
// Todo: we ideally should be able to run registrations in parallel
|
|
|
|
|
// but currently since some extensions need to be registered before
|
|
|
|
|
// others, we need to run them sequentially. We need a postInit hook
|
|
|
|
|
// to avoid this sequential async registration
|
2023-02-17 21:04:07 +01:00
|
|
|
for (let i = 0; i < extensions.length; i++) {
|
|
|
|
|
const extension = extensions[i];
|
2019-08-13 21:36:46 +02:00
|
|
|
const hasConfiguration = Array.isArray(extension);
|
2022-07-27 18:39:04 +02:00
|
|
|
try {
|
|
|
|
|
if (hasConfiguration) {
|
2023-02-17 21:04:07 +01:00
|
|
|
// Important: for some reason in the line below the type
|
|
|
|
|
// of extension is not recognized as [ExtensionRegister,
|
|
|
|
|
// ExtensionConfiguration] by babel DON"T CHANGE IT
|
|
|
|
|
// Same for the for loop above don't use
|
|
|
|
|
// for (const extension of extensions)
|
|
|
|
|
const ohifExtension = extension[0];
|
|
|
|
|
const configuration = extension[1];
|
2022-07-27 18:39:04 +02:00
|
|
|
await this.registerExtension(
|
|
|
|
|
ohifExtension,
|
|
|
|
|
configuration,
|
|
|
|
|
dataSources
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
await this.registerExtension(extension, {}, dataSources);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(error);
|
2019-08-13 21:36:46 +02:00
|
|
|
}
|
2022-07-27 18:39:04 +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
|
|
|
|
|
*/
|
2023-02-10 20:30:56 +01:00
|
|
|
public registerExtension = async (
|
|
|
|
|
extension: ExtensionRegister,
|
2022-07-27 18:39:04 +02:00
|
|
|
configuration = {},
|
|
|
|
|
dataSources = []
|
2023-02-10 20:30:56 +01:00
|
|
|
): Promise<void> => {
|
2019-08-13 21:36:46 +02:00
|
|
|
if (!extension) {
|
2021-05-28 20:32:48 +02:00
|
|
|
throw new Error('Attempting to register a null/undefined extension.');
|
2019-08-13 21:36:46 +02:00
|
|
|
}
|
|
|
|
|
|
2023-02-10 20:30:56 +01:00
|
|
|
const extensionId = extension.id;
|
2019-08-13 21:36:46 +02:00
|
|
|
|
|
|
|
|
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) {
|
2022-07-27 18:39:04 +02:00
|
|
|
await extension.preRegistration({
|
2019-11-14 15:50:37 +01:00
|
|
|
servicesManager: this._servicesManager,
|
2019-12-02 17:29:04 +01:00
|
|
|
commandsManager: this._commandsManager,
|
2020-08-19 21:02:59 +02:00
|
|
|
hotkeysManager: this._hotkeysManager,
|
2022-07-27 18:39:04 +02:00
|
|
|
extensionManager: this,
|
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
|
|
|
}
|
|
|
|
|
|
2020-07-03 19:18:59 +02:00
|
|
|
if (extension.onModeEnter) {
|
|
|
|
|
this._extensionLifeCycleHooks.onModeEnter[extensionId] =
|
|
|
|
|
extension.onModeEnter;
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-04 11:11:23 +02:00
|
|
|
if (extension.onModeExit) {
|
|
|
|
|
this._extensionLifeCycleHooks.onModeExit[extensionId] =
|
|
|
|
|
extension.onModeExit;
|
|
|
|
|
}
|
|
|
|
|
|
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;
|
2022-09-21 05:40:26 +02:00
|
|
|
case MODULE_TYPES.HANGING_PROTOCOL:
|
|
|
|
|
this._initHangingProtocolsModule(extensionModule, extensionId);
|
2020-05-13 12:06:13 +02:00
|
|
|
case MODULE_TYPES.TOOLBAR:
|
2020-05-15 19:07:21 +02:00
|
|
|
case MODULE_TYPES.VIEWPORT:
|
2020-05-13 12:06:13 +02:00
|
|
|
case MODULE_TYPES.PANEL:
|
|
|
|
|
case MODULE_TYPES.SOP_CLASS_HANDLER:
|
|
|
|
|
case MODULE_TYPES.CONTEXT:
|
|
|
|
|
case MODULE_TYPES.LAYOUT_TEMPLATE:
|
2022-11-24 22:28:10 +01:00
|
|
|
case MODULE_TYPES.CUSTOMIZATION:
|
2023-02-10 20:30:56 +01:00
|
|
|
case MODULE_TYPES.STATE_SYNC:
|
2022-07-27 18:39:04 +02:00
|
|
|
case MODULE_TYPES.UTILITY:
|
2020-05-13 12:06:13 +02:00
|
|
|
// Default for most extension points,
|
|
|
|
|
// Just adds each entry ready for consumption by mode.
|
|
|
|
|
extensionModule.forEach(element => {
|
2023-03-31 17:58:48 +02:00
|
|
|
if (!element.name) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
`Extension ID ${extensionId} module ${moduleType} element has no name`
|
|
|
|
|
);
|
|
|
|
|
}
|
2023-03-10 04:30:09 +01:00
|
|
|
const id = `${extensionId}.${moduleType}.${element.name}`;
|
|
|
|
|
element.id = id;
|
|
|
|
|
this.modulesMap[id] = element;
|
2020-05-13 12:06:13 +02:00
|
|
|
});
|
2020-05-15 19:07:21 +02:00
|
|
|
break;
|
2021-03-01 16:52:54 +01:00
|
|
|
default:
|
|
|
|
|
throw new Error(`Module type invalid: ${moduleType}`);
|
2020-05-13 12:06:13 +02:00
|
|
|
}
|
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-06-05 17:36:11 +02:00
|
|
|
if (dataSourceName === undefined) {
|
2020-06-26 11:01:13 +02:00
|
|
|
// Default to the activeDataSource
|
2020-06-05 17:36:11 +02:00
|
|
|
dataSourceName = this.activeDataSource;
|
|
|
|
|
}
|
|
|
|
|
|
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-06-26 11:01:13 +02:00
|
|
|
getActiveDataSource = () => {
|
2020-07-03 19:18:59 +02:00
|
|
|
return this.dataSourceMap[this.activeDataSource];
|
2020-06-26 11:01:13 +02:00
|
|
|
};
|
|
|
|
|
|
2023-07-20 17:43:05 +02:00
|
|
|
/**
|
|
|
|
|
* Gets the data source definition for the given data source name.
|
|
|
|
|
* If no data source name is provided, the active data source definition is
|
|
|
|
|
* returned.
|
|
|
|
|
* @param dataSourceName the data source name
|
|
|
|
|
* @returns the data source definition
|
|
|
|
|
*/
|
|
|
|
|
getDataSourceDef = dataSourceName => {
|
|
|
|
|
if (dataSourceName === undefined) {
|
|
|
|
|
// Default to the activeDataSource
|
|
|
|
|
dataSourceName = this.activeDataSource;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return this.dataSourceDefs[dataSourceName];
|
2021-05-28 20:32:48 +02:00
|
|
|
};
|
|
|
|
|
|
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 {
|
2023-03-22 22:45:28 +01:00
|
|
|
const extensionModule = extension[getModuleFnName]({
|
2020-06-15 03:18:29 +02:00
|
|
|
appConfig: this._appConfig,
|
2019-12-02 17:29:04 +01:00
|
|
|
commandsManager: this._commandsManager,
|
2020-06-15 03:18:29 +02:00
|
|
|
servicesManager: this._servicesManager,
|
2020-08-19 21:02:59 +02:00
|
|
|
hotkeysManager: this._hotkeysManager,
|
|
|
|
|
extensionManager: this,
|
2019-12-16 20:07:53 +01:00
|
|
|
configuration,
|
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) {
|
2023-03-22 22:45:28 +01:00
|
|
|
console.log(ex);
|
2021-03-01 16:52:54 +01:00
|
|
|
throw new Error(
|
2019-08-13 21:36:46 +02:00
|
|
|
`Exception thrown while trying to call ${getModuleFnName} for the ${extensionId} extension`
|
|
|
|
|
);
|
|
|
|
|
}
|
2020-05-09 20:23:43 +02:00
|
|
|
};
|
2019-08-13 21:36:46 +02:00
|
|
|
|
2022-09-21 05:40:26 +02:00
|
|
|
_initHangingProtocolsModule = (extensionModule, extensionId) => {
|
2023-02-10 22:46:09 +01:00
|
|
|
const { hangingProtocolService } = this._servicesManager.services;
|
2023-03-31 17:58:48 +02:00
|
|
|
extensionModule.forEach(({ name, protocol }) => {
|
2022-10-05 14:32:54 +02:00
|
|
|
if (protocol) {
|
|
|
|
|
// Only auto-register if protocol specified, otherwise let mode register
|
2023-03-31 17:58:48 +02:00
|
|
|
hangingProtocolService.addProtocol(name, protocol);
|
2022-10-05 14:32:54 +02:00
|
|
|
}
|
2022-09-21 05:40:26 +02:00
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2023-07-20 17:43:05 +02:00
|
|
|
/**
|
|
|
|
|
* Adds the given data source and optionally sets it as the active data source.
|
|
|
|
|
* The method does this by first creating the data source.
|
|
|
|
|
* @param dataSourceDef the data source definition to be added
|
|
|
|
|
* @param activate flag to indicate if the added data source should be set to the active data source
|
|
|
|
|
*/
|
|
|
|
|
addDataSource(
|
|
|
|
|
dataSourceDef: DataSourceDefinition,
|
|
|
|
|
options = { activate: false }
|
|
|
|
|
) {
|
|
|
|
|
const existingDataSource = this.getDataSources(dataSourceDef.sourceName);
|
|
|
|
|
if (existingDataSource?.[0]) {
|
|
|
|
|
// The data source already exists and cannot be added.
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this._createDataSourceInstance(dataSourceDef);
|
|
|
|
|
|
|
|
|
|
if (options.activate) {
|
|
|
|
|
this.setActiveDataSource(dataSourceDef.sourceName);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Updates the configuration of the given data source name. It first creates a new data source with
|
|
|
|
|
* the existing definition and the new configuration passed in.
|
|
|
|
|
* @param dataSourceName the name of the data source to update
|
|
|
|
|
* @param dataSourceConfiguration the new configuration to update the data source with
|
|
|
|
|
*/
|
|
|
|
|
updateDataSourceConfiguration(
|
|
|
|
|
dataSourceName: string,
|
|
|
|
|
dataSourceConfiguration: any
|
|
|
|
|
) {
|
|
|
|
|
const existingDataSource = this.getDataSources(dataSourceName);
|
|
|
|
|
if (!existingDataSource?.[0]) {
|
|
|
|
|
// Cannot update a non existent data source.
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const dataSourceDef = this.dataSourceDefs[dataSourceName];
|
|
|
|
|
// Update the configuration.
|
|
|
|
|
dataSourceDef.configuration = dataSourceConfiguration;
|
|
|
|
|
this._createDataSourceInstance(dataSourceDef);
|
|
|
|
|
|
|
|
|
|
if (this.activeDataSource === dataSourceName) {
|
|
|
|
|
// When the active data source is changed/set, fire an event to indicate that its configuration has changed.
|
|
|
|
|
this._broadcastEvent(
|
|
|
|
|
ExtensionManager.EVENTS.ACTIVE_DATA_SOURCE_CHANGED,
|
|
|
|
|
dataSourceDef
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Creates a data source instance from the given definition. The definition is
|
|
|
|
|
* added to dataSourceDefs and the created instance is added to dataSourceMap.
|
|
|
|
|
* @param dataSourceDef
|
|
|
|
|
* @returns
|
|
|
|
|
*/
|
|
|
|
|
_createDataSourceInstance(dataSourceDef: DataSourceDefinition) {
|
|
|
|
|
const module = this.getModuleEntry(dataSourceDef.namespace);
|
|
|
|
|
|
|
|
|
|
if (!module) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.dataSourceDefs[dataSourceDef.sourceName] = dataSourceDef;
|
|
|
|
|
|
2023-02-13 14:55:55 +01:00
|
|
|
const { userAuthenticationService } = this._servicesManager.services;
|
2023-07-20 17:43:05 +02:00
|
|
|
const dataSourceInstance = module.createDataSource(
|
|
|
|
|
dataSourceDef.configuration,
|
|
|
|
|
userAuthenticationService
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
this.dataSourceMap[dataSourceDef.sourceName] = [dataSourceInstance];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_initDataSourcesModule(
|
|
|
|
|
extensionModule,
|
|
|
|
|
extensionId,
|
|
|
|
|
dataSources: Array<DataSourceDefinition> = []
|
|
|
|
|
): void {
|
|
|
|
|
extensionModule.forEach(element => {
|
|
|
|
|
this.modulesMap[
|
|
|
|
|
`${extensionId}.${MODULE_TYPES.DATA_SOURCE}.${element.name}`
|
|
|
|
|
] = element;
|
2022-12-29 16:13:06 +01:00
|
|
|
});
|
2021-06-23 20:41:27 +02:00
|
|
|
|
2020-05-13 12:06:13 +02:00
|
|
|
extensionModule.forEach(element => {
|
|
|
|
|
const namespace = `${extensionId}.${MODULE_TYPES.DATA_SOURCE}.${element.name}`;
|
|
|
|
|
|
|
|
|
|
dataSources.forEach(dataSource => {
|
|
|
|
|
if (dataSource.namespace === namespace) {
|
2023-07-20 17:43:05 +02:00
|
|
|
this.addDataSource(dataSource);
|
2019-08-13 21:36:46 +02:00
|
|
|
}
|
2020-05-13 12:06:13 +02:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
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) {
|
2022-11-10 19:29:55 +01:00
|
|
|
return lower.charAt(0).toUpperCase() + lower.substring(1);
|
2019-08-13 21:36:46 +02:00
|
|
|
}
|