diff --git a/extensions/cornerstone/src/index.js b/extensions/cornerstone/src/index.js index 0d30d2232..985663238 100644 --- a/extensions/cornerstone/src/index.js +++ b/extensions/cornerstone/src/index.js @@ -23,7 +23,7 @@ export default { /** * 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 => { commandsManager.runCommand('jumpToImage', jumpData); }; - return ; + return ( + + ); }; - return ExtendedOHIFCornerstoneViewport; + return [ + { name: 'cornerstone', component: ExtendedOHIFCornerstoneViewport }, + ]; }, getToolbarModule() { return toolbarModule; diff --git a/extensions/cornerstone/src/init.js b/extensions/cornerstone/src/init.js index e2f807838..27e962b80 100644 --- a/extensions/cornerstone/src/init.js +++ b/extensions/cornerstone/src/init.js @@ -15,8 +15,6 @@ import measurementServiceMappingsFactory from './utils/measurementServiceMapping export default function init({ servicesManager, configuration }) { const { UIDialogService, MeasurementService } = servicesManager.services; - debugger; - const callInputDialog = (data, event, callback) => { if (UIDialogService) { let dialogId = UIDialogService.create({ diff --git a/extensions/default/src/DicomWebDataSource/index.js b/extensions/default/src/DicomWebDataSource/index.js index 74f7aa44a..e4c8f9dc9 100644 --- a/extensions/default/src/DicomWebDataSource/index.js +++ b/extensions/default/src/DicomWebDataSource/index.js @@ -165,8 +165,6 @@ function createDicomWebApi(dicomWebConfig) { } }); - debugger; - return imageIds; }, }); diff --git a/modes/example/src/index.js b/modes/example/src/index.js index fd48b5e58..706d0ca11 100644 --- a/modes/example/src/index.js +++ b/modes/example/src/index.js @@ -12,12 +12,25 @@ export default function mode({ modeConfiguration }) { routes: [ { path: 'viewer', - preInit: ({ toolbarManager }) => { - toolbarManager && - toolbarManager.setDefaultLoadOut([ - [], // Primary - [], // Secondary - ]); + init: ({ toolbarManager }) => { + toolbarManager.addButtons([ + { + id: 'StackScroll', // If id not given will use default in button definition. + 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 }) => { return { diff --git a/platform/core/src/ToolBarManager.js b/platform/core/src/ToolBarManager.js new file mode 100644 index 000000000..d94b1488c --- /dev/null +++ b/platform/core/src/ToolBarManager.js @@ -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); + } +} diff --git a/platform/core/src/extensions/ExtensionManager.js b/platform/core/src/extensions/ExtensionManager.js index 5fab3d8f3..27b0f2b44 100644 --- a/platform/core/src/extensions/ExtensionManager.js +++ b/platform/core/src/extensions/ExtensionManager.js @@ -17,6 +17,9 @@ export default class ExtensionManager { this.modules[moduleType] = []; }); this.dataSourceMap = {}; + + console.log('modules map'); + console.log(this.modulesMap); } /** @@ -55,11 +58,9 @@ export default class ExtensionManager { 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}`); + // Note: Mode framework cannot function without IDs. + log.warn(extension); + throw new Error(`Extension ID not set`); } if (this.registeredExtensionIds.includes(extensionId)) { @@ -89,25 +90,43 @@ export default class ExtensionManager { ); if (extensionModule) { - this._initSpecialModuleTypes( - extensionId, - moduleType, - extensionModule, - dataSources - ); + 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. + if (!extensionModule.forEach) { + debugger; + } + + extensionModule.forEach(element => { + this.modulesMap[ + `${extensionId}.${moduleType}.${element.name}` + ] = element; + }); + } this.modules[moduleType].push({ extensionId, 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]; }; + _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 * @param {string} moduleType @@ -162,64 +202,52 @@ export default class ExtensionManager { } }; - _initSpecialModuleTypes = ( - extensionId, - moduleType, - extensionModule, - dataSources - ) => { - switch (moduleType) { - case 'commandsModule': { - const { definitions, defaultContext } = extensionModule; - if (!definitions || Object.keys(definitions).length === 0) { - log.warn('Commands Module contains no command definitions'); - return; + _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]; + } } - this._initCommandsModule(definitions, defaultContext); - break; - } - case 'dataSourcesModule': { - extensionModule.forEach(element => { - const namespace = `${extensionId}.${moduleType}.${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, - ]; - } - } - }); - }); - - break; - } - default: - // code block - } - }; + extensionModule.forEach(element => { + this.modulesMap[ + `${extensionId}.${MODULE_TYPES.DATA_SOURCE}.${element.name}` + ] = element; + }); + } /** * * @private * @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)) { this._commandsManager.createContext(defaultContext); } - Object.keys(commandDefinitions).forEach(commandName => { - const commandDefinition = commandDefinitions[commandName]; + Object.keys(definitions).forEach(commandName => { + const commandDefinition = definitions[commandName]; const commandHasContextThatDoesNotExist = commandDefinition.context && !this._commandsManager.getContext(commandDefinition.context); diff --git a/platform/core/src/index.js b/platform/core/src/index.js index 6d4c691e1..296d91b6d 100644 --- a/platform/core/src/index.js +++ b/platform/core/src/index.js @@ -20,6 +20,7 @@ import ui from './ui'; import user from './user.js'; import dicomMetadataStore from './dicomMetadataStore'; import displaySetManager from './displaySetManager'; +import ToolBarManager from './ToolBarManager'; import utils, { hotkeys } from './utils/'; import { @@ -68,6 +69,7 @@ const OHIF = { IWebApiDataSource, dicomMetadataStore, displaySetManager, + ToolBarManager, }; export { @@ -105,6 +107,7 @@ export { IWebApiDataSource, dicomMetadataStore, displaySetManager, + ToolBarManager, }; export { OHIF }; diff --git a/platform/viewer/src/appInit.js b/platform/viewer/src/appInit.js index d489808b3..508c34dc7 100644 --- a/platform/viewer/src/appInit.js +++ b/platform/viewer/src/appInit.js @@ -47,8 +47,6 @@ function appInit(appConfigOrFunc, defaultExtensions) { MeasurementService, ]); - debugger; - /** * Example: [ext1, ext2, ext3] * Example2: [[ext1, config], ext2, [ext3, config]] diff --git a/platform/viewer/src/routes/ModeRoute.js b/platform/viewer/src/routes/ModeRoute.js index 5feacb448..b17848324 100644 --- a/platform/viewer/src/routes/ModeRoute.js +++ b/platform/viewer/src/routes/ModeRoute.js @@ -1,5 +1,5 @@ import React, { useContext, useEffect, useCallback } from 'react'; -import { displaySetManager } from '@ohif/core'; +import { displaySetManager, ToolBarManager } from '@ohif/core'; import ViewModelContext from './ViewModelContext'; import Compose from './Compose'; @@ -9,15 +9,21 @@ export default function ModeRoute({ dataSourceName, extensionManager, }) { - const { routes, sopClassHandlers, extensions } = mode; + const { routes, sopClassHandlers, extensions, init } = mode; const dataSources = extensionManager.getDataSources(dataSourceName); + const toolbarManager = new ToolBarManager(extensionManager); + // TODO: For now assume one unique datasource. const dataSource = dataSources[0]; + const route = routes[0]; + + route.init({ toolbarManager }); console.log(dataSource); + // Add toolbar state to the view model context? const { displaySetInstanceUids, setDisplaySetInstanceUids } = useContext( ViewModelContext ); @@ -42,11 +48,9 @@ export default function ModeRoute({ createDisplaySets(); }, [mode, dataSourceName, location]); - // Deal with toolbar. - // Only handling one route per mode for now // 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( layoutTemplateData.id );