添加按钮》调用命令》注册回调函数
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分发的;
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;
// 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‘, };
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)))
);
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