首页 > 其他 > 详细

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

时间:2020-07-08 15:45:10      阅读:69      评论:0      收藏:0      [点我收藏+]

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

 

 

 

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

原文:https://www.cnblogs.com/landv/p/13266957.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!