diff --git a/.gitignore b/.gitignore index 50fcc92b8..c4a7cf78c 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ docker/dcm4che/dcm4che-arc # Cypress test results videos/ +screenshots/ # Locize settings .locize diff --git a/extensions/cornerstone/src/commandsModule.js b/extensions/cornerstone/src/commandsModule.js index 49b856c81..db2627c34 100644 --- a/extensions/cornerstone/src/commandsModule.js +++ b/extensions/cornerstone/src/commandsModule.js @@ -2,6 +2,7 @@ import cornerstone from 'cornerstone-core'; import cornerstoneTools from 'cornerstone-tools'; import OHIF from '@ohif/core'; +import setCornerstoneLayout from './utils/setCornerstoneLayout.js'; import { getEnabledElement } from './state'; import CornerstoneViewportDownloadForm from './CornerstoneViewportDownloadForm'; const scroll = cornerstoneTools.import('util/scroll'); @@ -242,6 +243,9 @@ const commandsModule = ({ servicesManager }) => { cornerstoneTools.removeToolState(element, toolType, tool); cornerstone.updateImage(element); }, + setCornerstoneLayout: () => { + setCornerstoneLayout(); + } }; const definitions = { @@ -348,6 +352,12 @@ const commandsModule = ({ servicesManager }) => { storeContexts: [], options: {}, }, + setCornerstoneLayout: { + commandFn: actions.setCornerstoneLayout, + storeContexts: [], + options: {}, + context: 'VIEWER', + }, }; return { diff --git a/extensions/cornerstone/src/toolbarModule.js b/extensions/cornerstone/src/toolbarModule.js index f32cdfbf1..283d2e926 100644 --- a/extensions/cornerstone/src/toolbarModule.js +++ b/extensions/cornerstone/src/toolbarModule.js @@ -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 { diff --git a/extensions/cornerstone/src/utils/setCornerstoneLayout.js b/extensions/cornerstone/src/utils/setCornerstoneLayout.js new file mode 100644 index 000000000..e308c0d4a --- /dev/null +++ b/extensions/cornerstone/src/utils/setCornerstoneLayout.js @@ -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; diff --git a/extensions/vtk/src/toolbarComponents/VTKMPRToolbarButton.js b/extensions/vtk/src/toolbarComponents/VTKMPRToolbarButton.js new file mode 100644 index 000000000..7ec4758c5 --- /dev/null +++ b/extensions/vtk/src/toolbarComponents/VTKMPRToolbarButton.js @@ -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 ( + + {isVisible && ( + toolbarClickCallback(button, evt)} + isActive={isActive} + /> + )} + + ); +} + +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; diff --git a/extensions/vtk/src/toolbarModule.js b/extensions/vtk/src/toolbarModule.js index 805644dd2..9cfa49f63 100644 --- a/extensions/vtk/src/toolbarModule.js +++ b/extensions/vtk/src/toolbarModule.js @@ -1,4 +1,5 @@ import SlabThicknessToolbarComponent from './toolbarComponents/SlabThicknessToolbarComponent'; +import VTKMPRToolbarButton from './toolbarComponents/VTKMPRToolbarButton'; const TOOLBAR_BUTTON_TYPES = { 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 { definitions, defaultContext: 'ACTIVE_VIEWPORT::VTK', -}; +} diff --git a/platform/core/src/extensions/ExtensionManager.test.js b/platform/core/src/extensions/ExtensionManager.test.js index 5ed3fad8d..42965c4a1 100644 --- a/platform/core/src/extensions/ExtensionManager.test.js +++ b/platform/core/src/extensions/ExtensionManager.test.js @@ -226,7 +226,7 @@ describe('ExtensionManager.js', () => { return { definitions: { exampleDefinition: { - commandFn: () => {}, + commandFn: () => { }, storeContexts: [], options: {}, }, diff --git a/platform/viewer/cypress/support/aliases.js b/platform/viewer/cypress/support/aliases.js index 84aadbd1c..0547e99d1 100644 --- a/platform/viewer/cypress/support/aliases.js +++ b/platform/viewer/cypress/support/aliases.js @@ -44,6 +44,7 @@ export function initRouteAliases() { //Creating aliases for VTK tools buttons export function initVTKToolsAliases() { + cy.get('[data-cy="exit 2d mpr"]').as('exit2dmprBtn'); cy.get('[data-cy="crosshairs"]').as('crosshairsBtn'); cy.get('[data-cy="wwwc"]').as('wwwcBtn'); cy.get('[data-cy="rotate"]').as('rotateBtn'); diff --git a/platform/viewer/src/connectedComponents/ConnectedPluginSwitch.js b/platform/viewer/src/connectedComponents/ConnectedPluginSwitch.js deleted file mode 100644 index 130bd0eba..000000000 --- a/platform/viewer/src/connectedComponents/ConnectedPluginSwitch.js +++ /dev/null @@ -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 ; -}; - -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); diff --git a/platform/viewer/src/connectedComponents/PluginSwitch.css b/platform/viewer/src/connectedComponents/PluginSwitch.css deleted file mode 100644 index 1f61645d5..000000000 --- a/platform/viewer/src/connectedComponents/PluginSwitch.css +++ /dev/null @@ -1,3 +0,0 @@ -.icon-rotate-120 { - transform: rotate(120deg); -} diff --git a/platform/viewer/src/connectedComponents/PluginSwitch.js b/platform/viewer/src/connectedComponents/PluginSwitch.js deleted file mode 100644 index 12074625e..000000000 --- a/platform/viewer/src/connectedComponents/PluginSwitch.js +++ /dev/null @@ -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 && ( -
- -
- )} - - ); - } -} - -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; diff --git a/platform/viewer/src/connectedComponents/ToolbarRow.js b/platform/viewer/src/connectedComponents/ToolbarRow.js index 4957d7148..29ba3798f 100644 --- a/platform/viewer/src/connectedComponents/ToolbarRow.js +++ b/platform/viewer/src/connectedComponents/ToolbarRow.js @@ -16,7 +16,6 @@ import { commandsManager, extensionManager } from './../App.js'; import ConnectedCineDialog from './ConnectedCineDialog'; import ConnectedLayoutButton from './ConnectedLayoutButton'; -import ConnectedPluginSwitch from './ConnectedPluginSwitch.js'; class ToolbarRow extends Component { // TODO: Simplify these? isOpen can be computed if we say "any" value for selected, @@ -124,7 +123,6 @@ class ToolbarRow extends Component { {buttonComponents} -