feat: extension panels

* Snapshot -- Switching branches
* fix classes and hooks for panel componet
* Restore button functionality
* feat: support for expandableToolMenu
* Shift cornerstoneTools config up a layer, and use globalToolSync
* Remaining initCornerstoneTools changes
* Pull in Segmentation Plugin
* Shift MeasurementsPanel to an extension
* Note regarding where data is coming from
* Make sure measurement callback is available
* Bump core version
* Shift MeasurementsTable to a local extensions directory
* Register appCommands as an extension
* Update package dependencies and yarn lock
* Support for panel width
* Ability to pass props to our panel component
* Add a safety check around calling our tacked on method for tool options
* Left sidebar plugins + fix activeIndex prop passed to sidebar.
* fix: viewer height
Now in a shared container w/ top bar, so we need to factor that in when calculating height
* lock file latest
* Update thumbnails if studies has changes
* bump cornerstone version to resolve globalToolSynch history issue
* Default panel to open; studies optional
* Bump minor version for ohif-cornestone-extension
* Simplify button logic
* Accommodate odd roundedButtonGroup value change emit
* Map viewers + selectedViewport instead of pulling off the window
This commit is contained in:
Danny Brown authored and GitHub committed 2019-07-04 14:09:13 -04:00
1 parent 514609708d
commit 5ac9048ef0
36 files changed
+554 -463

No files matched your search

+164 -9
View File
@@ -1,15 +1,18 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
//import OHIF from 'ohif-core';
//import { CineDialog } from 'react-viewerbase';
import classNames from 'classnames';
import { MODULE_TYPES } from 'ohif-core';
import OHIF from 'ohif-core';
import moment from 'moment';
import WhiteLabellingContext from '../WhiteLabellingContext.js';
import ConnectedHeader from './ConnectedHeader.js';
import ConnectedFlexboxLayout from './ConnectedFlexboxLayout.js';
import ConnectedToolbarRow from './ConnectedToolbarRow.js';
import ConnectedLabellingOverlay from './ConnectedLabellingOverlay';
import ConnectedStudyBrowser from './ConnectedStudyBrowser.js';
import ConnectedViewerMain from './ConnectedViewerMain.js';
import SidePanel from './../components/SidePanel.js';
import { extensionManager } from './../App.js';
import './Viewer.css';
/**
* Inits OHIF Hanging Protocol's onReady.
@@ -53,6 +56,10 @@ class Viewer extends Component {
studyInstanceUids: PropTypes.array,
onTimepointsUpdated: PropTypes.func,
onMeasurementsUpdated: PropTypes.func,
// window.store.getState().viewports.viewportSpecificData
viewports: PropTypes.object.isRequired,
// window.store.getState().viewports.activeViewportIndex
activeViewportIndex: PropTypes.number.isRequired,
};
constructor(props) {
@@ -75,6 +82,14 @@ class Viewer extends Component {
});
}
state = {
isLeftSidePanelOpen: true,
isRightSidePanelOpen: false,
selectedRightSidePanel: '',
selectedLeftSidePanel: 'studies', // TODO: Don't hardcode this
thumbnails: [],
};
retrieveMeasurements = (patientId, timepointIds) => {
OHIF.log.info('retrieveMeasurements');
// TODO: Retrieve the measurements from the latest available SR
@@ -171,8 +186,13 @@ class Viewer extends Component {
if (studies) {
const patientId = studies[0] && studies[0].patientId;
timepointApi.retrieveTimepoints({ patientId });
measurementApi.retrieveMeasurements(patientId, [currentTimepointId]);
this.setState({
thumbnails: _mapStudiesToThumbnails(studies),
});
}
}
@@ -181,14 +201,33 @@ class Viewer extends Component {
const { studies } = this.props;
const patientId = studies[0] && studies[0].patientId;
const currentTimepointId = this.currentTimepointId;
this.timepointApi.retrieveTimepoints({ patientId });
this.measurementApi.retrieveMeasurements(patientId, [currentTimepointId]);
this.setState({
thumbnails: _mapStudiesToThumbnails(studies),
});
}
}
render() {
let VisiblePanelLeft, VisiblePanelRight;
const panelExtensions = extensionManager.modules[MODULE_TYPES.PANEL];
panelExtensions.forEach(panelExt => {
panelExt.module.components.forEach(comp => {
if (comp.id === this.state.selectedRightSidePanel) {
VisiblePanelRight = comp.component;
} else if (comp.id === this.state.selectedLeftSidePanel) {
VisiblePanelLeft = comp.component;
}
});
});
return (
<>
{/* HEADER */}
<WhiteLabellingContext.Consumer>
{whiteLabelling => (
<ConnectedHeader home={false}>
@@ -196,16 +235,132 @@ class Viewer extends Component {
</ConnectedHeader>
)}
</WhiteLabellingContext.Consumer>
<div id="viewer" className="Viewer">
<ConnectedToolbarRow />
{/*<ConnectedStudyLoadingMonitor studies={this.props.studies} />*/}
{/*<StudyPrefetcher studies={this.props.studies} />*/}
<ConnectedFlexboxLayout studies={this.props.studies} />
<ConnectedLabellingOverlay />
{/* TOOLBAR */}
<ConnectedToolbarRow
isLeftSidePanelOpen={this.state.isLeftSidePanelOpen}
isRightSidePanelOpen={this.state.isRightSidePanelOpen}
selectedLeftSidePanel={
this.state.isLeftSidePanelOpen
? this.state.selectedLeftSidePanel
: ''
}
selectedRightSidePanel={
this.state.isRightSidePanelOpen
? this.state.selectedRightSidePanel
: ''
}
handleSidePanelChange={(side, selectedPanel) => {
const sideClicked = side && side[0].toUpperCase() + side.slice(1);
const openKey = `is${sideClicked}SidePanelOpen`;
const selectedKey = `selected${sideClicked}SidePanel`;
const updatedState = Object.assign({}, this.state);
const isOpen = updatedState[openKey];
const prevSelectedPanel = updatedState[selectedKey];
// RoundedButtonGroup returns `null` if selected button is clicked
const isSameSelectedPanel =
prevSelectedPanel === selectedPanel || selectedPanel === null;
updatedState[selectedKey] = selectedPanel || prevSelectedPanel;
const isClosedOrShouldClose = !isOpen || isSameSelectedPanel;
if (isClosedOrShouldClose) {
updatedState[openKey] = !updatedState[openKey];
}
this.setState(updatedState);
}}
/>
{/*<ConnectedStudyLoadingMonitor studies={this.props.studies} />*/}
{/*<StudyPrefetcher studies={this.props.studies} />*/}
{/* VIEWPORTS + SIDEPANELS */}
<div className="FlexboxLayout">
{/* LEFT */}
<SidePanel from="left" isOpen={this.state.isLeftSidePanelOpen}>
{VisiblePanelLeft ? (
<VisiblePanelLeft
viewports={this.props.viewports}
activeIndex={this.props.activeViewportIndex}
/>
) : (
<ConnectedStudyBrowser studies={this.state.thumbnails} />
)}
</SidePanel>
{/* MAIN */}
<div className={classNames('main-content')}>
<ConnectedViewerMain studies={this.props.studies} />
</div>
{/* RIGHT */}
<SidePanel from="right" isOpen={this.state.isRightSidePanelOpen}>
{VisiblePanelRight && (
<VisiblePanelRight
viewports={this.props.viewports}
activeIndex={this.props.activeViewportIndex}
/>
)}
</SidePanel>
</div>
<ConnectedLabellingOverlay />
</>
);
}
}
export default Viewer;
/**
* What types are these? Why do we have "mapping" dropped in here instead of in
* a mapping layer?
*
* TODO[react]:
* - Add sorting of display sets
* - Add useMiddleSeriesInstanceAsThumbnail
* - Add showStackLoadingProgressBar option
*
* @param {Study[]} studies
* @param {DisplaySet[]} studies[].displaySets
*/
const _mapStudiesToThumbnails = function(studies) {
return studies.map(study => {
const { studyInstanceUid } = study;
const thumbnails = study.displaySets.map(displaySet => {
const {
displaySetInstanceUid,
seriesDescription,
seriesNumber,
instanceNumber,
numImageFrames,
} = displaySet;
let imageId;
let altImageText = ' '; // modality
if (displaySet.images && displaySet.images.length) {
imageId = displaySet.images[0].getImageId();
} else {
altImageText = 'SR';
}
return {
imageId,
altImageText,
displaySetInstanceUid,
seriesDescription,
seriesNumber,
instanceNumber,
numImageFrames,
};
});
return {
studyInstanceUid,
thumbnails,
};
});
};