[OHIF-Viewers]医疗数字阅片-医学影像-事件总线管理器

[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 (
            <>
                
{buttonComponents} { /*fanyinote �±���Viewer���������Ͻǵ� ����������ļ�¼ֵ�������¼ֵ��Ҫapi��֧�ֲ��ܱ���������Ĭ�Ͻ����DZ���js�洢���� ��������Գ��� */}
{ this.buttonGroups.right.length && ( )}
); } } 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 ( ); } } 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:' },
        });

 

 

 

你可能感兴趣的:([OHIF-Viewers]医疗数字阅片-医学影像-事件总线管理器)