前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[OHIF-Viewers]医疗数字阅片-医学影像-事件总线管理器

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

作者头像
landv
发布2020-07-09 15:58:27
1.2K0
发布2020-07-09 15:58:27
举报
文章被收录于专栏:landvlandv

[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:' },
        });
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-07-08 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • [OHIF-Viewers]医疗数字阅片-医学影像-事件总线管理器
    • App.js
      • CommandsManager.js定义
        • toolbarModule.js 这里添加按钮结构
          • ToolbarRow.js中调用命令
            • HeyFrameTag.js 中回调命令
            相关产品与服务
            事件总线
            腾讯云事件总线(EventBridge)是一款安全,稳定,高效的云上事件连接器,作为流数据和事件的自动收集、处理、分发管道,通过可视化的配置,实现事件源(例如:Kafka,审计,数据库等)和目标对象(例如:CLS,SCF等)的快速连接,当前 EventBridge 已接入 100+ 云上服务,助力分布式事件驱动架构的快速构建。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档