fix: 🐛 1241: Make Plugin switch part of ToolbarModule (#1322)
* fix: 🐛 Make Plugin switch part of ToolbarModule Closes: #1241 * Move setCornerstoneViewport command into cornerstone extension * Exposing extensionManager into extension's get modules functions * Remove Plugin switch * Updating set cornerstonelayout to work on VIEWER context * Creating a customComponent for 2D MPR button * Use extensionManager to show or not buttons Adding isHidden option into toolbarModules * Make buttons hidden depending on extension registration * Fix unit test after changes on extensionManager * Remove workaround for extensionManager * Use studyMeatadataManager and remove TODO's * Remove storeContext not needed * Moving Exit 2D MPR into cornerstone extension with VTK context * Remove PluginSwitch files * Fix E2E tests * Adding cypress screenshots into gitignore * Remove extensionManager from getModuleFn functions and moving CustomComponent for VTK button to VTK extension Co-authored-by: Danny Brown <danny.ri.brown@gmail.com>
This commit is contained in:
parent
a436436def
commit
6540e36818
1
.gitignore
vendored
1
.gitignore
vendored
@ -30,6 +30,7 @@ docker/dcm4che/dcm4che-arc
|
|||||||
|
|
||||||
# Cypress test results
|
# Cypress test results
|
||||||
videos/
|
videos/
|
||||||
|
screenshots/
|
||||||
|
|
||||||
# Locize settings
|
# Locize settings
|
||||||
.locize
|
.locize
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import cornerstone from 'cornerstone-core';
|
|||||||
import cornerstoneTools from 'cornerstone-tools';
|
import cornerstoneTools from 'cornerstone-tools';
|
||||||
import OHIF from '@ohif/core';
|
import OHIF from '@ohif/core';
|
||||||
|
|
||||||
|
import setCornerstoneLayout from './utils/setCornerstoneLayout.js';
|
||||||
import { getEnabledElement } from './state';
|
import { getEnabledElement } from './state';
|
||||||
import CornerstoneViewportDownloadForm from './CornerstoneViewportDownloadForm';
|
import CornerstoneViewportDownloadForm from './CornerstoneViewportDownloadForm';
|
||||||
const scroll = cornerstoneTools.import('util/scroll');
|
const scroll = cornerstoneTools.import('util/scroll');
|
||||||
@ -242,6 +243,9 @@ const commandsModule = ({ servicesManager }) => {
|
|||||||
cornerstoneTools.removeToolState(element, toolType, tool);
|
cornerstoneTools.removeToolState(element, toolType, tool);
|
||||||
cornerstone.updateImage(element);
|
cornerstone.updateImage(element);
|
||||||
},
|
},
|
||||||
|
setCornerstoneLayout: () => {
|
||||||
|
setCornerstoneLayout();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const definitions = {
|
const definitions = {
|
||||||
@ -348,6 +352,12 @@ const commandsModule = ({ servicesManager }) => {
|
|||||||
storeContexts: [],
|
storeContexts: [],
|
||||||
options: {},
|
options: {},
|
||||||
},
|
},
|
||||||
|
setCornerstoneLayout: {
|
||||||
|
commandFn: actions.setCornerstoneLayout,
|
||||||
|
storeContexts: [],
|
||||||
|
options: {},
|
||||||
|
context: 'VIEWER',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -237,6 +237,15 @@ const definitions = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'Exit2DMPR',
|
||||||
|
label: 'Exit 2D MPR',
|
||||||
|
icon: 'times',
|
||||||
|
//
|
||||||
|
type: TOOLBAR_BUTTON_TYPES.COMMAND,
|
||||||
|
commandName: 'setCornerstoneLayout',
|
||||||
|
context: 'ACTIVE_VIEWPORT::VTK',
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
22
extensions/cornerstone/src/utils/setCornerstoneLayout.js
Normal file
22
extensions/cornerstone/src/utils/setCornerstoneLayout.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { redux } from '@ohif/core';
|
||||||
|
|
||||||
|
const { setLayout } = redux.actions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the current layout with a simple Cornerstone one
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
const setCornerstoneLayout = () => {
|
||||||
|
const layout = {
|
||||||
|
numRows: 1,
|
||||||
|
numColumns: 1,
|
||||||
|
viewports: [{ plugin: 'cornerstone' }],
|
||||||
|
};
|
||||||
|
|
||||||
|
const action = setLayout(layout);
|
||||||
|
|
||||||
|
window.store.dispatch(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default setCornerstoneLayout;
|
||||||
87
extensions/vtk/src/toolbarComponents/VTKMPRToolbarButton.js
Normal file
87
extensions/vtk/src/toolbarComponents/VTKMPRToolbarButton.js
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { ToolbarButton } from '@ohif/ui';
|
||||||
|
import { utils } from '@ohif/core';
|
||||||
|
|
||||||
|
const { studyMetadataManager } = utils;
|
||||||
|
|
||||||
|
let isVisible = true;
|
||||||
|
|
||||||
|
const _isDisplaySetReconstructable = (viewportSpecificData = {}, activeViewportIndex) => {
|
||||||
|
if (!viewportSpecificData[activeViewportIndex]) {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const { displaySetInstanceUid, studyInstanceUid } = viewportSpecificData[
|
||||||
|
activeViewportIndex
|
||||||
|
];
|
||||||
|
|
||||||
|
const studies = studyMetadataManager.all();
|
||||||
|
|
||||||
|
const study = studies.find(
|
||||||
|
study => study.studyInstanceUID === studyInstanceUid
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!study) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const displaySet = study._displaySets.find(set => set.displaySetInstanceUid === displaySetInstanceUid);
|
||||||
|
|
||||||
|
if (!displaySet) {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
return displaySet.isReconstructable;
|
||||||
|
};
|
||||||
|
|
||||||
|
function VTKMPRToolbarButton({
|
||||||
|
parentContext,
|
||||||
|
toolbarClickCallback,
|
||||||
|
button,
|
||||||
|
activeButtons,
|
||||||
|
isActive,
|
||||||
|
className,
|
||||||
|
}) {
|
||||||
|
const { id, label, icon } = button;
|
||||||
|
const { viewportSpecificData, activeViewportIndex } = useSelector(state => {
|
||||||
|
const { viewports = {} } = state;
|
||||||
|
const { viewportSpecificData, activeViewportIndex } = viewports;
|
||||||
|
|
||||||
|
return {
|
||||||
|
viewportSpecificData,
|
||||||
|
activeViewportIndex,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
isVisible = _isDisplaySetReconstructable(
|
||||||
|
viewportSpecificData,
|
||||||
|
activeViewportIndex,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{isVisible && (
|
||||||
|
<ToolbarButton
|
||||||
|
key={id}
|
||||||
|
label={label}
|
||||||
|
icon={icon}
|
||||||
|
onClick={evt => toolbarClickCallback(button, evt)}
|
||||||
|
isActive={isActive}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
VTKMPRToolbarButton.propTypes = {
|
||||||
|
parentContext: PropTypes.object.isRequired,
|
||||||
|
toolbarClickCallback: PropTypes.func.isRequired,
|
||||||
|
button: PropTypes.object.isRequired,
|
||||||
|
activeButtons: PropTypes.array.isRequired,
|
||||||
|
isActive: PropTypes.bool,
|
||||||
|
className: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default VTKMPRToolbarButton;
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import SlabThicknessToolbarComponent from './toolbarComponents/SlabThicknessToolbarComponent';
|
import SlabThicknessToolbarComponent from './toolbarComponents/SlabThicknessToolbarComponent';
|
||||||
|
import VTKMPRToolbarButton from './toolbarComponents/VTKMPRToolbarButton';
|
||||||
|
|
||||||
const TOOLBAR_BUTTON_TYPES = {
|
const TOOLBAR_BUTTON_TYPES = {
|
||||||
COMMAND: 'command',
|
COMMAND: 'command',
|
||||||
@ -115,9 +116,19 @@ const definitions = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: '2DMPR',
|
||||||
|
label: '2D MPR',
|
||||||
|
icon: 'cube',
|
||||||
|
//
|
||||||
|
CustomComponent: VTKMPRToolbarButton,
|
||||||
|
type: TOOLBAR_BUTTON_TYPES.COMMAND,
|
||||||
|
commandName: 'mpr2d',
|
||||||
|
context: 'ACTIVE_VIEWPORT::CORNERSTONE',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
definitions,
|
definitions,
|
||||||
defaultContext: 'ACTIVE_VIEWPORT::VTK',
|
defaultContext: 'ACTIVE_VIEWPORT::VTK',
|
||||||
};
|
}
|
||||||
|
|||||||
@ -226,7 +226,7 @@ describe('ExtensionManager.js', () => {
|
|||||||
return {
|
return {
|
||||||
definitions: {
|
definitions: {
|
||||||
exampleDefinition: {
|
exampleDefinition: {
|
||||||
commandFn: () => {},
|
commandFn: () => { },
|
||||||
storeContexts: [],
|
storeContexts: [],
|
||||||
options: {},
|
options: {},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -44,6 +44,7 @@ export function initRouteAliases() {
|
|||||||
|
|
||||||
//Creating aliases for VTK tools buttons
|
//Creating aliases for VTK tools buttons
|
||||||
export function initVTKToolsAliases() {
|
export function initVTKToolsAliases() {
|
||||||
|
cy.get('[data-cy="exit 2d mpr"]').as('exit2dmprBtn');
|
||||||
cy.get('[data-cy="crosshairs"]').as('crosshairsBtn');
|
cy.get('[data-cy="crosshairs"]').as('crosshairsBtn');
|
||||||
cy.get('[data-cy="wwwc"]').as('wwwcBtn');
|
cy.get('[data-cy="wwwc"]').as('wwwcBtn');
|
||||||
cy.get('[data-cy="rotate"]').as('rotateBtn');
|
cy.get('[data-cy="rotate"]').as('rotateBtn');
|
||||||
|
|||||||
@ -1,72 +0,0 @@
|
|||||||
import OHIF from '@ohif/core';
|
|
||||||
import React from 'react';
|
|
||||||
import PluginSwitch from './PluginSwitch.js';
|
|
||||||
import { commandsManager } from './../App.js';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
const { setLayout } = OHIF.redux.actions;
|
|
||||||
|
|
||||||
const ConnectedPluginSwitch = props => {
|
|
||||||
return <PluginSwitch {...props} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
|
||||||
const { activeViewportIndex, layout, viewportSpecificData } = state.viewports;
|
|
||||||
|
|
||||||
return {
|
|
||||||
activeViewportIndex,
|
|
||||||
viewportSpecificData,
|
|
||||||
layout,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => {
|
|
||||||
return {
|
|
||||||
setLayout: data => {
|
|
||||||
dispatch(setLayout(data));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/*function setSingleLayoutData(originalArray, viewportIndex, data) {
|
|
||||||
const viewports = originalArray.slice();
|
|
||||||
const layoutData = Object.assign({}, viewports[viewportIndex], data);
|
|
||||||
|
|
||||||
viewports[viewportIndex] = layoutData;
|
|
||||||
|
|
||||||
return viewports;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
const mergeProps = (propsFromState, propsFromDispatch, ownProps) => {
|
|
||||||
const { activeViewportIndex, viewportSpecificData } = propsFromState;
|
|
||||||
const { studies } = ownProps;
|
|
||||||
const { setLayout } = propsFromDispatch;
|
|
||||||
|
|
||||||
const mpr = () => {
|
|
||||||
commandsManager.runCommand('mpr2d');
|
|
||||||
};
|
|
||||||
|
|
||||||
const exitMpr = () => {
|
|
||||||
const layout = {
|
|
||||||
numRows: 1,
|
|
||||||
numColumns: 1,
|
|
||||||
viewports: [{ plugin: 'cornerstone' }],
|
|
||||||
};
|
|
||||||
|
|
||||||
setLayout(layout);
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
mpr,
|
|
||||||
exitMpr,
|
|
||||||
activeViewportIndex,
|
|
||||||
viewportSpecificData,
|
|
||||||
studies,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps,
|
|
||||||
mergeProps
|
|
||||||
)(ConnectedPluginSwitch);
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
.icon-rotate-120 {
|
|
||||||
transform: rotate(120deg);
|
|
||||||
}
|
|
||||||
@ -1,106 +0,0 @@
|
|||||||
import React, { Component } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { ToolbarButton } from '@ohif/ui';
|
|
||||||
import './PluginSwitch.css';
|
|
||||||
|
|
||||||
class PluginSwitch extends Component {
|
|
||||||
static propTypes = {
|
|
||||||
mpr: PropTypes.func,
|
|
||||||
activeViewportIndex: PropTypes.number,
|
|
||||||
viewportSpecificData: PropTypes.object,
|
|
||||||
studies: PropTypes.array,
|
|
||||||
exitMpr: PropTypes.func,
|
|
||||||
};
|
|
||||||
|
|
||||||
static defaultProps = {};
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
isPlugSwitchOn: false,
|
|
||||||
label: '2D MPR',
|
|
||||||
icon: 'cube',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
handleClick = () => {
|
|
||||||
if (this.state.isPlugSwitchOn) {
|
|
||||||
this.setState({
|
|
||||||
isPlugSwitchOn: false,
|
|
||||||
label: '2D MPR',
|
|
||||||
icon: 'cube',
|
|
||||||
});
|
|
||||||
this.props.exitMpr();
|
|
||||||
} else {
|
|
||||||
this.setState({
|
|
||||||
isPlugSwitchOn: true,
|
|
||||||
label: 'Exit 2D MPR',
|
|
||||||
icon: 'times',
|
|
||||||
});
|
|
||||||
this.props.mpr();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { label, icon } = this.state;
|
|
||||||
|
|
||||||
// Render exit mpr if switched on, otherwise check if mpr button should be displayed.
|
|
||||||
const shouldRender =
|
|
||||||
this.state.isPlugSwitchOn || _shouldRenderMpr2DButton.call(this);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{shouldRender && (
|
|
||||||
<div className="PluginSwitch">
|
|
||||||
<ToolbarButton
|
|
||||||
label={label}
|
|
||||||
icon={icon}
|
|
||||||
onClick={this.handleClick}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _shouldRenderMpr2DButton() {
|
|
||||||
const { viewportSpecificData, studies, activeViewportIndex } = this.props;
|
|
||||||
|
|
||||||
if (!viewportSpecificData[activeViewportIndex]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { displaySetInstanceUid, studyInstanceUid } = viewportSpecificData[
|
|
||||||
activeViewportIndex
|
|
||||||
];
|
|
||||||
|
|
||||||
const displaySet = _getDisplaySet(
|
|
||||||
studies,
|
|
||||||
studyInstanceUid,
|
|
||||||
displaySetInstanceUid
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!displaySet) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return displaySet.isReconstructable;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _getDisplaySet(studies, studyInstanceUid, displaySetInstanceUid) {
|
|
||||||
const study = studies.find(
|
|
||||||
study => study.studyInstanceUid === studyInstanceUid
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!study) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const displaySet = study.displaySets.find(set => {
|
|
||||||
return set.displaySetInstanceUid === displaySetInstanceUid;
|
|
||||||
});
|
|
||||||
|
|
||||||
return displaySet;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PluginSwitch;
|
|
||||||
@ -16,7 +16,6 @@ import { commandsManager, extensionManager } from './../App.js';
|
|||||||
|
|
||||||
import ConnectedCineDialog from './ConnectedCineDialog';
|
import ConnectedCineDialog from './ConnectedCineDialog';
|
||||||
import ConnectedLayoutButton from './ConnectedLayoutButton';
|
import ConnectedLayoutButton from './ConnectedLayoutButton';
|
||||||
import ConnectedPluginSwitch from './ConnectedPluginSwitch.js';
|
|
||||||
|
|
||||||
class ToolbarRow extends Component {
|
class ToolbarRow extends Component {
|
||||||
// TODO: Simplify these? isOpen can be computed if we say "any" value for selected,
|
// TODO: Simplify these? isOpen can be computed if we say "any" value for selected,
|
||||||
@ -124,7 +123,6 @@ class ToolbarRow extends Component {
|
|||||||
</div>
|
</div>
|
||||||
{buttonComponents}
|
{buttonComponents}
|
||||||
<ConnectedLayoutButton />
|
<ConnectedLayoutButton />
|
||||||
<ConnectedPluginSwitch studies={this.props.studies} />
|
|
||||||
<div
|
<div
|
||||||
className="pull-right m-t-1 rm-x-1"
|
className="pull-right m-t-1 rm-x-1"
|
||||||
style={{ marginLeft: 'auto' }}
|
style={{ marginLeft: 'auto' }}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user