From 686d12da5c9d3d435b1e326c2a5caee36e2ed27c Mon Sep 17 00:00:00 2001 From: Rodrigo Antinarelli Date: Thu, 30 Jan 2020 15:26:06 -0300 Subject: [PATCH] fix: Set VTK viewport as active by interaction (#1139) * fix: Set VTK viewport as active by interaction * feat: listen for vtkscrollevent in wrapper component * fix: definitions can skip empty storeContexts key * hoc to set/pass in commandsManager * Bump minimum react-vtkjs-viewport version to leverage new event * Simplify to use onScroll event instead of passing down commandsManager to base component * fix: make sure we include @JamesAPetts bug fix Co-authored-by: Danny Brown --- extensions/vtk/package.json | 2 +- extensions/vtk/src/ConnectedVTKViewport.js | 5 +- extensions/vtk/src/OHIFVTKViewport.js | 6 ++ extensions/vtk/src/VTKViewport.css | 3 + extensions/vtk/src/VTKViewport.js | 58 ++++++++++++++++++++ extensions/vtk/src/commandsModule.js | 20 +++---- extensions/vtk/src/index.js | 5 +- extensions/vtk/src/withCommandsManager.js | 18 ++++++ platform/core/src/classes/CommandsManager.js | 2 +- yarn.lock | 36 ++++++++++-- 10 files changed, 133 insertions(+), 22 deletions(-) create mode 100644 extensions/vtk/src/VTKViewport.css create mode 100644 extensions/vtk/src/VTKViewport.js create mode 100644 extensions/vtk/src/withCommandsManager.js diff --git a/extensions/vtk/package.json b/extensions/vtk/package.json index 307c67f63..cfd3fb215 100644 --- a/extensions/vtk/package.json +++ b/extensions/vtk/package.json @@ -49,7 +49,7 @@ "dependencies": { "@babel/runtime": "^7.5.5", "lodash.throttle": "^4.1.1", - "react-vtkjs-viewport": "^0.3.9" + "react-vtkjs-viewport": "^0.8.3" }, "devDependencies": { "@ohif/core": "^2.3.6", diff --git a/extensions/vtk/src/ConnectedVTKViewport.js b/extensions/vtk/src/ConnectedVTKViewport.js index baf50c2c4..1c1a500e6 100644 --- a/extensions/vtk/src/ConnectedVTKViewport.js +++ b/extensions/vtk/src/ConnectedVTKViewport.js @@ -1,6 +1,6 @@ import OHIF from '@ohif/core'; -import { View2D } from 'react-vtkjs-viewport'; import { connect } from 'react-redux'; +import VTKViewport from './VTKViewport'; const { setViewportActive, setViewportSpecificData } = OHIF.redux.actions; @@ -18,6 +18,7 @@ const mapStateToProps = (state, ownProps) => { const pluginDetails = viewportLayout.vtk || {}; return { + activeViewportIndex: state.viewports.activeViewportIndex, layout: state.viewports.layout, isActive, ...pluginDetails, @@ -75,6 +76,6 @@ const ConnectedVTKViewport = connect( mapStateToProps, mapDispatchToProps, mergeProps -)(View2D); +)(VTKViewport); export default ConnectedVTKViewport; diff --git a/extensions/vtk/src/OHIFVTKViewport.js b/extensions/vtk/src/OHIFVTKViewport.js index 32732e5cd..379c87af3 100644 --- a/extensions/vtk/src/OHIFVTKViewport.js +++ b/extensions/vtk/src/OHIFVTKViewport.js @@ -77,6 +77,11 @@ class OHIFVTKViewport extends Component { }), viewportIndex: PropTypes.number, children: PropTypes.node, + onScroll: PropTypes.func, + }; + + static defaultProps = { + onScroll: () => {}, }; static id = 'OHIFVTKViewport'; @@ -386,6 +391,7 @@ class OHIFVTKViewport extends Component { this.state.paintFilterBackgroundImageData } viewportIndex={this.props.viewportIndex} + onScroll={this.props.onScroll} /> )} diff --git a/extensions/vtk/src/VTKViewport.css b/extensions/vtk/src/VTKViewport.css new file mode 100644 index 000000000..10fc3ac70 --- /dev/null +++ b/extensions/vtk/src/VTKViewport.css @@ -0,0 +1,3 @@ +.vtk-viewport-handler svg { + pointer-events: none; +} diff --git a/extensions/vtk/src/VTKViewport.js b/extensions/vtk/src/VTKViewport.js new file mode 100644 index 000000000..fa77c262c --- /dev/null +++ b/extensions/vtk/src/VTKViewport.js @@ -0,0 +1,58 @@ +import React, { useEffect, useCallback } from 'react'; +import { View2D } from 'react-vtkjs-viewport'; +import PropTypes from 'prop-types'; + +import './VTKViewport.css'; + +const VTKViewport = props => { + const style = { width: '100%', height: '100%', position: 'relative' }; + + const setViewportActiveHandler = useCallback(() => { + const { setViewportActive, viewportIndex, activeViewportIndex } = props; + + if (viewportIndex !== activeViewportIndex) { + // set in Connected + setViewportActive(); + } + }); + + useEffect(() => { + const handleScrollEvent = evt => { + const vtkViewportApiReference = props.onScroll(props.viewportIndex) || {}; + const viewportUid = vtkViewportApiReference.uid; + const viewportWasScrolled = viewportUid === evt.detail.uid; + + if (viewportWasScrolled) { + setViewportActiveHandler(); + } + }; + + window.addEventListener('vtkscrollevent', handleScrollEvent); + return () => + window.removeEventListener('vtkscrollevent', handleScrollEvent); + }, [props, props.onScroll, props.viewportIndex, setViewportActiveHandler]); + + return ( +
+ +
+ ); +}; + +VTKViewport.propTypes = { + setViewportActive: PropTypes.func.isRequired, + viewportIndex: PropTypes.number.isRequired, + activeViewportIndex: PropTypes.number.isRequired, + /* Receives viewportIndex */ + onScroll: PropTypes.func, +}; + +VTKViewport.defaultProps = { + onScroll: () => {}, +}; + +export default VTKViewport; diff --git a/extensions/vtk/src/commandsModule.js b/extensions/vtk/src/commandsModule.js index 4e85d99cc..a0aadcc1d 100644 --- a/extensions/vtk/src/commandsModule.js +++ b/extensions/vtk/src/commandsModule.js @@ -103,6 +103,9 @@ const commandsModule = ({ commandsManager }) => { } const actions = { + getVtkApis: ({ index }) => { + return apis[index]; + }, axial: async ({ viewports }) => { const api = await _getActiveViewportVTKApi(viewports); @@ -266,11 +269,12 @@ const commandsModule = ({ commandsManager }) => { 'crosshairsWidget' ); + const uid = api.uid; const istyle = vtkInteractorStyleMPRCrosshairs.newInstance(); api.setInteractorStyle({ istyle, - configuration: { apis, apiIndex }, + configuration: { apis, apiIndex, uid }, }); }); }, @@ -296,55 +300,45 @@ const commandsModule = ({ commandsManager }) => { }, enableRotateTool: { commandFn: actions.enableRotateTool, - storeContexts: [], options: {}, }, enableCrosshairsTool: { commandFn: actions.enableCrosshairsTool, - storeContexts: [], options: {}, }, enableLevelTool: { commandFn: actions.enableLevelTool, - storeContexts: [], options: {}, }, setBlendModeToComposite: { commandFn: actions.setBlendModeToComposite, - storeContexts: [], options: { blendMode: BlendMode.COMPOSITE_BLEND }, }, setBlendModeToMaximumIntensity: { commandFn: actions.setBlendModeToMaximumIntensity, - storeContexts: [], options: { blendMode: BlendMode.MAXIMUM_INTENSITY_BLEND }, }, setBlendModeToMinimumIntensity: { commandFn: actions.setBlendMode, - storeContexts: [], options: { blendMode: BlendMode.MINIMUM_INTENSITY_BLEND }, }, setBlendModeToAverageIntensity: { commandFn: actions.setBlendMode, - storeContexts: [], options: { blendMode: BlendMode.AVERAGE_INTENSITY_BLEND }, }, setSlabThickness: { // TODO: How do we pass in a function argument? commandFn: actions.setSlabThickness, - storeContexts: [], options: {}, }, increaseSlabThickness: { commandFn: actions.changeSlabThickness, - storeContexts: [], options: { change: 3, }, }, decreaseSlabThickness: { commandFn: actions.changeSlabThickness, - storeContexts: [], options: { change: -3, }, @@ -355,6 +349,10 @@ const commandsModule = ({ commandsManager }) => { options: {}, context: 'VIEWER', }, + getVtkApiForViewportIndex: { + commandFn: actions.getVtkApis, + context: 'VIEWER', + }, }; return { diff --git a/extensions/vtk/src/index.js b/extensions/vtk/src/index.js index dbc53f6e0..f66061b43 100644 --- a/extensions/vtk/src/index.js +++ b/extensions/vtk/src/index.js @@ -1,6 +1,7 @@ import asyncComponent from './asyncComponent.js'; import commandsModule from './commandsModule.js'; import toolbarModule from './toolbarModule.js'; +import withCommandsManager from './withCommandsManager.js'; // This feels weird // import loadLocales from './loadLocales'; @@ -14,8 +15,8 @@ const vtkExtension = { */ id: 'vtk', - getViewportModule() { - return OHIFVTKViewport; + getViewportModule({ commandsManager }) { + return withCommandsManager(OHIFVTKViewport, commandsManager); }, getToolbarModule() { return toolbarModule; diff --git a/extensions/vtk/src/withCommandsManager.js b/extensions/vtk/src/withCommandsManager.js new file mode 100644 index 000000000..cec049547 --- /dev/null +++ b/extensions/vtk/src/withCommandsManager.js @@ -0,0 +1,18 @@ +import React from 'react'; + +export default function withCommandsManager(Component, commandsManager = {}) { + return class WithCommandsManager extends React.Component { + render() { + return ( + + commandsManager.runCommand('getVtkApiForViewportIndex', { + index: viewportIndex, + }) + } + /> + ); + } + }; +} diff --git a/platform/core/src/classes/CommandsManager.js b/platform/core/src/classes/CommandsManager.js index 52e70da11..ca4555206 100644 --- a/platform/core/src/classes/CommandsManager.js +++ b/platform/core/src/classes/CommandsManager.js @@ -161,7 +161,7 @@ export class CommandsManager { return; } - const { commandFn, storeContexts } = definition; + const { commandFn, storeContexts = [] } = definition; const definitionOptions = definition.options; let commandParams = {}; diff --git a/yarn.lock b/yarn.lock index b06bfd2fc..f5c75992a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1100,13 +1100,34 @@ pirates "^4.0.0" source-map-support "^0.5.9" -"@babel/runtime@7.1.2", "@babel/runtime@7.5.5", "@babel/runtime@7.6.0", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.6.3": +"@babel/runtime@7.1.2": + version "7.1.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.1.2.tgz#81c89935f4647706fc54541145e6b4ecfef4b8e3" + integrity sha512-Y3SCjmhSupzFB6wcv1KmmFucH6gDVnI30WjOcicV10ju0cZjak3Jcs67YLIXBrmZYw1xCrVeJPbycFwrqNyxpg== + dependencies: + regenerator-runtime "^0.12.0" + +"@babel/runtime@7.6.0": + version "7.6.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.6.0.tgz#4fc1d642a9fd0299754e8b5de62c631cf5568205" + integrity sha512-89eSBLJsxNxOERC0Op4vd+0Bqm6wRMqMbFtV3i0/fbaWw/mJ8Q3eBvgX0G4SyrOOLCtbu98HspF8o09MRT+KzQ== + dependencies: + regenerator-runtime "^0.13.2" + +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132" integrity sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ== dependencies: regenerator-runtime "^0.13.2" +"@babel/runtime@^7.6.0", "@babel/runtime@^7.6.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.3.tgz#0811944f73a6c926bb2ad35e918dcc1bfab279f1" + integrity sha512-fVHx1rzEmwB130VTkLnxR+HmxcTjGzH12LYQcFFoBwakMd3aOMD4OsRN7tGG/UOYE2ektgFrS8uACAoRk1CY0w== + dependencies: + regenerator-runtime "^0.13.2" + "@babel/template@^7.0.0", "@babel/template@^7.1.0", "@babel/template@^7.4.0", "@babel/template@^7.4.4", "@babel/template@^7.6.0": version "7.6.0" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.6.0.tgz#7f0159c7f5012230dad64cca42ec9bdb5c9536e6" @@ -15660,10 +15681,10 @@ react-transition-group@^4.1.1: loose-envify "^1.4.0" prop-types "^15.6.2" -react-vtkjs-viewport@^0.3.9: - version "0.3.9" - resolved "https://registry.yarnpkg.com/react-vtkjs-viewport/-/react-vtkjs-viewport-0.3.9.tgz#dc3dd5323c4cbaf5978cd64af2794b6b4ed61642" - integrity sha512-Sc3dRRz8tRSdUrdgL8tuYwVEl0CTjaZfDLqP5ufMXH2KjwBt3T5bjpk4xvzFyPc9CG6ywEX5MSa6eQ+NNjaUkw== +react-vtkjs-viewport@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/react-vtkjs-viewport/-/react-vtkjs-viewport-0.8.2.tgz#cdccf156cb089d32b935ebc3dc5f31213ba17bef" + integrity sha512-2OPbU5y4ay+E6qfZw4sW19uqfBnNw9kAF1PaZ/Sk5XMEEKnebplEof9ZZtE6nhPImFqMCfu4TXzYVHIAPmwFYA== dependencies: date-fns "^2.2.1" gl-matrix "^3.1.0" @@ -16038,6 +16059,11 @@ regenerator-runtime@^0.11.0, regenerator-runtime@^0.11.1: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== +regenerator-runtime@^0.12.0: + version "0.12.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de" + integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg== + regenerator-runtime@^0.13.1, regenerator-runtime@^0.13.2: version "0.13.3" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz#7cf6a77d8f5c6f60eb73c5fc1955b2ceb01e6bf5"