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

[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 (
        <ErrorBoundary context='App'>
          <Provider store={store}>
            <AppProvider config={this._appConfig}>
              <I18nextProvider i18n={i18n}>
                <OidcProvider store={store} userManager={this._userManager}>
                  <UserManagerContext.Provider value={this._userManager}>
                    <Router basename={routerBasename}>
                      <WhiteLabelingContext.Provider value={whiteLabeling}>
                        <SnackbarProvider service={UINotificationService}>
                          <DialogProvider service={UIDialogService}>
                            <ModalProvider
                              modal={OHIFModal}
                              service={UIModalService}
                            >
                              <OHIFStandaloneViewer
                                userManager={this._userManager}
                              />
                            </ModalProvider>
                          </DialogProvider>
                        </SnackbarProvider>
                      </WhiteLabelingContext.Provider>
                    </Router>
                  </UserManagerContext.Provider>
                </OidcProvider>
              </I18nextProvider>
            </AppProvider>
          </Provider>
         </ErrorBoundary>
       
            );
        }
        console.log("hit 初始页面");

        return (
      <ErrorBoundary context='App'>
        <Provider store={store}>
          <AppProvider config={this._appConfig}>
            <I18nextProvider i18n={i18n}>
              <Router basename={routerBasename}>
                <WhiteLabelingContext.Provider value={whiteLabeling}>
                  <SnackbarProvider service={UINotificationService}>
                    <DialogProvider service={UIDialogService}>
                      <ModalProvider modal={OHIFModal} service={UIModalService}>
                        <OHIFStandaloneViewer />
                      </ModalProvider>
                    </DialogProvider>
                  </SnackbarProvider>
                </WhiteLabelingContext.Provider>
              </Router>
            </I18nextProvider>
          </AppProvider>
        </Provider>
         </ErrorBoundary>
     
        );
    }

    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 (
            <>
                <div className="ToolbarRow">
                    <div className="pull-left m-t-1 p-y-1" style={{ padding: '10px' }}>
                        <RoundedButtonGroup
                            options={this.buttonGroups.left}
                            value={this.props.selectedLeftSidePanel || ''}
                            onValueChanged={onPressLeft}
                        />
                    </div>
                    {buttonComponents}
                    <ConnectedLayoutButton />
                    {/*fanyinote �±���Viewer���������Ͻǵ� ����������ļ�¼ֵ�������¼ֵ��Ҫapi��֧�ֲ��ܱ���������Ĭ�Ͻ����DZ���js�洢���� ��������Գ��� */}
                    <div
                        className="pull-right m-t-1 rm-x-1"
                        style={{ marginLeft: 'auto' }}
                    >
                        {this.buttonGroups.right.length && (
                            <RoundedButtonGroup
                                options={this.buttonGroups.right}
                                value={this.props.selectedRightSidePanel || ''}
                                onValueChanged={onPressRight}
                            />
                        )}
                    </div>
                </div>
            </>
        );
    }
}

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 (
            <CustomComponent
                parentContext={parentContext}
                toolbarClickCallback={_handleToolbarButtonClick.bind(this)}
                button={button}
                key={button.id}
                activeButtons={activeButtonsIds}
                isActive={isActive}
            />
        );
    }
}

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 (
        <ExpandableToolMenu
            key={button.id}
            label={button.label}
            icon={button.icon}
            buttons={childButtons}
            activeCommand={activeCommand}
        />
    );
}

function _getDefaultButtonComponent(button, activeButtons) {
    return (
        <ToolbarButton
            key={button.id}
            label={button.label}
            icon={button.icon}
            onClick={_handleToolbarButtonClick.bind(this, button)}
            isActive={activeButtons.map(button => 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 (
            <span className={"keyFrameTagStyle"} style={{ display: this.state.isShowKeyImageTag ? "block" : "none" }}>
                <svg t="1594088509903" className="icon" viewBox="0 0 1042 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1149" data-spm-anchor-id="a313x.7781069.0.i0" width="100%" height="100%"><path d="M759.12762 345.375135l-142.179449-21.081101-62.849217-127.997264a38.316227 38.316227 0 0 0-34.298752-21.22949 38.316227 38.316227 0 0 0-34.298752 21.22949l-64.384202 128.767188-141.030035 20.311177a38.321092 38.321092 0 0 0-21.076235 65.52875l102.322156 101.177608-24.141339 140.26011a38.308929 38.308929 0 0 0 15.32795 37.321284 38.307712 38.307712 0 0 0 40.237998 2.916714l126.852714-65.528751 126.08279 66.298675a38.316227 38.316227 0 0 0 40.237998-2.922795 38.313794 38.313794 0 0 0 15.326734-37.320068l-23.375063-141.025169 101.941451-99.258269a38.33812 38.33812 0 0 0 9.901987-39.34036 38.325957 38.325957 0 0 0-30.983088-26.193256l0.384354-1.914473z" fill="#1296db" p-id="1150" data-spm-anchor-id="a313x.7781069.0.i3" className=""></path><path d="M624.996501 455.361266a76.634886 76.634886 0 0 0-22.22565 68.983074l10.727863 64.382985-55.565948-30.278844a76.654347 76.654347 0 0 0-73.196375 0l-57.485287 30.278844 11.113433-63.619142a76.644616 76.644616 0 0 0-22.61122-69.362563l-49.436957-45.606794 63.999848-9.197743a76.639751 76.639751 0 0 0 60.549173-42.153688l28.745076-59.019055 28.738995 59.019055a76.649482 76.649482 0 0 0 59.020271 42.538042l63.998632 9.197743-46.371854 44.838086z" fill="#1296db" p-id="1151" data-spm-anchor-id="a313x.7781069.0.i6" className=""></path><path d="M902.838404 13.878108H136.376429A114.965708 114.965708 0 0 0 21.407072 128.847465v766.461974c0 63.495079 51.474278 114.969357 114.969357 114.969358h7.279621l375.954407-153.292882 375.948326 153.292882h7.279621a114.974222 114.974222 0 0 0 81.298222-33.671136 114.974222 114.974222 0 0 0 33.671135-81.298222V128.847465a114.969357 114.969357 0 0 0-33.671135-81.297006 114.970573 114.970573 0 0 0-81.298222-33.672351z m38.323525 881.431331c0.246911 18.985397-13.446317 35.286396-32.189669 38.323525L519.610457 776.124349 130.242573 933.632964c-18.738486-3.037128-32.431714-19.338127-32.188452-38.323525V128.847465c0-21.166243 17.156066-38.322308 38.322308-38.322309h766.461975c21.166243 0 38.323524 17.156066 38.323525 38.322309v766.461974z m0 0" fill="#1296db" p-id="1152" data-spm-anchor-id="a313x.7781069.0.i1"></path></svg>
            </span>
        )
    }

}
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:' },
        });

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • [OHIF-Viewers]医疗数字阅片-医学影像-REACT-React.createRef()-Refs and the DOM关于回调 refs 的说明

    Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。

    landv
  • [OHIF-Viewers]医疗数字阅片-医学影像-REACT向事件处理程序传递参数-.bind-传递函数给组件

    在循环中,通常我们会为事件处理函数传递额外的参数。例如,若 id 是你要删除那一行的 ID,以下两种方式都可以向事件处理函数传递参数:

    landv
  • [转]20分钟看懂大数据分布式计算

    这是一篇科普性质的文章,希望能过用一个通俗易懂的例子给非计算机专业背景的朋友讲清楚大数据分布式计算技术。大数据技术虽然包含存储、计算和分析等一系列庞杂的技术,但...

    landv
  • 前端图表可视化的应用实践总结

    腾讯企鹅辅导在学生上课结束后推送“学习报告”,是课程所提供的一项重要服务。家长在“学习报告”中能查看孩子上课时间及互动情况,答题及掌握知识点,作业考试分数,班级...

    IMWeb前端团队
  • 线上应用故障排查之一:高CPU占用

    线上应用故障排查之一:高CPU占用 一个应用占用CPU很高,除了确实是计算密集型应用之外,通常原因都是出现了死循环。 以我们最近出现的一个实际故障为例,介绍怎么...

    小小科
  • React组件方法中为什么要绑定this

    上例仅仅是一个组件类的定义,当在其他组件中调用或是使用ReactDOM.render( )方法将其渲染到界面上时会生成一个组件的实例,因为组件是可以复用的,面向...

    大史不说话
  • canvas 绘制贪吃蛇游戏

    思路,蛇由两个类组成,方块类和蛇类,蛇类的存在依赖于方块类。蛇类当中的body保存当前蛇类的所有的方块。绘图,直接遍历body内部的所有绘图方法。移动,根据保存...

    mySoul
  • React组件绑定this的四种方式

    用react进行开发组件时,我们需要关注一下组件内部方法this的指向,react定义组件的方式有两种,一种为函数组件,一种为类组件,类组件内部可以定义一些方法...

    挥刀北上
  • 双十一,教你给你女朋友不一样的表白(程序员版)

    https://ru23.com/11?name=front,这样会自动把源码中you换成女朋友的名字

    前端迷
  • RocketMQ 底层通信机制 源码分析

    RocketMQ 底层通讯是使用Netty来实现的。 下面我们通过源码分析下RocketMQ是怎么利用Netty进行通讯的。

    java404

扫码关注云+社区

领取腾讯云代金券