Merge branch 'master' of github.com:OHIF/Viewers

# Conflicts:
#	extensions/ohif-cornerstone-extension/package.json
#	package.json
#	src/components/Header/Header.js
#	src/components/Labelling/LabellingFlow.js
#	src/components/OHIFLogo/OHIFLogo.js
#	src/connectedComponents/ConnectedPluginSwitch.js
#	src/connectedComponents/ToolbarRow.js
#	src/utils/getDefaultToolbarButtons.js
#	src/utils/getDefaultToolbarButtons.test.js
#	yarn.lock
This commit is contained in:
dannyrb committed 2019-05-30 00:46:00 -04:00
commit ffbd6ad6e4
64 files changed
+988 -961

No files matched your search

@@ -2,15 +2,15 @@ import { connect } from 'react-redux';
import FlexboxLayout from './FlexboxLayout';
const mapStateToProps = state => {
return {
leftSidebarOpen: state.ui.leftSidebarOpen,
rightSidebarOpen: state.ui.rightSidebarOpen,
};
return {
leftSidebarOpen: state.ui.leftSidebarOpen,
rightSidebarOpen: state.ui.rightSidebarOpen,
};
};
const ConnectedFlexboxLayout = connect(
mapStateToProps,
null
mapStateToProps,
null
)(FlexboxLayout);
export default ConnectedFlexboxLayout;
+2 -2
View File
@@ -4,7 +4,7 @@ import { setUserPreferencesModalOpen } from '../redux/actions.js';
const mapStateToProps = state => {
return {
isOpen: state.ui.userPreferencesModalOpen
isOpen: state.ui.userPreferencesModalOpen,
};
};
@@ -12,7 +12,7 @@ const mapDispatchToProps = dispatch => {
return {
openUserPreferencesModal: () => {
dispatch(setUserPreferencesModalOpen(true));
}
},
};
};
@@ -1,24 +1,24 @@
import { connect } from 'react-redux'
import LabellingOverlay from './LabellingOverlay'
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
const labellingFlowData = state.ui.labelling;
return {
visible: false,
...labellingFlowData,
}
}
};
};
const ConnectedLabellingOverlay = connect(
mapStateToProps,
null
)(LabellingOverlay)
)(LabellingOverlay);
export default ConnectedLabellingOverlay
export default ConnectedLabellingOverlay;
@@ -1,41 +1,41 @@
import { connect } from 'react-redux';
import { LayoutButton } from 'react-viewerbase';
import OHIF from 'ohif-core'
import OHIF from 'ohif-core';
const { setLayout } = OHIF.redux.actions;
const mapStateToProps = state => {
return {
currentLayout: state.viewports.layout,
};
return {
currentLayout: state.viewports.layout,
};
};
const mapDispatchToProps = dispatch => {
return {
// TODO: Change if layout switched becomes more complex
onChange: selectedCell => {
let viewports = [];
const rows = selectedCell.row + 1;
const columns = selectedCell.col + 1;
const numViewports = rows * columns;
for (let i = 0; i < numViewports; i++) {
viewports.push({
height: `${100 / rows}%`,
width: `${100 / columns}%`
});
}
const layout = {
viewports
};
return {
// TODO: Change if layout switched becomes more complex
onChange: selectedCell => {
let viewports = [];
const rows = selectedCell.row + 1;
const columns = selectedCell.col + 1;
const numViewports = rows * columns;
for (let i = 0; i < numViewports; i++) {
viewports.push({
height: `${100 / rows}%`,
width: `${100 / columns}%`,
});
}
const layout = {
viewports,
};
dispatch(setLayout(layout))
}
};
dispatch(setLayout(layout));
},
};
};
const ConnectedLayoutButton = connect(
mapStateToProps,
mapDispatchToProps
mapStateToProps,
mapDispatchToProps
)(LayoutButton);
export default ConnectedLayoutButton;
@@ -27,7 +27,7 @@ const mapStateToProps = state => {
layout: state.viewports.layout,
activeViewportIndex: state.viewports.activeViewportIndex,
availablePlugins,
defaultPlugin
defaultPlugin,
};
};
@@ -1,41 +1,41 @@
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'
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
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(
@@ -43,54 +43,54 @@ 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)
eachData.displayText = displayFunction(measurement);
}
data.push(eachData)
})
})
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: [],
}
})
};
});
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]
const measurementData = measurementNumberList[0]
groupedMeasurements[groupedMeasurementsIndex];
const measurementData = measurementNumberList[0];
const {
measurementNumber,
lesionNamingNumber,
toolType,
} = measurementData
const measurementId = measurementData._id
} = measurementData;
const measurementId = measurementData._id;
//check if all measurements with same measurementNumber will have same LABEL
const tableMeasurement = {
@@ -109,30 +109,30 @@ function convertMeasurementsToTableData(toolCollections, timepoints) {
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);
});
});
// Sort measurements by lesion naming number
tableMeasurements.forEach(tm => {
tm.measurements.sort((m1, m2) =>
m1.lesionNamingNumber > m2.lesionNamingNumber ? 1 : -1
)
})
);
});
return tableMeasurements
return tableMeasurements;
}
function convertTimepointsToTableData(timepoints) {
if (!timepoints || !timepoints.length) {
return []
return [];
}
return [
@@ -140,11 +140,11 @@ function convertTimepointsToTableData(timepoints) {
label: 'Study date:',
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(
@@ -153,22 +153,22 @@ const mapStateToProps = state => {
),
timepointManager: state.timepointManager,
viewports: state.viewports,
}
}
};
};
const mapDispatchToProps = dispatch => {
return {
dispatchRelabel: (event, measurementData, viewportsState) => {
const activeViewportIndex =
(viewportsState && viewportsState.activeViewportIndex) || 0
(viewportsState && viewportsState.activeViewportIndex) || 0;
const enabledElements = cornerstone.getEnabledElements()
const enabledElements = cornerstone.getEnabledElements();
if (!enabledElements || enabledElements.length <= activeViewportIndex) {
OHIF.log.error('Failed to find the enabled element')
return
OHIF.log.error('Failed to find the enabled element');
return;
}
const { element } = enabledElements[activeViewportIndex]
const { element } = enabledElements[activeViewportIndex];
const eventData = {
event: {
@@ -176,33 +176,33 @@ const mapDispatchToProps = dispatch => {
clientY: event.clientY,
},
element,
}
};
const { toolType, measurementId } = measurementData
const { toolType, measurementId } = measurementData;
const tool = MeasurementApi.Instance.tools[toolType].find(measurement => {
return measurement._id === measurementId
})
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)
const toolForLocation = Object.assign({}, tool, { location: null });
getMeasurementLocationCallback(eventData, toolForLocation, options);
},
dispatchEditDescription: (event, measurementData, viewportsState) => {
const activeViewportIndex =
(viewportsState && viewportsState.activeViewportIndex) || 0
(viewportsState && viewportsState.activeViewportIndex) || 0;
const enabledElements = cornerstone.getEnabledElements()
const enabledElements = cornerstone.getEnabledElements();
if (!enabledElements || enabledElements.length <= activeViewportIndex) {
OHIF.log.error('Failed to find the enabled element')
return
OHIF.log.error('Failed to find the enabled element');
return;
}
const { element } = enabledElements[activeViewportIndex]
const { element } = enabledElements[activeViewportIndex];
const eventData = {
event: {
@@ -210,18 +210,18 @@ const mapDispatchToProps = dispatch => {
clientY: event.clientY,
},
element,
}
};
const { toolType, measurementId } = measurementData
const { toolType, measurementId } = measurementData;
const tool = MeasurementApi.Instance.tools[toolType].find(measurement => {
return measurement._id === measurementId
})
return measurement._id === measurementId;
});
const options = {
editDescriptionOnDialog: true,
}
};
getMeasurementLocationCallback(eventData, tool, options)
getMeasurementLocationCallback(eventData, tool, options);
},
dispatchJumpToRowItem: (
measurementData,
@@ -235,40 +235,40 @@ const mapDispatchToProps = dispatch => {
timepointManagerState,
dispatch,
options
)
);
actionData.viewportSpecificData.forEach(viewportSpecificData => {
const { viewportIndex, displaySet } = viewportSpecificData
const { viewportIndex, displaySet } = viewportSpecificData;
dispatch(setViewportSpecificData(viewportIndex, displaySet))
})
dispatch(setViewportSpecificData(viewportIndex, displaySet));
});
const { toolType, measurementNumber } = measurementData
const measurementApi = MeasurementApi.Instance
const { toolType, measurementNumber } = measurementData;
const measurementApi = MeasurementApi.Instance;
Object.keys(measurementApi.tools).forEach(toolType => {
const measurements = measurementApi.tools[toolType]
const measurements = measurementApi.tools[toolType];
measurements.forEach(measurement => {
measurement.active = false
})
})
measurement.active = false;
});
});
const measurementsToActive = measurementApi.tools[toolType].filter(
measurement => {
return measurement.measurementNumber === measurementNumber
return measurement.measurementNumber === measurementNumber;
}
)
);
measurementsToActive.forEach(measurementToActive => {
measurementToActive.active = true
})
measurementToActive.active = true;
});
measurementApi.syncMeasurementsAndToolData()
measurementApi.syncMeasurementsAndToolData();
cornerstone.getEnabledElements().forEach(enabledElement => {
cornerstone.updateImage(enabledElement.element)
})
cornerstone.updateImage(enabledElement.element);
});
// Needs to update viewports.layout state to set layout
//const layout = actionData.layout;
@@ -284,8 +284,8 @@ const mapDispatchToProps = dispatch => {
// (later): Needs to set some property on state.extensions.cornerstone to synchronize viewport scrolling
},
}
}
};
};
const mergeProps = (propsFromState, propsFromDispatch, ownProps) => {
return {
@@ -298,37 +298,37 @@ const mergeProps = (propsFromState, propsFromDispatch, ownProps) => {
// TODO: Tooltype should be on the level below? This should
// provide the entire row item?
const viewportsState = propsFromState.viewports
const timepointManagerState = propsFromState.timepointManager
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)
const viewportsState = propsFromState.viewports;
propsFromDispatch.dispatchRelabel(event, measurementData, viewportsState);
},
onEditDescriptionClick: (event, measurementData) => {
const viewportsState = propsFromState.viewports
const viewportsState = propsFromState.viewports;
propsFromDispatch.dispatchEditDescription(
event,
measurementData,
viewportsState
)
);
},
onDeleteClick: (event, measurementData) => {
const { MeasurementHandlers } = OHIF.measurements
const { MeasurementHandlers } = OHIF.measurements;
MeasurementHandlers.onRemoved({
detail: {
@@ -339,15 +339,15 @@ const mergeProps = (propsFromState, propsFromDispatch, ownProps) => {
measurementNumber: measurementData.measurementNumber,
},
},
})
});
},
}
}
};
};
const ConnectedMeasurementTable = connect(
mapStateToProps,
mapDispatchToProps,
mergeProps
)(MeasurementTable)
)(MeasurementTable);
export default ConnectedMeasurementTable
export default ConnectedMeasurementTable;
@@ -6,35 +6,35 @@ import cloneDeep from 'lodash.clonedeep';
// - Determine in which display set is active from Redux (activeViewportIndex and layout viewportData)
// - Pass in errors and stack loading progress from Redux
const mapStateToProps = (state, ownProps) => {
// If we know that the stack loading progress details have changed,
// we can try to update the component state so that the thumbnail
// progress bar is updated
const stackLoadingProgressMap = state.loading.progress;
const studiesWithLoadingData = cloneDeep(ownProps.studies);
// If we know that the stack loading progress details have changed,
// we can try to update the component state so that the thumbnail
// progress bar is updated
const stackLoadingProgressMap = state.loading.progress;
const studiesWithLoadingData = cloneDeep(ownProps.studies);
studiesWithLoadingData.forEach(study => {
study.thumbnails.forEach(data => {
const { displaySetInstanceUid } = data
const stackId = `StackProgress:${displaySetInstanceUid}`;
const stackProgressData = stackLoadingProgressMap[stackId];
studiesWithLoadingData.forEach(study => {
study.thumbnails.forEach(data => {
const { displaySetInstanceUid } = data;
const stackId = `StackProgress:${displaySetInstanceUid}`;
const stackProgressData = stackLoadingProgressMap[stackId];
let stackPercentComplete = 0;
if (stackProgressData) {
stackPercentComplete = stackProgressData.percentComplete;
}
let stackPercentComplete = 0;
if (stackProgressData) {
stackPercentComplete = stackProgressData.percentComplete;
}
data.stackPercentComplete = stackPercentComplete;
})
data.stackPercentComplete = stackPercentComplete;
});
});
return {
studies: studiesWithLoadingData
};
return {
studies: studiesWithLoadingData,
};
};
const ConnectedStudyBrowser = connect(
mapStateToProps,
null
mapStateToProps,
null
)(StudyBrowser);
export default ConnectedStudyBrowser;
@@ -1,23 +1,26 @@
import { connect } from 'react-redux';
import StudyLoadingMonitor from '../components/StudyLoadingMonitor.js';
import OHIF from 'ohif-core'
import OHIF from 'ohif-core';
const { setStudyLoadingProgress, clearStudyLoadingProgress } = OHIF.redux.actions;
const {
setStudyLoadingProgress,
clearStudyLoadingProgress,
} = OHIF.redux.actions;
const mapDispatchToProps = dispatch => {
return {
setStudyLoadingProgress: (progressId, progressData) => {
dispatch(setStudyLoadingProgress(progressId, progressData))
},
clearStudyLoadingProgress: progressId => {
dispatch(clearStudyLoadingProgress(progressId))
}
};
return {
setStudyLoadingProgress: (progressId, progressData) => {
dispatch(setStudyLoadingProgress(progressId, progressData));
},
clearStudyLoadingProgress: progressId => {
dispatch(clearStudyLoadingProgress(progressId));
},
};
};
const ConnectedStudyLoadingMonitor = connect(
null,
mapDispatchToProps
null,
mapDispatchToProps
)(StudyLoadingMonitor);
export default ConnectedStudyLoadingMonitor;
@@ -1,34 +1,34 @@
import { connect } from 'react-redux'
import ToolContextMenu from './ToolContextMenu'
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
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
availableTools = state.extensions.cornerstone.availableTools;
}
return {
...toolContextMenuData,
availableTools,
}
}
};
};
const ConnectedToolContextMenu = connect(
mapStateToProps,
null
)(ToolContextMenu)
)(ToolContextMenu);
export default ConnectedToolContextMenu
export default ConnectedToolContextMenu;
+25 -21
View File
@@ -5,34 +5,38 @@ import { setLeftSidebarOpen, setRightSidebarOpen } from '../redux/actions.js';
const defaultPlugin = 'cornerstone';
const mapStateToProps = state => {
const { layout, viewportSpecificData, activeViewportIndex } = state.viewports;
const pluginInLayout = layout.viewports[activeViewportIndex] && layout.viewports[activeViewportIndex].plugin;
const pluginInViewportData = viewportSpecificData[activeViewportIndex] && viewportSpecificData[activeViewportIndex].plugin;
const pluginInActiveViewport = pluginInLayout || pluginInViewportData || defaultPlugin;
// const extensionData = state.extensions[pluginInActiveViewport];
const { layout, viewportSpecificData, activeViewportIndex } = state.viewports;
const pluginInLayout =
layout.viewports[activeViewportIndex] &&
layout.viewports[activeViewportIndex].plugin;
const pluginInViewportData =
viewportSpecificData[activeViewportIndex] &&
viewportSpecificData[activeViewportIndex].plugin;
const pluginInActiveViewport =
pluginInLayout || pluginInViewportData || defaultPlugin;
// const extensionData = state.extensions[pluginInActiveViewport];
return {
pluginId: pluginInActiveViewport,
leftSidebarOpen: state.ui.leftSidebarOpen,
rightSidebarOpen: state.ui.rightSidebarOpen
};
return {
pluginId: pluginInActiveViewport,
leftSidebarOpen: state.ui.leftSidebarOpen,
rightSidebarOpen: state.ui.rightSidebarOpen,
};
};
const mapDispatchToProps = dispatch => {
return {
setLeftSidebarOpen: state => {
dispatch(setLeftSidebarOpen(state))
},
setRightSidebarOpen: state => {
dispatch(setRightSidebarOpen(state))
}
};
return {
setLeftSidebarOpen: state => {
dispatch(setLeftSidebarOpen(state));
},
setRightSidebarOpen: state => {
dispatch(setRightSidebarOpen(state));
},
};
};
const ConnectedToolbarRow = connect(
mapStateToProps,
mapDispatchToProps
mapStateToProps,
mapDispatchToProps
)(ToolbarRow);
export default ConnectedToolbarRow;
@@ -1,29 +1,29 @@
import { connect } from 'react-redux';
import { ToolbarSection } from 'react-viewerbase';
import OHIF from 'ohif-core'
import OHIF from 'ohif-core';
const { setToolActive } = OHIF.redux.actions;
const mapStateToProps = state => {
const activeButton = state.tools.buttons.find(tool => tool.active === true);
const activeButton = state.tools.buttons.find(tool => tool.active === true);
return {
buttons: state.tools.buttons,
activeCommand: activeButton && activeButton.command
};
return {
buttons: state.tools.buttons,
activeCommand: activeButton && activeButton.command,
};
};
const mapDispatchToProps = dispatch => {
return {
setToolActive: tool => {
dispatch(setToolActive(tool.command))
}
};
return {
setToolActive: tool => {
dispatch(setToolActive(tool.command));
},
};
};
const ConnectedToolbarSection = connect(
mapStateToProps,
mapDispatchToProps
mapStateToProps,
mapDispatchToProps
)(ToolbarSection);
export default ConnectedToolbarSection;
@@ -15,7 +15,7 @@ const mapStateToProps = state => {
: {},
hotKeysData: state.preferences[contextName]
? state.preferences[contextName].hotKeysData
: {}
: {},
};
};
@@ -36,7 +36,7 @@ const mapDispatchToProps = dispatch => {
dispatch(setUserPreferences());
dispatch(setUserPreferencesModalOpen(false));
OHIF.hotkeysUtil.setHotkeys();
}
},
};
};
+1 -1
View File
@@ -11,7 +11,7 @@ const mapDispatchToProps = dispatch => {
},
onMeasurementsUpdated: measurements => {
dispatch(setMeasurements(measurements));
}
},
};
};
+15 -15
View File
@@ -1,44 +1,44 @@
import { connect } from 'react-redux'
import ViewerMain from './ViewerMain'
import OHIF from 'ohif-core'
import { connect } from 'react-redux';
import ViewerMain from './ViewerMain';
import OHIF from 'ohif-core';
const {
setViewportSpecificData,
clearViewportSpecificData,
setToolActive,
setActiveViewportSpecificData,
} = OHIF.redux.actions
} = OHIF.redux.actions;
const mapStateToProps = state => {
const { activeViewportIndex, layout, viewportSpecificData } = state.viewports
const { activeViewportIndex, layout, viewportSpecificData } = state.viewports;
return {
activeViewportIndex,
layout,
viewportSpecificData,
}
}
};
};
const mapDispatchToProps = dispatch => {
return {
setViewportSpecificData: (viewportIndex, data) => {
dispatch(setViewportSpecificData(viewportIndex, data))
dispatch(setViewportSpecificData(viewportIndex, data));
},
clearViewportSpecificData: () => {
dispatch(clearViewportSpecificData())
dispatch(clearViewportSpecificData());
},
setToolActive: tool => {
dispatch(setToolActive(tool))
dispatch(setToolActive(tool));
},
setActiveViewportSpecificData: viewport => {
dispatch(setActiveViewportSpecificData(viewport))
dispatch(setActiveViewportSpecificData(viewport));
},
}
}
};
};
const ConnectedViewerMain = connect(
mapStateToProps,
mapDispatchToProps
)(ViewerMain)
)(ViewerMain);
export default ConnectedViewerMain
export default ConnectedViewerMain;
@@ -1,19 +1,19 @@
import { connect } from 'react-redux'
import ViewerRetrieveStudyData from './ViewerRetrieveStudyData.js'
import { connect } from 'react-redux';
import ViewerRetrieveStudyData from './ViewerRetrieveStudyData.js';
const isActive = a => a.active === true
const isActive = a => a.active === true;
const mapStateToProps = state => {
const activeServer = state.servers.servers.find(isActive)
const activeServer = state.servers.servers.find(isActive);
return {
server: activeServer,
}
}
};
};
const ConnectedViewerRetrieveStudyData = connect(
mapStateToProps,
null
)(ViewerRetrieveStudyData)
)(ViewerRetrieveStudyData);
export default ConnectedViewerRetrieveStudyData
export default ConnectedViewerRetrieveStudyData;
+25 -25
View File
@@ -1,38 +1,38 @@
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'
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,
}
};
state = {
studiesForBrowser: [],
}
};
componentDidMount() {
const studiesForBrowser = this.getStudiesForBrowser()
const studiesForBrowser = this.getStudiesForBrowser();
this.setState({
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 {
@@ -41,12 +41,12 @@ class FlexboxLayout extends Component {
seriesNumber,
instanceNumber,
numImageFrames,
} = displaySet
} = displaySet;
let imageId
let imageId;
if (displaySet.images && displaySet.images.length) {
imageId = displaySet.images[0].getImageId()
imageId = displaySet.images[0].getImageId();
}
return {
@@ -56,24 +56,24 @@ class FlexboxLayout extends Component {
seriesNumber,
instanceNumber,
numImageFrames,
}
})
};
});
return {
studyInstanceUid,
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
@@ -101,8 +101,8 @@ class FlexboxLayout extends Component {
<ConnectedMeasurementTable />
</div>
</div>
)
);
}
}
export default FlexboxLayout
export default FlexboxLayout;
+8 -8
View File
@@ -1,23 +1,23 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import LabellingManager from '../components/Labelling/LabellingManager'
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 null;
}
return <LabellingManager {...this.props} />
return <LabellingManager {...this.props} />;
}
}
export default LabellingOverlay
export default LabellingOverlay;
+1 -1
View File
@@ -5,7 +5,7 @@ import './PluginSwitch.css';
class PluginSwitch extends Component {
static propTypes = {
buttons: PropTypes.array
buttons: PropTypes.array,
};
static defaultProps = {};
+72 -71
View File
@@ -1,10 +1,10 @@
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 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'
import './ToolContextMenu.css';
const toolTypes = [
'Angle',
@@ -14,87 +14,87 @@ const toolTypes = [
'EllipticalRoi',
'CircleRoi',
'RectangleRoi',
]
];
let defaultDropdownItems = [
{
actionType: 'Delete',
action: ({ nearbyToolData, eventData }) => {
const element = eventData.element
const element = eventData.element;
cornerstoneTools.removeToolState(
element,
nearbyToolData.toolType,
nearbyToolData.tool
)
cornerstone.updateImage(element)
);
cornerstone.updateImage(element);
},
},
{
actionType: 'setLabel',
action: ({ nearbyToolData, eventData }) => {
const { tool } = nearbyToolData
const { tool } = nearbyToolData;
const options = {
skipAddLabelButton: true,
editLocation: true,
}
};
getMeasurementLocationCallback(eventData, tool, options)
getMeasurementLocationCallback(eventData, tool, options);
},
},
{
actionType: 'setDescription',
action: ({ nearbyToolData, eventData }) => {
const { tool } = nearbyToolData
const { tool } = nearbyToolData;
const options = {
editDescriptionOnDialog: true,
}
};
getMeasurementLocationCallback(eventData, tool, options)
getMeasurementLocationCallback(eventData, tool, options);
},
},
]
];
function getNearbyToolData(element, coords, toolTypes) {
const nearbyTool = {}
let pointNearTool = false
const nearbyTool = {};
let pointNearTool = false;
toolTypes.forEach(toolType => {
const toolData = cornerstoneTools.getToolState(element, toolType)
const toolData = cornerstoneTools.getToolState(element, toolType);
if (!toolData) {
return
return;
}
toolData.data.forEach(function(data, index) {
// TODO: Fix this, it's ugly
let toolInterface = cornerstoneTools.getToolForElement(element, toolType)
let toolInterface = cornerstoneTools.getToolForElement(element, toolType);
if (!toolInterface) {
toolInterface = cornerstoneTools.getToolForElement(
element,
`${toolType}Tool`
)
);
}
if (!toolInterface) {
throw new Error('Tool not found.')
throw new Error('Tool not found.');
}
if (toolInterface.pointNearTool(element, data, coords)) {
pointNearTool = true
nearbyTool.tool = data
nearbyTool.index = index
nearbyTool.toolType = toolType
pointNearTool = true;
nearbyTool.tool = data;
nearbyTool.index = index;
nearbyTool.toolType = toolType;
}
})
});
if (pointNearTool) {
return false
return false;
}
})
});
return pointNearTool ? nearbyTool : undefined
return pointNearTool ? nearbyTool : undefined;
}
function getDropdownItems(eventData, isTouchEvent = false, availableTools) {
@@ -103,7 +103,7 @@ function getDropdownItems(eventData, isTouchEvent = false, availableTools) {
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 (
@@ -111,36 +111,36 @@ function getDropdownItems(eventData, isTouchEvent = false, availableTools) {
nearbyToolData &&
nearbyToolData.toolType === 'arrowAnnotate'
) {
return
return;
}
let dropdownItems = []
let dropdownItems = [];
if (nearbyToolData) {
defaultDropdownItems.forEach(function(item) {
item.params = {
eventData,
nearbyToolData,
}
};
if (item.actionType === 'Delete') {
item.text = 'Delete measurement'
item.text = 'Delete measurement';
}
if (item.actionType === 'setLabel') {
item.text = 'Relabel'
item.text = 'Relabel';
}
if (item.actionType === 'setDescription') {
item.text = `${
nearbyToolData.tool.description ? 'Edit' : 'Add'
} Description`
} Description`;
}
dropdownItems.push(item)
})
dropdownItems.push(item);
});
}
return dropdownItems
return dropdownItems;
}
class ToolContextMenu extends Component {
@@ -150,43 +150,43 @@ class ToolContextMenu extends Component {
onClose: PropTypes.func,
availableTools: PropTypes.array,
visible: PropTypes.bool.isRequired,
}
};
static defaultProps = {
visible: true,
isTouchEvent: false,
}
};
constructor(props) {
super(props)
super(props);
this.mainElement = React.createRef()
this.mainElement = React.createRef();
}
render() {
if (!this.props.eventData) {
return null
return null;
}
const { isTouchEvent, eventData, availableTools } = this.props
const { isTouchEvent, eventData, availableTools } = this.props;
const dropdownItems = getDropdownItems(
eventData,
isTouchEvent,
availableTools
)
);
// Skip if there is no dropdown item
if (!dropdownItems.length) {
return ''
return '';
}
const dropdownComponents = dropdownItems.map(item => {
const itemOnClick = event => {
item.action(item.params)
item.action(item.params);
if (this.props.onClose) {
this.props.onClose()
this.props.onClose();
}
}
};
return (
<li key={item.actionType}>
@@ -194,32 +194,32 @@ class ToolContextMenu extends Component {
<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()
this.updateElementPosition();
}
}
};
componentDidUpdate = () => {
if (this.mainElement.current) {
this.updateElementPosition()
this.updateElementPosition();
}
}
};
updateElementPosition = () => {
const {
@@ -228,25 +228,26 @@ class ToolContextMenu extends Component {
offsetHeight,
offsetWidth,
offsetLeft,
} = this.mainElement.current
} = this.mainElement.current;
const { eventData } = this.props
const { eventData } = this.props;
if (offsetTop + offsetHeight > offsetParent.offsetHeight) {
const offBoundPixels =
offsetTop + offsetHeight - offsetParent.offsetHeight
const top = eventData.currentPoints.canvas.y - offBoundPixels
offsetTop + offsetHeight - offsetParent.offsetHeight;
const top = eventData.currentPoints.canvas.y - offBoundPixels;
this.mainElement.current.style.top = `${top > 0 ? top : 0}px`
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
const offBoundPixels =
offsetLeft + offsetWidth - offsetParent.offsetWidth;
const left = eventData.currentPoints.canvas.x - offBoundPixels;
this.mainElement.current.style.left = `${left > 0 ? left : 0}px`
this.mainElement.current.style.left = `${left > 0 ? left : 0}px`;
}
}
};
}
export default ToolContextMenu
export default ToolContextMenu;
+9 -9
View File
@@ -52,7 +52,7 @@ class Viewer extends Component {
studies: PropTypes.array,
studyInstanceUids: PropTypes.array,
onTimepointsUpdated: PropTypes.func,
onMeasurementsUpdated: PropTypes.func
onMeasurementsUpdated: PropTypes.func,
};
constructor(props) {
@@ -60,8 +60,8 @@ class Viewer extends Component {
OHIF.measurements.MeasurementApi.setConfiguration({
dataExchange: {
retrieve: this.retrieveMeasurements,
store: this.storeMeasurements
}
store: this.storeMeasurements,
},
});
OHIF.measurements.TimepointApi.setConfiguration({
@@ -70,8 +70,8 @@ class Viewer extends Component {
store: this.storeTimepoints,
remove: this.removeTimepoint,
update: this.updateTimepoint,
disassociate: this.disassociateStudy
}
disassociate: this.disassociateStudy,
},
});
}
@@ -115,8 +115,8 @@ class Viewer extends Component {
patientId: filter.patientId,
earliestDate,
latestDate,
isLocked: false
}
isLocked: false,
},
]);
};
@@ -158,11 +158,11 @@ class Viewer extends Component {
const currentTimepointId = 'TimepointId';
const timepointApi = new TimepointApi(currentTimepointId, {
onTimepointsUpdated: this.onTimepointsUpdated
onTimepointsUpdated: this.onTimepointsUpdated,
});
const measurementApi = new MeasurementApi(timepointApi, {
onMeasurementsUpdated: this.onMeasurementsUpdated
onMeasurementsUpdated: this.onMeasurementsUpdated,
});
const patientId = studies[0] && studies[0].patientId;
+62 -42
View File
@@ -1,9 +1,9 @@
import { Component } from 'react'
import React from 'react'
import PropTypes from 'prop-types'
import { OHIF } from 'ohif-core'
import ConnectedLayoutManager from './ConnectedLayoutManager.js'
import './ViewerMain.css'
import { Component } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import { OHIF } from 'ohif-core';
import ConnectedLayoutManager from './ConnectedLayoutManager.js';
import './ViewerMain.css';
class ViewerMain extends Component {
static propTypes = {
@@ -12,10 +12,10 @@ class ViewerMain extends Component {
clearViewportSpecificData: PropTypes.func.isRequired,
setToolActive: PropTypes.func.isRequired,
setActiveViewportSpecificData: PropTypes.func.isRequired,
}
};
constructor(props) {
super(props)
super(props);
// Initialize hotkeys
new OHIF.HotkeysUtil('viewer', {
@@ -23,39 +23,41 @@ class ViewerMain extends Component {
clearViewportSpecificData: props.clearViewportSpecificData,
setToolActive: props.setToolActive,
setActiveViewportSpecificData: props.setActiveViewportSpecificData,
})
});
this.state = {
displaySets: [],
}
};
this.cachedViewportData = {};
}
getDisplaySets(studies) {
const displaySets = []
const displaySets = [];
studies.forEach(study => {
study.displaySets.forEach(dSet => {
if (!dSet.plugin) {
dSet.plugin = 'cornerstone'
dSet.plugin = 'cornerstone';
}
displaySets.push(dSet)
})
})
displaySets.push(dSet);
});
});
return displaySets
return displaySets;
}
findDisplaySet(studies, studyInstanceUid, displaySetInstanceUid) {
const study = studies.find(study => {
return study.studyInstanceUid === studyInstanceUid
})
return study.studyInstanceUid === studyInstanceUid;
});
if (!study) {
return
return;
}
return study.displaySets.find(displaySet => {
return displaySet.displaySetInstanceUid === displaySetInstanceUid
})
return displaySet.displaySetInstanceUid === displaySetInstanceUid;
});
}
componentDidMount() {
@@ -63,50 +65,68 @@ class ViewerMain extends Component {
//window.addEventListener('beforeunload', unloadHandlers.beforeUnload);
// Get all the display sets for the viewer studies
const displaySets = this.getDisplaySets(this.props.studies)
const displaySets = this.getDisplaySets(this.props.studies);
this.setState({
displaySets,
})
});
}
getViewportData = () => {
const viewportData = []
const { layout, viewportSpecificData } = this.props
const viewportData = [];
const { layout, viewportSpecificData } = this.props;
for (
let viewportIndex = 0;
viewportIndex < layout.viewports.length;
viewportIndex++
) {
let displaySet = viewportSpecificData[viewportIndex]
let displaySet = viewportSpecificData[viewportIndex];
// If the viewport is empty, get one available in study
if (!displaySet || !displaySet.displaySetInstanceUid) {
const { displaySets } = this.state
// Use the cached display set in viewport if the new one is empty
if (displaySet && !displaySet.displaySetInstanceUid) {
displaySet = this.cachedViewportData[viewportIndex];
}
if (
displaySet &&
displaySet.studyInstanceUid &&
displaySet.displaySetInstanceUid
) {
// Get missing fields from original display set
const originalDisplaySet = this.findDisplaySet(
this.props.studies,
displaySet.studyInstanceUid,
displaySet.displaySetInstanceUid
);
viewportData.push(Object.assign({}, originalDisplaySet, displaySet));
} else {
// If the viewport is empty, get one available in study
const { displaySets } = this.state;
displaySet = displaySets.find(
ds =>
!viewportData.some(
v => v.displaySetInstanceUid === ds.displaySetInstanceUid
)
)
);
viewportData.push(Object.assign({}, displaySet));
}
viewportData.push(displaySet)
}
return viewportData
}
this.cachedViewportData = viewportData;
return viewportData;
};
setViewportData = ({ viewportIndex, item }) => {
const displaySet = this.findDisplaySet(
this.props.studies,
item.studyInstanceUid,
item.displaySetInstanceUid
)
);
this.props.setViewportSpecificData(viewportIndex, displaySet)
}
this.props.setViewportSpecificData(viewportIndex, displaySet);
};
render() {
return (
@@ -117,15 +137,15 @@ class ViewerMain extends Component {
setViewportData={this.setViewportData}
/>
</div>
)
);
}
componentWillUnmount() {
// Clear the entire viewport specific data
const { viewportSpecificData } = this.props
const { viewportSpecificData } = this.props;
Object.keys(viewportSpecificData).forEach(viewportIndex => {
this.props.clearViewportSpecificData(viewportIndex)
})
this.props.clearViewportSpecificData(viewportIndex);
});
// Remove beforeUnload event handler...
//window.removeEventListener('beforeunload', unloadHandlers.beforeUnload);
@@ -144,4 +164,4 @@ class ViewerMain extends Component {
}
}
export default ViewerMain
export default ViewerMain;
@@ -9,12 +9,12 @@ class ViewerRetrieveStudyData extends Component {
static propTypes = {
studyInstanceUids: PropTypes.array.isRequired,
seriesInstanceUids: PropTypes.array,
server: PropTypes.object
server: PropTypes.object,
};
state = {
studies: null,
error: null
error: null,
};
componentDidMount() {
@@ -33,12 +33,12 @@ class ViewerRetrieveStudyData extends Component {
const updatedStudies = createDisplaySets(studies);
this.setState({
studies: updatedStudies
studies: updatedStudies,
});
})
.catch(error => {
this.setState({
error: true
error: true,
});
throw new Error(error);