Update extension manager to ingest each module type. Add toolbar Manager.

This commit is contained in:
James A. Petts 2020-05-13 11:06:13 +01:00
parent a8340b9b69
commit c42aa37d7f
9 changed files with 186 additions and 85 deletions

View File

@ -23,7 +23,7 @@ export default {
/** /**
* Only required property. Should be a unique value across all extensions. * Only required property. Should be a unique value across all extensions.
*/ */
id: 'cornerstone', id: 'org.ohif.cornerstone',
/** /**
* *
@ -39,10 +39,14 @@ export default {
const onNewImageHandler = jumpData => { const onNewImageHandler = jumpData => {
commandsManager.runCommand('jumpToImage', jumpData); commandsManager.runCommand('jumpToImage', jumpData);
}; };
return <OHIFCornerstoneViewport {...props} onNewImage={onNewImageHandler} />; return (
<OHIFCornerstoneViewport {...props} onNewImage={onNewImageHandler} />
);
}; };
return ExtendedOHIFCornerstoneViewport; return [
{ name: 'cornerstone', component: ExtendedOHIFCornerstoneViewport },
];
}, },
getToolbarModule() { getToolbarModule() {
return toolbarModule; return toolbarModule;

View File

@ -15,8 +15,6 @@ import measurementServiceMappingsFactory from './utils/measurementServiceMapping
export default function init({ servicesManager, configuration }) { export default function init({ servicesManager, configuration }) {
const { UIDialogService, MeasurementService } = servicesManager.services; const { UIDialogService, MeasurementService } = servicesManager.services;
debugger;
const callInputDialog = (data, event, callback) => { const callInputDialog = (data, event, callback) => {
if (UIDialogService) { if (UIDialogService) {
let dialogId = UIDialogService.create({ let dialogId = UIDialogService.create({

View File

@ -165,8 +165,6 @@ function createDicomWebApi(dicomWebConfig) {
} }
}); });
debugger;
return imageIds; return imageIds;
}, },
}); });

View File

@ -12,12 +12,25 @@ export default function mode({ modeConfiguration }) {
routes: [ routes: [
{ {
path: 'viewer', path: 'viewer',
preInit: ({ toolbarManager }) => { init: ({ toolbarManager }) => {
toolbarManager && toolbarManager.addButtons([
toolbarManager.setDefaultLoadOut([ {
[], // Primary id: 'StackScroll', // If id not given will use default in button definition.
[], // Secondary namespace: 'org.ohif.cornerstone.toolbarModule.StackScroll',
]); },
{
id: 'Zoom',
namespace: 'org.ohif.cornerstone.toolbarModule.Zoom',
},
]);
// Could import layout selector here from org.ohif.default (when it exists!)
toolbarManager.setToolBarLayout([
// Primary
['StackScroll', { label: 'More', subMenu: ['Zoom'] }],
// Secondary
['StackScroll'],
]);
}, },
layoutTemplate: ({ routeProps }) => { layoutTemplate: ({ routeProps }) => {
return { return {

View File

@ -0,0 +1,55 @@
export default class toolBarManager {
constructor(extensionManager) {
this.toolBarLayout = [];
this.buttons = {};
this.extensionManager = extensionManager;
}
addButtons(buttons) {
buttons.forEach(button => {
const buttonDefinition = this.extensionManager.getModuleEntry(
button.namespace
);
const id = button.id || buttonDefinition.id;
this.buttons[id] = buttonDefinition;
});
console.log(this.buttons);
}
setToolBarLayout(layouts) {
const toolBarLayout = [];
layouts.forEach(layout => {
const toolBarDefinitions = [];
layout.forEach(element => {
if (typeof element === 'object') {
// process submenu.
const subMenuDefinition = { label: element.label, subMenu: [] };
element.subMenu.forEach(subMenuElement => {
const button = this.buttons[subMenuElement];
subMenuDefinition.subMenu.push(button);
});
toolBarDefinitions.push(subMenuDefinition);
} else {
const button = this.buttons[element];
toolBarDefinitions.push(button);
}
});
toolBarLayout.push(toolBarDefinitions);
});
this.toolBarLayout = toolBarLayout;
console.log(this.toolBarLayout);
}
}

View File

@ -17,6 +17,9 @@ export default class ExtensionManager {
this.modules[moduleType] = []; this.modules[moduleType] = [];
}); });
this.dataSourceMap = {}; this.dataSourceMap = {};
console.log('modules map');
console.log(this.modulesMap);
} }
/** /**
@ -55,11 +58,9 @@ export default class ExtensionManager {
let extensionId = extension.id; let extensionId = extension.id;
if (!extensionId) { if (!extensionId) {
extensionId = Math.random() // Note: Mode framework cannot function without IDs.
.toString(36) log.warn(extension);
.substr(2, 5); throw new Error(`Extension ID not set`);
log.warn(`Extension ID not set. Using random string ID: ${extensionId}`);
} }
if (this.registeredExtensionIds.includes(extensionId)) { if (this.registeredExtensionIds.includes(extensionId)) {
@ -89,25 +90,43 @@ export default class ExtensionManager {
); );
if (extensionModule) { if (extensionModule) {
this._initSpecialModuleTypes( switch (moduleType) {
extensionId, case MODULE_TYPES.COMMANDS:
moduleType, this._initCommandsModule(extensionModule);
extensionModule, break;
dataSources 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.
if (!extensionModule.forEach) {
debugger;
}
extensionModule.forEach(element => {
this.modulesMap[
`${extensionId}.${moduleType}.${element.name}`
] = element;
});
}
this.modules[moduleType].push({ this.modules[moduleType].push({
extensionId, extensionId,
module: extensionModule, module: extensionModule,
}); });
// TODO -> deal with command modules.
extensionModule.forEach(element => {
this.modulesMap[
`${extensionId}.${moduleType}.${element.name}`
] = element;
});
} }
}); });
@ -124,6 +143,27 @@ export default class ExtensionManager {
return this.dataSourceMap[dataSourceName]; return this.dataSourceMap[dataSourceName];
}; };
_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;
});
};
/** /**
* @private * @private
* @param {string} moduleType * @param {string} moduleType
@ -162,64 +202,52 @@ export default class ExtensionManager {
} }
}; };
_initSpecialModuleTypes = ( _initDataSourcesModule(extensionModule, extensionId, dataSources) {
extensionId, extensionModule.forEach(element => {
moduleType, const namespace = `${extensionId}.${MODULE_TYPES.DATA_SOURCE}.${element.name}`;
extensionModule,
dataSources dataSources.forEach(dataSource => {
) => { if (dataSource.namespace === namespace) {
switch (moduleType) { const dataSourceInstance = element.createDataSource(
case 'commandsModule': { dataSource.configuration
const { definitions, defaultContext } = extensionModule; );
if (!definitions || Object.keys(definitions).length === 0) {
log.warn('Commands Module contains no command definitions'); if (this.dataSourceMap[dataSource.sourceName]) {
return; this.dataSourceMap[dataSource.sourceName].push(dataSourceInstance);
} else {
this.dataSourceMap[dataSource.sourceName] = [dataSourceInstance];
}
} }
this._initCommandsModule(definitions, defaultContext); });
break; });
}
case 'dataSourcesModule': {
extensionModule.forEach(element => {
const namespace = `${extensionId}.${moduleType}.${element.name}`;
dataSources.forEach(dataSource => { extensionModule.forEach(element => {
if (dataSource.namespace === namespace) { this.modulesMap[
const dataSourceInstance = element.createDataSource( `${extensionId}.${MODULE_TYPES.DATA_SOURCE}.${element.name}`
dataSource.configuration ] = element;
); });
}
if (this.dataSourceMap[dataSource.sourceName]) {
this.dataSourceMap[dataSource.sourceName].push(
dataSourceInstance
);
} else {
this.dataSourceMap[dataSource.sourceName] = [
dataSourceInstance,
];
}
}
});
});
break;
}
default:
// code block
}
};
/** /**
* *
* @private * @private
* @param {Object[]} commandDefinitions * @param {Object[]} commandDefinitions
*/ */
_initCommandsModule = (commandDefinitions, defaultContext = 'VIEWER') => { _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';
if (!this._commandsManager.getContext(defaultContext)) { if (!this._commandsManager.getContext(defaultContext)) {
this._commandsManager.createContext(defaultContext); this._commandsManager.createContext(defaultContext);
} }
Object.keys(commandDefinitions).forEach(commandName => { Object.keys(definitions).forEach(commandName => {
const commandDefinition = commandDefinitions[commandName]; const commandDefinition = definitions[commandName];
const commandHasContextThatDoesNotExist = const commandHasContextThatDoesNotExist =
commandDefinition.context && commandDefinition.context &&
!this._commandsManager.getContext(commandDefinition.context); !this._commandsManager.getContext(commandDefinition.context);

View File

@ -20,6 +20,7 @@ import ui from './ui';
import user from './user.js'; import user from './user.js';
import dicomMetadataStore from './dicomMetadataStore'; import dicomMetadataStore from './dicomMetadataStore';
import displaySetManager from './displaySetManager'; import displaySetManager from './displaySetManager';
import ToolBarManager from './ToolBarManager';
import utils, { hotkeys } from './utils/'; import utils, { hotkeys } from './utils/';
import { import {
@ -68,6 +69,7 @@ const OHIF = {
IWebApiDataSource, IWebApiDataSource,
dicomMetadataStore, dicomMetadataStore,
displaySetManager, displaySetManager,
ToolBarManager,
}; };
export { export {
@ -105,6 +107,7 @@ export {
IWebApiDataSource, IWebApiDataSource,
dicomMetadataStore, dicomMetadataStore,
displaySetManager, displaySetManager,
ToolBarManager,
}; };
export { OHIF }; export { OHIF };

View File

@ -47,8 +47,6 @@ function appInit(appConfigOrFunc, defaultExtensions) {
MeasurementService, MeasurementService,
]); ]);
debugger;
/** /**
* Example: [ext1, ext2, ext3] * Example: [ext1, ext2, ext3]
* Example2: [[ext1, config], ext2, [ext3, config]] * Example2: [[ext1, config], ext2, [ext3, config]]

View File

@ -1,5 +1,5 @@
import React, { useContext, useEffect, useCallback } from 'react'; import React, { useContext, useEffect, useCallback } from 'react';
import { displaySetManager } from '@ohif/core'; import { displaySetManager, ToolBarManager } from '@ohif/core';
import ViewModelContext from './ViewModelContext'; import ViewModelContext from './ViewModelContext';
import Compose from './Compose'; import Compose from './Compose';
@ -9,15 +9,21 @@ export default function ModeRoute({
dataSourceName, dataSourceName,
extensionManager, extensionManager,
}) { }) {
const { routes, sopClassHandlers, extensions } = mode; const { routes, sopClassHandlers, extensions, init } = mode;
const dataSources = extensionManager.getDataSources(dataSourceName); const dataSources = extensionManager.getDataSources(dataSourceName);
const toolbarManager = new ToolBarManager(extensionManager);
// TODO: For now assume one unique datasource. // TODO: For now assume one unique datasource.
const dataSource = dataSources[0]; const dataSource = dataSources[0];
const route = routes[0];
route.init({ toolbarManager });
console.log(dataSource); console.log(dataSource);
// Add toolbar state to the view model context?
const { displaySetInstanceUids, setDisplaySetInstanceUids } = useContext( const { displaySetInstanceUids, setDisplaySetInstanceUids } = useContext(
ViewModelContext ViewModelContext
); );
@ -42,11 +48,9 @@ export default function ModeRoute({
createDisplaySets(); createDisplaySets();
}, [mode, dataSourceName, location]); }, [mode, dataSourceName, location]);
// Deal with toolbar.
// Only handling one route per mode for now // Only handling one route per mode for now
// You can test via http://localhost:3000/example-mode/dicomweb // You can test via http://localhost:3000/example-mode/dicomweb
const layoutTemplateData = routes[0].layoutTemplate({ location }); const layoutTemplateData = route.layoutTemplate({ location });
const layoutTemplateModuleEntry = extensionManager.getModuleEntry( const layoutTemplateModuleEntry = extensionManager.getModuleEntry(
layoutTemplateData.id layoutTemplateData.id
); );