Merge pull request #1814 from dannyrb/dannyrb/ohif-154/restore-command-functionality-for-toolbar

[OHIF-154] Dannyrb/ohif 154/restore command functionality for toolbar
This commit is contained in:
Danny Brown 2020-06-26 14:27:25 -04:00 committed by GitHub
commit a59722a182
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 276 additions and 117 deletions

View File

@ -11,12 +11,19 @@ const { studyMetadataManager } = OHIF.utils;
const { setViewportSpecificData } = OHIF.redux.actions;
const commandsModule = ({ servicesManager }) => {
const { ViewportGridService } = servicesManager.services;
function _getActiveViewportsEnabledElement() {
const { activeViewportIndex } = ViewportGridService.getState();
return getEnabledElement(activeViewportIndex);
}
const actions = {
getCornerstoneLibraries: () => {
return { cornerstone, cornerstoneTools };
},
rotateViewport: ({ viewports, rotation }) => {
const enabledElement = getEnabledElement(viewports.activeViewportIndex);
rotateViewport: ({ rotation }) => {
const enabledElement = _getActiveViewportsEnabledElement();
if (enabledElement) {
let viewport = cornerstone.getViewport(enabledElement);
@ -25,8 +32,7 @@ const commandsModule = ({ servicesManager }) => {
}
},
flipViewportHorizontal: () => {
const activeViewportIndex = 0;
const enabledElement = getEnabledElement(activeViewportIndex);
const enabledElement = _getActiveViewportsEnabledElement();
if (enabledElement) {
let viewport = cornerstone.getViewport(enabledElement);
@ -34,8 +40,8 @@ const commandsModule = ({ servicesManager }) => {
cornerstone.setViewport(enabledElement, viewport);
}
},
flipViewportVertical: ({ viewports }) => {
const enabledElement = getEnabledElement(viewports.activeViewportIndex);
flipViewportVertical: () => {
const enabledElement = _getActiveViewportsEnabledElement();
if (enabledElement) {
let viewport = cornerstone.getViewport(enabledElement);
@ -43,8 +49,8 @@ const commandsModule = ({ servicesManager }) => {
cornerstone.setViewport(enabledElement, viewport);
}
},
scaleViewport: ({ direction, viewports }) => {
const enabledElement = getEnabledElement(viewports.activeViewportIndex);
scaleViewport: ({ direction }) => {
const enabledElement = _getActiveViewportsEnabledElement();
const step = direction * 0.15;
if (enabledElement) {
@ -58,16 +64,14 @@ const commandsModule = ({ servicesManager }) => {
}
},
resetViewport: () => {
const activeViewportIndex = 0;
const enabledElement = getEnabledElement(activeViewportIndex);
const enabledElement = _getActiveViewportsEnabledElement();
if (enabledElement) {
cornerstone.reset(enabledElement);
}
},
invertViewport: () => {
const activeViewportIndex = 0;
const enabledElement = getEnabledElement(activeViewportIndex);
const enabledElement = _getActiveViewportsEnabledElement();
if (enabledElement) {
let viewport = cornerstone.getViewport(enabledElement);
@ -83,8 +87,8 @@ const commandsModule = ({ servicesManager }) => {
}
cornerstoneTools.setToolActive(toolName, { mouseButtonMask: 1 });
},
clearAnnotations: ({ viewports }) => {
const element = getEnabledElement(viewports.activeViewportIndex);
clearAnnotations: () => {
const element = _getActiveViewportsEnabledElement();
if (!element) {
return;
}
@ -139,16 +143,16 @@ const commandsModule = ({ servicesManager }) => {
});
});
},
nextImage: ({ viewports }) => {
const enabledElement = getEnabledElement(viewports.activeViewportIndex);
nextImage: () => {
const enabledElement = _getActiveViewportsEnabledElement();
scroll(enabledElement, 1);
},
previousImage: ({ viewports }) => {
const enabledElement = getEnabledElement(viewports.activeViewportIndex);
previousImage: () => {
const enabledElement = _getActiveViewportsEnabledElement();
scroll(enabledElement, -1);
},
getActiveViewportEnabledElement: ({ viewports }) => {
const enabledElement = getEnabledElement(viewports.activeViewportIndex);
getActiveViewportEnabledElement: () => {
const enabledElement = _getActiveViewportsEnabledElement();
return enabledElement;
},
showDownloadViewportModal: () => {
@ -251,8 +255,8 @@ const commandsModule = ({ servicesManager }) => {
setCornerstoneLayout: () => {
setCornerstoneLayout();
},
setWindowLevel: ({ viewports, window, level }) => {
const enabledElement = getEnabledElement(viewports.activeViewportIndex);
setWindowLevel: ({ window, level }) => {
const enabledElement = _getActiveViewportsEnabledElement();
if (enabledElement) {
let viewport = cornerstone.getViewport(enabledElement);
@ -326,7 +330,7 @@ const commandsModule = ({ servicesManager }) => {
},
getActiveViewportEnabledElement: {
commandFn: actions.getActiveViewportEnabledElement,
storeContexts: ['viewports'],
storeContexts: [],
options: {},
},
rotateViewportCW: {
@ -336,7 +340,7 @@ const commandsModule = ({ servicesManager }) => {
},
rotateViewportCCW: {
commandFn: actions.rotateViewport,
storeContexts: ['viewports'],
storeContexts: [],
options: { rotation: -90 },
},
invertViewport: {
@ -346,7 +350,7 @@ const commandsModule = ({ servicesManager }) => {
},
flipViewportVertical: {
commandFn: actions.flipViewportVertical,
storeContexts: ['viewports'],
storeContexts: [],
options: {},
},
flipViewportHorizontal: {
@ -356,17 +360,17 @@ const commandsModule = ({ servicesManager }) => {
},
scaleUpViewport: {
commandFn: actions.scaleViewport,
storeContexts: ['viewports'],
storeContexts: [],
options: { direction: 1 },
},
scaleDownViewport: {
commandFn: actions.scaleViewport,
storeContexts: ['viewports'],
storeContexts: [],
options: { direction: -1 },
},
fitViewportToWindow: {
commandFn: actions.scaleViewport,
storeContexts: ['viewports'],
storeContexts: [],
options: { direction: 0 },
},
resetViewport: {
@ -376,17 +380,17 @@ const commandsModule = ({ servicesManager }) => {
},
clearAnnotations: {
commandFn: actions.clearAnnotations,
storeContexts: ['viewports'],
storeContexts: [],
options: {},
},
nextImage: {
commandFn: actions.nextImage,
storeContexts: ['viewports'],
storeContexts: [],
options: {},
},
previousImage: {
commandFn: actions.previousImage,
storeContexts: ['viewports'],
storeContexts: [],
options: {},
},
// TOOLS
@ -408,7 +412,7 @@ const commandsModule = ({ servicesManager }) => {
},
setWindowLevel: {
commandFn: actions.setWindowLevel,
storeContexts: ['viewports'],
storeContexts: [],
options: {},
},
};

View File

@ -6,6 +6,8 @@ import merge from 'lodash.merge';
import initCornerstoneTools from './initCornerstoneTools.js';
import initWADOImageLoader from './initWADOImageLoader.js';
import measurementServiceMappingsFactory from './utils/measurementServiceMappings/measurementServiceMappingsFactory';
//
import { setEnabledElement } from './state';
/**
*
@ -54,6 +56,22 @@ export default function init({ servicesManager, configuration }) {
};
initCornerstoneTools(defaultCsToolsConfig);
// TODO: Extensions are still registered at time of registration globally
// These should be registered as a part of mode route spin up,
// and they need to self-clean on mode route destroy
// THIS
// is a way for extensions that "depend" on this extension to notify it of
// new cornerstone enabled elements so it's commands continue to work.
const handleOhifCornerstoneEnabledElementEvent = function(evt) {
const { viewportIndex, enabledElement } = evt.detail;
setEnabledElement(viewportIndex, enabledElement);
};
document.addEventListener(
'ohif-cornerstone-enabled-element-event',
handleOhifCornerstoneEnabledElementEvent
);
const toolsGroupedByType = {
touch: [csTools.PanMultiTouchTool, csTools.ZoomTouchPinchTool],

View File

@ -15,7 +15,7 @@ const DEFAULT_LAYOUT = {
function LayoutSelector() {
const [isOpen, setIsOpen] = useState(false);
const [viewportGridState, dispatch] = useViewportGrid();
const [viewportGridState, viewportGridService] = useViewportGrid();
const closeOnOutsideClick = () => {
if (isOpen) {
@ -51,13 +51,7 @@ function LayoutSelector() {
DropdownContent !== null && (
<DropdownContent
onSelection={({ numRows, numCols }) => {
dispatch({
type: 'SET_LAYOUT',
payload: {
numCols,
numRows,
},
});
viewportGridService.setLayout({ numCols, numRows });
}}
/>
)

View File

@ -25,7 +25,7 @@ function OHIFCornerstoneSRViewport({
viewportIndex,
DisplaySetService,
}) {
const [viewportGrid, dispatchViewportGrid] = useViewportGrid();
const [viewportGrid, viewportGridService] = useViewportGrid();
const [measurementSelected, setMeasurementSelected] = useState(0);
const [measurementCount, setMeasurementCount] = useState(1);
const [viewportData, setViewportData] = useState(null);

View File

@ -19,10 +19,7 @@ function PanelStudyBrowserTracking({
// doesn't have to have such an intense shape. This works well enough for now.
// Tabs --> Studies --> DisplaySets --> Thumbnails
const [{ StudyInstanceUIDs }, dispatchImageViewer] = useImageViewer();
const [
{ activeViewportIndex, viewports },
dispatchViewportGrid,
] = useViewportGrid();
const [{ activeViewportIndex, viewports }] = useViewportGrid();
const [
trackedMeasurements,
sendTrackedMeasurementsEvent,

View File

@ -38,10 +38,7 @@ function TrackedCornerstoneViewport({
viewportIndex,
}) {
const [trackedMeasurements] = useTrackedMeasurements();
const [
{ activeViewportIndex, viewports },
] = useViewportGrid();
const [{ activeViewportIndex, viewports }] = useViewportGrid();
// viewportIndex, onSubmit
const [viewportDialogState, viewportDialogApi] = useViewportDialog();
const [viewportData, setViewportData] = useState(null);
@ -122,6 +119,18 @@ function TrackedCornerstoneViewport({
}
setElement(targetElement);
const OHIFCornerstoneEnabledElementEvent = new CustomEvent(
'ohif-cornerstone-enabled-element-event',
{
detail: {
enabledElement: targetElement,
viewportIndex,
},
}
);
document.dispatchEvent(OHIFCornerstoneEnabledElementEvent);
};
useEffect(() => {

View File

@ -30,8 +30,9 @@ import {
//
DicomMetadataStore,
DisplaySetService,
ToolBarSerivce,
ToolBarSerivce, // TODO: Typo
MeasurementService,
ViewportGridService,
} from './services';
import IWebApiDataSource from './DataSources/IWebApiDataSource';
@ -70,7 +71,8 @@ const OHIF = {
UIViewportDialogService,
DisplaySetService,
MeasurementService,
ToolBarSerivce,
ToolBarSerivce, // TODO: TYPO
ViewportGridService,
IWebApiDataSource,
DicomMetadataStore,
//
@ -112,6 +114,7 @@ export {
DisplaySetService,
MeasurementService,
ToolBarSerivce,
ViewportGridService,
IWebApiDataSource,
DicomMetadataStore,
ViewModelProvider,

View File

@ -0,0 +1,63 @@
const name = 'ViewportGridService';
const publicAPI = {
name,
getState: _getState,
setActiveViewportIndex: _setActiveViewportIndex,
setDisplaysetForViewport: _setDisplaysetForViewport,
setLayout: _setLayout,
setServiceImplementation,
};
const serviceImplementation = {
_getState: () => console.warn('getState() NOT IMPLEMENTED'),
_setActiveViewportIndex: () => console.warn('setActiveViewportIndex() NOT IMPLEMENTED'),
_setDisplaysetForViewport: () => console.warn('setDisplaysetForViewport() NOT IMPLEMENTED'),
_setLayout: () => console.warn('setLayout() NOT IMPLEMENTED'),
};
function _getState() {
return serviceImplementation._getState();
}
function _setActiveViewportIndex(index) {
return serviceImplementation._setActiveViewportIndex(index);
}
function _setDisplaysetForViewport({ viewportIndex, displaySetInstanceUID }) {
return serviceImplementation._setDisplaysetForViewport({
viewportIndex,
displaySetInstanceUID,
});
}
function _setLayout({ numCols, numRows }) {
return serviceImplementation._setLayout({ numCols, numRows });
}
function setServiceImplementation({
getState: getStateImplementation,
setActiveViewportIndex: setActiveViewportIndexImplementation,
setDisplaysetForViewport: setDisplaysetForViewportImplementation,
setLayout: setLayoutImplementation,
}) {
if (getStateImplementation) {
serviceImplementation._getState = getStateImplementation;
}
if (setActiveViewportIndexImplementation) {
serviceImplementation._setActiveViewportIndex = setActiveViewportIndexImplementation;
}
if (setDisplaysetForViewportImplementation) {
serviceImplementation._setDisplaysetForViewport = setDisplaysetForViewportImplementation;
}
if (setLayoutImplementation) {
serviceImplementation._setLayout = setLayoutImplementation;
}
}
export default {
name,
create: ({ configuration = {} }) => {
return publicAPI;
},
};

View File

@ -0,0 +1,3 @@
import ViewportGridService from './ViewportGridService';
export default ViewportGridService;

View File

@ -7,6 +7,7 @@ import UIViewportDialogService from './UIViewportDialogService';
import DicomMetadataStore from './DicomMetadataStore';
import DisplaySetService from './DisplaySetService';
import ToolBarSerivce from './ToolBarService';
import ViewportGridService from './ViewportGridService';
export {
MeasurementService,
@ -18,4 +19,5 @@ export {
DicomMetadataStore,
DisplaySetService,
ToolBarSerivce,
ViewportGridService,
};

View File

@ -1,20 +1,133 @@
import React, { createContext, useContext, useReducer } from 'react';
import React, {
createContext,
useCallback,
useContext,
useEffect,
useReducer,
} from 'react';
import PropTypes from 'prop-types';
// export const VIEWPORT_GRID_DEFAULT_VALUE = {
// numCols: 1,
// numRows: 1,
// activeViewportIndex: 0,
// viewports: [],
// };
const DEFAULT_STATE = {
numRows: 1,
numCols: 1,
viewports: [],
activeViewportIndex: 0,
};
export const ViewportGridContext = createContext();
export const ViewportGridContext = createContext(DEFAULT_STATE);
export function ViewportGridProvider({ children, service }) {
const viewportGridReducer = (state, action) => {
switch (action.type) {
case 'SET_ACTIVE_VIEWPORT_INDEX':
return { ...state, ...{ activeViewportIndex: action.payload } };
case 'SET_DISPLAYSET_FOR_VIEWPORT': {
const { viewportIndex, displaySetInstanceUID } = action.payload;
const viewports = state.viewports.slice();
viewports[viewportIndex] = { displaySetInstanceUID };
return { ...state, ...{ viewports } };
}
case 'SET_LAYOUT': {
const { numCols, numRows } = action.payload;
const numPanes = numCols * numRows;
const viewports = state.viewports.slice();
const activeViewportIndex =
state.activeViewportIndex >= numPanes ? 0 : state.activeViewportIndex;
while (viewports.length < numPanes) {
viewports.push({});
}
while (viewports.length > numPanes) {
viewports.pop();
}
return {
...state,
...{ activeViewportIndex, numCols, numRows, viewports },
};
}
default:
return action.payload;
}
};
const [viewportGridState, dispatch] = useReducer(
viewportGridReducer,
DEFAULT_STATE,
);
const getState = useCallback(() => viewportGridState, [viewportGridState]);
const setActiveViewportIndex = useCallback(
index => dispatch({ type: 'SET_ACTIVE_VIEWPORT_INDEX', payload: index }),
[dispatch]
);
const setDisplaysetForViewport = useCallback(
({ viewportIndex, displaySetInstanceUID }) =>
dispatch({
type: 'SET_DISPLAYSET_FOR_VIEWPORT',
payload: {
viewportIndex,
displaySetInstanceUID,
},
}),
[dispatch]
);
const setLayout = useCallback(
({ numCols, numRows }) =>
dispatch({
type: 'SET_LAYOUT',
payload: {
numCols,
numRows,
},
}),
[dispatch]
);
/**
* Sets the implementation of a modal service that can be used by extensions.
*
* @returns void
*/
useEffect(() => {
if (service) {
service.setServiceImplementation({
getState,
setActiveViewportIndex,
setDisplaysetForViewport,
setLayout,
});
}
}, [
getState,
service,
setActiveViewportIndex,
setDisplaysetForViewport,
setLayout,
]);
const api = {
// getState,
setActiveViewportIndex,
setDisplaysetForViewport,
setLayout,
};
export function ViewportGridProvider({ reducer, initialState, children }) {
return (
<ViewportGridContext.Provider value={useReducer(reducer, initialState)}>
<ViewportGridContext.Provider value={[viewportGridState, api]}>
{children}
</ViewportGridContext.Provider>
);
}
ViewportGridProvider.propTypes = {
children: PropTypes.any,
service: PropTypes.shape({
setServiceImplementation: PropTypes.func,
}).isRequired,
};
export const useViewportGrid = () => useContext(ViewportGridContext);

View File

@ -55,61 +55,15 @@ function App({ config, defaultExtensions }) {
UIModalService,
UINotificationService,
UIViewportDialogService,
ViewportGridService, // TODO: Should this be a "UI" Service?
} = servicesManager.services;
// A UI Service may need to use the ViewportGrid context
const viewportGridReducer = (state, action) => {
console.log(state, action);
switch (action.type) {
case 'SET_ACTIVE_VIEWPORT_INDEX':
return { ...state, ...{ activeViewportIndex: action.payload } };
case 'SET_DISPLAYSET_FOR_VIEWPORT': {
const { viewportIndex, displaySetInstanceUID } = action.payload;
const viewports = state.viewports.slice();
viewports[viewportIndex] = { displaySetInstanceUID };
return { ...state, ...{ viewports } };
}
case 'SET_LAYOUT': {
const { numCols, numRows } = action.payload;
const numPanes = numCols * numRows;
const viewports = state.viewports.slice();
const activeViewportIndex =
state.activeViewportIndex >= numPanes ? 0 : state.activeViewportIndex;
while (viewports.length < numPanes) {
viewports.push({});
}
while (viewports.length > numPanes) {
viewports.pop();
}
return {
...state,
...{ activeViewportIndex, numCols, numRows, viewports },
};
}
default:
return action.payload;
}
};
return (
<AppConfigProvider value={appConfigState}>
<I18nextProvider i18n={i18n}>
<Router basename={routerBasename}>
<ThemeWrapper>
<ViewportGridProvider
initialState={{
numRows: 1,
numCols: 1,
viewports: [],
activeViewportIndex: 0,
}}
reducer={viewportGridReducer}
>
<ViewportGridProvider service={ViewportGridService}>
<ViewportDialogProvider service={UIViewportDialogService}>
<SnackbarProvider service={UINotificationService}>
<DialogProvider service={UIDialogService}>

View File

@ -10,6 +10,7 @@ import {
MeasurementService,
DisplaySetService,
ToolBarSerivce,
ViewportGridService,
// utils,
// redux as reduxOHIF,
} from '@ohif/core';
@ -51,6 +52,7 @@ function appInit(appConfigOrFunc, defaultExtensions) {
MeasurementService,
DisplaySetService,
ToolBarSerivce,
ViewportGridService,
]);
/**

View File

@ -12,11 +12,11 @@ function ViewerViewportGrid(props) {
const { servicesManager, viewportComponents, dataSource } = props;
const [
{ numCols, numRows, activeViewportIndex, viewports },
dispatch,
viewportGridService,
] = useViewportGrid();
const setActiveViewportIndex = index => {
dispatch({ type: 'SET_ACTIVE_VIEWPORT_INDEX', payload: index });
viewportGridService.setActiveViewportIndex(index);
};
// TODO -> Need some way of selecting which displaySets hit the viewports.
@ -96,12 +96,9 @@ function ViewerViewportGrid(props) {
// ]);
const onDropHandler = (viewportIndex, { displaySetInstanceUID }) => {
console.warn(`DROPPED: ${displaySetInstanceUID}`);
dispatch({
type: 'SET_DISPLAYSET_FOR_VIEWPORT',
payload: {
viewportIndex,
displaySetInstanceUID,
},
viewportGridService.setDisplaysetForViewport({
viewportIndex,
displaySetInstanceUID,
});
};