From ae0858bdd31132448cb0218c7a7dab9a7606aa4f Mon Sep 17 00:00:00 2001 From: dannyrb Date: Fri, 31 May 2019 11:55:44 -0400 Subject: [PATCH 01/26] Shift store init to own directory --- src/App.js | 20 +++------------ src/connectedComponents/ConnectedHeader.js | 4 +-- .../ConnectedToolbarRow.js | 8 ++++-- .../ConnectedUserPreferencesModal.js | 6 ++--- src/store/index.js | 25 +++++++++++++++++++ src/{redux => store/layout}/actions.js | 0 src/{redux/ui.js => store/layout/reducers.js} | 6 +++-- 7 files changed, 43 insertions(+), 26 deletions(-) create mode 100644 src/store/index.js rename src/{redux => store/layout}/actions.js (100%) rename src/{redux/ui.js => store/layout/reducers.js} (92%) diff --git a/src/App.js b/src/App.js index 2d73bb449..6215a8a99 100644 --- a/src/App.js +++ b/src/App.js @@ -1,8 +1,6 @@ import './config'; -import { OidcProvider, reducer as oidcReducer } from 'redux-oidc'; import React, { Component } from 'react'; -import { combineReducers, createStore } from 'redux'; import { getDefaultToolbarButtons, getUserManagerForOpenIdConnectClient, @@ -17,32 +15,20 @@ import OHIFDicomMicroscopyExtension from 'ohif-dicom-microscopy-extension'; import OHIFDicomPDFExtension from 'ohif-dicom-pdf-extension'; import OHIFStandaloneViewer from './OHIFStandaloneViewer'; import OHIFVTKExtension from '@ohif/extension-vtk'; +import { OidcProvider } from 'redux-oidc'; import PropTypes from 'prop-types'; import { Provider } from 'react-redux'; import { BrowserRouter as Router } from 'react-router-dom'; import WhiteLabellingContext from './WhiteLabellingContext'; import setupTools from './setupTools'; -import ui from './redux/ui.js'; +import store from './store'; const { ExtensionManager } = OHIF.extensions; -const { reducers, localStorage } = OHIF.redux; - -reducers.ui = ui; -reducers.oidc = oidcReducer; - -const combined = combineReducers(reducers); -const store = createStore(combined, localStorage.loadState()); - -store.subscribe(() => { - localStorage.saveState({ - preferences: store.getState().preferences, - }); -}); setupTools(store); const children = { - viewport: [], + viewport: [], }; /** TODO: extensions should be passed in as prop as soon as we have the extensions as separate packages and then registered by ExtensionsManager */ diff --git a/src/connectedComponents/ConnectedHeader.js b/src/connectedComponents/ConnectedHeader.js index f07576589..73644c976 100644 --- a/src/connectedComponents/ConnectedHeader.js +++ b/src/connectedComponents/ConnectedHeader.js @@ -1,6 +1,6 @@ -import { connect } from 'react-redux'; import Header from '../components/Header/Header.js'; -import { setUserPreferencesModalOpen } from '../redux/actions.js'; +import { connect } from 'react-redux'; +import { setUserPreferencesModalOpen } from '../store/layout/actions.js'; const mapStateToProps = state => { return { diff --git a/src/connectedComponents/ConnectedToolbarRow.js b/src/connectedComponents/ConnectedToolbarRow.js index 58ca7b087..b887ff9c4 100644 --- a/src/connectedComponents/ConnectedToolbarRow.js +++ b/src/connectedComponents/ConnectedToolbarRow.js @@ -1,6 +1,10 @@ -import { connect } from 'react-redux'; +import { + setLeftSidebarOpen, + setRightSidebarOpen, +} from './../store/layout/actions.js'; + import ToolbarRow from './ToolbarRow'; -import { setLeftSidebarOpen, setRightSidebarOpen } from '../redux/actions.js'; +import { connect } from 'react-redux'; const defaultPlugin = 'cornerstone'; diff --git a/src/connectedComponents/ConnectedUserPreferencesModal.js b/src/connectedComponents/ConnectedUserPreferencesModal.js index 8d55b0ccd..26ed515eb 100644 --- a/src/connectedComponents/ConnectedUserPreferencesModal.js +++ b/src/connectedComponents/ConnectedUserPreferencesModal.js @@ -1,8 +1,8 @@ -import { connect } from 'react-redux'; -import { UserPreferencesModal } from 'react-viewerbase'; import OHIF from 'ohif-core'; -import { setUserPreferencesModalOpen } from '../redux/actions.js'; +import { UserPreferencesModal } from 'react-viewerbase'; import cloneDeep from 'lodash.clonedeep'; +import { connect } from 'react-redux'; +import { setUserPreferencesModalOpen } from './../store/layout/actions.js'; const { setUserPreferences } = OHIF.redux.actions; diff --git a/src/store/index.js b/src/store/index.js new file mode 100644 index 000000000..04c81a2d6 --- /dev/null +++ b/src/store/index.js @@ -0,0 +1,25 @@ +import { combineReducers, createStore } from 'redux'; + +import layoutReducers from './layout/reducers.js'; +import { reducer as oidcReducer } from 'redux-oidc'; +import { redux } from 'ohif-core'; + +// Combine our ohif-core, ui, and oidc reducers +// Set init data, using values found in localStorage +const { reducers, localStorage } = redux; + +reducers.ui = layoutReducers; +reducers.oidc = oidcReducer; + +const combined = combineReducers(reducers); +const store = createStore(combined, localStorage.loadState()); + +// When the store's preferences change, +// Update our cached preferences in localStorage +store.subscribe(() => { + localStorage.saveState({ + preferences: store.getState().preferences, + }); +}); + +export default store; diff --git a/src/redux/actions.js b/src/store/layout/actions.js similarity index 100% rename from src/redux/actions.js rename to src/store/layout/actions.js diff --git a/src/redux/ui.js b/src/store/layout/reducers.js similarity index 92% rename from src/redux/ui.js rename to src/store/layout/reducers.js index 586d8d5e1..dde5baa45 100644 --- a/src/redux/ui.js +++ b/src/store/layout/reducers.js @@ -16,11 +16,12 @@ const ui = (state = defaultState, action) => { return Object.assign({}, state, { userPreferencesModalOpen: action.state, }); - case 'SET_LABELLING_FLOW_DATA': + case 'SET_LABELLING_FLOW_DATA': { const labelling = Object.assign({}, action.labellingFlowData); return Object.assign({}, state, { labelling }); - case 'SET_TOOL_CONTEXT_MENU_DATA': + } + case 'SET_TOOL_CONTEXT_MENU_DATA': { const contextMenu = Object.assign({}, state.contextMenu); contextMenu[action.viewportIndex] = Object.assign( @@ -29,6 +30,7 @@ const ui = (state = defaultState, action) => { ); return Object.assign({}, state, { contextMenu }); + } case 'RESET_LABELLING_AND_CONTEXT_MENU': return Object.assign({}, state, { labelling: defaultState.labelling, From d9e45261f31e869c01adddda6aefe6bb4f70300b Mon Sep 17 00:00:00 2001 From: dannyrb Date: Fri, 31 May 2019 16:25:10 -0400 Subject: [PATCH 02/26] Registering some initial commands --- src/commands/README.md | 1 + src/commands/cornerstone.js | 169 ++++++++++++++++++++++++++++++++++++ src/commands/index.js | 52 +++++++++++ src/commands/viewer.js | 36 ++++++++ 4 files changed, 258 insertions(+) create mode 100644 src/commands/README.md create mode 100644 src/commands/cornerstone.js create mode 100644 src/commands/index.js create mode 100644 src/commands/viewer.js diff --git a/src/commands/README.md b/src/commands/README.md new file mode 100644 index 000000000..61c515e7b --- /dev/null +++ b/src/commands/README.md @@ -0,0 +1 @@ +# Commands diff --git a/src/commands/cornerstone.js b/src/commands/cornerstone.js new file mode 100644 index 000000000..ab8974ea4 --- /dev/null +++ b/src/commands/cornerstone.js @@ -0,0 +1,169 @@ +import cornerstone from 'cornerstone-core'; + +const definitions = { + rotateViewportCW: { + commandFn: actions.rotateViewport, + storeContexts: ['viewports'], + options: { rotation: 90 }, + }, + rotateViewportCCW: { + commandFn: actions.rotateViewport, + storeContexts: ['viewports'], + options: { rotation: -90 }, + }, + invertViewport: { + commandFn: actions.invertViewport, + storeContexts: ['viewports'], + options: {}, + }, + flipViewportVertical: { + commandFn: actions.flipViewportVertical, + storeContexts: ['viewports'], + options: {}, + }, + flipViewportHorizontal: { + commandFn: actions.flipViewportHorizontal, + storeContexts: ['viewports'], + options: {}, + }, + scaleUpViewport: { + keys: '', + commandFn: actions.scaleViewport, + storeContexts: ['viewports'], + options: { direction: 1 }, + }, + scaleDownViewport: { + keys: '', + commandFn: actions.scaleViewport, + storeContexts: ['viewports'], + options: { direction: -1 }, + }, + fitViewportToWindow: { + commandFn: actions.scaleViewport, + storeContexts: ['viewports'], + options: { direction: 0 }, + }, + resetViewport: { + commandFn: actions.resetViewport, + storeContexts: ['viewports'], + options: {}, + }, + // TODO: Clear Annotations + // TODO: Next/Previous image + // TODO: First/Last image + // Next/Previous series/DisplaySet + nextViewportDisplaySet: { + commandFn: actions.updateViewportDisplaySet, + storeContexts: [], + options: { direction: 1 }, + }, + previousViewportDisplaySet: { + commandFn: actions.updateViewportDisplaySet, + storeContexts: [], + options: { direction: 1 }, + }, +}; + +const actions = { + rotateViewport: ({ viewports, rotation }) => { + const enabledElement = _getActiveViewportEnabledElement( + viewports.viewportSpecificData, + viewports.activeViewportIndex + ); + + if (enabledElement) { + let viewport = cornerstone.getViewport(enabledElement); + viewport.rotation += rotation; + cornerstone.setViewport(enabledElement, viewport); + } + }, + flipViewportHorizontal: ({ viewports }) => { + const enabledElement = _getActiveViewportEnabledElement( + viewports.viewportSpecificData, + viewports.activeViewportIndex + ); + + if (enabledElement) { + let viewport = cornerstone.getViewport(enabledElement); + viewport.hflip = !viewport.hflip; + cornerstone.setViewport(enabledElement, viewport); + } + }, + flipViewportVertical: ({ viewports }) => { + const enabledElement = _getActiveViewportEnabledElement( + viewports.viewportSpecificData, + viewports.activeViewportIndex + ); + + if (enabledElement) { + let viewport = cornerstone.getViewport(enabledElement); + viewport.vflip = !viewport.vflip; + cornerstone.setViewport(enabledElement, viewport); + } + }, + scaleViewport: ({ viewports, direction }) => { + const enabledElement = _getActiveViewportEnabledElement( + viewports.viewportSpecificData, + viewports.activeViewportIndex + ); + const step = direction * 0.15; + + if (enabledElement) { + if (step) { + let viewport = cornerstone.getViewport(enabledElement); + viewport.scale += step; + cornerstone.setViewport(enabledElement, viewport); + } else { + cornerstone.fitToWindow(enabledElement); + } + } + }, + resetViewport: ({ viewports }) => { + const enabledElement = _getActiveViewportEnabledElement( + viewports.viewportSpecificData, + viewports.activeViewportIndex + ); + + if (enabledElement) { + cornerstone.reset(enabledElement); + } + }, + + invertViewport: ({ viewports }) => { + const enabledElement = _getActiveViewportEnabledElement( + viewports.viewportSpecificData, + viewports.activeViewportIndex + ); + + if (enabledElement) { + let viewport = cornerstone.getViewport(enabledElement); + viewport.invert = !viewport.invert; + cornerstone.setViewport(enabledElement, viewport); + } + }, + updateViewportDisplaySet: ({ direction }) => { + // TODO + console.warn('updateDisplaySet: ', direction); + }, + clearAnnotations: () => { + console.warn('clearAnnotations: not yet implemented'); + // const toolState = + // cornerstoneTools.globalImageIdSpecificToolStateManager.toolState; + // if (!toolState) return; + // Object.keys(toolState).forEach(imageId => { + // if (!cornerstoneImageId || cornerstoneImageId === imageId) + // delete toolState[imageId]; + // }); + }, +}; + +/** + * Grabs `dom` reference for the enabledElement of + * the active viewport + */ +function _getActiveViewportEnabledElement(viewports, activeIndex) { + const activeViewport = viewports[activeIndex] || {}; + return activeViewport.dom; +} + +export default definitions; diff --git a/src/commands/index.js b/src/commands/index.js new file mode 100644 index 000000000..b577eb2b8 --- /dev/null +++ b/src/commands/index.js @@ -0,0 +1,52 @@ +import { CommandsManager } from 'ohif-core'; +import cornerstoneCommandDefinitions from 'cornerstone.js'; +import viewerCommandDefinitions from 'viewer.js'; + +const CONTEXTS = { + viewer: 'VIEWER', + cornerstone: 'VIEWER::CORNERSTONE', +}; + +/** + * Register all commands + */ +function init() { + _registerViewerCommands(); + _registerCornerstoneCommands(); +} + +/** + * Register all Viewer commands + * + * @private + */ +function _registerViewerCommands() { + const commandContext = CONTEXTS.viewer; + + CommandsManager.createContext(commandContext); + Object.keys(viewerCommandDefinitions).forEach(commandName => { + const commandDefinition = viewerCommandDefinitions[commandName]; + + CommandsManager.register(commandContext, commandName, commandDefinition); + }); +} + +/** + * Register all Cornerstone commands + * + * @private + */ +function _registerCornerstoneCommands() { + const commandContext = CONTEXTS.cornerstone; + + CommandsManager.createContext(commandContext); + Object.keys(cornerstoneCommandDefinitions).forEach(commandName => { + const commandDefinition = cornerstoneCommandDefinitions[commandName]; + + CommandsManager.register(commandContext, commandName, commandDefinition); + }); +} + +export default { + init, +}; diff --git a/src/commands/viewer.js b/src/commands/viewer.js new file mode 100644 index 000000000..7f0bc0f4d --- /dev/null +++ b/src/commands/viewer.js @@ -0,0 +1,36 @@ +import { redux } from 'ohif-core'; +import store from './store'; +const { setViewportActive } = redux.actions; + +const definitions = { + // Next/Previous active viewport + incrementActiveViewport: { + commandFn: actions.updateActiveViewport, + storeContexts: ['viewports'], + options: { direction: 1 }, + }, + decrementActiveViewport: { + commandFn: actions.updateActiveViewport, + storeContexts: ['viewports'], + options: { direction: -1 }, + }, +}; + +const actions = { + updateViewportDisplaySet: ({ direction }) => { + // TODO + console.warn('updateDisplaySet: ', direction); + }, + updateActiveViewport: ({ viewports, direction }) => { + const { viewportSpecificData, activeViewportIndex } = viewports; + const maxIndex = Object.keys(viewportSpecificData).length - 1; + + let newIndex = activeViewportIndex + direction; + newIndex = newIndex > maxIndex ? 0 : newIndex; + newIndex = newIndex < 0 ? maxIndex : newIndex; + + store.dispatch(setViewportActive(newIndex)); + }, +}; + +export default definitions; From fabe9818559cb7f8c1ef90c7cea266c8a9453e70 Mon Sep 17 00:00:00 2001 From: dannyrb Date: Fri, 31 May 2019 22:09:05 -0400 Subject: [PATCH 03/26] WIP hotkeys code --- .../ConnectedViewerMain.js | 21 +++++----- src/connectedComponents/ViewerMain.js | 33 +++++++++------- src/hotkeys/index.js | 38 +++++++++++++++++++ 3 files changed, 68 insertions(+), 24 deletions(-) create mode 100644 src/hotkeys/index.js diff --git a/src/connectedComponents/ConnectedViewerMain.js b/src/connectedComponents/ConnectedViewerMain.js index 69dc37260..c51e307f9 100644 --- a/src/connectedComponents/ConnectedViewerMain.js +++ b/src/connectedComponents/ConnectedViewerMain.js @@ -1,12 +1,12 @@ -import { connect } from 'react-redux'; -import ViewerMain from './ViewerMain'; import OHIF from 'ohif-core'; +import ViewerMain from './ViewerMain'; +import { connect } from 'react-redux'; const { setViewportSpecificData, clearViewportSpecificData, - setToolActive, - setActiveViewportSpecificData, + // setToolActive, + // setActiveViewportSpecificData, } = OHIF.redux.actions; const mapStateToProps = state => { @@ -16,6 +16,7 @@ const mapStateToProps = state => { activeViewportIndex, layout, viewportSpecificData, + viewports: state.viewports, }; }; @@ -27,12 +28,12 @@ const mapDispatchToProps = dispatch => { clearViewportSpecificData: () => { dispatch(clearViewportSpecificData()); }, - setToolActive: tool => { - dispatch(setToolActive(tool)); - }, - setActiveViewportSpecificData: viewport => { - dispatch(setActiveViewportSpecificData(viewport)); - }, + // setToolActive: tool => { + // dispatch(setToolActive(tool)); + // }, + // setActiveViewportSpecificData: viewport => { + // dispatch(setActiveViewportSpecificData(viewport)); + // }, }; }; diff --git a/src/connectedComponents/ViewerMain.js b/src/connectedComponents/ViewerMain.js index 01ebb9b91..7a76493b4 100644 --- a/src/connectedComponents/ViewerMain.js +++ b/src/connectedComponents/ViewerMain.js @@ -1,29 +1,32 @@ -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 ConnectedLayoutManager from './ConnectedLayoutManager.js'; +import { OHIF } from 'ohif-core'; +import PropTypes from 'prop-types'; +import React from 'react'; +import hotkeys from './../hotkeys.js'; + class ViewerMain extends Component { static propTypes = { - studies: PropTypes.array.isRequired, + activeViewportIndex: PropTypes.number.isRequired, + layout: PropTypes.object, + viewportSpecificData: PropTypes.object, setViewportSpecificData: PropTypes.func.isRequired, clearViewportSpecificData: PropTypes.func.isRequired, - setToolActive: PropTypes.func.isRequired, - setActiveViewportSpecificData: PropTypes.func.isRequired, }; constructor(props) { super(props); // Initialize hotkeys - new OHIF.HotkeysUtil('viewer', { - setViewportSpecificData: props.setViewportSpecificData, - clearViewportSpecificData: props.clearViewportSpecificData, - setToolActive: props.setToolActive, - setActiveViewportSpecificData: props.setActiveViewportSpecificData, - }); + // new OHIF.HotkeysUtil('viewer', { + // setViewportSpecificData: props.setViewportSpecificData, + // clearViewportSpecificData: props.clearViewportSpecificData, + // setToolActive: props.setToolActive, + // setActiveViewportSpecificData: props.setActiveViewportSpecificData, + // }); + hotkeys.init(); this.state = { displaySets: [], @@ -147,6 +150,8 @@ class ViewerMain extends Component { this.props.clearViewportSpecificData(viewportIndex); }); + hotkeys.clear(); + // Remove beforeUnload event handler... //window.removeEventListener('beforeunload', unloadHandlers.beforeUnload); // Destroy the synchronizer used to update reference lines diff --git a/src/hotkeys/index.js b/src/hotkeys/index.js new file mode 100644 index 000000000..31add2027 --- /dev/null +++ b/src/hotkeys/index.js @@ -0,0 +1,38 @@ +import { HotkeysManager } from 'ohif-core'; +import store from './store'; + +function initialize() { + // binding, method, + const numHotkeys = hotkeyConfig.length; + for (let i = 0; i < numHotkeys; i++) { + const { keys, actionName, actionContexts, options } = hotkeyConfig[i]; + const action = actions[actionName]; + + Mousetrap.bind(keys, () => { + let actionParams = options; + actionContexts.forEach(context => { + actionParams[context] = store.getState()[context]; + }); + action(actionParams); + }); + } + + const hotkeysPreferences = store.getState().preferences; + const hotkeysViewerContext = hotkeysPreferences.viewer; + const hotkeysData = hotkeysViewerContext.hotkeysData; + + Object.keys(hotkeysData).forEach(hotkeyName => { + // context, + const hotkeyCommandName = hotkeysData[hotkeyName]; + HotkeysManager.register(); + }); +} + +function clear() { + HotkeysManager.clear(); +} + +export default { + clear, + initialize, +}; From d29f996e61c6ba33ffe0d60e1212364a59fec0c2 Mon Sep 17 00:00:00 2001 From: dannyrb Date: Sun, 2 Jun 2019 15:44:01 -0400 Subject: [PATCH 04/26] formatting --- docs/latest/advanced/extensions.md | 76 ++++++++++++++++++++++++------ public/config/default.js | 20 +++++++- 2 files changed, 81 insertions(+), 15 deletions(-) diff --git a/docs/latest/advanced/extensions.md b/docs/latest/advanced/extensions.md index 634fe5bd8..681c219d5 100644 --- a/docs/latest/advanced/extensions.md +++ b/docs/latest/advanced/extensions.md @@ -1,10 +1,27 @@ # Extensions -Extensions add new functionality to the viewer by registering one or more modules. They go one step further than configuration in that they allow us to inject custom React components, so long as they adhere to the module's interface. This can be something as simple as adding a new button to the toolbar, or as complex as a new viewport capable of rendering volumes in 3D. +Extensions add new functionality to the viewer by registering one or more +modules. They go one step further than configuration in that they allow us to +inject custom React components, so long as they adhere to the module's +interface. This can be something as simple as adding a new button to the +toolbar, or as complex as a new viewport capable of rendering volumes in 3D. + +- [Overview](#overview) +- [Modules](#modules) + - [Viewport](#viewport) + - [Toolbar](#toolbar) + - [SOP Class Handler](#sopclasshandler) + - [Panel](#panel) + - [Commands](#commands) + - [Hotkeys](#hotkeys) ## Overview -At a glance, an extension is a class or object that has a `getExtensionId()` method, and one or more "module" methods. You can find an abbreviated extension below, or [view the source](https://github.com/OHIF/Viewers/blob/react/extensions/ohif-cornerstone-extension/src/OHIFCornerstoneExtension.js#L32-L65) of our `cornerstone` viewport extension. +At a glance, an extension is a class or object that has a `getExtensionId()` +method, and one or more "module" methods. You can find an abbreviated extension +below, or +[view the source](https://github.com/OHIF/Viewers/blob/react/extensions/ohif-cornerstone-extension/src/OHIFCornerstoneExtension.js#L32-L65) +of our `cornerstone` viewport extension. ```js class myCustomExtension { @@ -36,11 +53,18 @@ class myCustomExtension { ### Modules -There are a few different kinds of modules. Each kind of module allows us to extend the viewer in a different way, and provides a consistent API for us to do so. You can find a full list of the [different types of modules `in ohif-core`](https://github.com/OHIF/ohif-core/blob/43c08a29eff3fb646a0e83a03a236ddd84f4a6e8/src/plugins.js#L1-L6). Information on each type of module, it's API, and how we determine when/where it should be used is included below: +There are a few different kinds of modules. Each kind of module allows us to +extend the viewer in a different way, and provides a consistent API for us to do +so. You can find a full list of the +[different types of modules `in ohif-core`](https://github.com/OHIF/ohif-core/blob/43c08a29eff3fb646a0e83a03a236ddd84f4a6e8/src/plugins.js#L1-L6). +Information on each type of module, it's API, and how we determine when/where it +should be used is included below: #### Viewport -An extension can register a Viewport Module by providing a `getViewportModule()` method that returns a React Component. The React component will receive the following props: +An extension can register a Viewport Module by providing a `getViewportModule()` +method that returns a React Component. The React component will receive the +following props: ```js children: PropTypes.arrayOf(PropTypes.element) @@ -52,7 +76,8 @@ children: PropTypes.node, customProps: PropTypes.object ``` -Viewport components are managed by the `LayoutManager`. Which Viewport component is used depends on: +Viewport components are managed by the `LayoutManager`. Which Viewport component +is used depends on: - The Layout Configuration - Registered SopClassHandlers @@ -62,11 +87,15 @@ Viewport components are managed by the `LayoutManager`. Which Viewport component
An example of three Viewports
-For a complete example implementation, [check out the OHIFCornerstoneViewport](https://github.com/OHIF/Viewers/blob/react/extensions/ohif-cornerstone-extension/src/OHIFCornerstoneViewport.js). +For a complete example implementation, +[check out the OHIFCornerstoneViewport](https://github.com/OHIF/Viewers/blob/react/extensions/ohif-cornerstone-extension/src/OHIFCornerstoneViewport.js). #### Toolbar -An extension can register a Toolbar Module by providing a `getToolbarModule()` method that returns a React Component. The component does not receive any props. If you want to modify or react to state, you will need to connect to the redux store. +An extension can register a Toolbar Module by providing a `getToolbarModule()` +method that returns a React Component. The component does not receive any props. +If you want to modify or react to state, you will need to connect to the redux +store. ![Toolbar Extension](../assets/img/extensions-toolbar.gif) @@ -74,7 +103,8 @@ An extension can register a Toolbar Module by providing a `getToolbarModule()` m Toolbar components are rendered in the `ToolbarRow` component. -For a complete example implementation, [check out the OHIFCornerstoneViewport's Toolbar Module](https://github.com/OHIF/Viewers/blob/react/extensions/ohif-cornerstone-extension/src/ToolbarModule.js). +For a complete example implementation, +[check out the OHIFCornerstoneViewport's Toolbar Module](https://github.com/OHIF/Viewers/blob/react/extensions/ohif-cornerstone-extension/src/ToolbarModule.js). #### SopClassHandler @@ -84,18 +114,32 @@ For a complete example implementation, [check out the OHIFCornerstoneViewport's > The panel module is not yet in use. +#### Commands + +... + +#### Hotkeys + +... + ### Registering Extensions -Extensions are registered for the application at startup. The `ExtensionManager`, exposed by `ohif-core`, registers a list of extensions with our application's store. Each module provided by the extension becomes available via `state.plugins.availablePlugins`, and consists of three parts: id, type ([PLUGIN_TYPE](https://github.com/OHIF/ohif-core/blob/43c08a29eff3fb646a0e83a03a236ddd84f4a6e8/src/plugins.js#L1-L6)), and the return value of the module method. +Extensions are registered for the application at startup. The +`ExtensionManager`, exposed by `ohif-core`, registers a list of extensions with +our application's store. Each module provided by the extension becomes available +via `state.plugins.availablePlugins`, and consists of three parts: id, type +([PLUGIN_TYPE](https://github.com/OHIF/ohif-core/blob/43c08a29eff3fb646a0e83a03a236ddd84f4a6e8/src/plugins.js#L1-L6)), +and the return value of the module method. -In a future version, we will likely expose a way to provide the extensions you would like included at startup. +In a future version, we will likely expose a way to provide the extensions you +would like included at startup. _app.js_ ```js -import { createStore, combineReducers } from "redux"; -import OHIF from "ohif-core"; -import OHIFCornerstoneExtension from "ohif-cornerstone-extension"; +import { createStore, combineReducers } from 'redux'; +import OHIF from 'ohif-core'; +import OHIFCornerstoneExtension from 'ohif-cornerstone-extension'; const combined = combineReducers(OHIF.redux.reducers); const store = createStore(combined); @@ -108,6 +152,10 @@ ExtensionManager.registerExtensions(store, extensions); ## OHIF Maintained Extensions -A small number of powerful extensions for popular use cases are maintained by OHIF. They're co-located in the [`OHIF/Viewers`](https://github.com/OHIF/Viewers/tree/react/) repository, in the top level [`extensions/`](https://github.com/OHIF/Viewers/tree/react/extensions) directory. +A small number of powerful extensions for popular use cases are maintained by +OHIF. They're co-located in the +[`OHIF/Viewers`](https://github.com/OHIF/Viewers/tree/react/) repository, in the +top level [`extensions/`](https://github.com/OHIF/Viewers/tree/react/extensions) +directory. {% include "./_maintained-extensions-table.md" %} diff --git a/public/config/default.js b/public/config/default.js index 1a1834155..f4f1497c2 100644 --- a/public/config/default.js +++ b/public/config/default.js @@ -19,4 +19,22 @@ window.config = { }, ], }, -} + // + userPreferences: [ + { + order: 0, + title: 'preference page title', + items: [ + { + order: 0, + label: 'Scale Viewport Up', + command: 'ScaleViewport', + }, + ], + }, + ], + // + hotkeys: { + ScaleViewport: ['+'], + }, +}; From b0706e5544b111ad804d001e668dbba9173c4fd5 Mon Sep 17 00:00:00 2001 From: dannyrb Date: Sun, 2 Jun 2019 15:44:27 -0400 Subject: [PATCH 05/26] Playing with a hotkeys + commands setup and config --- src/App.js | 11 ++++++++ src/hotkeys/index.js | 64 ++++++++++++++++++++++---------------------- 2 files changed, 43 insertions(+), 32 deletions(-) diff --git a/src/App.js b/src/App.js index 6215a8a99..9c50f99ec 100644 --- a/src/App.js +++ b/src/App.js @@ -23,8 +23,19 @@ import WhiteLabellingContext from './WhiteLabellingContext'; import setupTools from './setupTools'; import store from './store'; +// Register all of our commands and hotkeys +import commands from './commands'; + const { ExtensionManager } = OHIF.extensions; +commands.init(); +// window.config.userPreferences +Object.keys(window.config.hotkeys).forEach(commandName => { + const keys = window.config.hotkeys[commandName]; + // OHIF.HotkeysManager.set(); + OHIF.HotkeysManager.registerHotkeys(commandName, keys); +}); + setupTools(store); const children = { diff --git a/src/hotkeys/index.js b/src/hotkeys/index.js index 31add2027..ca4e8366d 100644 --- a/src/hotkeys/index.js +++ b/src/hotkeys/index.js @@ -1,38 +1,38 @@ -import { HotkeysManager } from 'ohif-core'; -import store from './store'; +// import { HotkeysManager } from 'ohif-core'; +// import store from './store'; -function initialize() { - // binding, method, - const numHotkeys = hotkeyConfig.length; - for (let i = 0; i < numHotkeys; i++) { - const { keys, actionName, actionContexts, options } = hotkeyConfig[i]; - const action = actions[actionName]; +// function initialize() { +// // binding, method, +// const numHotkeys = hotkeyConfig.length; +// for (let i = 0; i < numHotkeys; i++) { +// const { keys, actionName, actionContexts, options } = hotkeyConfig[i]; +// const action = actions[actionName]; - Mousetrap.bind(keys, () => { - let actionParams = options; - actionContexts.forEach(context => { - actionParams[context] = store.getState()[context]; - }); - action(actionParams); - }); - } +// Mousetrap.bind(keys, () => { +// let actionParams = options; +// actionContexts.forEach(context => { +// actionParams[context] = store.getState()[context]; +// }); +// action(actionParams); +// }); +// } - const hotkeysPreferences = store.getState().preferences; - const hotkeysViewerContext = hotkeysPreferences.viewer; - const hotkeysData = hotkeysViewerContext.hotkeysData; +// const hotkeysPreferences = store.getState().preferences; +// const hotkeysViewerContext = hotkeysPreferences.viewer; +// const hotkeysData = hotkeysViewerContext.hotkeysData; - Object.keys(hotkeysData).forEach(hotkeyName => { - // context, - const hotkeyCommandName = hotkeysData[hotkeyName]; - HotkeysManager.register(); - }); -} +// Object.keys(hotkeysData).forEach(hotkeyName => { +// // context, +// const hotkeyCommandName = hotkeysData[hotkeyName]; +// HotkeysManager.register(); +// }); +// } -function clear() { - HotkeysManager.clear(); -} +// function clear() { +// HotkeysManager.clear(); +// } -export default { - clear, - initialize, -}; +// export default { +// clear, +// initialize, +// }; From c1f8984c1b46549662b770f4bcbc49725defd67c Mon Sep 17 00:00:00 2001 From: dannyrb Date: Sun, 2 Jun 2019 22:43:04 -0400 Subject: [PATCH 06/26] Switch folder; dependency injection; note about moving to extensions --- src/{commands => appCommands}/README.md | 0 src/{commands => appCommands}/cornerstone.js | 128 +++++++++---------- src/appCommands/index.js | 60 +++++++++ src/{commands => appCommands}/viewer.js | 30 ++--- src/commands/index.js | 52 -------- 5 files changed, 139 insertions(+), 131 deletions(-) rename src/{commands => appCommands}/README.md (100%) rename src/{commands => appCommands}/cornerstone.js (100%) create mode 100644 src/appCommands/index.js rename src/{commands => appCommands}/viewer.js (96%) delete mode 100644 src/commands/index.js diff --git a/src/commands/README.md b/src/appCommands/README.md similarity index 100% rename from src/commands/README.md rename to src/appCommands/README.md diff --git a/src/commands/cornerstone.js b/src/appCommands/cornerstone.js similarity index 100% rename from src/commands/cornerstone.js rename to src/appCommands/cornerstone.js index ab8974ea4..89699a226 100644 --- a/src/commands/cornerstone.js +++ b/src/appCommands/cornerstone.js @@ -1,69 +1,5 @@ import cornerstone from 'cornerstone-core'; -const definitions = { - rotateViewportCW: { - commandFn: actions.rotateViewport, - storeContexts: ['viewports'], - options: { rotation: 90 }, - }, - rotateViewportCCW: { - commandFn: actions.rotateViewport, - storeContexts: ['viewports'], - options: { rotation: -90 }, - }, - invertViewport: { - commandFn: actions.invertViewport, - storeContexts: ['viewports'], - options: {}, - }, - flipViewportVertical: { - commandFn: actions.flipViewportVertical, - storeContexts: ['viewports'], - options: {}, - }, - flipViewportHorizontal: { - commandFn: actions.flipViewportHorizontal, - storeContexts: ['viewports'], - options: {}, - }, - scaleUpViewport: { - keys: '', - commandFn: actions.scaleViewport, - storeContexts: ['viewports'], - options: { direction: 1 }, - }, - scaleDownViewport: { - keys: '', - commandFn: actions.scaleViewport, - storeContexts: ['viewports'], - options: { direction: -1 }, - }, - fitViewportToWindow: { - commandFn: actions.scaleViewport, - storeContexts: ['viewports'], - options: { direction: 0 }, - }, - resetViewport: { - commandFn: actions.resetViewport, - storeContexts: ['viewports'], - options: {}, - }, - // TODO: Clear Annotations - // TODO: Next/Previous image - // TODO: First/Last image - // Next/Previous series/DisplaySet - nextViewportDisplaySet: { - commandFn: actions.updateViewportDisplaySet, - storeContexts: [], - options: { direction: 1 }, - }, - previousViewportDisplaySet: { - commandFn: actions.updateViewportDisplaySet, - storeContexts: [], - options: { direction: 1 }, - }, -}; - const actions = { rotateViewport: ({ viewports, rotation }) => { const enabledElement = _getActiveViewportEnabledElement( @@ -157,6 +93,70 @@ const actions = { }, }; +const definitions = { + rotateViewportCW: { + commandFn: actions.rotateViewport, + storeContexts: ['viewports'], + options: { rotation: 90 }, + }, + rotateViewportCCW: { + commandFn: actions.rotateViewport, + storeContexts: ['viewports'], + options: { rotation: -90 }, + }, + invertViewport: { + commandFn: actions.invertViewport, + storeContexts: ['viewports'], + options: {}, + }, + flipViewportVertical: { + commandFn: actions.flipViewportVertical, + storeContexts: ['viewports'], + options: {}, + }, + flipViewportHorizontal: { + commandFn: actions.flipViewportHorizontal, + storeContexts: ['viewports'], + options: {}, + }, + scaleUpViewport: { + keys: '', + commandFn: actions.scaleViewport, + storeContexts: ['viewports'], + options: { direction: 1 }, + }, + scaleDownViewport: { + keys: '', + commandFn: actions.scaleViewport, + storeContexts: ['viewports'], + options: { direction: -1 }, + }, + fitViewportToWindow: { + commandFn: actions.scaleViewport, + storeContexts: ['viewports'], + options: { direction: 0 }, + }, + resetViewport: { + commandFn: actions.resetViewport, + storeContexts: ['viewports'], + options: {}, + }, + // TODO: Clear Annotations + // TODO: Next/Previous image + // TODO: First/Last image + // Next/Previous series/DisplaySet + nextViewportDisplaySet: { + commandFn: actions.updateViewportDisplaySet, + storeContexts: [], + options: { direction: 1 }, + }, + previousViewportDisplaySet: { + commandFn: actions.updateViewportDisplaySet, + storeContexts: [], + options: { direction: 1 }, + }, +}; + /** * Grabs `dom` reference for the enabledElement of * the active viewport diff --git a/src/appCommands/index.js b/src/appCommands/index.js new file mode 100644 index 000000000..66ab50fa6 --- /dev/null +++ b/src/appCommands/index.js @@ -0,0 +1,60 @@ +import cornerstoneCommandDefinitions from './cornerstone.js'; +import viewerCommandDefinitions from './viewer.js'; + +const CONTEXTS = { + viewer: 'VIEWER', + cornerstone: 'VIEWER::CORNERSTONE', +}; + +/** + * Register all commands. + * TODO: Extensions should self-register their commands + */ +function init(commandsManager) { + _registerViewerCommands(commandsManager); + _registerCornerstoneCommands(commandsManager); +} + +/** + * Register all Viewer commands + * + * @private + */ +function _registerViewerCommands(commandsManager) { + const commandContext = CONTEXTS.viewer; + + commandsManager.createContext(commandContext); + Object.keys(viewerCommandDefinitions).forEach(commandName => { + const commandDefinition = viewerCommandDefinitions[commandName]; + + commandsManager.registerCommand( + commandContext, + commandName, + commandDefinition + ); + }); +} + +/** + * Register all Cornerstone commands + * + * @private + */ +function _registerCornerstoneCommands(commandsManager) { + const commandContext = CONTEXTS.cornerstone; + + commandsManager.createContext(commandContext); + Object.keys(cornerstoneCommandDefinitions).forEach(commandName => { + const commandDefinition = cornerstoneCommandDefinitions[commandName]; + + commandsManager.registerCommand( + commandContext, + commandName, + commandDefinition + ); + }); +} + +export default { + init, +}; diff --git a/src/commands/viewer.js b/src/appCommands/viewer.js similarity index 96% rename from src/commands/viewer.js rename to src/appCommands/viewer.js index 7f0bc0f4d..fa7c02772 100644 --- a/src/commands/viewer.js +++ b/src/appCommands/viewer.js @@ -1,21 +1,7 @@ import { redux } from 'ohif-core'; -import store from './store'; +import store from './../store'; const { setViewportActive } = redux.actions; -const definitions = { - // Next/Previous active viewport - incrementActiveViewport: { - commandFn: actions.updateActiveViewport, - storeContexts: ['viewports'], - options: { direction: 1 }, - }, - decrementActiveViewport: { - commandFn: actions.updateActiveViewport, - storeContexts: ['viewports'], - options: { direction: -1 }, - }, -}; - const actions = { updateViewportDisplaySet: ({ direction }) => { // TODO @@ -33,4 +19,18 @@ const actions = { }, }; +const definitions = { + // Next/Previous active viewport + incrementActiveViewport: { + commandFn: actions.updateActiveViewport, + storeContexts: ['viewports'], + options: { direction: 1 }, + }, + decrementActiveViewport: { + commandFn: actions.updateActiveViewport, + storeContexts: ['viewports'], + options: { direction: -1 }, + }, +}; + export default definitions; diff --git a/src/commands/index.js b/src/commands/index.js deleted file mode 100644 index b577eb2b8..000000000 --- a/src/commands/index.js +++ /dev/null @@ -1,52 +0,0 @@ -import { CommandsManager } from 'ohif-core'; -import cornerstoneCommandDefinitions from 'cornerstone.js'; -import viewerCommandDefinitions from 'viewer.js'; - -const CONTEXTS = { - viewer: 'VIEWER', - cornerstone: 'VIEWER::CORNERSTONE', -}; - -/** - * Register all commands - */ -function init() { - _registerViewerCommands(); - _registerCornerstoneCommands(); -} - -/** - * Register all Viewer commands - * - * @private - */ -function _registerViewerCommands() { - const commandContext = CONTEXTS.viewer; - - CommandsManager.createContext(commandContext); - Object.keys(viewerCommandDefinitions).forEach(commandName => { - const commandDefinition = viewerCommandDefinitions[commandName]; - - CommandsManager.register(commandContext, commandName, commandDefinition); - }); -} - -/** - * Register all Cornerstone commands - * - * @private - */ -function _registerCornerstoneCommands() { - const commandContext = CONTEXTS.cornerstone; - - CommandsManager.createContext(commandContext); - Object.keys(cornerstoneCommandDefinitions).forEach(commandName => { - const commandDefinition = cornerstoneCommandDefinitions[commandName]; - - CommandsManager.register(commandContext, commandName, commandDefinition); - }); -} - -export default { - init, -}; From 6b0ef0904e2e8bca3f06c3e3c565cc978ad65696 Mon Sep 17 00:00:00 2001 From: dannyrb Date: Sun, 2 Jun 2019 22:44:13 -0400 Subject: [PATCH 07/26] Shift initialization of OHIF classes to app.js --- src/App.js | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/App.js b/src/App.js index 9c50f99ec..d9a9dad86 100644 --- a/src/App.js +++ b/src/App.js @@ -8,13 +8,20 @@ import { } from './utils/index.js'; import ConnectedToolContextMenu from './connectedComponents/ConnectedToolContextMenu'; -import OHIF from 'ohif-core'; +import { + CommandsManager, + HotkeysManager, + extensions, + redux, + utils, +} from 'ohif-core'; import OHIFCornerstoneExtension from '@ohif/extension-cornerstone'; import OHIFDicomHtmlExtension from 'ohif-dicom-html-extension'; import OHIFDicomMicroscopyExtension from 'ohif-dicom-microscopy-extension'; import OHIFDicomPDFExtension from 'ohif-dicom-pdf-extension'; import OHIFStandaloneViewer from './OHIFStandaloneViewer'; import OHIFVTKExtension from '@ohif/extension-vtk'; +import appCommands from './appCommands'; import { OidcProvider } from 'redux-oidc'; import PropTypes from 'prop-types'; import { Provider } from 'react-redux'; @@ -23,19 +30,21 @@ import WhiteLabellingContext from './WhiteLabellingContext'; import setupTools from './setupTools'; import store from './store'; -// Register all of our commands and hotkeys -import commands from './commands'; +// ~~~~ APP SETUP +const _commandsManager = new CommandsManager(); +const _hotkeysManager = new HotkeysManager(_commandsManager); -const { ExtensionManager } = OHIF.extensions; - -commands.init(); +appCommands.init(_commandsManager); // window.config.userPreferences Object.keys(window.config.hotkeys).forEach(commandName => { const keys = window.config.hotkeys[commandName]; - // OHIF.HotkeysManager.set(); - OHIF.HotkeysManager.registerHotkeys(commandName, keys); + // HotkeysManager.set(); + console.log(`registering hotkey: ${commandName} to binding: ${keys}`); + _hotkeysManager.registerHotkeys(commandName, keys); }); +// ~~~~ END EPP SETUP + setupTools(store); const children = { @@ -43,21 +52,20 @@ const children = { }; /** TODO: extensions should be passed in as prop as soon as we have the extensions as separate packages and then registered by ExtensionsManager */ -const extensions = [ +extensions.ExtensionManager.registerExtensions(store, [ new OHIFCornerstoneExtension({ children }), new OHIFVTKExtension(), new OHIFDicomPDFExtension(), new OHIFDicomHtmlExtension(), new OHIFDicomMicroscopyExtension(), -]; -ExtensionManager.registerExtensions(store, extensions); +]); // TODO[react] Use a provider when the whole tree is React window.store = store; function handleServers(servers) { if (servers) { - OHIF.utils.addServers(servers, store); + utils.addServers(servers, store); } } @@ -80,9 +88,7 @@ class App extends Component { // const defaultButtons = getDefaultToolbarButtons(this.props.routerBasename); - const buttonsAction = OHIF.redux.actions.setAvailableButtons( - defaultButtons - ); + const buttonsAction = redux.actions.setAvailableButtons(defaultButtons); store.dispatch(buttonsAction); From 03d66233ff78a17e1483daed586a9623447ab453 Mon Sep 17 00:00:00 2001 From: dannyrb Date: Sun, 2 Jun 2019 23:04:24 -0400 Subject: [PATCH 08/26] Make sure we're registering a hotkey for a command that exists --- public/config/default.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/config/default.js b/public/config/default.js index f4f1497c2..11108d5c7 100644 --- a/public/config/default.js +++ b/public/config/default.js @@ -28,13 +28,13 @@ window.config = { { order: 0, label: 'Scale Viewport Up', - command: 'ScaleViewport', + command: 'scaleUpViewport', }, ], }, ], // hotkeys: { - ScaleViewport: ['+'], + scaleUpViewport: ['s'], }, }; From 56de51a21d457a53940ad770a48c2122e881c8e1 Mon Sep 17 00:00:00 2001 From: dannyrb Date: Mon, 3 Jun 2019 12:21:50 -0400 Subject: [PATCH 09/26] Kill old code --- src/hotkeys/index.js | 38 -------------------------------------- 1 file changed, 38 deletions(-) delete mode 100644 src/hotkeys/index.js diff --git a/src/hotkeys/index.js b/src/hotkeys/index.js deleted file mode 100644 index ca4e8366d..000000000 --- a/src/hotkeys/index.js +++ /dev/null @@ -1,38 +0,0 @@ -// import { HotkeysManager } from 'ohif-core'; -// import store from './store'; - -// function initialize() { -// // binding, method, -// const numHotkeys = hotkeyConfig.length; -// for (let i = 0; i < numHotkeys; i++) { -// const { keys, actionName, actionContexts, options } = hotkeyConfig[i]; -// const action = actions[actionName]; - -// Mousetrap.bind(keys, () => { -// let actionParams = options; -// actionContexts.forEach(context => { -// actionParams[context] = store.getState()[context]; -// }); -// action(actionParams); -// }); -// } - -// const hotkeysPreferences = store.getState().preferences; -// const hotkeysViewerContext = hotkeysPreferences.viewer; -// const hotkeysData = hotkeysViewerContext.hotkeysData; - -// Object.keys(hotkeysData).forEach(hotkeyName => { -// // context, -// const hotkeyCommandName = hotkeysData[hotkeyName]; -// HotkeysManager.register(); -// }); -// } - -// function clear() { -// HotkeysManager.clear(); -// } - -// export default { -// clear, -// initialize, -// }; From 9326b5e63d93e40c60060130ac1dfb1504bd36a1 Mon Sep 17 00:00:00 2001 From: dannyrb Date: Mon, 3 Jun 2019 12:22:06 -0400 Subject: [PATCH 10/26] Actions and reducers for managing UI contexts --- src/store/layout/actions.js | 19 +++++++++++++++++++ src/store/layout/reducers.js | 18 ++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/store/layout/actions.js b/src/store/layout/actions.js index 552e24a46..54e3a16ca 100644 --- a/src/store/layout/actions.js +++ b/src/store/layout/actions.js @@ -1,3 +1,18 @@ +export const addActiveContext = state => ({ + type: 'ADD_ACTIVE_CONTEXT', + state, +}); + +export const removeActiveContext = state => ({ + type: 'REMOVE_ACTIVE_CONTEXT', + state, +}); + +export const clearActiveContexts = state => ({ + type: 'CLEAR_ACTIVE_CONTEXTS', + state, +}); + export const setLeftSidebarOpen = state => ({ type: 'SET_LEFT_SIDEBAR_OPEN', state, @@ -14,6 +29,10 @@ export const setUserPreferencesModalOpen = state => ({ }); const actions = { + addActiveContext, + removeActiveContext, + clearActiveContexts, + // setLeftSidebarOpen, setRightSidebarOpen, setUserPreferencesModalOpen, diff --git a/src/store/layout/reducers.js b/src/store/layout/reducers.js index dde5baa45..ea87caef4 100644 --- a/src/store/layout/reducers.js +++ b/src/store/layout/reducers.js @@ -4,10 +4,28 @@ const defaultState = { userPreferencesModalOpen: false, labelling: {}, contextMenu: {}, + activeContexts: [], }; const ui = (state = defaultState, action) => { switch (action.type) { + // ~ ACTIVE CONTEXTS + // https://redux.js.org/recipes/structuring-reducers/immutable-update-patterns#inserting-and-removing-items-in-arrays + case 'ADD_ACTIVE_CONTEXT': { + const shallowCopy = Object.assign({}, state); + shallowCopy.activeContexts = [...shallowCopy.activeContexts, action.item]; + return shallowCopy; + } + case 'REMOVE_ACTIVE_CONTEXT': { + const shallowCopy = Object.assign({}, state); + shallowCopy.activeContexts = shallowCopy.activeContexts.filter( + item => item !== action.item + ); + return shallowCopy; + } + case 'CLEAR_ACTIVE_CONTEXTS': + return Object.assign({}, state, { activeContexts: [] }); + // ~ SIDEBAR case 'SET_LEFT_SIDEBAR_OPEN': return Object.assign({}, state, { leftSidebarOpen: action.state }); case 'SET_RIGHT_SIDEBAR_OPEN': From 9b838ab3261b16d014976fe9a239276ae1c2a534 Mon Sep 17 00:00:00 2001 From: dannyrb Date: Mon, 3 Jun 2019 12:22:25 -0400 Subject: [PATCH 11/26] Provide store to commands manager; hard code active contexts --- src/App.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/App.js b/src/App.js index d9a9dad86..0ccf0c513 100644 --- a/src/App.js +++ b/src/App.js @@ -31,7 +31,7 @@ import setupTools from './setupTools'; import store from './store'; // ~~~~ APP SETUP -const _commandsManager = new CommandsManager(); +const _commandsManager = new CommandsManager(store); const _hotkeysManager = new HotkeysManager(_commandsManager); appCommands.init(_commandsManager); @@ -43,6 +43,16 @@ Object.keys(window.config.hotkeys).forEach(commandName => { _hotkeysManager.registerHotkeys(commandName, keys); }); +// Force active contexts for now. These should be set in Viewer/ActiveViewer +store.dispatch({ + type: 'ADD_ACTIVE_CONTEXT', + item: 'VIEWER', +}); +store.dispatch({ + type: 'ADD_ACTIVE_CONTEXT', + item: 'VIEWER::CORNERSTONE', +}); + // ~~~~ END EPP SETUP setupTools(store); From 66e3b9f85724c9b9683b07cba8f68a6b817df00e Mon Sep 17 00:00:00 2001 From: dannyrb Date: Mon, 3 Jun 2019 12:43:36 -0400 Subject: [PATCH 12/26] Commands list for cornerstone extension --- .../ohif-cornerstone-extension/README.md | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/extensions/ohif-cornerstone-extension/README.md b/extensions/ohif-cornerstone-extension/README.md index 82af692dd..98f92b8a0 100644 --- a/extensions/ohif-cornerstone-extension/README.md +++ b/extensions/ohif-cornerstone-extension/README.md @@ -1 +1,31 @@ # @ohif/extension-cornerstone + +## Commands + +This extensions includes the following `Commands` and `Command Definitions`. These can be registered with `@ohif/core`'s `CommandManager`. After registering the commands, they can be bound to `hotkeys` using the `HotkeysManager` and listed in the `UserPreferences` modal. + +You can read more about [`Commands`](), [`Hotkeys`](), and the [`UserPreferences` Modal]() in their respective locations in the OHIF Viewer's documentation. + +| Command Name | Description | Store Contexts | +| ---------------------------- | ----------- | -------------- | +| `rotateViewportCW` | | viewports | +| `rotateViewportCCW` | | viewports | +| `invertViewport` | | viewports | +| `flipViewportVertical` | | viewports | +| `flipViewportHorizontal` | | viewports | +| `scaleUpViewport` | | viewports | +| `scaleDownViewport` | | viewports | +| `fitViewportToWindow` | | viewports | +| `resetViewport` | | viewports | +| clearAnnotations | TODO | | +| next/previous Image | TODO | | +| first/last Image | TODO | | +| `nextViewportDisplaySet` | | none | +| `previousViewportDisplaySet` | | none | + + + + + From afa6d78e42d09ab96d96afede97cbdae69b63557 Mon Sep 17 00:00:00 2001 From: dannyrb Date: Mon, 3 Jun 2019 13:22:05 -0400 Subject: [PATCH 13/26] push WIP --- src/connectedComponents/ViewerMain.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/connectedComponents/ViewerMain.js b/src/connectedComponents/ViewerMain.js index 7a76493b4..04c9b1adc 100644 --- a/src/connectedComponents/ViewerMain.js +++ b/src/connectedComponents/ViewerMain.js @@ -5,7 +5,6 @@ import ConnectedLayoutManager from './ConnectedLayoutManager.js'; import { OHIF } from 'ohif-core'; import PropTypes from 'prop-types'; import React from 'react'; -import hotkeys from './../hotkeys.js'; class ViewerMain extends Component { static propTypes = { @@ -26,7 +25,7 @@ class ViewerMain extends Component { // setToolActive: props.setToolActive, // setActiveViewportSpecificData: props.setActiveViewportSpecificData, // }); - hotkeys.init(); + // hotkeys.init(); this.state = { displaySets: [], @@ -150,7 +149,9 @@ class ViewerMain extends Component { this.props.clearViewportSpecificData(viewportIndex); }); - hotkeys.clear(); + // TODO: These don't have to be viewer specific? + // Could qualify for other routes? + // hotkeys.destroy(); // Remove beforeUnload event handler... //window.removeEventListener('beforeunload', unloadHandlers.beforeUnload); From 3f5660722e3b491b7a077ad0d0b205064465e0b6 Mon Sep 17 00:00:00 2001 From: dannyrb Date: Mon, 3 Jun 2019 14:27:03 -0400 Subject: [PATCH 14/26] Defaults for hotkeys --- .../ohif-cornerstone-extension/README.md | 23 +++++++++++++++++++ public/config/default.js | 22 +++++++++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/extensions/ohif-cornerstone-extension/README.md b/extensions/ohif-cornerstone-extension/README.md index 98f92b8a0..199da64ca 100644 --- a/extensions/ohif-cornerstone-extension/README.md +++ b/extensions/ohif-cornerstone-extension/README.md @@ -23,6 +23,29 @@ You can read more about [`Commands`](), [`Hotkeys`](), and the [`UserPreferences | `nextViewportDisplaySet` | | none | | `previousViewportDisplaySet` | | none | +### TODO: + +_SET TOOL_ + +- [] Default Tool +- [] Set Zoom Tool +- [] Set WWWC Tool +- [] Set Pan Tool +- [] Set Angle Measurement Tool +- [] Set Stack Scroll Tool +- [] Set Magnify Tool +- [] Set Length Tool +- [] Set Annotate Tool +- [] Set Pixel Probe Tool +- [] Set Elliptical ROI Tool +- [] Set Rectangle ROI Tool + +_OTHER_ + +- Show/Hide CINE +- W/L Presets +- W/L Presets config + diff --git a/public/config/default.js b/public/config/default.js index 11108d5c7..47aa41fe0 100644 --- a/public/config/default.js +++ b/public/config/default.js @@ -35,6 +35,26 @@ window.config = { ], // hotkeys: { - scaleUpViewport: ['s'], + // ~ Global + incrementActiveViewport: ['right'], + decrementActiveViewport: ['left'], + // Supported Keys: https://craig.is/killing/mice + // ~ Cornerstone Extension + rotateViewportCW: ['r'], + rotateViewportCCW: ['l'], + invertViewport: ['i'], + flipViewportVertical: ['h'], + flipViewportHorizontal: ['v'], + scaleUpViewport: ['+'], + scaleDownViewport: ['-'], + fitViewportToWindow: ['='], + resetViewport: ['space'], + // clearAnnotations + // nextImage + // previousImage + // firstImage + // lastImage + nextViewportDisplaySet: ['pageup'], + previousViewportDisplaySet: ['pagedown'], }, }; From a636df8fdcf25c952b3a9beee3fe11ae9e583d2e Mon Sep 17 00:00:00 2001 From: dannyrb Date: Mon, 3 Jun 2019 15:41:49 -0400 Subject: [PATCH 15/26] Pass in CommandsManager config --- src/App.js | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/App.js b/src/App.js index 0ccf0c513..3273ba325 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,12 @@ import './config'; +import { + CommandsManager, + HotkeysManager, + extensions, + redux, + utils, +} from 'ohif-core'; import React, { Component } from 'react'; import { getDefaultToolbarButtons, @@ -8,33 +15,34 @@ import { } from './utils/index.js'; import ConnectedToolContextMenu from './connectedComponents/ConnectedToolContextMenu'; -import { - CommandsManager, - HotkeysManager, - extensions, - redux, - utils, -} from 'ohif-core'; import OHIFCornerstoneExtension from '@ohif/extension-cornerstone'; import OHIFDicomHtmlExtension from 'ohif-dicom-html-extension'; import OHIFDicomMicroscopyExtension from 'ohif-dicom-microscopy-extension'; import OHIFDicomPDFExtension from 'ohif-dicom-pdf-extension'; import OHIFStandaloneViewer from './OHIFStandaloneViewer'; import OHIFVTKExtension from '@ohif/extension-vtk'; -import appCommands from './appCommands'; import { OidcProvider } from 'redux-oidc'; import PropTypes from 'prop-types'; import { Provider } from 'react-redux'; import { BrowserRouter as Router } from 'react-router-dom'; import WhiteLabellingContext from './WhiteLabellingContext'; +import appCommands from './appCommands'; import setupTools from './setupTools'; import store from './store'; // ~~~~ APP SETUP -const _commandsManager = new CommandsManager(store); +const commandsManagerConfig = { + getAppState: () => store.getState(), + getActiveContexts: () => store.getState().ui.activeContexts, +}; + +const _commandsManager = new CommandsManager(commandsManagerConfig); const _hotkeysManager = new HotkeysManager(_commandsManager); +// TODO: Should be done in extensions w/ commandsModule +// ADD COMMANDS appCommands.init(_commandsManager); + // window.config.userPreferences Object.keys(window.config.hotkeys).forEach(commandName => { const keys = window.config.hotkeys[commandName]; From 347680fdf5f4de70cd73c7afd14aa799cba2934e Mon Sep 17 00:00:00 2001 From: dannyrb Date: Wed, 5 Jun 2019 15:57:00 -0400 Subject: [PATCH 16/26] Setting an active tool --- public/config/default.js | 2 ++ src/appCommands/cornerstone.js | 18 +++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/public/config/default.js b/public/config/default.js index 47aa41fe0..c53ad9274 100644 --- a/public/config/default.js +++ b/public/config/default.js @@ -56,5 +56,7 @@ window.config = { // lastImage nextViewportDisplaySet: ['pageup'], previousViewportDisplaySet: ['pagedown'], + // ~ Cornerstone Tools + setZoomTool: ['z'], }, }; diff --git a/src/appCommands/cornerstone.js b/src/appCommands/cornerstone.js index 89699a226..53e6ea7cc 100644 --- a/src/appCommands/cornerstone.js +++ b/src/appCommands/cornerstone.js @@ -1,4 +1,8 @@ import cornerstone from 'cornerstone-core'; +import { redux } from 'ohif-core'; +import store from './../store/'; + +const { setToolActive } = redux.actions; const actions = { rotateViewport: ({ viewports, rotation }) => { @@ -64,7 +68,6 @@ const actions = { cornerstone.reset(enabledElement); } }, - invertViewport: ({ viewports }) => { const enabledElement = _getActiveViewportEnabledElement( viewports.viewportSpecificData, @@ -77,6 +80,13 @@ const actions = { cornerstone.setViewport(enabledElement, viewport); } }, + // This has a weird hard dependency on the tools that are available as toolbar + // buttons. You can see this in `ohif-core/src/redux/reducers/tools.js` + // the `toolName` needs to equal the button's `command` property. + // NOTE: It would be nice if `hotkeys` could set this, instead of creating a command per tool + setCornerstoneToolActive: ({ toolName }) => { + store.dispatch(setToolActive(toolName)); + }, updateViewportDisplaySet: ({ direction }) => { // TODO console.warn('updateDisplaySet: ', direction); @@ -155,6 +165,12 @@ const definitions = { storeContexts: [], options: { direction: 1 }, }, + // TOOLS + setZoomTool: { + commandFn: actions.setCornerstoneToolActive, + storeContexts: [], + options: { toolName: 'Zoom' }, + }, }; /** From 49fe19508934a7c1bfeb2051c49342603dece353 Mon Sep 17 00:00:00 2001 From: dannyrb Date: Wed, 5 Jun 2019 21:29:50 -0400 Subject: [PATCH 17/26] Change HotkeyDefinition to include label --- public/config/default.js | 74 +++++++++++++++++++++++----------------- src/App.js | 13 ++----- 2 files changed, 46 insertions(+), 41 deletions(-) diff --git a/public/config/default.js b/public/config/default.js index c53ad9274..c9e2bd5ab 100644 --- a/public/config/default.js +++ b/public/config/default.js @@ -19,44 +19,56 @@ window.config = { }, ], }, - // - userPreferences: [ - { - order: 0, - title: 'preference page title', - items: [ - { - order: 0, - label: 'Scale Viewport Up', - command: 'scaleUpViewport', - }, - ], - }, - ], - // - hotkeys: { + /** + * This maps registered commands to default hotkey values. + */ + hotkeys: [ // ~ Global - incrementActiveViewport: ['right'], - decrementActiveViewport: ['left'], + { + commandName: 'incrementActiveViewport', + label: 'Next Image Viewport', + keys: ['right'], + }, + { + commandName: 'decrementActiveViewport', + label: 'Previous Image Viewport', + keys: ['left'], + }, // Supported Keys: https://craig.is/killing/mice // ~ Cornerstone Extension - rotateViewportCW: ['r'], - rotateViewportCCW: ['l'], - invertViewport: ['i'], - flipViewportVertical: ['h'], - flipViewportHorizontal: ['v'], - scaleUpViewport: ['+'], - scaleDownViewport: ['-'], - fitViewportToWindow: ['='], - resetViewport: ['space'], + { commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] }, + { commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] }, + { commandName: 'invertViewport', label: 'Invert', keys: ['i'] }, + { + commandName: 'flipViewportVertical', + label: 'Flip Horizontally', + keys: ['h'], + }, + { + commandName: 'flipViewportHorizontal', + label: 'Flip Vertically', + keys: ['v'], + }, + { commandName: 'scaleUpViewport', label: 'Zoom In', keys: ['+'] }, + { commandName: 'scaleDownViewport', label: 'Zoom Out', keys: ['-'] }, + { commandName: 'fitViewportToWindow', label: 'Zoom to Fit', keys: ['='] }, + { commandName: 'resetViewport', label: 'Reset', keys: ['space'] }, // clearAnnotations // nextImage // previousImage // firstImage // lastImage - nextViewportDisplaySet: ['pageup'], - previousViewportDisplaySet: ['pagedown'], + { + commandName: 'nextViewportDisplaySet', + label: 'Previous Series', + keys: ['pagedown'], + }, + { + commandName: 'previousViewportDisplaySet', + label: 'Next Series', + keys: ['pageup'], + }, // ~ Cornerstone Tools - setZoomTool: ['z'], - }, + { commandName: 'setZoomTool', label: 'Zoom', keys: ['z'] }, + ], }; diff --git a/src/App.js b/src/App.js index 3273ba325..895d69192 100644 --- a/src/App.js +++ b/src/App.js @@ -40,16 +40,9 @@ const _commandsManager = new CommandsManager(commandsManagerConfig); const _hotkeysManager = new HotkeysManager(_commandsManager); // TODO: Should be done in extensions w/ commandsModule -// ADD COMMANDS +// ~~ ADD COMMANDS appCommands.init(_commandsManager); - -// window.config.userPreferences -Object.keys(window.config.hotkeys).forEach(commandName => { - const keys = window.config.hotkeys[commandName]; - // HotkeysManager.set(); - console.log(`registering hotkey: ${commandName} to binding: ${keys}`); - _hotkeysManager.registerHotkeys(commandName, keys); -}); +_hotkeysManager.setHotkeys(window.config.hotkeys, true); // Force active contexts for now. These should be set in Viewer/ActiveViewer store.dispatch({ @@ -61,7 +54,7 @@ store.dispatch({ item: 'VIEWER::CORNERSTONE', }); -// ~~~~ END EPP SETUP +// ~~~~ END APP SETUP setupTools(store); From f16812c81ff1f5471b1353a1bd9a03888094f40e Mon Sep 17 00:00:00 2001 From: dannyrb Date: Wed, 5 Jun 2019 21:30:18 -0400 Subject: [PATCH 18/26] Remove FA external dep include --- public/index.html | 6 ------ 1 file changed, 6 deletions(-) diff --git a/public/index.html b/public/index.html index f644ad78e..ca03fcdde 100644 --- a/public/index.html +++ b/public/index.html @@ -29,12 +29,6 @@ href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700|Sanchez&display=swap" rel="stylesheet" /> - From 2d6f9c1a0503fb02b67d7b13f28cb6b330bcba4b Mon Sep 17 00:00:00 2001 From: dannyrb Date: Thu, 6 Jun 2019 09:15:12 -0400 Subject: [PATCH 19/26] A way to tell CircleCI not to run for this change --- publishStaticSite.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/publishStaticSite.sh b/publishStaticSite.sh index 26c3e89c6..cd5529882 100755 --- a/publishStaticSite.sh +++ b/publishStaticSite.sh @@ -9,4 +9,5 @@ echo 'PUBLISHING' ./node_modules/.bin/gh-pages \ --silent \ --repo https://$GITHUB_TOKEN@github.com/OHIF/Viewers.git \ + --message 'Autogenerated Message: [ci skip]' \ --dist docs/latest/_book From e0cbc8ef4dff3f57d2e349878d28364f3558922c Mon Sep 17 00:00:00 2001 From: dannyrb Date: Thu, 6 Jun 2019 14:41:31 -0400 Subject: [PATCH 20/26] No need for connected user preferences when header can contain info --- src/App.js | 10 +-- src/components/Header/Header.js | 62 +++++++++++++++---- src/connectedComponents/ConnectedHeader.js | 14 +---- .../ConnectedUserPreferencesModal.js | 48 -------------- src/store/layout/actions.js | 6 -- src/store/layout/reducers.js | 5 -- 6 files changed, 58 insertions(+), 87 deletions(-) delete mode 100644 src/connectedComponents/ConnectedUserPreferencesModal.js diff --git a/src/App.js b/src/App.js index 895d69192..ec51550c8 100644 --- a/src/App.js +++ b/src/App.js @@ -36,13 +36,13 @@ const commandsManagerConfig = { getActiveContexts: () => store.getState().ui.activeContexts, }; -const _commandsManager = new CommandsManager(commandsManagerConfig); -const _hotkeysManager = new HotkeysManager(_commandsManager); +const commandsManager = new CommandsManager(commandsManagerConfig); +const hotkeysManager = new HotkeysManager(commandsManager); // TODO: Should be done in extensions w/ commandsModule // ~~ ADD COMMANDS -appCommands.init(_commandsManager); -_hotkeysManager.setHotkeys(window.config.hotkeys, true); +appCommands.init(commandsManager); +hotkeysManager.setHotkeys(window.config.hotkeys, true); // Force active contexts for now. These should be set in Viewer/ActiveViewer store.dispatch({ @@ -148,3 +148,5 @@ class App extends Component { } export default App; + +export { commandsManager, hotkeysManager }; diff --git a/src/components/Header/Header.js b/src/components/Header/Header.js index 51c3ad744..9d53d8215 100644 --- a/src/components/Header/Header.js +++ b/src/components/Header/Header.js @@ -1,16 +1,18 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { Link, withRouter } from 'react-router-dom'; -import { Dropdown } from 'react-viewerbase'; import './Header.css'; + +import { Link, withRouter } from 'react-router-dom'; +import React, { Component } from 'react'; + +import { Dropdown } from 'react-viewerbase'; import OHIFLogo from '../OHIFLogo/OHIFLogo.js'; -import ConnectedUserPreferencesModal from '../../connectedComponents/ConnectedUserPreferencesModal.js'; +import PropTypes from 'prop-types'; +import { UserPreferencesModal } from 'react-viewerbase'; +import { hotkeysManager } from './../../App.js'; class Header extends Component { static propTypes = { home: PropTypes.bool.isRequired, location: PropTypes.object.isRequired, - openUserPreferencesModal: PropTypes.func, children: PropTypes.node, }; @@ -19,12 +21,25 @@ class Header extends Component { children: OHIFLogo(), }; + // 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(); + // }, + constructor(props) { super(props); + this.state = { isUserPreferencesOpen: false }; - this.state = { - userPreferencesOpen: false, - }; + const onClick = this.toggleUserPreferences.bind(this); this.options = [ { @@ -32,7 +47,7 @@ class Header extends Component { icon: { name: 'user', }, - onClick: this.props.openUserPreferencesModal, + onClick: onClick, }, { title: 'About', @@ -42,6 +57,24 @@ class Header extends Component { link: 'http://ohif.org', }, ]; + + this.hotKeysData = hotkeysManager.hotkeyDefinitions; + } + + toggleUserPreferences() { + const isOpen = this.state.isUserPreferencesOpen; + + this.setState({ + isUserPreferencesOpen: !isOpen, + }); + } + + onUserPreferencesSave({ windowLevelData, hotKeysData }) { + console.log(windowLevelData); + console.log(hotKeysData); + + // TODO: Update hotkeysManager + // TODO: reset `this.hotKeysData` } render() { @@ -75,7 +108,14 @@ class Header extends Component {
INVESTIGATIONAL USE ONLY - +
); diff --git a/src/connectedComponents/ConnectedHeader.js b/src/connectedComponents/ConnectedHeader.js index 73644c976..1f39315b6 100644 --- a/src/connectedComponents/ConnectedHeader.js +++ b/src/connectedComponents/ConnectedHeader.js @@ -1,6 +1,5 @@ import Header from '../components/Header/Header.js'; import { connect } from 'react-redux'; -import { setUserPreferencesModalOpen } from '../store/layout/actions.js'; const mapStateToProps = state => { return { @@ -8,17 +7,6 @@ const mapStateToProps = state => { }; }; -const mapDispatchToProps = dispatch => { - return { - openUserPreferencesModal: () => { - dispatch(setUserPreferencesModalOpen(true)); - }, - }; -}; - -const ConnectedHeader = connect( - mapStateToProps, - mapDispatchToProps -)(Header); +const ConnectedHeader = connect(mapStateToProps)(Header); export default ConnectedHeader; diff --git a/src/connectedComponents/ConnectedUserPreferencesModal.js b/src/connectedComponents/ConnectedUserPreferencesModal.js deleted file mode 100644 index 26ed515eb..000000000 --- a/src/connectedComponents/ConnectedUserPreferencesModal.js +++ /dev/null @@ -1,48 +0,0 @@ -import OHIF from 'ohif-core'; -import { UserPreferencesModal } from 'react-viewerbase'; -import cloneDeep from 'lodash.clonedeep'; -import { connect } from 'react-redux'; -import { setUserPreferencesModalOpen } from './../store/layout/actions.js'; - -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; diff --git a/src/store/layout/actions.js b/src/store/layout/actions.js index 54e3a16ca..319628c08 100644 --- a/src/store/layout/actions.js +++ b/src/store/layout/actions.js @@ -23,11 +23,6 @@ export const setRightSidebarOpen = state => ({ state, }); -export const setUserPreferencesModalOpen = state => ({ - type: 'SET_USER_PREFERENCES_MODAL_OPEN', - state, -}); - const actions = { addActiveContext, removeActiveContext, @@ -35,7 +30,6 @@ const actions = { // setLeftSidebarOpen, setRightSidebarOpen, - setUserPreferencesModalOpen, }; export default actions; diff --git a/src/store/layout/reducers.js b/src/store/layout/reducers.js index ea87caef4..6659d72a7 100644 --- a/src/store/layout/reducers.js +++ b/src/store/layout/reducers.js @@ -1,7 +1,6 @@ const defaultState = { leftSidebarOpen: true, rightSidebarOpen: false, - userPreferencesModalOpen: false, labelling: {}, contextMenu: {}, activeContexts: [], @@ -30,10 +29,6 @@ const ui = (state = defaultState, action) => { return Object.assign({}, state, { leftSidebarOpen: action.state }); case 'SET_RIGHT_SIDEBAR_OPEN': return Object.assign({}, state, { rightSidebarOpen: action.state }); - case 'SET_USER_PREFERENCES_MODAL_OPEN': - return Object.assign({}, state, { - userPreferencesModalOpen: action.state, - }); case 'SET_LABELLING_FLOW_DATA': { const labelling = Object.assign({}, action.labellingFlowData); From a119ca33f052ba4c46ba34c921eee1d8ec1374b7 Mon Sep 17 00:00:00 2001 From: dannyrb Date: Thu, 6 Jun 2019 15:01:32 -0400 Subject: [PATCH 21/26] Hide our user preferences modal for now --- src/components/Header/Header.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/Header/Header.js b/src/components/Header/Header.js index 9d53d8215..774f07c9f 100644 --- a/src/components/Header/Header.js +++ b/src/components/Header/Header.js @@ -6,7 +6,7 @@ import React, { Component } from 'react'; import { Dropdown } from 'react-viewerbase'; import OHIFLogo from '../OHIFLogo/OHIFLogo.js'; import PropTypes from 'prop-types'; -import { UserPreferencesModal } from 'react-viewerbase'; +// import { UserPreferencesModal } from 'react-viewerbase'; import { hotkeysManager } from './../../App.js'; class Header extends Component { @@ -42,13 +42,13 @@ class Header extends Component { const onClick = this.toggleUserPreferences.bind(this); this.options = [ - { - title: 'Preferences ', - icon: { - name: 'user', - }, - onClick: onClick, - }, + // { + // title: 'Preferences ', + // icon: { + // name: 'user', + // }, + // onClick: onClick, + // }, { title: 'About', icon: { @@ -108,14 +108,14 @@ class Header extends Component {
INVESTIGATIONAL USE ONLY - + /> */}
); From 5325d2d2fe600a44699ea5251e1f67de552caf9a Mon Sep 17 00:00:00 2001 From: dannyrb Date: Sat, 8 Jun 2019 15:08:09 -0400 Subject: [PATCH 22/26] bump versions --- package.json | 2 +- yarn.lock | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index b2408d0bf..99e807d32 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "hammerjs": "^2.0.8", "lodash.isequal": "4.5.0", "moment": "^2.24.0", - "ohif-core": "0.5.6", + "ohif-core": "0.5.7", "ohif-dicom-html-extension": "^0.0.2", "ohif-dicom-pdf-extension": "^0.0.6", "oidc-client": "1.7.x", diff --git a/yarn.lock b/yarn.lock index a69761b26..7f5197da6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7609,11 +7609,6 @@ joi@^11.1.1: isemail "3.x.x" topo "2.x.x" -jquery@^3.3.1: - version "3.4.0" - resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.0.tgz#8de513fa0fa4b2c7d2e48a530e26f0596936efdf" - integrity sha512-ggRCXln9zEqv6OqAGXFEcshF5dSBvCkzj6Gm2gzuR5fWawaX8t7cxKVkkygKODrDAzKdoYw3l/e3pm3vlT4IbQ== - js-levenshtein@^1.1.3: version "1.1.6" resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d" @@ -8938,6 +8933,11 @@ moment@2.24.0, moment@>=1.6.0, moment@^2.23.0, moment@^2.24.0: resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== +mousetrap@^1.6.3: + version "1.6.3" + resolved "https://registry.yarnpkg.com/mousetrap/-/mousetrap-1.6.3.tgz#80fee49665fd478bccf072c9d46bdf1bfed3558a" + integrity sha512-bd+nzwhhs9ifsUrC2tWaSgm24/oo2c83zaRyZQF06hYA6sANfsXHtnZ19AbbbDXCDzeH5nZBSQ4NvCjgD62tJA== + move-concurrently@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" @@ -9702,18 +9702,18 @@ octokit-pagination-methods@^1.1.0: resolved "https://registry.yarnpkg.com/octokit-pagination-methods/-/octokit-pagination-methods-1.1.0.tgz#cf472edc9d551055f9ef73f6e42b4dbb4c80bea4" integrity sha512-fZ4qZdQ2nxJvtcasX7Ghl+WlWS/d9IgnBIwFZXVNNZUmzpno91SX5bc5vuxiuKoCtK78XxGGNuSCrDC7xYB3OQ== -ohif-core@0.5.6: - version "0.5.6" - resolved "https://registry.yarnpkg.com/ohif-core/-/ohif-core-0.5.6.tgz#b626b2fdd5bebf53f4914a8701bedf2dc2c43b71" - integrity sha512-qe5/tHjnyPAGoNEdYZBJEejojBBOE9G5Y54vuTUnu2TUKkdbFPg2Kj/WzmLBMG/3PHcjMCqFm1JllEwcntRvOg== +ohif-core@0.5.7: + version "0.5.7" + resolved "https://registry.yarnpkg.com/ohif-core/-/ohif-core-0.5.7.tgz#698ddff79f96fec68e4546e0386c850d5dccdd18" + integrity sha512-ymxojfaTQqsRZ5X2T2zWlVlTMUOYAz0V8BFgo6aWyZTEHw2u/3GgKPZiZ/OeRNTQOmam+KQj1jncuYbOecZ4nQ== dependencies: "@babel/runtime" "^7.2.0" ajv "^6.10.0" dicomweb-client "^0.4.2" isomorphic-base64 "^1.0.2" - jquery "^3.3.1" lodash.clonedeep "^4.5.0" lodash.merge "^4.6.1" + mousetrap "^1.6.3" validate.js "^0.12.0" ohif-dicom-html-extension@^0.0.2: From 7f95c1284a847e02e310509920968c560089fcd6 Mon Sep 17 00:00:00 2001 From: dannyrb Date: Sat, 8 Jun 2019 15:12:19 -0400 Subject: [PATCH 23/26] fix: command direction for previousViewportDisplaySet --- src/appCommands/cornerstone.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/appCommands/cornerstone.js b/src/appCommands/cornerstone.js index 53e6ea7cc..110dda402 100644 --- a/src/appCommands/cornerstone.js +++ b/src/appCommands/cornerstone.js @@ -163,7 +163,7 @@ const definitions = { previousViewportDisplaySet: { commandFn: actions.updateViewportDisplaySet, storeContexts: [], - options: { direction: 1 }, + options: { direction: -1 }, }, // TOOLS setZoomTool: { From 78068bed4b994bca5a3e805828542b0ffe966caa Mon Sep 17 00:00:00 2001 From: dannyrb Date: Sat, 8 Jun 2019 15:25:39 -0400 Subject: [PATCH 24/26] push default hotkeys to a shared location --- public/config/default.js | 55 ++----------------------------- public/config/defaults/hotkeys.js | 52 +++++++++++++++++++++++++++++ public/config/netlify.js | 5 ++- 3 files changed, 59 insertions(+), 53 deletions(-) create mode 100644 public/config/defaults/hotkeys.js diff --git a/public/config/default.js b/public/config/default.js index c9e2bd5ab..e33e64e63 100644 --- a/public/config/default.js +++ b/public/config/default.js @@ -1,3 +1,5 @@ +import hotkeys from 'defaults/hotkeys.js'; + window.config = { // default: '/' routerBasename: '/', @@ -19,56 +21,5 @@ window.config = { }, ], }, - /** - * This maps registered commands to default hotkey values. - */ - hotkeys: [ - // ~ Global - { - commandName: 'incrementActiveViewport', - label: 'Next Image Viewport', - keys: ['right'], - }, - { - commandName: 'decrementActiveViewport', - label: 'Previous Image Viewport', - keys: ['left'], - }, - // Supported Keys: https://craig.is/killing/mice - // ~ Cornerstone Extension - { commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] }, - { commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] }, - { commandName: 'invertViewport', label: 'Invert', keys: ['i'] }, - { - commandName: 'flipViewportVertical', - label: 'Flip Horizontally', - keys: ['h'], - }, - { - commandName: 'flipViewportHorizontal', - label: 'Flip Vertically', - keys: ['v'], - }, - { commandName: 'scaleUpViewport', label: 'Zoom In', keys: ['+'] }, - { commandName: 'scaleDownViewport', label: 'Zoom Out', keys: ['-'] }, - { commandName: 'fitViewportToWindow', label: 'Zoom to Fit', keys: ['='] }, - { commandName: 'resetViewport', label: 'Reset', keys: ['space'] }, - // clearAnnotations - // nextImage - // previousImage - // firstImage - // lastImage - { - commandName: 'nextViewportDisplaySet', - label: 'Previous Series', - keys: ['pagedown'], - }, - { - commandName: 'previousViewportDisplaySet', - label: 'Next Series', - keys: ['pageup'], - }, - // ~ Cornerstone Tools - { commandName: 'setZoomTool', label: 'Zoom', keys: ['z'] }, - ], + hotkeys, }; diff --git a/public/config/defaults/hotkeys.js b/public/config/defaults/hotkeys.js new file mode 100644 index 000000000..284ffbd86 --- /dev/null +++ b/public/config/defaults/hotkeys.js @@ -0,0 +1,52 @@ +/** + * This maps registered commands to default hotkey values. + */ +export default [ + // ~ Global + { + commandName: 'incrementActiveViewport', + label: 'Next Image Viewport', + keys: ['right'], + }, + { + commandName: 'decrementActiveViewport', + label: 'Previous Image Viewport', + keys: ['left'], + }, + // Supported Keys: https://craig.is/killing/mice + // ~ Cornerstone Extension + { commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] }, + { commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] }, + { commandName: 'invertViewport', label: 'Invert', keys: ['i'] }, + { + commandName: 'flipViewportVertical', + label: 'Flip Horizontally', + keys: ['h'], + }, + { + commandName: 'flipViewportHorizontal', + label: 'Flip Vertically', + keys: ['v'], + }, + { commandName: 'scaleUpViewport', label: 'Zoom In', keys: ['+'] }, + { commandName: 'scaleDownViewport', label: 'Zoom Out', keys: ['-'] }, + { commandName: 'fitViewportToWindow', label: 'Zoom to Fit', keys: ['='] }, + { commandName: 'resetViewport', label: 'Reset', keys: ['space'] }, + // clearAnnotations + // nextImage + // previousImage + // firstImage + // lastImage + { + commandName: 'nextViewportDisplaySet', + label: 'Previous Series', + keys: ['pagedown'], + }, + { + commandName: 'previousViewportDisplaySet', + label: 'Next Series', + keys: ['pageup'], + }, + // ~ Cornerstone Tools + { commandName: 'setZoomTool', label: 'Zoom', keys: ['z'] }, +]; diff --git a/public/config/netlify.js b/public/config/netlify.js index 179130c9a..9f2669a25 100644 --- a/public/config/netlify.js +++ b/public/config/netlify.js @@ -1,3 +1,5 @@ +import hotkeys from 'defaults/hotkeys.js'; + window.config = { routerBasename: '/demo', servers: { @@ -16,4 +18,5 @@ window.config = { }, ], }, -} + hotkeys, +}; From 9d9b3a77492b0a72d8328a7879b1780e925e6c2a Mon Sep 17 00:00:00 2001 From: dannyrb Date: Sat, 8 Jun 2019 15:26:11 -0400 Subject: [PATCH 25/26] Make sure our app doesn't blow up if hotkeys aren't provided --- src/App.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/App.js b/src/App.js index f99be84fe..3c2a90e37 100644 --- a/src/App.js +++ b/src/App.js @@ -42,7 +42,9 @@ const hotkeysManager = new HotkeysManager(commandsManager); // TODO: Should be done in extensions w/ commandsModule // ~~ ADD COMMANDS appCommands.init(commandsManager); -hotkeysManager.setHotkeys(window.config.hotkeys, true); +if (window.config.hotkeys) { + hotkeysManager.setHotkeys(window.config.hotkeys, true); +} // Force active contexts for now. These should be set in Viewer/ActiveViewer store.dispatch({ From fddcc746d922221d31f154b69513e89f8a45b100 Mon Sep 17 00:00:00 2001 From: dannyrb Date: Sat, 8 Jun 2019 21:23:00 -0400 Subject: [PATCH 26/26] No ESM in vanilla modules --- public/config/default.js | 54 +++++++++++++++++++++++++++++-- public/config/defaults/hotkeys.js | 52 ----------------------------- public/config/netlify.js | 52 +++++++++++++++++++++++++++-- 3 files changed, 100 insertions(+), 58 deletions(-) delete mode 100644 public/config/defaults/hotkeys.js diff --git a/public/config/default.js b/public/config/default.js index e33e64e63..2c7c92018 100644 --- a/public/config/default.js +++ b/public/config/default.js @@ -1,5 +1,3 @@ -import hotkeys from 'defaults/hotkeys.js'; - window.config = { // default: '/' routerBasename: '/', @@ -21,5 +19,55 @@ window.config = { }, ], }, - hotkeys, + // Extensions should be able to suggest default values for these? + // Or we can require that these be explicitly set + hotkeys: [ + // ~ Global + { + commandName: 'incrementActiveViewport', + label: 'Next Image Viewport', + keys: ['right'], + }, + { + commandName: 'decrementActiveViewport', + label: 'Previous Image Viewport', + keys: ['left'], + }, + // Supported Keys: https://craig.is/killing/mice + // ~ Cornerstone Extension + { commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] }, + { commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] }, + { commandName: 'invertViewport', label: 'Invert', keys: ['i'] }, + { + commandName: 'flipViewportVertical', + label: 'Flip Horizontally', + keys: ['h'], + }, + { + commandName: 'flipViewportHorizontal', + label: 'Flip Vertically', + keys: ['v'], + }, + { commandName: 'scaleUpViewport', label: 'Zoom In', keys: ['+'] }, + { commandName: 'scaleDownViewport', label: 'Zoom Out', keys: ['-'] }, + { commandName: 'fitViewportToWindow', label: 'Zoom to Fit', keys: ['='] }, + { commandName: 'resetViewport', label: 'Reset', keys: ['space'] }, + // clearAnnotations + // nextImage + // previousImage + // firstImage + // lastImage + { + commandName: 'nextViewportDisplaySet', + label: 'Previous Series', + keys: ['pagedown'], + }, + { + commandName: 'previousViewportDisplaySet', + label: 'Next Series', + keys: ['pageup'], + }, + // ~ Cornerstone Tools + { commandName: 'setZoomTool', label: 'Zoom', keys: ['z'] }, + ], }; diff --git a/public/config/defaults/hotkeys.js b/public/config/defaults/hotkeys.js deleted file mode 100644 index 284ffbd86..000000000 --- a/public/config/defaults/hotkeys.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * This maps registered commands to default hotkey values. - */ -export default [ - // ~ Global - { - commandName: 'incrementActiveViewport', - label: 'Next Image Viewport', - keys: ['right'], - }, - { - commandName: 'decrementActiveViewport', - label: 'Previous Image Viewport', - keys: ['left'], - }, - // Supported Keys: https://craig.is/killing/mice - // ~ Cornerstone Extension - { commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] }, - { commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] }, - { commandName: 'invertViewport', label: 'Invert', keys: ['i'] }, - { - commandName: 'flipViewportVertical', - label: 'Flip Horizontally', - keys: ['h'], - }, - { - commandName: 'flipViewportHorizontal', - label: 'Flip Vertically', - keys: ['v'], - }, - { commandName: 'scaleUpViewport', label: 'Zoom In', keys: ['+'] }, - { commandName: 'scaleDownViewport', label: 'Zoom Out', keys: ['-'] }, - { commandName: 'fitViewportToWindow', label: 'Zoom to Fit', keys: ['='] }, - { commandName: 'resetViewport', label: 'Reset', keys: ['space'] }, - // clearAnnotations - // nextImage - // previousImage - // firstImage - // lastImage - { - commandName: 'nextViewportDisplaySet', - label: 'Previous Series', - keys: ['pagedown'], - }, - { - commandName: 'previousViewportDisplaySet', - label: 'Next Series', - keys: ['pageup'], - }, - // ~ Cornerstone Tools - { commandName: 'setZoomTool', label: 'Zoom', keys: ['z'] }, -]; diff --git a/public/config/netlify.js b/public/config/netlify.js index 9f2669a25..49cd1e7e4 100644 --- a/public/config/netlify.js +++ b/public/config/netlify.js @@ -1,5 +1,3 @@ -import hotkeys from 'defaults/hotkeys.js'; - window.config = { routerBasename: '/demo', servers: { @@ -18,5 +16,53 @@ window.config = { }, ], }, - hotkeys, + hotkeys: [ + // ~ Global + { + commandName: 'incrementActiveViewport', + label: 'Next Image Viewport', + keys: ['right'], + }, + { + commandName: 'decrementActiveViewport', + label: 'Previous Image Viewport', + keys: ['left'], + }, + // Supported Keys: https://craig.is/killing/mice + // ~ Cornerstone Extension + { commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] }, + { commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] }, + { commandName: 'invertViewport', label: 'Invert', keys: ['i'] }, + { + commandName: 'flipViewportVertical', + label: 'Flip Horizontally', + keys: ['h'], + }, + { + commandName: 'flipViewportHorizontal', + label: 'Flip Vertically', + keys: ['v'], + }, + { commandName: 'scaleUpViewport', label: 'Zoom In', keys: ['+'] }, + { commandName: 'scaleDownViewport', label: 'Zoom Out', keys: ['-'] }, + { commandName: 'fitViewportToWindow', label: 'Zoom to Fit', keys: ['='] }, + { commandName: 'resetViewport', label: 'Reset', keys: ['space'] }, + // clearAnnotations + // nextImage + // previousImage + // firstImage + // lastImage + { + commandName: 'nextViewportDisplaySet', + label: 'Previous Series', + keys: ['pagedown'], + }, + { + commandName: 'previousViewportDisplaySet', + label: 'Next Series', + keys: ['pageup'], + }, + // ~ Cornerstone Tools + { commandName: 'setZoomTool', label: 'Zoom', keys: ['z'] }, + ], };