🧹🧹🧹 Don't mind me. Just moving things around

This commit is contained in:
dannyrb committed 2019-04-15 16:44:36 -04:00
1 parent 9a0d295ee6
commit 72cdebccf4
83 files changed
+479 -38

No files matched your search

@@ -0,0 +1,16 @@
import { connect } from 'react-redux';
import FlexboxLayout from './FlexboxLayout';
const mapStateToProps = state => {
return {
leftSidebarOpen: state.ui.leftSidebarOpen,
rightSidebarOpen: state.ui.rightSidebarOpen,
};
};
const ConnectedFlexboxLayout = connect(
mapStateToProps,
null
)(FlexboxLayout);
export default ConnectedFlexboxLayout;
@@ -0,0 +1,24 @@
import { connect } from 'react-redux';
import Header from '../components/Header/Header.js';
import { setUserPreferencesModalOpen } from '../redux/actions.js';
const mapStateToProps = state => {
return {
isOpen: state.ui.userPreferencesModalOpen
};
};
const mapDispatchToProps = dispatch => {
return {
openUserPreferencesModal: () => {
dispatch(setUserPreferencesModalOpen(true));
}
};
};
const ConnectedHeader = connect(
mapStateToProps,
mapDispatchToProps
)(Header);
export default ConnectedHeader;
@@ -0,0 +1,41 @@
import { connect } from 'react-redux';
import { LayoutButton } from 'react-viewerbase';
import OHIF from 'ohif-core'
const { setLayout } = OHIF.redux.actions;
const mapStateToProps = state => {
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
};
dispatch(setLayout(layout))
}
};
};
const ConnectedLayoutButton = connect(
mapStateToProps,
mapDispatchToProps
)(LayoutButton);
export default ConnectedLayoutButton;
@@ -0,0 +1,34 @@
import { connect } from 'react-redux';
import { LayoutManager } from 'react-viewerbase';
import OHIF from 'ohif-core';
const mapStateToProps = state => {
const viewportPluginIds = state.plugins.availablePlugins.filter(plugin => plugin.type === OHIF.plugins.PLUGIN_TYPES.VIEWPORT).map(plugin => plugin.id);
const availablePlugins = {}
viewportPluginIds.forEach(id => {
const plugin = OHIF.plugins.availablePlugins.find(plugin => plugin.id === id);
if (plugin) {
availablePlugins[id] = plugin.component;
}
});
// TODO Use something like state.plugins.defaultPlugin[OHIF.plugins.PLUGIN_TYPES.VIEWPORT]
let defaultPlugin;
if (viewportPluginIds && viewportPluginIds.length) {
defaultPlugin = viewportPluginIds[0];
}
return {
layout: state.viewports.layout,
activeViewportIndex: state.viewports.activeViewportIndex,
availablePlugins,
defaultPlugin
};
};
const ConnectedLayoutManager = connect(
mapStateToProps,
null
)(LayoutManager);
export default ConnectedLayoutManager;
@@ -0,0 +1,137 @@
import { connect } from 'react-redux';
import { MeasurementTable } from 'react-viewerbase';
import OHIF from 'ohif-core';
import moment from 'moment';
function groupBy(list, props) {
return list.reduce((a, b) => {
(a[b[props]] = a[b[props]] || []).push(b);
return a;
}, {});
}
function getAllTools() {
const config = OHIF.measurements.MeasurementApi.getConfiguration();
let tools = [];
config.measurementTools.forEach(
toolGroup => (tools = tools.concat(toolGroup.childTools))
);
return tools;
}
function getMeasurementText(measurementData) {
const { location, description } = measurementData;
let text = '...';
if (location) {
text = location;
if (description) {
text += `(${description})`;
}
}
return text;
}
function getDataForEachMeasurementNumber(
measurementNumberList,
timepoints,
displayFunction
) {
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);
}
data.push(eachData);
});
});
return data;
}
function convertMeasurementsToTableData(toolCollections, timepoints) {
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;
// Group by measurementNumber so we can display then all in the same line
const groupedMeasurements = groupBy(toolMeasurements, 'measurementNumber');
Object.keys(groupedMeasurements).forEach(groupedMeasurementsIndex => {
const measurementNumberList =
groupedMeasurements[groupedMeasurementsIndex];
//check if all measurements with same measurementNumber will have same LABEL
const tableMeasurement = {
measurementId: measurementNumberList[0]._id,
label: getMeasurementText(measurementNumberList[0]),
hasWarnings: false, //TODO
warningTitle: '', //TODO
isSplitLesion: false, //TODO
warningList: [], //TODO
data: getDataForEachMeasurementNumber(
measurementNumberList,
timepoints,
displayFunction
)
};
// find the group object for the tool
const toolGroupMeasurements = tableMeasurements.find(group => {
return group.groupId === tool.toolGroup;
});
// inject the new measurement for this measurementNumer
toolGroupMeasurements.measurements.push(tableMeasurement);
});
});
return tableMeasurements;
}
function convertTimepointsToTableData(timepoints) {
if (!timepoints || !timepoints.length) {
return [];
}
return [
{
label: 'Study date:',
date: moment(timepoints[0].latestDate).format('DD-MMM-YY')
}
];
}
const mapStateToProps = state => {
const { timepoints, measurements } = state.timepointManager;
return {
timepoints: convertTimepointsToTableData(timepoints),
measurementCollection: convertMeasurementsToTableData(
measurements,
timepoints
)
};
};
const ConnectedMeasurementTable = connect(
mapStateToProps,
null
)(MeasurementTable);
export default ConnectedMeasurementTable;
@@ -0,0 +1,159 @@
import { connect } from 'react-redux';
import PluginSwitch from './PluginSwitch.js';
import OHIF from 'ohif-core';
const { setLayout } = OHIF.redux.actions;
const mapStateToProps = state => {
const { activeViewportIndex, layout } = state.viewports;
return {
activeViewportIndex,
layout
};
};
const mapDispatchToProps = dispatch => {
return {
setLayout: data => {
dispatch(setLayout(data));
}
};
};
function setSingleLayoutData(originalArray, viewportIndex, data) {
const viewports = originalArray.slice();
const layoutData = Object.assign({}, viewports[viewportIndex], data);
viewports[viewportIndex] = layoutData;
return viewports;
}
const mergeProps = (propsFromState, propsFromDispatch, ownProps) => {
const { activeViewportIndex, layout } = propsFromState;
const { setLayout } = propsFromDispatch;
// TODO: Do not display certain options if the current display set
// cannot be displayed using these view types
const Icons = 'icons.svg';
const buttons = [
{
text: 'Acquired',
type: 'command',
iconClasses: 'fa fa-bars',
active: false,
onClick: click => {
console.warn('Original Acquisition');
const layoutData = setSingleLayoutData(
layout.viewports,
activeViewportIndex,
{ plugin: 'cornerstone' }
);
setLayout({ viewports: layoutData });
}
},
{
text: 'Axial',
svgUrl: `${Icons}#cube`,
svgClasses: 'icon-rotate-120',
active: false,
onClick: click => {
console.warn('Axial');
const data = {
plugin: 'vtk',
vtk: {
mode: 'mpr',
sliceNormal: [0, 0, 1]
}
};
const layoutData = setSingleLayoutData(
layout.viewports,
activeViewportIndex,
data
);
setLayout({ viewports: layoutData });
}
},
{
text: 'Sagittal',
svgUrl: `${Icons}#cube`,
active: false,
onClick: click => {
console.warn('Sagittal');
const data = {
plugin: 'vtk',
vtk: {
mode: 'mpr',
sliceNormal: [1, 0, 0]
}
};
const layoutData = setSingleLayoutData(
layout.viewports,
activeViewportIndex,
data
);
setLayout({ viewports: layoutData });
}
},
{
text: 'Coronal',
svgUrl: `${Icons}#cube`,
svgClasses: 'fa-rotate-90 fa-flip-horizontal',
active: false,
onClick: click => {
console.warn('Coronal');
const data = {
plugin: 'vtk',
vtk: {
mode: 'mpr',
sliceNormal: [0, 1, 0]
}
};
const layoutData = setSingleLayoutData(
layout.viewports,
activeViewportIndex,
data
);
setLayout({ viewports: layoutData });
}
}
/*{
text: '3D',
svgUrl: `${Icons}#cube`,
onClick: (click) => {
console.warn('3D Perspective');
const data = {
plugin: 'vtk',
vtk: {
mode: '3d',
}
};
const layoutData = setSingleLayoutData(layout.viewports, activeViewportIndex, data);
setLayout({ viewports: layoutData });
}
}*/
];
return {
buttons
};
};
const ConnectedPluginSwitch = connect(
mapStateToProps,
mapDispatchToProps,
mergeProps
)(PluginSwitch);
export default ConnectedPluginSwitch;
@@ -0,0 +1,40 @@
import { connect } from 'react-redux';
import { StudyBrowser } from 'react-viewerbase';
import cloneDeep from 'lodash.clonedeep';
// TODO
// - 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);
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;
}
data.stackPercentComplete = stackPercentComplete;
})
});
return {
studies: studiesWithLoadingData
};
};
const ConnectedStudyBrowser = connect(
mapStateToProps,
null
)(StudyBrowser);
export default ConnectedStudyBrowser;
@@ -0,0 +1,23 @@
import { connect } from 'react-redux';
import StudyLoadingMonitor from '../components/StudyLoadingMonitor.js';
import OHIF from 'ohif-core'
const { setStudyLoadingProgress, clearStudyLoadingProgress } = OHIF.redux.actions;
const mapDispatchToProps = dispatch => {
return {
setStudyLoadingProgress: (progressId, progressData) => {
dispatch(setStudyLoadingProgress(progressId, progressData))
},
clearStudyLoadingProgress: progressId => {
dispatch(clearStudyLoadingProgress(progressId))
}
};
};
const ConnectedStudyLoadingMonitor = connect(
null,
mapDispatchToProps
)(StudyLoadingMonitor);
export default ConnectedStudyLoadingMonitor;
@@ -0,0 +1,38 @@
import { connect } from 'react-redux';
import ToolbarRow from './ToolbarRow';
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];
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))
}
};
};
const ConnectedToolbarRow = connect(
mapStateToProps,
mapDispatchToProps
)(ToolbarRow);
export default ConnectedToolbarRow;
@@ -0,0 +1,29 @@
import { connect } from 'react-redux';
import { ToolbarSection } from 'react-viewerbase';
import OHIF from 'ohif-core'
const { setToolActive } = OHIF.redux.actions;
const mapStateToProps = state => {
const activeButton = state.tools.buttons.find(tool => tool.active === true);
return {
buttons: state.tools.buttons,
activeCommand: activeButton && activeButton.command
};
};
const mapDispatchToProps = dispatch => {
return {
setToolActive: tool => {
dispatch(setToolActive(tool.command))
}
};
};
const ConnectedToolbarSection = connect(
mapStateToProps,
mapDispatchToProps
)(ToolbarSection);
export default ConnectedToolbarSection;
@@ -0,0 +1,48 @@
import { connect } from 'react-redux';
import { UserPreferencesModal } from 'react-viewerbase';
import OHIF from 'ohif-core';
import { setUserPreferencesModalOpen } from '../redux/actions.js';
import cloneDeep from 'lodash.clonedeep';
const { setUserPreferences } = OHIF.redux.actions;
const mapStateToProps = state => {
const contextName = window.store.getState().commandContext.context;
return {
isOpen: state.ui.userPreferencesModalOpen,
windowLevelData: state.preferences[contextName]
? state.preferences[contextName].windowLevelData
: {},
hotKeysData: state.preferences[contextName]
? state.preferences[contextName].hotKeysData
: {}
};
};
const mapDispatchToProps = dispatch => {
return {
onCancel: () => {
dispatch(setUserPreferencesModalOpen(false));
},
onSave: data => {
const contextName = window.store.getState().commandContext.context;
const preferences = cloneDeep(window.store.getState().preferences);
preferences[contextName] = data;
dispatch(setUserPreferences(preferences));
dispatch(setUserPreferencesModalOpen(false));
OHIF.hotkeysUtil.setHotkeys(data.hotKeysData);
},
onResetToDefaults: () => {
dispatch(setUserPreferences());
dispatch(setUserPreferencesModalOpen(false));
OHIF.hotkeysUtil.setHotkeys();
}
};
};
const ConnectedUserPreferencesModal = connect(
mapStateToProps,
mapDispatchToProps
)(UserPreferencesModal);
export default ConnectedUserPreferencesModal;
@@ -0,0 +1,23 @@
import { connect } from 'react-redux';
import Viewer from './Viewer.js';
import OHIF from 'ohif-core';
const { setTimepoints, setMeasurements } = OHIF.redux.actions;
const mapDispatchToProps = dispatch => {
return {
onTimepointsUpdated: timepoints => {
dispatch(setTimepoints(timepoints));
},
onMeasurementsUpdated: measurements => {
dispatch(setMeasurements(measurements));
}
};
};
const ConnectedViewer = connect(
null,
mapDispatchToProps
)(Viewer);
export default ConnectedViewer;
@@ -0,0 +1,19 @@
import { connect } from 'react-redux';
import ViewerRetrieveStudyData from './ViewerRetrieveStudyData.js';
const isActive = (a) => a.active === true;
const mapStateToProps = state => {
const activeServer = state.servers.servers.find(isActive);
return {
server: activeServer,
};
};
const ConnectedViewerRetrieveStudyData = connect(
mapStateToProps,
null
)(ViewerRetrieveStudyData);
export default ConnectedViewerRetrieveStudyData;
+48
View File
@@ -0,0 +1,48 @@
.FlexboxLayout {
display: flex;
flex: 1;
flex-flow: row nowrap;
align-items: stretch;
height: calc(100% - var(--toolbar-height));
width: 100%;
overflow: hidden;
}
.sidebar-menu {
height: 100%;
/* required transformation to make inner fixed elements relative to this one*/
transform: scale(1);
transition: var(--sidebar-transition);
}
.sidebar-left {
border-right: var(--ui-border-thickness) solid var(--ui-border-color);
flex: 1;
margin-left: calc(var(--left-sidebar-menu-width) * -1);
max-width: var(--left-sidebar-menu-width);
order: 1;
}
.sidebar-left.sidebar-open {
margin-left: 0;
}
.main-content {
flex: 1;
height: 100%;
order: 2;
overflow: hidden;
transition: var(--sidebar-transition);
width: 100%;
}
.sidebar-right {
flex: 1;
margin-right: calc(var(--right-sidebar-menu-width) * -1);
max-width: var(--right-sidebar-menu-width);
order: 3;
}
.sidebar-right.sidebar-open {
margin-right: 0;
}
+108
View File
@@ -0,0 +1,108 @@
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';
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();
this.setState({
studiesForBrowser
});
}
getStudiesForBrowser = () => {
const { studies } = this.props;
// TODO[react]:
// - Add sorting of display sets
// - Add useMiddleSeriesInstanceAsThumbnail
// - Add showStackLoadingProgressBar option
return studies.map(study => {
const { studyInstanceUid } = study;
const thumbnails = study.displaySets.map(displaySet => {
const {
displaySetInstanceUid,
seriesDescription,
seriesNumber,
instanceNumber,
numImageFrames
} = displaySet;
let imageId;
if (displaySet.images && displaySet.images.length) {
imageId = displaySet.images[0].getImageId();
}
return {
imageId,
displaySetInstanceUid,
seriesDescription,
seriesNumber,
instanceNumber,
numImageFrames
};
});
return {
studyInstanceUid,
thumbnails
};
});
};
render() {
let mainContentClassName = 'main-content';
if (this.props.leftSidebarOpen) {
mainContentClassName += ' sidebar-left-open';
}
if (this.props.rightSidebarOpen) {
mainContentClassName += ' sidebar-right-open';
}
// TODO[react]: Make ConnectedMeasurementTable extension with state.timepointManager
return (
<div className="FlexboxLayout">
<div
className={
this.props.leftSidebarOpen
? 'sidebar-menu sidebar-left sidebar-open'
: 'sidebar-menu sidebar-left'
}
>
<ConnectedStudyBrowser studies={this.state.studiesForBrowser} />
</div>
<div className={mainContentClassName}>
<ViewerMain studies={this.props.studies} />
</div>
<div
className={
this.props.rightSidebarOpen
? 'sidebar-menu sidebar-right sidebar-open'
: 'sidebar-menu sidebar-right'
}
>
<ConnectedMeasurementTable />
</div>
</div>
);
}
}
export default FlexboxLayout;
+3
View File
@@ -0,0 +1,3 @@
.icon-rotate-120 {
transform: rotate(120deg);
}
+22
View File
@@ -0,0 +1,22 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { ExpandableToolMenu } from 'react-viewerbase';
import './PluginSwitch.css';
class PluginSwitch extends Component {
static propTypes = {
buttons: PropTypes.array
};
static defaultProps = {};
render() {
return (
<div className="PluginSwitch">
<ExpandableToolMenu buttons={this.props.buttons} />
</div>
);
}
}
export default PluginSwitch;
+17
View File
@@ -0,0 +1,17 @@
.ToolbarRow {
border-bottom: var(--ui-border-thickness) solid var(--ui-border-color);
flex: 0 0 auto;
height: var(--toolbar-height);
padding-left: 1rem;
padding-right: 1rem;
position: relative;
transition: height 300ms ease;
width: 100%;
display: flex;
align-items: center;
}
ToolbarRow.expanded {
height: calc(var(--toolbar-height) + var(--toolbar-drawer-height));
}
+102
View File
@@ -0,0 +1,102 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import OHIF from 'ohif-core';
import { RoundedButtonGroup } from 'react-viewerbase';
import ConnectedLayoutButton from './ConnectedLayoutButton';
import ConnectedPluginSwitch from './ConnectedPluginSwitch.js';
import './ToolbarRow.css';
const Icons = 'icons.svg';
class ToolbarRow extends Component {
static propTypes = {
leftSidebarOpen: PropTypes.bool.isRequired,
rightSidebarOpen: PropTypes.bool.isRequired,
setLeftSidebarOpen: PropTypes.func,
setRightSidebarOpen: PropTypes.func,
pluginId: PropTypes.string
};
static defaultProps = {
leftSidebarOpen: false,
rightSidebarOpen: false
};
onLeftSidebarValueChanged = value => {
this.props.setLeftSidebarOpen(!!value);
};
onRightSidebarValueChanged = value => {
this.props.setRightSidebarOpen(!!value);
};
render() {
const leftSidebarToggle = [
{
value: 'studies',
svgLink: `${Icons}#icon-studies`,
svgWidth: 15,
svgHeight: 13,
bottomLabel: 'Series'
}
];
const rightSidebarToggle = [
{
value: 'measurements',
svgLink: `${Icons}#icon-measurements-lesions`,
svgWidth: 15,
svgHeight: 13,
bottomLabel: 'Measurements'
}
];
const leftSidebarValue = this.props.leftSidebarOpen
? leftSidebarToggle[0].value
: null;
const rightSidebarValue = this.props.rightSidebarOpen
? rightSidebarToggle[0].value
: null;
const currentPluginId = this.props.pluginId;
const { PLUGIN_TYPES, availablePlugins } = OHIF.plugins;
const plugin = availablePlugins.find(entry => {
return (
entry.type === PLUGIN_TYPES.TOOLBAR && entry.id === currentPluginId
);
});
let pluginComp;
if (plugin) {
const PluginComponent = plugin.component;
pluginComp = <PluginComponent />;
}
return (
<div className="ToolbarRow">
<div className="pull-left m-t-1 p-y-1" style={{ padding: '10px' }}>
<RoundedButtonGroup
options={leftSidebarToggle}
value={leftSidebarValue}
onValueChanged={this.onLeftSidebarValueChanged}
/>
</div>
{pluginComp}
<ConnectedLayoutButton />
<ConnectedPluginSwitch />
<div className="pull-right m-t-1 rm-x-1" style={{ marginLeft: 'auto' }}>
<RoundedButtonGroup
options={rightSidebarToggle}
value={rightSidebarValue}
onValueChanged={this.onRightSidebarValueChanged}
/>
</div>
</div>
);
}
}
export default ToolbarRow;
+10
View File
@@ -0,0 +1,10 @@
#viewer {
background-color: black;
width: 100%;
}
.loadingTextDiv {
color: var(--text-secondary-color);
font-size: 30px;
}
+193
View File
@@ -0,0 +1,193 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
//import OHIF from 'ohif-core';
//import { CineDialog } from 'react-viewerbase';
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 './Viewer.css';
/**
* Inits OHIF Hanging Protocol's onReady.
* It waits for OHIF Hanging Protocol to be ready to instantiate the ProtocolEngine
* Hanging Protocol will use OHIF LayoutManager to render viewports properly
*/
/*const initHangingProtocol = () => {
// When Hanging Protocol is ready
HP.ProtocolStore.onReady(() => {
// Gets all StudyMetadata objects: necessary for Hanging Protocol to access study metadata
const studyMetadataList = OHIF.viewer.StudyMetadataList.all();
// Instantiate StudyMetadataSource: necessary for Hanging Protocol to get study metadata
const studyMetadataSource = new OHIF.studies.classes.OHIFStudyMetadataSource();
// Get prior studies map
const studyPriorsMap = OHIF.studylist.functions.getStudyPriorsMap(studyMetadataList);
// Creates Protocol Engine object with required arguments
const ProtocolEngine = new HP.ProtocolEngine(layoutManager, studyMetadataList, studyPriorsMap, studyMetadataSource);
// Sets up Hanging Protocol engine
HP.setEngine(ProtocolEngine);
});
};*/
/*const viewportUtils = OHIF.viewerbase.viewportUtils;
OHIF.viewer.functionList = {
toggleCineDialog: viewportUtils.toggleCineDialog,
toggleCinePlay: viewportUtils.toggleCinePlay,
clearTools: viewportUtils.clearTools,
resetViewport: viewportUtils.resetViewport,
invert: viewportUtils.invert
};*/
class Viewer extends Component {
static propTypes = {
studies: PropTypes.array,
studyInstanceUids: PropTypes.array,
onTimepointsUpdated: PropTypes.func,
onMeasurementsUpdated: PropTypes.func
};
constructor(props) {
super(props);
OHIF.measurements.MeasurementApi.setConfiguration({
dataExchange: {
retrieve: this.retrieveMeasurements,
store: this.storeMeasurements
}
});
OHIF.measurements.TimepointApi.setConfiguration({
dataExchange: {
retrieve: this.retrieveTimepoints,
store: this.storeTimepoints,
remove: this.removeTimepoint,
update: this.updateTimepoint,
disassociate: this.disassociateStudy
}
});
}
retrieveMeasurements = (patientId, timepointIds) => {
OHIF.log.info('retrieveMeasurements');
// TODO: Retrieve the measurements from the latest available SR
return Promise.resolve();
};
storeMeasurements = (measurementData, timepointIds) => {
OHIF.log.info('storeMeasurements');
// TODO: Store the measurements into a new SR sent to the active server
return Promise.resolve();
};
retrieveTimepoints = filter => {
OHIF.log.info('retrieveTimepoints');
// Get the earliest and latest study date
let earliestDate = new Date().toISOString();
let latestDate = new Date().toISOString();
if (this.props.studies) {
latestDate = new Date('1000-01-01').toISOString();
this.props.studies.forEach(study => {
const studyDate = moment(study.studyDate, 'YYYYMMDD').toISOString();
if (studyDate < earliestDate) {
earliestDate = studyDate;
}
if (studyDate > latestDate) {
latestDate = studyDate;
}
});
}
// Return a generic timepoint
return Promise.resolve([
{
timepointType: 'baseline',
timepointId: 'TimepointId',
studyInstanceUids: this.props.studyInstanceUids,
patientId: filter.patientId,
earliestDate,
latestDate,
isLocked: false
}
]);
};
storeTimepoints = timepointData => {
OHIF.log.info('storeTimepoints');
return Promise.resolve();
};
updateTimepoint = (timepointData, query) => {
OHIF.log.info('updateTimepoint');
return Promise.resolve();
};
removeTimepoint = timepointId => {
OHIF.log.info('removeTimepoint');
return Promise.resolve();
};
disassociateStudy = (timepointIds, studyInstanceUid) => {
OHIF.log.info('disassociateStudy');
return Promise.resolve();
};
onTimepointsUpdated = timepoints => {
if (this.props.onTimepointsUpdated) {
this.props.onTimepointsUpdated(timepoints);
}
};
onMeasurementsUpdated = measurements => {
if (this.props.onMeasurementsUpdated) {
this.props.onMeasurementsUpdated(measurements);
}
};
componentDidMount() {
const { studies } = this.props;
const { TimepointApi, MeasurementApi } = OHIF.measurements;
const currentTimepointId = 'TimepointId';
const timepointApi = new TimepointApi(currentTimepointId, {
onTimepointsUpdated: this.onTimepointsUpdated
});
const measurementApi = new MeasurementApi(timepointApi, {
onMeasurementsUpdated: this.onMeasurementsUpdated
});
const patientId = studies[0] && studies[0].patientId;
timepointApi.retrieveTimepoints({ patientId });
measurementApi.retrieveMeasurements(patientId, [currentTimepointId]);
}
render() {
return (
<>
<WhiteLabellingContext.Consumer>
{whiteLabelling => (
<ConnectedHeader home={false}>
{whiteLabelling.logoComponent}
</ConnectedHeader>
)}
</WhiteLabellingContext.Consumer>
<div id="viewer" className="Viewer">
<ConnectedToolbarRow />
{/*<ConnectedStudyLoadingMonitor studies={this.props.studies} />*/}
{/*<StudyPrefetcher studies={this.props.studies} />*/}
<ConnectedFlexboxLayout studies={this.props.studies} />
</div>
</>
);
}
}
export default Viewer;
+41
View File
@@ -0,0 +1,41 @@
#viewer {
height: calc(100% - var(--top-bar-height));
}
#viewer > .loadingTextDiv {
color: var(--text-secondary-color);
font-size: 30px;
height: 100%;
line-height: calc(100% - var(--top-bar-height));
}
.ViewerMain {
width: 100%;
height: 100%;
transition: all 0.3s ease;
}
.ViewerMain>div {
width: 100%;
height: 100%;
}
.ViewerMain #imageViewerViewports .viewportContainer {
/*theme('border', '%s solid $uiBorderColorDark' % $viewportBorderThickness)*/
/*float: left;
position: relative;*/
outline: 0; /* Prevent blue outline in Chrome */
}
.ViewerMain #imageViewerViewports .viewportContainer:hover,
.ViewerMain #imageViewerViewports .viewportContainer:active,
.ViewerMain #imageViewerViewports .viewportContainer:hover.active {
outline: 0; /* Prevent blue outline in Chrome */
}
/* &:hover
//theme('border', '%s solid $uiBorderColor' % $viewportBorderThickness)
&.active, &:hover.active
//theme('border', '%s solid $uiBorderColorActive' % $viewportBorderThickness)
*/
+110
View File
@@ -0,0 +1,110 @@
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 {
state = {
viewportData: []
};
static propTypes = {
studies: PropTypes.array.isRequired
};
constructor(props) {
super(props);
OHIF.hotkeysUtil.setup('viewer');
}
getDisplaySets(studies) {
const displaySets = [];
studies.forEach(study => {
study.displaySets.forEach(dSet => {
displaySets.push(dSet);
});
});
return displaySets;
}
findDisplaySet(studies, studyInstanceUid, displaySetInstanceUid) {
const study = studies.find(study => {
return study.studyInstanceUid === studyInstanceUid;
});
if (!study) {
return;
}
return study.displaySets.find(displaySet => {
return displaySet.displaySetInstanceUid === displaySetInstanceUid;
});
}
componentDidMount() {
// Add beforeUnload event handler to check for unsaved changes
//window.addEventListener('beforeunload', unloadHandlers.beforeUnload);
// Get all the display sets for the viewer studies
const displaySets = this.getDisplaySets(this.props.studies);
this.setState({
viewportData: displaySets
});
}
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
});
};
render() {
return (
<div className="ViewerMain">
<ConnectedLayoutManager
studies={this.props.studies}
viewportData={this.state.viewportData}
setViewportData={this.setViewportData}
/>
</div>
);
}
componentWillUnmount() {
// Remove beforeUnload event handler...
//window.removeEventListener('beforeunload', unloadHandlers.beforeUnload);
// Destroy the synchronizer used to update reference lines
//OHIF.viewer.updateImageSynchronizer.destroy();
// TODO: Instruct all plugins to clean up themselves
//
// Clear references to all stacks in the StackManager
//StackManager.clearStacks();
// @TypeSafeStudies
// Clears OHIF.viewer.Studies collection
//OHIF.viewer.Studies.removeAll();
// @TypeSafeStudies
// Clears OHIF.viewer.StudyMetadataList collection
//OHIF.viewer.StudyMetadataList.removeAll();
}
}
export default ViewerMain;
@@ -0,0 +1,64 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import OHIF from 'ohif-core';
import ConnectedViewer from './ConnectedViewer.js';
const { createDisplaySets } = OHIF.utils;
class ViewerRetrieveStudyData extends Component {
static propTypes = {
studyInstanceUids: PropTypes.array.isRequired,
seriesInstanceUids: PropTypes.array,
server: PropTypes.object
};
state = {
studies: null,
error: null
};
componentDidMount() {
// TODO: Avoid using timepoints here
//const params = { studyInstanceUids, seriesInstanceUids, timepointId, timepointsFilter={} };
const { studyInstanceUids, seriesInstanceUids, server } = this.props;
const promise = OHIF.studies.retrieveStudiesMetadata(
server,
studyInstanceUids,
seriesInstanceUids
);
// Render the viewer when the data is ready
promise
.then(studies => {
const updatedStudies = createDisplaySets(studies);
this.setState({
studies: updatedStudies
});
})
.catch(error => {
this.setState({
error: true
});
throw new Error(error);
});
}
render() {
if (this.state.error) {
return <div>Error: {JSON.stringify(this.state.error)}</div>;
} else if (!this.state.studies) {
return <div>Loading...</div>;
}
return (
<ConnectedViewer
studies={this.state.studies}
studyInstanceUids={this.props.studyInstanceUids}
/>
);
}
}
export default ViewerRetrieveStudyData;