[OHIF-251] - Double click to maximise viewport/ revert to previous layout. (#1860)

* WIP

* double click to maximise implemented at the viewport grid layer.

* Remove memory of cached layout when the layout is changed, or a different displaySet is set.

* WIP fix double click for cornerstone.

* Update CST

* Prevent maximising when double clicking the action bar

* Update extensions/dicom-sr/src/OHIFCornerstoneSRViewport.js

* Apply suggestions from code review

* Fix conflicts.

Co-authored-by: Danny Brown <danny.ri.brown@gmail.com>
This commit is contained in:
James Petts 2020-07-03 18:12:33 +01:00 committed by GitHub
parent 5ee4598bbe
commit 4dfe144bf8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 178 additions and 40 deletions

View File

@ -33,7 +33,7 @@
"@ohif/ui": "^0.50.0",
"cornerstone-core": "^2.3.0",
"cornerstone-math": "^0.1.8",
"cornerstone-tools": "4.17.0",
"cornerstone-tools": "4.18.0",
"cornerstone-wado-image-loader": "^3.1.2",
"dcmjs": "0.14.2",
"dicom-parser": "^1.8.3",

View File

@ -38,12 +38,13 @@ export default function init({ servicesManager, configuration }) {
],
onSubmit: ({ action, value }) => {
switch (action.id) {
case 'save': callback(value.label);
case 'save':
callback(value.label);
}
UIDialogService.dismiss({ id: dialogId });
},
body: ({ value, setValue }) => {
const onChangeHandler = (event) => {
const onChangeHandler = event => {
event.persist();
setValue(value => ({ ...value, label: event.target.value }));
};
@ -93,7 +94,7 @@ export default function init({ servicesManager, configuration }) {
// THIS
// is a way for extensions that "depend" on this extension to notify it of
// new cornerstone enabled elements so it's commands continue to work.
const handleOhifCornerstoneEnabledElementEvent = function (evt) {
const handleOhifCornerstoneEnabledElementEvent = function(evt) {
const { viewportIndex, enabledElement } = evt.detail;
setEnabledElement(viewportIndex, enabledElement);
@ -271,7 +272,10 @@ const _connectToolsToMeasurementService = measurementService => {
const csToolsVer4MeasurementSource = _initMeasurementService(
measurementService
);
_connectMeasurementServiceToTools(measurementService, csToolsVer4MeasurementSource);
_connectMeasurementServiceToTools(
measurementService,
csToolsVer4MeasurementSource
);
const { addOrUpdate, remove } = csToolsVer4MeasurementSource;
const elementEnabledEvt = cornerstone.EVENTS.ELEMENT_ENABLED;
@ -336,14 +340,18 @@ const _connectToolsToMeasurementService = measurementService => {
});
};
const _connectMeasurementServiceToTools = (measurementService, measurementSource) => {
const { MEASUREMENTS_CLEARED, MEASUREMENT_REMOVED } = measurementService.EVENTS;
const _connectMeasurementServiceToTools = (
measurementService,
measurementSource
) => {
const {
MEASUREMENTS_CLEARED,
MEASUREMENT_REMOVED,
} = measurementService.EVENTS;
const sourceId = measurementSource.id;
measurementService.subscribe(MEASUREMENTS_CLEARED, () => {
cornerstoneTools.globalImageIdSpecificToolStateManager.restoreToolState(
{}
);
cornerstoneTools.globalImageIdSpecificToolStateManager.restoreToolState({});
cornerstone.getEnabledElements().forEach(enabledElement => {
cornerstone.updateImage(enabledElement.element);
});

View File

@ -30,7 +30,7 @@
"peerDependencies": {
"@ohif/core": "^0.50.0",
"cornerstone-core": "^2.2.8",
"cornerstone-tools": "4.17.0",
"cornerstone-tools": "4.18.0",
"dcmjs": "0.14.2",
"prop-types": "^15.6.2",
"react": "^16.8.6",

View File

@ -30,7 +30,7 @@
"peerDependencies": {
"@ohif/core": "^0.50.0",
"cornerstone-core": "^2.2.8",
"cornerstone-tools": "4.17.0",
"cornerstone-tools": "4.18.0",
"dcmjs": "0.14.2",
"prop-types": "^15.6.2",
"react": "^16.8.6",

View File

@ -33,7 +33,7 @@
"@ohif/ui": "^0.50.0",
"cornerstone-core": "^2.3.0",
"cornerstone-math": "^0.1.8",
"cornerstone-tools": "4.17.0",
"cornerstone-tools": "4.18.0",
"cornerstone-wado-image-loader": "^3.1.2",
"dcmjs": "0.14.2",
"dicom-parser": "^1.8.3",

View File

@ -399,14 +399,23 @@ function OHIFCornerstoneSRViewport({
});
}
const label =
viewports.length > 1
? _viewportLabels[firstViewportIndexWithMatchingDisplaySetUid]
: '';
return (
<>
<ViewportActionBar
onDoubleClick={evt => {
evt.stopPropagation();
evt.preventDefault();
}}
onSeriesChange={onMeasurementChange}
onHydrationClick={hydrateMeasurementService}
showNavArrows={viewportIndex === activeViewportIndex}
studyData={{
label: _viewportLabels[firstViewportIndexWithMatchingDisplaySetUid],
label,
isTracked: false,
isLocked: displaySet.isLocked,
isHydrated,
@ -432,18 +441,23 @@ function OHIFCornerstoneSRViewport({
},
}}
/>
<CornerstoneViewport
onElementEnabled={onElementEnabled}
viewportIndex={viewportIndex}
imageIds={imageIds}
imageIdIndex={currentImageIdIndex}
// TODO: ViewportGrid Context?
isActive={true} // todo
isStackPrefetchEnabled={true} // todo
isPlaying={false}
frameRate={24}
isOverlayVisible={false}
/>
<div
className="relative flex flex-row w-full h-full"
onDoubleClick={_onDoubleClick}
>
<CornerstoneViewport
onElementEnabled={onElementEnabled}
viewportIndex={viewportIndex}
imageIds={imageIds}
imageIdIndex={currentImageIdIndex}
// TODO: ViewportGrid Context?
isActive={true} // todo
isStackPrefetchEnabled={true} // todo
isPlaying={false}
frameRate={24}
isOverlayVisible={false}
/>
</div>
{childrenWithProps}
</>
);
@ -573,4 +587,14 @@ function _addToolDataToCornerstoneTools(data, toolType, imageId) {
toolData.data.push(data);
}
function _onDoubleClick() {
const cancelActiveManipulatorsForElement = cornerstoneTools.getModule(
'manipulatorState'
).setters.cancelActiveManipulatorsForElement;
const enabledElements = cornerstoneTools.store.state.enabledElements;
enabledElements.forEach(element => {
cancelActiveManipulatorsForElement(element);
});
}
export default OHIFCornerstoneSRViewport;

View File

@ -28,7 +28,7 @@
},
"peerDependencies": {
"@ohif/core": "^0.50.0",
"cornerstone-tools": "4.17.0",
"cornerstone-tools": "4.18.0",
"dcmjs": "0.14.2",
"prop-types": "^15.6.2",
"react": "^16.13.1",

View File

@ -295,6 +295,10 @@ function TrackedCornerstoneViewport({
return (
<>
<ViewportActionBar
onDoubleClick={evt => {
evt.stopPropagation();
evt.preventDefault();
}}
onSeriesChange={direction => switchMeasurement(direction)}
showNavArrows={showNavArrows}
studyData={{
@ -324,7 +328,10 @@ function TrackedCornerstoneViewport({
}}
/>
{/* TODO: Viewport interface to accept stack or layers of content like this? */}
<div className="relative flex flex-row w-full h-full">
<div
className="relative flex flex-row w-full h-full"
onDoubleClick={_onDoubleClick}
>
<CornerstoneViewport
onElementEnabled={onElementEnabled}
viewportIndex={viewportIndex}
@ -406,6 +413,18 @@ function _getCornerstoneStack(displaySet, dataSource) {
return stack;
}
function _onDoubleClick() {
const cancelActiveManipulatorsForElement = cornerstoneTools.getModule(
'manipulatorState'
).setters.cancelActiveManipulatorsForElement;
const enabledElements = cornerstoneTools.store.state.enabledElements;
enabledElements.forEach(element => {
cancelActiveManipulatorsForElement(element);
});
}
/**
* Builds the viewport data from a datasource and a displayset.
*
@ -413,6 +432,7 @@ function _getCornerstoneStack(displaySet, dataSource) {
* @param {Object} displaySet
* @return {Object} viewport data
*/
async function _getViewportData(dataSource, displaySet) {
const stack = _getCornerstoneStack(displaySet, dataSource);

View File

@ -57,7 +57,7 @@
"@ohif/core": "^2.9.6",
"@ohif/ui": "^2.0.0",
"cornerstone-core": "^2.3.0",
"cornerstone-tools": "4.17.0",
"cornerstone-tools": "4.18.0",
"cornerstone-wado-image-loader": "^3.1.2",
"dicom-parser": "^1.8.3",
"gh-pages": "^2.0.1",

12
lerna-debug.log Normal file
View File

@ -0,0 +1,12 @@
0 silly argv { _: [ 'run' ],
0 silly argv stream: true,
0 silly argv lernaVersion: '3.22.0',
0 silly argv '$0':
0 silly argv '/Users/jamesapetts/cornerstonejs/Viewers/node_modules/.bin/lerna',
0 silly argv script: 'dev:viewer' }
1 notice cli v3.22.0
2 verbose rootPath /Users/jamesapetts/cornerstonejs/Viewers
3 info versioning independent
4 error JSONError: Unexpected token < in JSON at position 871 while parsing near '...ne-core": "^2.3.0",<<<<<<< HEAD "co...' in platform/core/package.json
4 error at module.exports (/Users/jamesapetts/cornerstonejs/Viewers/node_modules/parse-json/index.js:26:19)
4 error at parse (/Users/jamesapetts/cornerstonejs/Viewers/node_modules/load-json-file/index.js:15:9)

View File

@ -31,7 +31,7 @@
},
"peerDependencies": {
"cornerstone-core": "^2.3.0",
"cornerstone-tools": "4.17.0",
"cornerstone-tools": "4.18.0",
"cornerstone-wado-image-loader": "^3.1.2",
"dicom-parser": "^1.8.3"
},

View File

@ -6,14 +6,18 @@ const publicAPI = {
setActiveViewportIndex: _setActiveViewportIndex,
setDisplaysetForViewport: _setDisplaysetForViewport,
setLayout: _setLayout,
setCachedLayout: _setCachedLayout,
setServiceImplementation,
};
const serviceImplementation = {
_getState: () => console.warn('getState() NOT IMPLEMENTED'),
_setActiveViewportIndex: () => console.warn('setActiveViewportIndex() NOT IMPLEMENTED'),
_setDisplaysetForViewport: () => console.warn('setDisplaysetForViewport() NOT IMPLEMENTED'),
_setActiveViewportIndex: () =>
console.warn('setActiveViewportIndex() NOT IMPLEMENTED'),
_setDisplaysetForViewport: () =>
console.warn('setDisplaysetForViewport() NOT IMPLEMENTED'),
_setLayout: () => console.warn('setLayout() NOT IMPLEMENTED'),
_setCachedLayout: () => console.warn('setCachedLayout() NOT IMPLEMENTED'),
};
function _getState() {
@ -35,10 +39,15 @@ function _setLayout({ numCols, numRows }) {
return serviceImplementation._setLayout({ numCols, numRows });
}
function _setCachedLayout({ numCols, numRows, viewports }) {
return serviceImplementation._setLayout({ numCols, numRows, viewports });
}
function setServiceImplementation({
getState: getStateImplementation,
setActiveViewportIndex: setActiveViewportIndexImplementation,
setDisplaysetForViewport: setDisplaysetForViewportImplementation,
setCachedLayout: setCachedLayoutImplementation,
setLayout: setLayoutImplementation,
}) {
if (getStateImplementation) {
@ -53,6 +62,9 @@ function setServiceImplementation({
if (setLayoutImplementation) {
serviceImplementation._setLayout = setLayoutImplementation;
}
if (setCachedLayoutImplementation) {
serviceImplementation._setCachedLayout = setCachedLayoutImplementation;
}
}
export default {

View File

@ -16,6 +16,7 @@ const ViewportActionBar = ({
showPatientInfo: patientInfoVisibility,
onSeriesChange,
onHydrationClick,
onDoubleClick,
}) => {
const [showPatientInfo, setShowPatientInfo] = useState(patientInfoVisibility);
@ -135,7 +136,10 @@ const ViewportActionBar = ({
};
return (
<div className="flex items-center p-2 border-b select-none border-primary-light min-h-12">
<div
className="flex items-center p-2 border-b select-none border-primary-light min-h-12"
onDoubleClick={onDoubleClick}
>
<div className="flex flex-grow">
<div className="flex items-center">
{renderIconStatus()}

View File

@ -11,6 +11,7 @@ function ViewportPane({
className,
isActive,
onDrop,
onDoubleClick,
onInteraction,
acceptDropsFor,
}) {
@ -57,6 +58,7 @@ function ViewportPane({
// https://reactjs.org/docs/events.html#mouse-events
// https://stackoverflow.com/questions/8378243/catch-scrolling-event-on-overflowhidden-element
onMouseDown={onInteractionHandler}
onDoubleClick={onDoubleClick}
onClick={onInteractionHandler}
onScroll={onInteractionHandler}
onWheel={onInteractionHandler}
@ -95,6 +97,8 @@ ViewportPane.propTypes = {
onDrop: PropTypes.func.isRequired,
/** Called when the viewportPane is interacted with by the user */
onInteraction: PropTypes.func.isRequired,
/** Executed when the pane is double clicked */
onDoubleClick: PropTypes.func,
};
const noop = () => {};

View File

@ -32,7 +32,7 @@ export function ViewportGridProvider({ children, service }) {
viewports[viewportIndex] = { displaySetInstanceUID, imageIndex };
return { ...state, ...{ viewports } };
return { ...state, ...{ viewports }, cachedLayout: undefined };
}
case 'SET_LAYOUT': {
const { numCols, numRows } = action.payload;
@ -51,8 +51,12 @@ export function ViewportGridProvider({ children, service }) {
return {
...state,
...{ activeViewportIndex, numCols, numRows, viewports },
cachedLayout: undefined,
};
}
case 'SET_CACHED_LAYOUT': {
return { ...state, cachedLayout: action.payload };
}
default:
return action.payload;
}
@ -93,6 +97,15 @@ export function ViewportGridProvider({ children, service }) {
[dispatch]
);
const setCachedLayout = useCallback(
payload =>
dispatch({
type: 'SET_CACHED_LAYOUT',
payload,
}),
[dispatch]
);
/**
* Sets the implementation of a modal service that can be used by extensions.
*
@ -105,6 +118,7 @@ export function ViewportGridProvider({ children, service }) {
setActiveViewportIndex,
setDisplaysetForViewport,
setLayout,
setCachedLayout,
});
}
}, [
@ -113,6 +127,7 @@ export function ViewportGridProvider({ children, service }) {
setActiveViewportIndex,
setDisplaysetForViewport,
setLayout,
setCachedLayout,
]);
const api = {
@ -120,6 +135,7 @@ export function ViewportGridProvider({ children, service }) {
setActiveViewportIndex,
setDisplaysetForViewport,
setLayout,
setCachedLayout,
};
return (

View File

@ -67,7 +67,7 @@
"classnames": "^2.2.6",
"core-js": "^3.2.1",
"cornerstone-math": "^0.1.8",
"cornerstone-tools": "4.17.0",
"cornerstone-tools": "4.18.0",
"cornerstone-wado-image-loader": "^3.1.2",
"dcmjs": "0.14.2",
"dicom-parser": "^1.8.3",

View File

@ -11,7 +11,7 @@ const { ImageSet } = classes;
function ViewerViewportGrid(props) {
const { servicesManager, viewportComponents, dataSource } = props;
const [
{ numCols, numRows, activeViewportIndex, viewports },
{ numCols, numRows, activeViewportIndex, viewports, cachedLayout },
viewportGridService,
] = useViewportGrid();
@ -67,6 +67,43 @@ function ViewerViewportGrid(props) {
};
};
const onDoubleClick = viewportIndex => {
if (cachedLayout) {
viewportGridService.setLayout({
numCols: cachedLayout.numCols,
numRows: cachedLayout.numRows,
});
cachedLayout.viewports.forEach((viewport, viewportIndex) => {
viewportGridService.setDisplaysetForViewport({
viewportIndex,
displaySetInstanceUID: viewport.displaySetInstanceUID,
});
});
return;
}
const cachedViewports = viewports.map(viewport => {
return {
displaySetInstanceUID: viewport.displaySetInstanceUID,
};
});
viewportGridService.setDisplaysetForViewport({
viewportIndex: 0,
displaySetInstanceUID: viewports[viewportIndex].displaySetInstanceUID,
});
viewportGridService.setLayout({ numCols: 1, numRows: 1 });
viewportGridService.setCachedLayout({
numCols,
numRows,
viewports: cachedViewports,
});
};
// TODO:
// Hmm... Should a "displaySet" being added update the viewport based on HP?
// I guess it might.
@ -127,6 +164,7 @@ function ViewerViewportGrid(props) {
acceptDropsFor="displayset"
onDrop={onDropHandler.bind(null, viewportIndex)}
onInteraction={onInterationHandler}
onDoubleClick={() => onDoubleClick(viewportIndex)}
isActive={activeViewportIndex === viewportIndex}
>
<ViewportComponent

View File

@ -6766,10 +6766,10 @@ cornerstone-math@^0.1.8:
resolved "https://registry.yarnpkg.com/cornerstone-math/-/cornerstone-math-0.1.8.tgz#68ab1f9e4fdcd7c5cb23a0d2eb4263f9f894f1c5"
integrity sha512-x7NEQHBtVG7j1yeyj/aRoKTpXv1Vh2/H9zNLMyqYJDtJkNng8C4Q8M3CgZ1qer0Yr7eVq2x+Ynmj6kfOm5jXKw==
cornerstone-tools@4.17.0:
version "4.17.0"
resolved "https://registry.yarnpkg.com/cornerstone-tools/-/cornerstone-tools-4.17.0.tgz#a97d6ebeb9745e9f226946e05329e856ebcaee1e"
integrity sha512-UPXWPzrb+2XwAXfQLc+ifSb0qp0SBZ3ojlGqt1iq9j85oQuy93avuEN6k2R8BV4NgQFz4okIEJdyKMoBwmrZ8g==
cornerstone-tools@4.18.0:
version "4.18.0"
resolved "https://registry.yarnpkg.com/cornerstone-tools/-/cornerstone-tools-4.18.0.tgz#3b9497390fe7a7479db6f480d3219cc20c97e128"
integrity sha512-r1IslS/SaybyZq95jhaHltuYuA/c8h6C6iUMYGsuMh1laENYV9Nm0XsI0Ud+w1v5ZgvbHu52bw0rUQmWkOHszA==
dependencies:
"@babel/runtime" "7.1.2"
cornerstone-math "0.1.7"