[OHIF-Viewers]医疗数字阅片-医学影像-事件总线管理器
添加按钮》调用命令》注册回调函数
App.js
import React, { Component } from 'react'; import { OidcProvider } from 'redux-oidc'; import { I18nextProvider } from 'react-i18next'; import PropTypes from 'prop-types'; import { Provider } from 'react-redux'; import { BrowserRouter as Router } from 'react-router-dom'; import { hot } from 'react-hot-loader/root'; import OHIFCornerstoneExtension from '@ohif/extension-cornerstone'; import { SnackbarProvider, ModalProvider, DialogProvider, OHIFModal, ErrorBoundary } from '@ohif/ui'; import { CommandsManager, ExtensionManager, ServicesManager, HotkeysManager, UINotificationService, UIModalService, UIDialogService, MeasurementService, utils, redux as reduxOHIF, } from '@ohif/core'; import i18n from '@ohif/i18n'; // TODO: This should not be here //import './config'; import { setConfiguration } from './config'; /** Utils */ import { getUserManagerForOpenIdConnectClient, initWebWorkers, } from './utils/index.js'; /** Extensions */ import { GenericViewerCommands, MeasurementsPanel } from './appExtensions'; /** Viewer */ import OHIFStandaloneViewer from './OHIFStandaloneViewer'; /** Store */ import { getActiveContexts } from './store/layout/selectors.js'; import store from './store'; /** Contexts */ import WhiteLabelingContext from './context/WhiteLabelingContext'; import UserManagerContext from './context/UserManagerContext'; import { AppProvider, useAppContext, CONTEXTS } from './context/AppContext'; /** ~~~~~~~~~~~~~ Application Setup */ const commandsManagerConfig = { getAppState: () => store.getState(), getActiveContexts: () => getActiveContexts(store.getState()), }; /** Managers */ const commandsManager = new CommandsManager(commandsManagerConfig); const servicesManager = new ServicesManager(); const hotkeysManager = new HotkeysManager(commandsManager, servicesManager); let extensionManager; /** ~~~~~~~~~~~~~ End Application Setup */ // TODO[react] Use a provider when the whole tree is React window.store = store; window.ohif = window.ohif || {}; window.ohif.app = { commandsManager, hotkeysManager, servicesManager, extensionManager, }; class App extends Component { static propTypes = { config: PropTypes.oneOfType([ PropTypes.func, PropTypes.shape({ routerBasename: PropTypes.string.isRequired, oidc: PropTypes.array, whiteLabeling: PropTypes.shape({ createLogoComponentFn: PropTypes.func, }), extensions: PropTypes.array, }), ]).isRequired, defaultExtensions: PropTypes.array, }; static defaultProps = { config: { showStudyList: true, oidc: [], extensions: [], }, defaultExtensions: [], }; _appConfig; _userManager; constructor(props) { super(props); const { config, defaultExtensions } = props; const appDefaultConfig = { showStudyList: true, cornerstoneExtensionConfig: {}, extensions: [], routerBasename: '/', }; this._appConfig = { ...appDefaultConfig, ...(typeof config === 'function' ? config({ servicesManager }) : config), }; const { servers, hotkeys: appConfigHotkeys, cornerstoneExtensionConfig, extensions, oidc, } = this._appConfig; setConfiguration(this._appConfig); this.initUserManager(oidc); _initServices([ UINotificationService, UIModalService, UIDialogService, MeasurementService, ]); _initExtensions( [...defaultExtensions, ...extensions], cornerstoneExtensionConfig, this._appConfig ); /* * Must run after extension commands are registered * if there is no hotkeys from localStorage set up from config. */ _initHotkeys(appConfigHotkeys); _initServers(servers); initWebWorkers(); } render() { const { whiteLabeling, routerBasename } = this._appConfig; const { UINotificationService, UIDialogService, UIModalService, MeasurementService, } = servicesManager.services; //拥有 _userManager 模块才会走, 这个要对接OIDC模块,就是开放身份认证系统 if (this._userManager) { return (); } console.log("hit 初始页面"); return ( ); } initUserManager(oidc) { if (oidc && !!oidc.length) { const firstOpenIdClient = this._appConfig.oidc[0]; const { protocol, host } = window.location; const { routerBasename } = this._appConfig; const baseUri = `${protocol}//${host}${routerBasename}`; const redirect_uri = firstOpenIdClient.redirect_uri || '/callback'; const silent_redirect_uri = firstOpenIdClient.silent_redirect_uri || '/silent-refresh.html'; const post_logout_redirect_uri = firstOpenIdClient.post_logout_redirect_uri || '/'; const openIdConnectConfiguration = Object.assign({}, firstOpenIdClient, { redirect_uri: _makeAbsoluteIfNecessary(redirect_uri, baseUri), silent_redirect_uri: _makeAbsoluteIfNecessary( silent_redirect_uri, baseUri ), post_logout_redirect_uri: _makeAbsoluteIfNecessary( post_logout_redirect_uri, baseUri ), }); this._userManager = getUserManagerForOpenIdConnectClient( store, openIdConnectConfiguration ); } } } function _initServices(services) { servicesManager.registerServices(services); } /** * @param */ function _initExtensions(extensions, cornerstoneExtensionConfig, appConfig) { extensionManager = new ExtensionManager({ commandsManager, servicesManager, appConfig, api: { contexts: CONTEXTS, hooks: { useAppContext } } }); const requiredExtensions = [ GenericViewerCommands, [OHIFCornerstoneExtension, cornerstoneExtensionConfig], /* WARNING: MUST BE REGISTERED _AFTER_ OHIFCornerstoneExtension */ MeasurementsPanel, ]; const mergedExtensions = requiredExtensions.concat(extensions); extensionManager.registerExtensions(mergedExtensions); } /** * * @param {Object} appConfigHotkeys - Default hotkeys, as defined by app config */ function _initHotkeys(appConfigHotkeys) { // TODO: Use something more resilient // TODO: Mozilla has a special library for this const userPreferredHotkeys = JSON.parse( localStorage.getItem('hotkey-definitions') || '{}' ); // TODO: hotkeysManager.isValidDefinitionObject(/* */) const hasUserPreferences = userPreferredHotkeys && Object.keys(userPreferredHotkeys).length > 0; if (hasUserPreferences) { hotkeysManager.setHotkeys(userPreferredHotkeys); } else { hotkeysManager.setHotkeys(appConfigHotkeys); } hotkeysManager.setDefaultHotKeys(appConfigHotkeys); } function _initServers(servers) { if (servers) { utils.addServers(servers, store); } } function _isAbsoluteUrl(url) { return url.includes('http://') || url.includes('https://'); } function _makeAbsoluteIfNecessary(url, base_url) { if (_isAbsoluteUrl(url)) { return url; } /* * Make sure base_url and url are not duplicating slashes. */ if (base_url[base_url.length - 1] === '/') { base_url = base_url.slice(0, base_url.length - 1); } return base_url + url; } /* * Only wrap/use hot if in dev. */ const ExportedApp = process.env.NODE_ENV === 'development' ? hot(App) : App; export default ExportedApp; export { commandsManager, extensionManager, hotkeysManager, servicesManager };
其中这里是进行定义管理器
/** Managers */ const commandsManager = new CommandsManager(commandsManagerConfig); const servicesManager = new ServicesManager(); const hotkeysManager = new HotkeysManager(commandsManager, servicesManager); let extensionManager; /** ~~~~~~~~~~~~~ End Application Setup */ // TODO[react] Use a provider when the whole tree is React window.store = store; window.ohif = window.ohif || {}; window.ohif.app = { commandsManager, hotkeysManager, servicesManager, extensionManager, };
commandsManager管理整个系统的命令 和 回调函数, 现有的头部所有按钮命令都是通过commandsManager分发的;
CommandsManager.js定义
import log from '../log.js'; /** * The definition of a command * * @typedef {Object} CommandDefinition * @property {Function} commandFn - Command to call * @property {Array} storeContexts - Array of string of modules required from store * @property {Object} options - Object of params to pass action */ /** * The Commands Manager tracks named commands (or functions) that are scoped to * a context. When we attempt to run a command with a given name, we look for it * in our active contexts. If found, we run the command, passing in any application * or call specific data specified in the command's definition. * * NOTE: A more robust version of the CommandsManager lives in v1. If you're looking * to extend this class, please check it's source before adding new methods. */ export class CommandsManager { constructor({ getAppState, getActiveContexts } = {}) { this.contexts = {}; if (!getAppState || !getActiveContexts) { log.warn( 'CommandsManager was instantiated without getAppState() or getActiveContexts()' ); } this._getAppState = getAppState; this._getActiveContexts = getActiveContexts; } /** * Allows us to create commands "per context". An example would be the "Cornerstone" * context having a `SaveImage` command, and the "VTK" context having a `SaveImage` * command. The distinction of a context allows us to call the command in either * context, and have faith that the correct command will be run. * * @method * @param {string} contextName - Namespace for commands * @returns {undefined} */ createContext(contextName) { if (!contextName) { return; } if (this.contexts[contextName]) { return this.clearContext(contextName); } this.contexts[contextName] = {}; } /** * Returns all command definitions for a given context * * @method * @param {string} contextName - Namespace for commands * @returns {Object} - the matched context */ getContext(contextName) { const context = this.contexts[contextName]; if (!context) { return; } return context; } /** * Clears all registered commands for a given context. * * @param {string} contextName - Namespace for commands * @returns {undefined} */ clearContext(contextName) { if (!contextName) { return; } this.contexts[contextName] = {}; } /** * Register a new command with the command manager. Scoped to a context, and * with a definition to assist command callers w/ providing the necessary params * * @method * @param {string} contextName - Namespace for command; often scoped to the extension that added it * @param {string} commandName - Unique name identifying the command * @param {CommandDefinition} definition - {@link CommandDefinition} */ registerCommand(contextName, commandName, definition) { if (typeof definition !== 'object') { return; } const context = this.getContext(contextName); if (!context) { return; } context[commandName] = definition; } /** * Finds a command with the provided name if it exists in the specified context, * or a currently active context. * * @method * @param {String} commandName - Command to find * @param {String} [contextName] - Specific command to look in. Defaults to current activeContexts */ getCommand(commandName, contextName) { let contexts = []; if (contextName) { const context = this.getContext(contextName); if (context) { contexts.push(context); } } else { const activeContexts = this._getActiveContexts(); activeContexts.forEach(activeContext => { const context = this.getContext(activeContext); if (context) { contexts.push(context); } }); } if (contexts.length === 0) { return; } let foundCommand; contexts.forEach(context => { if (context[commandName]) { foundCommand = context[commandName]; } }); return foundCommand; } /** * * @method * @param {String} commandName * @param {Object} [options={}] - Extra options to pass the command. Like a mousedown event * @param {String} [contextName] */ runCommand(commandName, options = {}, contextName) { const definition = this.getCommand(commandName, contextName); if (!definition) { log.warn(`Command "${commandName}" not found in current context`); return; } const { commandFn, storeContexts = [] } = definition; const definitionOptions = definition.options; let commandParams = {}; const appState = this._getAppState(); storeContexts.forEach(context => { commandParams[context] = appState[context]; }); commandParams = Object.assign( {}, commandParams, // Required store contexts definitionOptions, // "Command configuration" options // "Time of call" info ); if (typeof commandFn !== 'function') { log.warn(`No commandFn was defined for command "${commandName}"`); return; } else { return commandFn(commandParams); } } } export default CommandsManager;
toolbarModule.js 这里添加按钮结构
// TODO: A way to add Icons that don't already exist? // - Register them and add // - Include SVG Source/Inline? // - By URL, or own component? // What KINDS of toolbar buttons do we have... // - One's that dispatch commands // - One's that set tool's active // - More custom, like CINE // - Built in for one's like this, or custom components? // Visible? // Disabled? // Based on contexts or misc. criteria? // -- ACTIVE_ROUTE::VIEWER // -- ACTIVE_VIEWPORT::CORNERSTONE // setToolActive commands should receive the button event that triggered // so we can do the "bind to this button" magic const TOOLBAR_BUTTON_TYPES = { COMMAND: 'command', SET_TOOL_ACTIVE: 'setToolActive', BUILT_IN: 'builtIn', }; const TOOLBAR_BUTTON_BEHAVIORS = { CINE: 'CINE', DOWNLOAD_SCREEN_SHOT: 'DOWNLOAD_SCREEN_SHOT', }; /* TODO: Export enums through a extension manager. */ const enums = { TOOLBAR_BUTTON_TYPES, TOOLBAR_BUTTON_BEHAVIORS, }; const definitions = [ { id: 'StackScroll', label: 'Stack Scroll', icon: 'bars', // type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE, commandName: 'setToolActive', commandOptions: { toolName: 'StackScroll' }, }, { id: 'Zoom', label: 'Zoom', icon: 'search-plus', // type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE, commandName: 'setToolActive', commandOptions: { toolName: 'Zoom' }, }, { id: 'Wwwc', label: 'Levels', icon: 'level', // type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE, commandName: 'setToolActive', commandOptions: { toolName: 'Wwwc' }, }, { id: 'Pan', label: 'Pan', icon: 'arrows', // type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE, commandName: 'setToolActive', commandOptions: { toolName: 'Pan' }, }, { id: 'Length', label: 'Length', icon: 'measure-temp', // type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE, commandName: 'setToolActive', commandOptions: { toolName: 'Length' }, }, { id: 'ArrowAnnotate', label: 'Annotate', icon: 'measure-non-target', // type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE, commandName: 'setToolActive', commandOptions: { toolName: 'ArrowAnnotate' }, }, { id: 'Angle', label: 'Angle', icon: 'angle-left', // type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE, commandName: 'setToolActive', commandOptions: { toolName: 'Angle' }, }, { id: 'Reset', label: 'Reset', icon: 'reset', // type: TOOLBAR_BUTTON_TYPES.COMMAND, commandName: 'resetViewport', }, { id: 'KeyFrame', label: '关键帧', icon: 'star', // type: TOOLBAR_BUTTON_TYPES.BUILT_IN, options: { behavior: "SetKeyFrame", }, }, { id: 'Cine', label: 'CINE', icon: 'youtube', // type: TOOLBAR_BUTTON_TYPES.BUILT_IN, options: { behavior: TOOLBAR_BUTTON_BEHAVIORS.CINE, }, }, { id: 'More', label: 'More', icon: 'ellipse-circle', buttons: [ { id: 'Magnify', label: 'Magnify', icon: 'circle', // type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE, commandName: 'setToolActive', commandOptions: { toolName: 'Magnify' }, }, { id: 'WwwcRegion', label: 'ROI Window', icon: 'stop', // type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE, commandName: 'setToolActive', commandOptions: { toolName: 'WwwcRegion' }, }, { id: 'DragProbe', label: 'Probe', icon: 'dot-circle', // type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE, commandName: 'setToolActive', commandOptions: { toolName: 'DragProbe' }, }, { id: 'EllipticalRoi', label: 'Ellipse', icon: 'circle-o', // type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE, commandName: 'setToolActive', commandOptions: { toolName: 'EllipticalRoi' }, }, { id: 'RectangleRoi', label: 'Rectangle', icon: 'square-o', // type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE, commandName: 'setToolActive', commandOptions: { toolName: 'RectangleRoi' }, }, { id: 'Invert', label: 'Invert', icon: 'adjust', // type: TOOLBAR_BUTTON_TYPES.COMMAND, commandName: 'invertViewport', }, { id: 'RotateRight', label: 'Rotate Right', icon: 'rotate-right', // type: TOOLBAR_BUTTON_TYPES.COMMAND, commandName: 'rotateViewportCW', }, { id: 'FlipH', label: 'Flip H', icon: 'ellipse-h', // type: TOOLBAR_BUTTON_TYPES.COMMAND, commandName: 'flipViewportHorizontal', }, { id: 'FlipV', label: 'Flip V', icon: 'ellipse-v', // type: TOOLBAR_BUTTON_TYPES.COMMAND, commandName: 'flipViewportVertical', }, { id: 'Clear', label: 'Clear', icon: 'trash', // type: TOOLBAR_BUTTON_TYPES.COMMAND, commandName: 'clearAnnotations', }, { id: 'Bidirectional', label: 'Bidirectional', icon: 'measure-target', // type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE, commandName: 'setToolActive', commandOptions: { toolName: 'Bidirectional' }, }, { id: 'Download', label: 'Download', icon: 'create-screen-capture', // type: TOOLBAR_BUTTON_TYPES.BUILT_IN, options: { behavior: TOOLBAR_BUTTON_BEHAVIORS.DOWNLOAD_SCREEN_SHOT, togglable: true, }, }, ], }, { id: 'Exit2DMPR', label: 'Exit 2D MPR', icon: 'times', // type: TOOLBAR_BUTTON_TYPES.COMMAND, commandName: 'setCornerstoneLayout', context: 'ACTIVE_VIEWPORT::VTK', }, ]; export default { definitions, defaultContext: 'ACTIVE_VIEWPORT::CORNERSTONE', };
ToolbarRow.js中调用命令
import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { withTranslation } from 'react-i18next'; import { MODULE_TYPES } from '@ohif/core'; import { ExpandableToolMenu, RoundedButtonGroup, ToolbarButton, withModal, withDialog, } from '@ohif/ui'; import './ToolbarRow.css'; import { commandsManager, extensionManager } from './../App.js'; import ConnectedCineDialog from './ConnectedCineDialog'; import ConnectedLayoutButton from './ConnectedLayoutButton'; import { withAppContext } from '../context/AppContext'; class ToolbarRow extends Component { // TODO: Simplify these? isOpen can be computed if we say "any" value for selected, // closed if selected is null/undefined static propTypes = { isLeftSidePanelOpen: PropTypes.bool.isRequired, isRightSidePanelOpen: PropTypes.bool.isRequired, selectedLeftSidePanel: PropTypes.string.isRequired, selectedRightSidePanel: PropTypes.string.isRequired, handleSidePanelChange: PropTypes.func.isRequired, activeContexts: PropTypes.arrayOf(PropTypes.string).isRequired, studies: PropTypes.array, t: PropTypes.func.isRequired, // NOTE: withDialog, withModal HOCs dialog: PropTypes.any, modal: PropTypes.any, }; static defaultProps = { studies: [], }; constructor(props) { super(props); const toolbarButtonDefinitions = _getVisibleToolbarButtons.call(this); // TODO: // If it's a tool that can be active... Mark it as active? // - Tools that are on/off? // - Tools that can be bound to multiple buttons? // Normal ToolbarButtons... // Just how high do we need to hoist this state? // Why ToolbarRow instead of just Toolbar? Do we have any others? this.state = { toolbarButtons: toolbarButtonDefinitions, activeButtons: [], }; this.seriesPerStudyCount = []; this._handleBuiltIn = _handleBuiltIn.bind(this); this.updateButtonGroups(); } updateButtonGroups() { //fanyinote panelModules.module.menuOptions.label �Dz˵�ʵ�ʵ� ���� const panelModules = extensionManager.modules[MODULE_TYPES.PANEL]; this.buttonGroups = { left: [], right: [], }; // ~ FIND MENU OPTIONS panelModules.forEach(panelExtension => { const panelModule = panelExtension.module; const defaultContexts = Array.from(panelModule.defaultContext); panelModule.menuOptions.forEach(menuOption => { const contexts = Array.from(menuOption.context || defaultContexts); const hasActiveContext = this.props.activeContexts.some(actx => contexts.includes(actx) ); // It's a bit beefy to pass studies; probably only need to be reactive on `studyInstanceUIDs` and activeViewport? // Note: This does not cleanly handle `studies` prop updating with panel open const isDisabled = typeof menuOption.isDisabled === 'function' && menuOption.isDisabled(this.props.studies); if (hasActiveContext && !isDisabled) { //�����ݽṹ���� ��ť���� menuOptionEntry "{"value":"measurement-panel","icon":"list","bottomLabel":"Measurements"}" //bottomLabel ������ ui�ϵľ���textֵ const menuOptionEntry = { value: menuOption.target, icon: menuOption.icon, bottomLabel: menuOption.label, }; const from = menuOption.from || 'right'; this.buttonGroups[from].push(menuOptionEntry); } }); }); // TODO: This should come from extensions, instead of being baked in this.buttonGroups.left.unshift({ value: 'studies', icon: 'th-large', bottomLabel: this.props.t('Series'), }); } componentDidUpdate(prevProps) { const activeContextsChanged = prevProps.activeContexts !== this.props.activeContexts; const prevStudies = prevProps.studies; const studies = this.props.studies; const seriesPerStudyCount = this.seriesPerStudyCount; let studiesUpdated = false; if (prevStudies.length !== studies.length) { studiesUpdated = true; } else { for (let i = 0; i < studies.length; i++) { if (studies[i].series.length !== seriesPerStudyCount[i]) { seriesPerStudyCount[i] = studies[i].series.length; studiesUpdated = true; break; } } } if (studiesUpdated) { this.updateButtonGroups(); } if (activeContextsChanged) { this.setState( { toolbarButtons: _getVisibleToolbarButtons.call(this), }, this.closeCineDialogIfNotApplicable ); } } closeCineDialogIfNotApplicable = () => { const { dialog } = this.props; let { dialogId, activeButtons, toolbarButtons } = this.state; if (dialogId) { const cineButtonPresent = toolbarButtons.find( button => button.options && button.options.behavior === 'CINE' ); if (!cineButtonPresent) { dialog.dismiss({ id: dialogId }); activeButtons = activeButtons.filter( button => button.options && button.options.behavior !== 'CINE' ); this.setState({ dialogId: null, activeButtons }); } } }; render() { const buttonComponents = _getButtonComponents.call( this, this.state.toolbarButtons, this.state.activeButtons ); const onPress = (side, value) => { this.props.handleSidePanelChange(side, value); }; const onPressLeft = onPress.bind(this, 'left'); const onPressRight = onPress.bind(this, 'right'); { /* fanyinote ���±ߵ�<>��һ����д�ı�ǩ��Ŀ����Ϊ�˰���һ���ڲ�Ԫ��*/ } return ( <>> ); } } function _getCustomButtonComponent(button, activeButtons) { const CustomComponent = button.CustomComponent; const isValidComponent = typeof CustomComponent === 'function'; // Check if its a valid customComponent. Later on an CustomToolbarComponent interface could be implemented. if (isValidComponent) { const parentContext = this; const activeButtonsIds = activeButtons.map(button => button.id); const isActive = activeButtonsIds.includes(button.id); return ({buttonComponents}{ /*fanyinote �±���Viewer���������Ͻǵ� ����������ļ�¼ֵ�������¼ֵ��Ҫapi��֧�ֲ��ܱ���������Ĭ�Ͻ����DZ���js�洢���� ��������Գ��� */} { this.buttonGroups.right.length && ()} ); } } function _getExpandableButtonComponent(button, activeButtons) { // Iterate over button definitions and update `onClick` behavior let activeCommand; const childButtons = button.buttons.map(childButton => { childButton.onClick = _handleToolbarButtonClick.bind(this, childButton); if (activeButtons.map(button => button.id).indexOf(childButton.id) > -1) { activeCommand = childButton.id; } return childButton; }); return ( ); } function _getDefaultButtonComponent(button, activeButtons) { return ( button.id).includes(button.id)} /> ); } /** * Determine which extension buttons should be showing, if they're * active, and what their onClick behavior should be. */ function _getButtonComponents(toolbarButtons, activeButtons) { const _this = this; return toolbarButtons.map(button => { const hasCustomComponent = button.CustomComponent; const hasNestedButtonDefinitions = button.buttons && button.buttons.length; if (hasCustomComponent) { return _getCustomButtonComponent.call(_this, button, activeButtons); } if (hasNestedButtonDefinitions) { return _getExpandableButtonComponent.call(_this, button, activeButtons); } return _getDefaultButtonComponent.call(_this, button, activeButtons); }); } /** * TODO: DEPRECATE * This is used exclusively in `extensions/cornerstone/src` * We have better ways with new UI Services to trigger "builtin" behaviors * * A handy way for us to handle different button types. IE. firing commands for * buttons, or initiation built in behavior. * * @param {*} button * @param {*} evt * @param {*} props */ function _handleToolbarButtonClick(button, evt, props) { const { activeButtons } = this.state; console.log("_handleToolbarButtonClick"); console.log(button, evt, props); if (button.commandName) { const options = Object.assign({ evt }, button.commandOptions); commandsManager.runCommand(button.commandName, options); } // TODO: Use Types ENUM // TODO: We can update this to be a `getter` on the extension to query // For the active tools after we apply our updates? if (button.type === 'setToolActive') { const toggables = activeButtons.filter( ({ options }) => options && !options.togglable ); this.setState({ activeButtons: [...toggables, button] }); } else if (button.type === 'builtIn') { this._handleBuiltIn(button); } } /** * */ function _getVisibleToolbarButtons() { const toolbarModules = extensionManager.modules[MODULE_TYPES.TOOLBAR]; const toolbarButtonDefinitions = []; toolbarModules.forEach(extension => { const { definitions, defaultContext } = extension.module; definitions.forEach(definition => { const context = definition.context || defaultContext; if (this.props.activeContexts.includes(context)) { toolbarButtonDefinitions.push(definition); } }); }); return toolbarButtonDefinitions; } function _handleBuiltIn(button) { /* TODO: Keep cine button active until its unselected. */ const { dialog, t } = this.props; const { dialogId } = this.state; const { id, options } = button; if (options.behavior === 'CINE') { if (dialogId) { dialog.dismiss({ id: dialogId }); this.setState(state => ({ dialogId: null, activeButtons: [ ...state.activeButtons.filter(button => button.id !== id), ], })); } else { const spacing = 20; const { x, y } = document .querySelector(`.ViewerMain`) .getBoundingClientRect(); const newDialogId = dialog.create({ content: ConnectedCineDialog, defaultPosition: { x: x + spacing || 0, y: y + spacing || 0, }, }); this.setState(state => ({ dialogId: newDialogId, activeButtons: [...state.activeButtons, button], })); } } if (options.behavior === 'DOWNLOAD_SCREEN_SHOT') { commandsManager.runCommand('showDownloadViewportModal', { title: t('Download High Quality Image'), }); } if (options.behavior === 'SetKeyFrame') { commandsManager.runCommand('SetKeyFrame', { testData:"test data", }); } } export default withTranslation(['Common', 'ViewportDownloadForm'])( withModal(withDialog(withAppContext(ToolbarRow))) );
HeyFrameTag.js 中回调命令
import React from 'react';
import keyFrameTagStyle from "./HeyFrameTag.css";
class HeyFrameTag extends React.Component {
constructor(props) {
super(props);
//this.state = { b: this.props.imageIdIndex }
this.state = { isShowKeyImageTag: false, currentFrameInfo: null, keyFrameData: [] }
//最近一次更新时间 少于30秒 那么返回 不更新 lastKeyFrameListUpdateDate
this.lastKeyFrameListUpdateDate = null;
this.registerCommand();
}
registerCommand() {
//注册 设置关键帧 handerSetKeyFrameCommand 命令
let contextName = 'ACTIVE_VIEWPORT::CORNERSTONE';
window.ohif.app.commandsManager.registerCommand(contextName, 'SetKeyFrame', {
commandFn: this.handerSetKeyFrameCommand.bind(this),
storeContexts: ['viewers'],
options: { passMeToCommandFn: ':wave:' },
});
window.ohif.app.commandsManager.registerCommand(contextName, 'BindKeyFrame', {
commandFn: this.handerBindKeyFrameCommand.bind(this),
storeContexts: ['viewers'],
options: { passMeToCommandFn: ':wave:' },
});
}
addKeyFrameToKeyFrameData() {
var sopInstanceUID = this.state.currentFrameInfo.SOPInstanceUID;
var keyFrameData = this.state.keyFrameData.slice();
var indexOf = keyFrameData.indexOf(sopInstanceUID);
if (indexOf == -1) {
keyFrameData.push(sopInstanceUID);
}
this.setState({ keyFrameData });
return keyFrameData;
}
removeKeyFrameToKeyFrameData() {
var sopInstanceUID = this.state.currentFrameInfo.SOPInstanceUID;
var keyFrameData = this.state.keyFrameData.slice();
var indexOf = keyFrameData.indexOf(sopInstanceUID);
if (indexOf >= -1) {
keyFrameData.splice(indexOf, 1);
}
this.setState({ keyFrameData });
return keyFrameData;
}
handerSetKeyFrameCommand(cmdParam) {
var studyInstanceUID = this.state.currentFrameInfo.StudyInstanceUID;
console.log("handerSetKeyFrameCommand param=", cmdParam, "this.state.isShowKeyImageTag=", this.state.isShowKeyImageTag, "currentFrameInfo=", this.state.currentFrameInfo);
var isAddKeyFrameToKeyFrameData = !this.state.isShowKeyImageTag;
var keyFrameData = []
if (isAddKeyFrameToKeyFrameData) keyFrameData = this.addKeyFrameToKeyFrameData();
else keyFrameData = this.removeKeyFrameToKeyFrameData();
var server = window.config.servers.dicomWeb[0];
var paramUpdateKeyFrame = { StudyInstanceUID: studyInstanceUID, KeyFrameData: keyFrameData };
fetch(server.wadoRoot + '/Measurement/UpdateKeyFrame',
{
mode: 'cors',
method: 'POST',
body: JSON.stringify(paramUpdateKeyFrame)
})
.then(res => res.json())
.then(data => {
console.log(data);
})
.catch(e => console.log('错误:', e));
this.setState({ isShowKeyImageTag: isAddKeyFrameToKeyFrameData });
}
handerBindKeyFrameCommand(cmdParam) {
console.log("handerBindKeyFrameCommand param=", cmdParam, "this.state.isShowKeyImageTag=", this.state.isShowKeyImageTag);
this.setState({ currentFrameInfo: cmdParam });
this.getKeyFrameData(cmdParam);
}
getKeyFrameData(param) {
let self = this;
if (!param) return;
//最近一次更新时间 少于30秒 那么返回 不更新 lastKeyFrameListUpdateDate
var nowTime = new Date().getTime();
if (this.lastKeyFrameListUpdateDate && (nowTime - this.lastKeyFrameListUpdateDate < 30 * 1000)) {
console.log("不更新", this.lastKeyFrameListUpdateDate);
var keyFrameData = this.state.keyFrameData;
if (keyFrameData.indexOf(this.state.currentFrameInfo.SOPInstanceUID) > -1) {
this.setState({ isShowKeyImageTag: true, keyFrameData: keyFrameData });
} else {
this.setState({ isShowKeyImageTag: false, keyFrameData: keyFrameData });
}
return;
}
this.lastKeyFrameListUpdateDate = nowTime;
var server = window.config.servers.dicomWeb[0];
var paramGetKeyFrameList = { StudyInstanceUID: param.StudyInstanceUID };
fetch(server.wadoRoot + '/Measurement/KeyFrameList',
{
mode: 'cors',
method: 'POST',
body: JSON.stringify(paramGetKeyFrameList)
})
.then(res => res.json())
.then(data => {
console.log(data);
if (data.Data.KeyFrame && data.Data.KeyFrame.KeyFrameData) {
var keyFrameData = data.Data.KeyFrame.KeyFrameData;
if (keyFrameData.indexOf(self.state.currentFrameInfo.SOPInstanceUID) > -1) {
this.setState({ isShowKeyImageTag: true, keyFrameData: keyFrameData });
} else {
this.setState({ isShowKeyImageTag: false, keyFrameData: keyFrameData });
}
}
})
.catch(e => console.log('错误:', e));
}
componentWillMount() {
}
componentDidMount() {
}
render() {
return (
)
}
}
export default HeyFrameTag;
其中这里是回调注注册,正常情况下不用在这里注册,此处注册纯属偷懒.
正常注册位置在这里/Viewers/extensions/cornerstone/src/commandsModule.js
window.ohif.app.commandsManager.registerCommand(contextName, 'SetKeyFrame', { commandFn: this.handerSetKeyFrameCommand.bind(this), storeContexts: ['viewers'], options: { passMeToCommandFn: ':wave:' }, });