Add measurement table functions (#415)

* fix(measurements): Add measurement table functions
- Support for labeling measurements and adding measurement description
- Jump to measurement when clicked on measurement table

* fix(layout): Fix the issues with layout when switched with different plugins

* fix(measurements): Support for deleting measurements in measurement table

* fix(measurements): Add context menu for measurements

* fix(dependencies): Bump ohif-core to 0.4.2
This commit is contained in:
Evren Ozkan authored and GitHub committed 2019-05-02 17:12:06 -04:00
1 parent 0bab19985e
commit c9ddb23d9c
33 files changed
+2219 -151

No files matched your search

@@ -0,0 +1,24 @@
import { connect } from 'react-redux'
import LabellingOverlay from './LabellingOverlay'
const mapStateToProps = state => {
if (!state.ui || !state.ui.labelling) {
return {
visible: false,
}
}
const labellingFlowData = state.ui.labelling
return {
visible: false,
...labellingFlowData,
}
}
const ConnectedLabellingOverlay = connect(
mapStateToProps,
null
)(LabellingOverlay)
export default ConnectedLabellingOverlay
@@ -1,35 +1,41 @@
import { connect } from 'react-redux';
import { MeasurementTable } from 'react-viewerbase';
import OHIF from 'ohif-core';
import moment from 'moment';
import { connect } from 'react-redux'
import { MeasurementTable } from 'react-viewerbase'
import OHIF from 'ohif-core'
import moment from 'moment'
import cornerstone from 'cornerstone-core'
import jumpToRowItem from '../lib/jumpToRowItem.js'
import getMeasurementLocationCallback from '../lib/getMeasurementLocationCallback'
const { setViewportSpecificData } = OHIF.redux.actions;
const { MeasurementApi } = OHIF.measurements
function groupBy(list, props) {
return list.reduce((a, b) => {
(a[b[props]] = a[b[props]] || []).push(b);
return a;
}, {});
(a[b[props]] = a[b[props]] || []).push(b)
return a
}, {})
}
function getAllTools() {
const config = OHIF.measurements.MeasurementApi.getConfiguration();
let tools = [];
const config = OHIF.measurements.MeasurementApi.getConfiguration()
let tools = []
config.measurementTools.forEach(
toolGroup => (tools = tools.concat(toolGroup.childTools))
);
)
return tools;
return tools
}
function getMeasurementText(measurementData) {
const { location, description } = measurementData;
let text = '...';
const { location, description } = measurementData
let text = '...'
if (location) {
text = location;
text = location
if (description) {
text += `(${description})`;
text += `(${description})`
}
}
return text;
return text
}
function getDataForEachMeasurementNumber(
@@ -37,51 +43,62 @@ function getDataForEachMeasurementNumber(
timepoints,
displayFunction
) {
const data = [];
const data = []
// on each measurement number we should get each measurement data by available timepoint
measurementNumberList.forEach(measurement => {
timepoints.forEach(timepoint => {
const eachData = {
displayText: '...'
};
if (measurement.timepointId === timepoint.timepointId) {
eachData.displayText = displayFunction(measurement);
displayText: '...',
}
data.push(eachData);
});
});
if (measurement.timepointId === timepoint.timepointId) {
eachData.displayText = displayFunction(measurement)
}
data.push(eachData)
})
})
return data;
return data
}
function convertMeasurementsToTableData(toolCollections, timepoints) {
const config = OHIF.measurements.MeasurementApi.getConfiguration();
const toolGroups = config.measurementTools;
const tools = getAllTools();
const config = OHIF.measurements.MeasurementApi.getConfiguration()
const toolGroups = config.measurementTools
const tools = getAllTools()
const tableMeasurements = toolGroups.map(toolGroup => {
return {
groupName: toolGroup.name,
groupId: toolGroup.id,
measurements: []
};
});
measurements: [],
}
})
Object.keys(toolCollections).forEach(toolId => {
const toolMeasurements = toolCollections[toolId];
const tool = tools.find(tool => tool.id === toolId);
const { displayFunction } = tool.options.measurementTable;
const toolMeasurements = toolCollections[toolId]
const tool = tools.find(tool => tool.id === toolId)
const { displayFunction } = tool.options.measurementTable
// Group by measurementNumber so we can display then all in the same line
const groupedMeasurements = groupBy(toolMeasurements, 'measurementNumber');
const groupedMeasurements = groupBy(toolMeasurements, 'measurementNumber')
Object.keys(groupedMeasurements).forEach(groupedMeasurementsIndex => {
const measurementNumberList =
groupedMeasurements[groupedMeasurementsIndex];
groupedMeasurements[groupedMeasurementsIndex]
const {
measurementNumber,
lesionNamingNumber,
toolType,
} = measurementNumberList[0]
const measurementId = measurementNumberList[0]._id
//check if all measurements with same measurementNumber will have same LABEL
const tableMeasurement = {
measurementId: measurementNumberList[0]._id,
label: getMeasurementText(measurementNumberList[0]),
measurementId,
measurementNumber,
lesionNamingNumber,
toolType,
hasWarnings: false, //TODO
warningTitle: '', //TODO
isSplitLesion: false, //TODO
@@ -90,48 +107,239 @@ function convertMeasurementsToTableData(toolCollections, timepoints) {
measurementNumberList,
timepoints,
displayFunction
)
};
),
}
// find the group object for the tool
const toolGroupMeasurements = tableMeasurements.find(group => {
return group.groupId === tool.toolGroup;
});
return group.groupId === tool.toolGroup
})
// inject the new measurement for this measurementNumer
toolGroupMeasurements.measurements.push(tableMeasurement);
});
});
toolGroupMeasurements.measurements.push(tableMeasurement)
})
})
return tableMeasurements;
return tableMeasurements
}
function convertTimepointsToTableData(timepoints) {
if (!timepoints || !timepoints.length) {
return [];
return []
}
return [
{
label: 'Study date:',
date: moment(timepoints[0].latestDate).format('DD-MMM-YY')
}
];
date: moment(timepoints[0].latestDate).format('DD-MMM-YY'),
},
]
}
const mapStateToProps = state => {
const { timepoints, measurements } = state.timepointManager;
const { timepoints, measurements } = state.timepointManager
return {
timepoints: convertTimepointsToTableData(timepoints),
measurementCollection: convertMeasurementsToTableData(
measurements,
timepoints
)
};
};
),
timepointManager: state.timepointManager,
viewports: state.viewports,
}
}
const mapDispatchToProps = dispatch => {
return {
dispatchRelabel: (event, measurementData, viewportsState) => {
const activeViewportIndex =
(viewportsState && viewportsState.activeViewportIndex) || 0
const enabledElements = cornerstone.getEnabledElements()
if (!enabledElements || enabledElements.length <= activeViewportIndex) {
OHIF.log.error('Failed to find the enabled element')
return
}
const { element } = enabledElements[activeViewportIndex]
const eventData = {
event: {
clientX: event.clientX,
clientY: event.clientY,
},
element,
}
const { toolType, measurementId } = measurementData
const tool = MeasurementApi.Instance.tools[toolType].find(measurement => {
return measurement._id === measurementId
})
const options = {
skipAddLabelButton: true,
editLocation: true,
}
// Clone the tool not to set empty location initially
const toolForLocation = Object.assign({}, tool, { location: null })
getMeasurementLocationCallback(eventData, toolForLocation, options)
},
dispatchEditDescription: (event, measurementData, viewportsState) => {
const activeViewportIndex =
(viewportsState && viewportsState.activeViewportIndex) || 0
const enabledElements = cornerstone.getEnabledElements()
if (!enabledElements || enabledElements.length <= activeViewportIndex) {
OHIF.log.error('Failed to find the enabled element')
return
}
const { element } = enabledElements[activeViewportIndex]
const eventData = {
event: {
clientX: event.clientX,
clientY: event.clientY,
},
element,
}
const { toolType, measurementId } = measurementData
const tool = MeasurementApi.Instance.tools[toolType].find(measurement => {
return measurement._id === measurementId
})
const options = {
editDescriptionOnDialog: true,
}
getMeasurementLocationCallback(eventData, tool, options)
},
dispatchJumpToRowItem: (
measurementData,
viewportsState,
timepointManagerState,
options
) => {
const actionData = jumpToRowItem(
measurementData,
viewportsState,
timepointManagerState,
dispatch,
options
)
actionData.viewportSpecificData.forEach(viewportSpecificData => {
const { viewportIndex, displaySet } = viewportSpecificData;
dispatch(setViewportSpecificData(viewportIndex, displaySet));
})
const { toolType, measurementNumber } = measurementData
const measurementApi = MeasurementApi.Instance
Object.keys(measurementApi.tools).forEach(toolType => {
const measurements = measurementApi.tools[toolType]
measurements.forEach(measurement => {
measurement.active = false
})
})
const measurementsToActive = measurementApi.tools[toolType].filter(
measurement => {
return measurement.measurementNumber === measurementNumber
}
)
measurementsToActive.forEach(measurementToActive => {
measurementToActive.active = true
})
measurementApi.syncMeasurementsAndToolData()
cornerstone.getEnabledElements().forEach(enabledElement => {
cornerstone.updateImage(enabledElement.element)
})
// Needs to update viewports.layout state to set layout
//const layout = actionData.layout;
//dispatch(setLayout(layout))
// Needs to update viewports.activeViewportIndex to the first updated viewport
//const viewportIndex = actionData.viewportIndex;
//dispatch(setViewportActive(viewportIndex));
// Needs to update timepointsManager.measurements state to set active measurementId
// TODO: Not yet implemented
//dispatch(setActiveMeasurement(measurementData.measurementId))
// (later): Needs to set some property on state.extensions.cornerstone to synchronize viewport scrolling
},
}
}
const mergeProps = (propsFromState, propsFromDispatch, ownProps) => {
return {
timepoints: propsFromState.timepoints,
measurementCollection: propsFromState.measurementCollection,
selectedMeasurementNumber: ownProps.selectedMeasurementNumber,
...propsFromDispatch,
onItemClick: (event, measurementData) => {
// TODO: Add timepointId to .data for measurementData?
// TODO: Tooltype should be on the level below? This should
// provide the entire row item?
const viewportsState = propsFromState.viewports
const timepointManagerState = propsFromState.timepointManager
// TODO: invertViewportTimepointsOrder should be stored in / read from user preferences
// TODO: childToolKey should come from the measurement table when it supports child tools
const options = {
invertViewportTimepointsOrder: false,
childToolKey: null,
}
propsFromDispatch.dispatchJumpToRowItem(
measurementData,
viewportsState,
timepointManagerState,
options
)
},
onRelabelClick: (event, measurementData) => {
const viewportsState = propsFromState.viewports
propsFromDispatch.dispatchRelabel(event, measurementData, viewportsState)
},
onEditDescriptionClick: (event, measurementData) => {
const viewportsState = propsFromState.viewports
propsFromDispatch.dispatchEditDescription(
event,
measurementData,
viewportsState
)
},
onDeleteClick: (event, measurementData) => {
const { MeasurementHandlers } = OHIF.measurements;
MeasurementHandlers.onRemoved({
detail: {
toolType: measurementData.toolType,
measurementData: {
_id: measurementData.measurementId,
lesionNamingNumber: measurementData.lesionNamingNumber,
measurementNumber: measurementData.measurementNumber
}
}
});
}
}
}
const ConnectedMeasurementTable = connect(
mapStateToProps,
null
)(MeasurementTable);
mapDispatchToProps,
mergeProps
)(MeasurementTable)
export default ConnectedMeasurementTable;
export default ConnectedMeasurementTable
@@ -0,0 +1,34 @@
import { connect } from 'react-redux'
import ToolContextMenu from './ToolContextMenu'
const mapStateToProps = (state, ownProps) => {
if (!state.ui || !state.ui.contextMenu) {
return {
visible: false,
}
}
const { viewportIndex } = ownProps
const toolContextMenuData = state.ui.contextMenu[viewportIndex]
let availableTools
if (
state.extensions &&
state.extensions.cornerstone &&
state.extensions.cornerstone.availableTools
) {
availableTools = state.extensions.cornerstone.availableTools
}
return {
...toolContextMenuData,
availableTools,
}
}
const ConnectedToolContextMenu = connect(
mapStateToProps,
null
)(ToolContextMenu)
export default ConnectedToolContextMenu
@@ -0,0 +1,33 @@
import { connect } from 'react-redux';
import ViewerMain from './ViewerMain';
import OHIF from 'ohif-core';
const { setViewportSpecificData, clearViewportSpecificData } = OHIF.redux.actions;
const mapStateToProps = state => {
const { activeViewportIndex, layout, viewportSpecificData } = state.viewports;
return {
activeViewportIndex,
layout,
viewportSpecificData
}
};
const mapDispatchToProps = dispatch => {
return {
setViewportSpecificData: (viewportIndex, data) => {
dispatch(setViewportSpecificData(viewportIndex, data));
},
clearViewportSpecificData: () => {
dispatch(clearViewportSpecificData());
}
};
};
const ConnectedViewerMain = connect(
mapStateToProps,
mapDispatchToProps
)(ViewerMain);
export default ConnectedViewerMain;
+32 -32
View File
@@ -1,38 +1,38 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import ConnectedStudyBrowser from './ConnectedStudyBrowser.js';
import ViewerMain from './ViewerMain.js';
import ConnectedMeasurementTable from './ConnectedMeasurementTable';
import './FlexboxLayout.css';
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import ConnectedStudyBrowser from './ConnectedStudyBrowser.js'
import ConnectedViewerMain from './ConnectedViewerMain.js'
import ConnectedMeasurementTable from './ConnectedMeasurementTable'
import './FlexboxLayout.css'
class FlexboxLayout extends Component {
static propTypes = {
studies: PropTypes.array.isRequired,
leftSidebarOpen: PropTypes.bool.isRequired,
rightSidebarOpen: PropTypes.bool.isRequired
};
rightSidebarOpen: PropTypes.bool.isRequired,
}
state = {
studiesForBrowser: []
};
studiesForBrowser: [],
}
componentDidMount() {
const studiesForBrowser = this.getStudiesForBrowser();
const studiesForBrowser = this.getStudiesForBrowser()
this.setState({
studiesForBrowser
});
studiesForBrowser,
})
}
getStudiesForBrowser = () => {
const { studies } = this.props;
const { studies } = this.props
// TODO[react]:
// - Add sorting of display sets
// - Add useMiddleSeriesInstanceAsThumbnail
// - Add showStackLoadingProgressBar option
return studies.map(study => {
const { studyInstanceUid } = study;
const { studyInstanceUid } = study
const thumbnails = study.displaySets.map(displaySet => {
const {
@@ -40,13 +40,13 @@ class FlexboxLayout extends Component {
seriesDescription,
seriesNumber,
instanceNumber,
numImageFrames
} = displaySet;
numImageFrames,
} = displaySet
let imageId;
let imageId
if (displaySet.images && displaySet.images.length) {
imageId = displaySet.images[0].getImageId();
imageId = displaySet.images[0].getImageId()
}
return {
@@ -55,25 +55,25 @@ class FlexboxLayout extends Component {
seriesDescription,
seriesNumber,
instanceNumber,
numImageFrames
};
});
numImageFrames,
}
})
return {
studyInstanceUid,
thumbnails
};
});
};
thumbnails,
}
})
}
render() {
let mainContentClassName = 'main-content';
let mainContentClassName = 'main-content'
if (this.props.leftSidebarOpen) {
mainContentClassName += ' sidebar-left-open';
mainContentClassName += ' sidebar-left-open'
}
if (this.props.rightSidebarOpen) {
mainContentClassName += ' sidebar-right-open';
mainContentClassName += ' sidebar-right-open'
}
// TODO[react]: Make ConnectedMeasurementTable extension with state.timepointManager
@@ -89,7 +89,7 @@ class FlexboxLayout extends Component {
<ConnectedStudyBrowser studies={this.state.studiesForBrowser} />
</div>
<div className={mainContentClassName}>
<ViewerMain studies={this.props.studies} />
<ConnectedViewerMain studies={this.props.studies} />
</div>
<div
className={
@@ -101,8 +101,8 @@ class FlexboxLayout extends Component {
<ConnectedMeasurementTable />
</div>
</div>
);
)
}
}
export default FlexboxLayout;
export default FlexboxLayout
@@ -0,0 +1,23 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import LabellingManager from '../components/Labelling/LabellingManager'
class LabellingOverlay extends Component {
static propTypes = {
visible: PropTypes.bool.isRequired,
}
static defaultProps = {
visible: false,
}
render() {
if (!this.props.visible) {
return null
}
return <LabellingManager {...this.props} />
}
}
export default LabellingOverlay
@@ -0,0 +1,34 @@
.ToolContextMenu {
position: absolute;
background-color: white;
border: 1px solid white;
border-radius: 5px;
z-index: 1000;
display: block;
width: 170px;
}
.ToolContextMenu > ul {
list-style-type: none;
padding-left: 0;
margin: 0;
}
.ToolContextMenu > ul > li > button {
padding: 10px;
font-size: 14px;
border: none;
color: #516873;
border-radius: 3px;
outline: none;
cursor: pointer;
background: none;
}
.ToolContextMenu > ul > li > button:hover {
color: #16202b;
}
.ToolContextMenu > ul > li > button:active {
color: #79f9fe;
}
+244
View File
@@ -0,0 +1,244 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import cornerstone from 'cornerstone-core'
import cornerstoneTools from 'cornerstone-tools'
import getMeasurementLocationCallback from '../lib/getMeasurementLocationCallback'
import './ToolContextMenu.css'
const toolTypes = ['Angle', 'Length']
let defaultDropdownItems = [
{
actionType: 'Delete',
action: ({ nearbyToolData, eventData }) => {
const element = eventData.element
cornerstoneTools.removeToolState(
element,
nearbyToolData.toolType,
nearbyToolData.tool
)
cornerstone.updateImage(element)
},
},
{
actionType: 'setLabel',
action: ({ nearbyToolData, eventData }) => {
const { tool } = nearbyToolData
const options = {
skipAddLabelButton: true,
editLocation: true,
}
getMeasurementLocationCallback(eventData, tool, options)
},
},
{
actionType: 'setDescription',
action: ({ nearbyToolData, eventData }) => {
const { tool } = nearbyToolData
const options = {
editDescriptionOnDialog: true,
}
getMeasurementLocationCallback(eventData, tool, options)
},
},
]
function getNearbyToolData(element, coords, toolTypes) {
const nearbyTool = {}
let pointNearTool = false
toolTypes.forEach(toolType => {
const toolData = cornerstoneTools.getToolState(element, toolType)
if (!toolData) {
return
}
toolData.data.forEach(function(data, index) {
// TODO: Fix this, it's ugly
let toolInterface = cornerstoneTools.getToolForElement(element, toolType)
if (!toolInterface) {
toolInterface = cornerstoneTools.getToolForElement(
element,
`${toolType}Tool`
)
}
if (!toolInterface) {
throw new Error('Tool not found.')
}
if (toolInterface.pointNearTool(element, data, coords)) {
pointNearTool = true
nearbyTool.tool = data
nearbyTool.index = index
nearbyTool.toolType = toolType
}
})
if (pointNearTool) {
return false
}
})
return pointNearTool ? nearbyTool : undefined
}
function getDropdownItems(eventData, isTouchEvent = false, availableTools) {
const nearbyToolData = getNearbyToolData(
eventData.element,
eventData.currentPoints.canvas,
toolTypes,
availableTools
)
// Annotate tools for touch events already have a press handle to edit it, has a better UX for deleting it
if (
isTouchEvent &&
nearbyToolData &&
nearbyToolData.toolType === 'arrowAnnotate'
) {
return
}
let dropdownItems = []
if (nearbyToolData) {
defaultDropdownItems.forEach(function(item) {
item.params = {
eventData,
nearbyToolData,
}
if (item.actionType === 'Delete') {
item.text = 'Delete measurement'
}
if (item.actionType === 'setLabel') {
item.text = 'Relabel'
}
if (item.actionType === 'setDescription') {
item.text = `${
nearbyToolData.tool.description ? 'Edit' : 'Add'
} Description`
}
dropdownItems.push(item)
})
}
return dropdownItems
}
class ToolContextMenu extends Component {
static propTypes = {
isTouchEvent: PropTypes.bool.isRequired,
eventData: PropTypes.object,
onClose: PropTypes.func,
availableTools: PropTypes.array,
visible: PropTypes.bool.isRequired,
}
static defaultProps = {
visible: true,
isTouchEvent: false,
}
constructor(props) {
super(props)
this.mainElement = React.createRef()
}
render() {
if (!this.props.eventData) {
return null
}
const { isTouchEvent, eventData, availableTools } = this.props
const dropdownItems = getDropdownItems(
eventData,
isTouchEvent,
availableTools
)
// Skip if there is no dropdown item
if (!dropdownItems.length) {
return ''
}
const dropdownComponents = dropdownItems.map(item => {
const itemOnClick = event => {
item.action(item.params)
if (this.props.onClose) {
this.props.onClose()
}
}
return (
<li key={item.actionType}>
<button className="form-action" onClick={itemOnClick}>
<span key={item.actionType}>{item.text}</span>
</button>
</li>
)
})
const position = {
top: `${eventData.currentPoints.canvas.y}px`,
left: `${eventData.currentPoints.canvas.x}px`,
}
return (
<div className="ToolContextMenu" style={position} ref={this.mainElement}>
<ul className="bounded">{dropdownComponents}</ul>
</div>
)
}
componentDidMount = () => {
if (this.mainElement.current) {
this.updateElementPosition()
}
}
componentDidUpdate = () => {
if (this.mainElement.current) {
this.updateElementPosition()
}
}
updateElementPosition = () => {
const {
offsetParent,
offsetTop,
offsetHeight,
offsetWidth,
offsetLeft,
} = this.mainElement.current
const { eventData } = this.props
if (offsetTop + offsetHeight > offsetParent.offsetHeight) {
const offBoundPixels =
offsetTop + offsetHeight - offsetParent.offsetHeight
const top = eventData.currentPoints.canvas.y - offBoundPixels
this.mainElement.current.style.top = `${top > 0 ? top : 0}px`
}
if (offsetLeft + offsetWidth > offsetParent.offsetWidth) {
const offBoundPixels = offsetLeft + offsetWidth - offsetParent.offsetWidth
const left = eventData.currentPoints.canvas.x - offBoundPixels
this.mainElement.current.style.left = `${left > 0 ? left : 0}px`
}
}
}
export default ToolContextMenu
+2
View File
@@ -9,6 +9,7 @@ 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 './Viewer.css';
/**
* Inits OHIF Hanging Protocol's onReady.
@@ -184,6 +185,7 @@ class Viewer extends Component {
{/*<ConnectedStudyLoadingMonitor studies={this.props.studies} />*/}
{/*<StudyPrefetcher studies={this.props.studies} />*/}
<ConnectedFlexboxLayout studies={this.props.studies} />
<ConnectedLabellingOverlay />
</div>
</>
);
+38 -19
View File
@@ -6,23 +6,28 @@ import ConnectedLayoutManager from './ConnectedLayoutManager.js';
import './ViewerMain.css';
class ViewerMain extends Component {
state = {
viewportData: []
};
static propTypes = {
studies: PropTypes.array.isRequired
studies: PropTypes.array.isRequired,
setViewportSpecificData: PropTypes.func.isRequired,
clearViewportSpecificData: PropTypes.func.isRequired
};
constructor(props) {
super(props);
OHIF.hotkeysUtil.setup('viewer');
this.state = {
displaySets: []
}
}
getDisplaySets(studies) {
const displaySets = [];
studies.forEach(study => {
study.displaySets.forEach(dSet => {
if (!dSet.plugin) {
dSet.plugin = 'cornerstone';
}
displaySets.push(dSet);
});
});
@@ -52,29 +57,37 @@ class ViewerMain extends Component {
const displaySets = this.getDisplaySets(this.props.studies);
this.setState({
viewportData: displaySets
displaySets
});
}
getViewportData = () => {
const viewportData = [];
const { layout, viewportSpecificData } = this.props;
for (let viewportIndex = 0; viewportIndex < layout.viewports.length; viewportIndex++) {
let displaySet = viewportSpecificData[viewportIndex];
// If the viewport is empty, get one available in study
if (!displaySet || !displaySet.displaySetInstanceUid) {
const { displaySets } = this.state;
displaySet = displaySets.find(ds => !viewportData.some(v => v.displaySetInstanceUid === ds.displaySetInstanceUid));
}
viewportData.push(displaySet);
}
return viewportData;
};
setViewportData = ({ viewportIndex, item }) => {
// TODO: Replace this with mapDispatchToProps call
// if we decide to put viewport info into redux
// Note: Use Slice because React does a shallow equality check. Mutating the array
// would not trigger a re-render. We have to create a copy.
const updatedViewportData = this.state.viewportData.slice(0);
const displaySet = this.findDisplaySet(
this.props.studies,
item.studyInstanceUid,
item.displaySetInstanceUid
);
updatedViewportData[viewportIndex] = Object.assign({}, displaySet);
this.setState({
viewportData: updatedViewportData
});
this.props.setViewportSpecificData(viewportIndex, displaySet);
};
render() {
@@ -82,7 +95,7 @@ class ViewerMain extends Component {
<div className="ViewerMain">
<ConnectedLayoutManager
studies={this.props.studies}
viewportData={this.state.viewportData}
viewportData={this.getViewportData()}
setViewportData={this.setViewportData}
/>
</div>
@@ -90,6 +103,12 @@ class ViewerMain extends Component {
}
componentWillUnmount() {
// Clear the entire viewport specific data
const { viewportSpecificData } = this.props;
Object.keys(viewportSpecificData).forEach((viewportIndex) => {
this.props.clearViewportSpecificData(viewportIndex);
});
// Remove beforeUnload event handler...
//window.removeEventListener('beforeunload', unloadHandlers.beforeUnload);
// Destroy the synchronizer used to update reference lines