diff --git a/docs/latest/advanced/extensions.md b/docs/latest/advanced/extensions.md index 681c219d5..dc4284271 100644 --- a/docs/latest/advanced/extensions.md +++ b/docs/latest/advanced/extensions.md @@ -8,57 +8,121 @@ 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) + - [Toolbar](#toolbar) + - [Panel](#panel) + - [Viewport](#viewport) + - [SOP Class Handler](#sopclasshandler) ## 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 javascript object that has an `id` property, and +one or more "module" methods. You can find an abbreviated extension below, or +[view the source][example-ext-src] of our example extension. ```js -class myCustomExtension { +export default { + /** + * Only required property. Should be a unique value across all extensions. + */ + id: 'example-extension', - /** Required */ - getExtensionId: () => 'my-extension-id'; + /** + * Registers one or more named commands scoped to a context. Commands are + * the primary means for... + */ + getCommandsModule() { + return { + defaultContext: 'VIEWER' + actions: { ... }, + definitions: { ... } + } + }, - /** React component that receives props from ConnectLayoutManager - * If more than one viewport module is registered, SopClassHandler - * is used to help determine which component is used */ - getViewportModule: () => reactViewportComponent; + /** + * Allows you to provide toolbar definitions that will be merged with any + * existing application toolbar configuration. Used to determine which + * buttons should be visible when, their order, what happens when they're + * clicked, etc. + */ + getToolbarModule() { + return { + definitions: [ ... ], + defaultContext: 'ACTIVE_VIEWPORT::CORNERSTONE' + } + } - /** React component that adds buttons/behavior to the viewer Toolbar */ - getToolbarModule: () => reactToolbarComponent; + /** + * Not yet implemented + */ + getPanelModule: () => null, + + /** + * Registers a ReactComponent that should be used to render data in a + * Viewport. The first registered viewport is our "default viewport". If + * more than one viewport is registered, we use `SopClassHandlers` to + * determine which viewport should be used. + */ + getViewportModule: () => reactViewportComponent, /** Provides a whitelist of SOPClassUIDs the viewport is capable of rendering. * Can modify default behavior for methods like `getDisplaySetFromSeries` */ getSopClassHandler: () => { id: 'some-other-unique-id', - type: PLUGIN_TYPES.SOP_CLASS_HANDLER, - sopClassUids: ['string'], - getDisplaySetFromSeries: (series, study, dicomWebClient, authorizationHeaders) => ... - }; - - // Not yet used - getPanelModule: () => null; + sopClassUids: [ ... ], + getDisplaySetFromSeries: (series, study, dicomWebClient, authorizationHeaders) => { ... } + }, } ``` ### 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 module types. Each module type 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`][module-types]. Information on each type of module, it's API, +and how we determine when/where it should be used is included below. + +> NOTE: Modifying the extensions/modules registered to the OHIF Viewer currently +> requires us to import and pass extensions to the ExtensionManager in +> `src/App.js`, then rebuild the application. Long-term, we intend to make it +> possible to accomplish this without a build step. + +#### Commands + +The Commands Module allows us to register one or more commands scoped to +specific contexts. Commands can be run by [hotkeys][#], [toolbar buttons][#], +and any registered custom react component (like a [viewport][#] or [panel][#]). +Here is a simple example commands module: + +```js +{ + getCommandsModule() { + return { + actions: { + speak: ({ viewports, words }) => { + console.log(viewports, words); + }, + }, + definitions: { + rotateViewportCW: { + commandFn: actions.rotateViewport, + storeContexts: ['viewports'], + options: { rotation: 90 } + }, + rotateViewportCCW: { + commandFn: actions.rotateViewport, + storeContexts: ['viewports'], + options: { rotation: -90 }, + context: 'ACTIVE_VIEWER::CORNERSTONE' + }, + }, + defaultContext: 'VIEWER' + } + } +} +``` #### Viewport @@ -114,10 +178,6 @@ For a complete example implementation, > The panel module is not yet in use. -#### Commands - -... - #### Hotkeys ... @@ -159,3 +219,12 @@ top level [`extensions/`](https://github.com/OHIF/Viewers/tree/react/extensions) directory. {% include "./_maintained-extensions-table.md" %} + + + + +[example-ext-src]: https://github.com/OHIF/Viewers/blob/master/extensions/_ohif-example-extension/src/index.js) +[module-types]: https://github.com/OHIF/ohif-core/blob/43c08a29eff3fb646a0e83a03a236ddd84f4a6e8/src/plugins.js#L1-L6 + diff --git a/extensions/_ohif-example-extension/src/index.js b/extensions/_ohif-example-extension/src/index.js new file mode 100644 index 000000000..2d4e65b30 --- /dev/null +++ b/extensions/_ohif-example-extension/src/index.js @@ -0,0 +1,69 @@ +/** + * + */ +export default { + /** + * Only required property. Should be a unique value across all extensions. + */ + id: 'example-extension', + + getViewportModule() {}, + getSopClassHandlerModule() { + return sopClassHandlerModule; + }, + getPanelModule() {}, + getToolbarModule() {}, + getCommandsModule(/* store */) { + return commandsModule; + }, +}; + +/** + * + */ +const commandsModule = { + actions: { + // Store Contexts + Options + exampleAction: ({ viewports, param1 }) => { + console.log(`There are ${viewports.length} viewports`); + console.log(`param1's value is: ${param1}`); + }, + }, + definitions: { + exampleActionDef: { + commandFn: this.actions.exampleAction, + storeContexts: ['viewports'], + options: { param1: 'hello world' }, + }, + }, +}; + +/** + * + */ +const sopClassHandlerModule = { + id: 'OHIFDicomHtmlSopClassHandler', + sopClassUids: Object.values({ + BASIC_TEXT_SR: '1.2.840.10008.5.1.4.1.1.88.11', + ENHANCED_SR: '1.2.840.10008.5.1.4.1.1.88.22', + COMPREHENSIVE_SR: '1.2.840.10008.5.1.4.1.1.88.33', + PROCEDURE_LOG_STORAGE: '1.2.840.10008.5.1.4.1.1.88.40', + MAMMOGRAPHY_CAD_SR: '1.2.840.10008.5.1.4.1.1.88.50', + CHEST_CAD_SR: '1.2.840.10008.5.1.4.1.1.88.65', + X_RAY_RADIATION_DOSE_SR: '1.2.840.10008.5.1.4.1.1.88.67', + }), + getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) { + const instance = series.getFirstInstance(); + + return { + plugin: 'html', + displaySetInstanceUid: 0, //utils.guid(), + wadoRoot: study.getData().wadoRoot, + wadoUri: instance.getData().wadouri, + sopInstanceUid: instance.getSOPInstanceUID(), + seriesInstanceUid: series.getSeriesInstanceUID(), + studyInstanceUid: study.getStudyInstanceUID(), + authorizationHeaders, + }; + }, +}; diff --git a/extensions/ohif-cornerstone-extension/.prettierrc b/extensions/ohif-cornerstone-extension/.prettierrc index 92cde390a..12348ec6a 100644 --- a/extensions/ohif-cornerstone-extension/.prettierrc +++ b/extensions/ohif-cornerstone-extension/.prettierrc @@ -1,3 +1,9 @@ { - "singleQuote": true -} \ No newline at end of file + "trailingComma": "es5", + "printWidth": 80, + "proseWrap": "always", + "tabWidth": 2, + "semi": true, + "singleQuote": true, + "endOfLine": "lf" +} diff --git a/extensions/ohif-cornerstone-extension/README.md b/extensions/ohif-cornerstone-extension/README.md index 199da64ca..cc3d8c10c 100644 --- a/extensions/ohif-cornerstone-extension/README.md +++ b/extensions/ohif-cornerstone-extension/README.md @@ -1,54 +1,89 @@ # @ohif/extension-cornerstone -## Commands +![npm (scoped)](https://img.shields.io/npm/v/@ohif/extension-cornerstone.svg?style=flat-square) -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. +This extension adds support for viewing and manipulating 2D medical images via a +viewport. The underlying implementation wraps the +`cornerstonejs/react-cornerstone-viewport`, and provides basic commands and +toolbar buttons for common actions. -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 | +#### Index -### TODO: +Extension Id: `cornerstone` -_SET TOOL_ +- [Commands Module](#commands-module) +- [Toolbar Module](#toolbar-module) +- [Viewport Module](#viewport-module) -- [] 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 +## Commands Module -_OTHER_ +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. -- Show/Hide CINE -- W/L Presets -- W/L Presets config +You can read more about [`Commands`][docs-commands], [`Hotkeys`][docs-hotkeys], +and the [`UserPreferences` Modal][docs-userprefs] 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` | | | +| `previousViewportDisplaySet` | | | +| `setToolActive` | Activates tool for primary button/touch | | + +## Toolbar Module + +Our toolbar module contains definitions for: + +- `StackScroll` +- `Zoom` +- `Wwwc` +- `Pan` +- `Length` +- `Angle` +- `Reset` +- `Cine` + +All use the `ACTIVE_VIEWPORT::CORNERSTONE` context. + +## Viewport Module + +Our Viewport wraps [cornerstonejs/react-cornerstone-viewport][react-viewport] +and is connected the redux store. This module is the most prone to change as we +hammer out our Viewport interface. + +## Resources + +### Repositories + +- [cornerstonejs/react-cornerstone-viewport][react-viewport] +- [cornerstonejs/cornerstoneTools][cornerstone-tools] +- [cornerstonejs/cornerstone][cornerstone] +[docs-commands]: https://www.com +[docs-hotkeys]: https://www.com +[docs-userprefs]: htt +[react-viewport]: https://github.com/cornerstonejs/react-cornerstone-viewport +[cornerstone-tools]: https://github.com/cornerstonejs/cornerstoneTools +[cornerstone]: https://github.com/cornerstonejs/cornerstone diff --git a/extensions/ohif-cornerstone-extension/package.json b/extensions/ohif-cornerstone-extension/package.json index c784b3546..05199b5a3 100644 --- a/extensions/ohif-cornerstone-extension/package.json +++ b/extensions/ohif-cornerstone-extension/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-cornerstone", - "version": "0.0.36", + "version": "0.0.37", "description": "OHIF extension for Cornerstone", "author": "OHIF", "license": "MIT", @@ -26,7 +26,7 @@ "dcmjs": "^0.3.8", "dicom-parser": "^1.8.3", "hammerjs": "^2.0.8", - "ohif-core": "^0.3.5", + "ohif-core": "^0.6.0", "prop-types": "^15.6.2", "react": "^15.0.0 || ^16.0.0", "react-dom": "^15.0.0 || ^16.0.0", diff --git a/extensions/ohif-cornerstone-extension/src/ConnectedCornerstoneViewport.js b/extensions/ohif-cornerstone-extension/src/ConnectedCornerstoneViewport.js index 3188723f4..c92f5e3ee 100644 --- a/extensions/ohif-cornerstone-extension/src/ConnectedCornerstoneViewport.js +++ b/extensions/ohif-cornerstone-extension/src/ConnectedCornerstoneViewport.js @@ -1,16 +1,11 @@ -import { connect } from 'react-redux'; import CornerstoneViewport from 'react-cornerstone-viewport'; import OHIF from 'ohif-core'; +import { connect } from 'react-redux'; import throttle from 'lodash.throttle'; -const { - setViewportActive, - setViewportSpecificData, - clearViewportSpecificData -} = OHIF.redux.actions; +const { setViewportActive, setViewportSpecificData } = OHIF.redux.actions; const mapStateToProps = (state, ownProps) => { - const activeButton = state.tools.buttons.find(tool => tool.active === true); let dataFromStore; if (state.extensions && state.extensions.cornerstone) { @@ -26,12 +21,14 @@ const mapStateToProps = (state, ownProps) => { return { layout: state.viewports.layout, isActive, - activeTool: activeButton && activeButton.command, + // TODO: Need a cleaner and more versatile way. + // Currently justing using escape hatch + commands + // activeTool: activeButton && activeButton.command, ...dataFromStore, enableStackPrefetch: isActive, //stack: viewportSpecificData.stack, cineToolData: viewportSpecificData.cine, - viewport: viewportSpecificData.viewport + viewport: viewportSpecificData.viewport, }; }; @@ -57,7 +54,9 @@ const mapDispatchToProps = (dispatch, ownProps) => { const enabledElement = event.detail.element; dispatch( setViewportSpecificData(viewportIndex, { - dom: enabledElement + // TODO: Hack to make sure our plugin info is available from the outset + plugin: 'cornerstone', + dom: enabledElement, }) ); }, @@ -66,18 +65,18 @@ const mapDispatchToProps = (dispatch, ownProps) => { const { onAdded, onRemoved, - onModified + onModified, } = OHIF.measurements.MeasurementHandlers; const actions = { added: onAdded, removed: onRemoved, modified: throttle(event => { return onModified(event); - }, 300) + }, 300), }; return actions[action](event); - } + }, }; }; diff --git a/extensions/ohif-cornerstone-extension/src/ConnectedToolbarSection.js b/extensions/ohif-cornerstone-extension/src/ConnectedToolbarSection.js deleted file mode 100644 index 0f388535f..000000000 --- a/extensions/ohif-cornerstone-extension/src/ConnectedToolbarSection.js +++ /dev/null @@ -1,29 +0,0 @@ -import { connect } from 'react-redux'; -import { ToolbarSection } from 'react-viewerbase'; -import OHIF from 'ohif-core' - -const { setToolActive } = OHIF.redux.actions; - -const mapStateToProps = state => { - const activeButton = state.tools.buttons.find(tool => tool.active === true); - - return { - buttons: state.tools.buttons, - activeCommand: activeButton && activeButton.command - }; -}; - -const mapDispatchToProps = dispatch => { - return { - setToolActive: tool => { - dispatch(setToolActive(tool.command)) - } - }; -}; - -const ConnectedToolbarSection = connect( - mapStateToProps, - mapDispatchToProps -)(ToolbarSection); - -export default ConnectedToolbarSection; diff --git a/extensions/ohif-cornerstone-extension/src/OHIFCornerstoneExtension.js b/extensions/ohif-cornerstone-extension/src/OHIFCornerstoneExtension.js deleted file mode 100644 index abf239939..000000000 --- a/extensions/ohif-cornerstone-extension/src/OHIFCornerstoneExtension.js +++ /dev/null @@ -1,65 +0,0 @@ -import React, { Component } from 'react'; -import OHIFCornerstoneViewport from './OHIFCornerstoneViewport.js'; -import ToolbarModule from './ToolbarModule.js'; - -/** - * Pass in 'children' to a React component. The purpose of this is to - * allow end users of this extension to pass in components which will - * be rendered on top of the base components. - * - * @param WrappedComponent - * @param children - * @return {function(*): *} - */ -function componentWithProps(WrappedComponent, children, customProps) { - return function(props) { - const extraProps = { - customProps - }; - if (children.viewport) { - extraProps.children = children.viewport; - } - - const mergedProps = Object.assign({}, props, extraProps); - - return ; - }; -} - -// Note: If you are authoring extensions which use stateful libraries (e.g. cornerstone-core, react-redux) as peerDependencies and are also duplicated at the application level, try using 'yalc' to link it to the application, rather than yarn link. This can help fix 'module not found' issues. -// https://github.com/whitecolor/yalc - -export default class OHIFCornerstoneExtension { - constructor(props) { - const { children = {}, customProps = {} } = props; - this.children = children; - this.customProps = customProps; - } - - /** - * Extension ID is a unique id, might be used for namespacing extension specific redux actions/reducers (?) - */ - getExtensionId() { - return 'cornerstone'; - } - - getViewportModule() { - return componentWithProps( - OHIFCornerstoneViewport, - this.children, - this.customProps - ); - } - - getSopClassHandler() { - return null; - } - - getPanelModule() { - return null; - } - - getToolbarModule() { - return ToolbarModule; - } -} diff --git a/extensions/ohif-cornerstone-extension/src/OHIFCornerstoneViewport.js b/extensions/ohif-cornerstone-extension/src/OHIFCornerstoneViewport.js index d23129a81..1c1e1d789 100644 --- a/extensions/ohif-cornerstone-extension/src/OHIFCornerstoneViewport.js +++ b/extensions/ohif-cornerstone-extension/src/OHIFCornerstoneViewport.js @@ -1,9 +1,11 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import OHIF from 'ohif-core'; -import ConnectedCornerstoneViewport from './ConnectedCornerstoneViewport'; -import cornerstone from 'cornerstone-core'; import './config'; + +import React, { Component } from 'react'; + +import ConnectedCornerstoneViewport from './ConnectedCornerstoneViewport'; +import OHIF from 'ohif-core'; +import PropTypes from 'prop-types'; +import cornerstone from 'cornerstone-core'; import handleSegmentationStorage from './handleSegmentationStorage.js'; const { StackManager } = OHIF.utils; @@ -76,11 +78,11 @@ class OHIFCornerstoneViewport extends Component { } if (!studyInstanceUid) { - throw new Error('StudyInstanceUID not provided.') + throw new Error('StudyInstanceUID not provided.'); } if (!displaySetInstanceUid) { - throw new Error('StudyInstanceUID not provided.') + throw new Error('StudyInstanceUID not provided.'); } // Create shortcut to displaySet @@ -123,7 +125,9 @@ class OHIFCornerstoneViewport extends Component { if (index > -1) { stack.currentImageIdIndex = index; } else { - console.warn('SOPInstanceUID provided was not found in specified DisplaySet'); + console.warn( + 'SOPInstanceUID provided was not found in specified DisplaySet' + ); } } diff --git a/extensions/ohif-cornerstone-extension/src/ToolbarModule.js b/extensions/ohif-cornerstone-extension/src/ToolbarModule.js deleted file mode 100644 index ddb5ea30a..000000000 --- a/extensions/ohif-cornerstone-extension/src/ToolbarModule.js +++ /dev/null @@ -1,43 +0,0 @@ -import React, { Component } from 'react'; - -import ConnectedCineDialog from './ConnectedCineDialog'; -import ConnectedToolbarSection from './ConnectedToolbarSection'; -import { ToolbarButton } from 'react-viewerbase'; - -class ToolbarModule extends Component { - state = { - cineDialogOpen: false - }; - - onClickCineToolbarButton = () => { - this.setState({ - cineDialogOpen: !this.state.cineDialogOpen - }); - }; - - render() { - const cineDialogContainerStyle = { - display: this.state.cineDialogOpen ? 'block' : 'none', - position: 'absolute', - top: '82px', - zIndex: 999 - }; - - return ( -
- - -
- -
-
- ); - } -} - -export default ToolbarModule; diff --git a/src/appCommands/cornerstone.js b/extensions/ohif-cornerstone-extension/src/commandsModule.js similarity index 87% rename from src/appCommands/cornerstone.js rename to extensions/ohif-cornerstone-extension/src/commandsModule.js index 110dda402..d87569456 100644 --- a/src/appCommands/cornerstone.js +++ b/extensions/ohif-cornerstone-extension/src/commandsModule.js @@ -1,8 +1,5 @@ import cornerstone from 'cornerstone-core'; -import { redux } from 'ohif-core'; -import store from './../store/'; - -const { setToolActive } = redux.actions; +import cornerstoneTools from 'cornerstone-tools'; const actions = { rotateViewport: ({ viewports, rotation }) => { @@ -80,12 +77,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)); + // TODO: this is receiving `evt` from `ToolbarRow`. We could use it to have + // better mouseButtonMask sets. + setToolActive: ({ toolName }) => { + if (!toolName) { + console.warn('No toolname provided to setToolActive command'); + } + cornerstoneTools.setToolActive(toolName, { mouseButtonMask: 1 }); }, updateViewportDisplaySet: ({ direction }) => { // TODO @@ -130,13 +128,11 @@ const definitions = { options: {}, }, scaleUpViewport: { - keys: '', commandFn: actions.scaleViewport, storeContexts: ['viewports'], options: { direction: 1 }, }, scaleDownViewport: { - keys: '', commandFn: actions.scaleViewport, storeContexts: ['viewports'], options: { direction: -1 }, @@ -166,10 +162,10 @@ const definitions = { options: { direction: -1 }, }, // TOOLS - setZoomTool: { - commandFn: actions.setCornerstoneToolActive, + setToolActive: { + commandFn: actions.setToolActive, storeContexts: [], - options: { toolName: 'Zoom' }, + options: {}, }, }; @@ -182,4 +178,8 @@ function _getActiveViewportEnabledElement(viewports, activeIndex) { return activeViewport.dom; } -export default definitions; +export default { + actions, + definitions, + defaultContext: 'ACTIVE_VIEWPORT::CORNERSTONE', +}; diff --git a/extensions/ohif-cornerstone-extension/src/config.js b/extensions/ohif-cornerstone-extension/src/config.js index 4127c0901..b8f75e7bf 100644 --- a/extensions/ohif-cornerstone-extension/src/config.js +++ b/extensions/ohif-cornerstone-extension/src/config.js @@ -1,7 +1,7 @@ +import Hammer from 'hammerjs'; import cornerstone from 'cornerstone-core'; import cornerstoneMath from 'cornerstone-math'; import cornerstoneTools from 'cornerstone-tools'; -import Hammer from 'hammerjs'; // For debugging window.cornerstoneTools = cornerstoneTools; @@ -17,12 +17,9 @@ const fontFamily = 'Roboto, OpenSans, HelveticaNeue-Light, Helvetica Neue Light, Helvetica Neue, Helvetica, Arial, Lucida Grande, sans-serif'; cornerstoneTools.textStyle.setFont(`16px ${fontFamily}`); -// Set the tool width +// Tool styles/colors cornerstoneTools.toolStyle.setToolWidth(2); -// Set color for inactive tools cornerstoneTools.toolColors.setToolColor('rgb(255, 255, 0)'); - -// Set color for active tools cornerstoneTools.toolColors.setActiveColor('rgb(0, 255, 0)'); cornerstoneTools.store.state.touchProximity = 40; diff --git a/extensions/ohif-cornerstone-extension/src/handleSegmentationStorage.js b/extensions/ohif-cornerstone-extension/src/handleSegmentationStorage.js index c6de26091..a65d667d4 100644 --- a/extensions/ohif-cornerstone-extension/src/handleSegmentationStorage.js +++ b/extensions/ohif-cornerstone-extension/src/handleSegmentationStorage.js @@ -1,7 +1,8 @@ +import * as dcmjs from 'dcmjs'; + import OHIF from 'ohif-core'; import cornerstone from 'cornerstone-core'; import cornerstoneTools from 'cornerstone-tools'; -import * as dcmjs from 'dcmjs'; const { StackManager } = OHIF.utils; @@ -27,28 +28,6 @@ function getDisplaySetsBySeries(studies, studyInstanceUid, seriesInstanceUid) { }); } -function getCornerstoneStack(studies, studyInstanceUid, displaySetInstanceUid) { - const study = studies.find( - study => study.studyInstanceUid === studyInstanceUid - ); - - // Create shortcut to displaySet - const displaySet = getDisplaySet( - studies, - studyInstanceUid, - displaySetInstanceUid - ); - - // Get stack from Stack Manager - const stack = StackManager.findOrCreateStack(study, displaySet); - - // Clone the stack here so we don't mutate it later - const stackClone = Object.assign({}, stack); - stackClone.currentImageIdIndex = 0; - - return stackClone; -} - function parseSeg(arrayBuffer, imageIds) { return dcmjs.adapters.Cornerstone.Segmentation.generateToolState( imageIds, @@ -77,7 +56,7 @@ function retrieveDicomData(wadoUri) { // TODO: Authorization header depends on the server. If we ever have multiple servers // we will need to figure out how / when to pass this information in. return fetch(wadoUri, { - headers: OHIF.DICOMWeb.getAuthorizationHeader() + headers: OHIF.DICOMWeb.getAuthorizationHeader(), }).then(response => response.arrayBuffer()); } @@ -144,7 +123,7 @@ async function handleSegmentationStorage( return { studyInstanceUid, displaySetInstanceUid, - stack + stack, }; } diff --git a/extensions/ohif-cornerstone-extension/src/index.js b/extensions/ohif-cornerstone-extension/src/index.js index 9205d285a..b148fa2e7 100644 --- a/extensions/ohif-cornerstone-extension/src/index.js +++ b/extensions/ohif-cornerstone-extension/src/index.js @@ -1,3 +1,23 @@ -import OHIFCornerstoneExtension from './OHIFCornerstoneExtension.js'; +import OHIFCornerstoneViewport from './OHIFCornerstoneViewport.js'; +import commandsModule from './commandsModule.js'; +import toolbarModule from './toolbarModule.js'; -export default OHIFCornerstoneExtension; +/** + * + */ +export default { + /** + * Only required property. Should be a unique value across all extensions. + */ + id: 'cornerstone', + + getViewportModule() { + return OHIFCornerstoneViewport; + }, + getToolbarModule() { + return toolbarModule; + }, + getCommandsModule() { + return commandsModule; + }, +}; diff --git a/extensions/ohif-cornerstone-extension/src/toolbarModule.js b/extensions/ohif-cornerstone-extension/src/toolbarModule.js new file mode 100644 index 000000000..3fd206ade --- /dev/null +++ b/extensions/ohif-cornerstone-extension/src/toolbarModule.js @@ -0,0 +1,106 @@ +// TODO: A way to add Icons that don't already exist? +// - Register them and add +// - Include SVG Source/Inline? +// - By URL, or own component? + +// TODO: `ohif-core` toolbar builder? + +// What KINDS of toolbar buttons do we have... +// - One's that dispatch commands +// - One's that set tool's active +// - More custom, like CINE +// - Built in for one's like this, or custom components? + +// Visible? +// Disabled? +// Based on contexts or misc. criteria? +// -- ACTIVE_ROUTE::VIEWER +// -- ACTIVE_VIEWPORT::CORNERSTONE +// setToolActive commands should receive the button event that triggered +// so we can do the "bind to this butyon" magic + +const TOOLBAR_BUTTON_TYPES = { + COMMAND: 'command', + SET_TOOL_ACTIVE: 'setToolActive', + BUILT_IN: 'builtIn', +}; + +const definitions = [ + { + id: 'StackScroll', + label: 'Stack Scroll', + icon: 'bars', + // + type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE, + commandName: 'setToolActive', + commandOptions: { toolName: 'StackScroll' }, + }, + { + id: 'Zoom', + label: 'Zoom', + icon: 'search-plus', + // + type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE, + commandName: 'setToolActive', + commandOptions: { toolName: 'Zoom' }, + }, + { + id: 'Wwwc', + label: 'Levels', + icon: 'level', + // + type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE, + commandName: 'setToolActive', + commandOptions: { toolName: 'Wwwc' }, + }, + { + id: 'Pan', + label: 'Pan', + icon: 'arrows', + // + type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE, + commandName: 'setToolActive', + commandOptions: { toolName: 'Pan' }, + }, + { + id: 'Length', + label: 'Length', + icon: 'measure-temp', + // + type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE, + commandName: 'setToolActive', + commandOptions: { toolName: 'Length' }, + }, + { + id: 'Angle', + label: 'Angle', + icon: 'angle-left', + // + type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE, + commandName: 'setToolActive', + commandOptions: { toolName: 'Angle' }, + }, + { + id: 'Reset', + label: 'Reset', + icon: 'reset', + // + type: TOOLBAR_BUTTON_TYPES.COMMAND, + commandName: 'resetViewport', + }, + { + id: 'Cine', + label: 'CINE', + icon: 'youtube', + // + type: TOOLBAR_BUTTON_TYPES.BUILT_IN, + options: { + behavior: 'CINE', + }, + }, +]; + +export default { + definitions, + defaultContext: 'ACTIVE_VIEWPORT::CORNERSTONE', +}; diff --git a/extensions/ohif-dicom-html-extension/.prettierrc b/extensions/ohif-dicom-html-extension/.prettierrc index 92cde390a..12348ec6a 100644 --- a/extensions/ohif-dicom-html-extension/.prettierrc +++ b/extensions/ohif-dicom-html-extension/.prettierrc @@ -1,3 +1,9 @@ { - "singleQuote": true -} \ No newline at end of file + "trailingComma": "es5", + "printWidth": 80, + "proseWrap": "always", + "tabWidth": 2, + "semi": true, + "singleQuote": true, + "endOfLine": "lf" +} diff --git a/extensions/ohif-dicom-html-extension/package.json b/extensions/ohif-dicom-html-extension/package.json index 38681a2c6..723f51b59 100644 --- a/extensions/ohif-dicom-html-extension/package.json +++ b/extensions/ohif-dicom-html-extension/package.json @@ -1,6 +1,6 @@ { - "name": "ohif-dicom-html-extension", - "version": "0.0.2", + "name": "@ohif/extension-dicom-html", + "version": "0.0.3", "description": "OHIF extension for rendering structured reports to HTML", "author": "OHIF", "license": "MIT", @@ -20,11 +20,14 @@ }, "peerDependencies": { "dcmjs": "^0.3.3", - "ohif-core": "^0.2.6", + "ohif-core": "^0.6.0", "prop-types": "^15.6.2", "react": "^15.0.0 || ^16.0.0", "react-dom": "^15.0.0 || ^16.0.0" }, + "dependencies": { + "@babel/runtime": "^7.2.0" + }, "devDependencies": { "@babel/core": "^7.2.2", "@babel/plugin-external-helpers": "^7.2.0", @@ -80,7 +83,7 @@ "files": [ "dist" ], - "dependencies": { - "@babel/runtime": "^7.2.0" + "publishConfig": { + "access": "public" } } diff --git a/extensions/ohif-dicom-html-extension/src/OHIFDicomHtmlExtension.js b/extensions/ohif-dicom-html-extension/src/OHIFDicomHtmlExtension.js deleted file mode 100644 index b661aebf2..000000000 --- a/extensions/ohif-dicom-html-extension/src/OHIFDicomHtmlExtension.js +++ /dev/null @@ -1,27 +0,0 @@ -import OHIFDicomHtmlViewport from './OHIFDicomHtmlViewport.js'; -import OHIFDicomHtmlSopClassHandler from './OHIFDicomHtmlSopClassHandler.js'; - -export default class OHIFDicomHtmlExtension { - /** - * Extension ID is a unique id, might be used for namespacing extension specific redux actions/reducers (?) - */ - getExtensionId() { - return 'html'; - } - - getViewportModule() { - return OHIFDicomHtmlViewport; - } - - getSopClassHandler() { - return OHIFDicomHtmlSopClassHandler; - } - - getPanelModuleDefinition() { - return null; - } - - getToolbarModuleDefinition() { - return null; - } -} diff --git a/extensions/ohif-dicom-html-extension/src/OHIFDicomHtmlSopClassHandler.js b/extensions/ohif-dicom-html-extension/src/OHIFDicomHtmlSopClassHandler.js index caef91682..7423da779 100644 --- a/extensions/ohif-dicom-html-extension/src/OHIFDicomHtmlSopClassHandler.js +++ b/extensions/ohif-dicom-html-extension/src/OHIFDicomHtmlSopClassHandler.js @@ -1,7 +1,4 @@ -import OHIF from 'ohif-core'; - -const { plugins, utils } = OHIF; -const { PLUGIN_TYPES } = plugins; +import { MODULE_TYPES, utils } from 'ohif-core'; // TODO: Should probably use dcmjs for this const SOP_CLASS_UIDS = { @@ -20,7 +17,7 @@ const sopClassUids = Object.values(SOP_CLASS_UIDS); // same SOP Class const OHIFDicomHtmlSopClassHandler = { id: 'OHIFDicomHtmlSopClassHandler', - type: PLUGIN_TYPES.SOP_CLASS_HANDLER, + type: MODULE_TYPES.SOP_CLASS_HANDLER, sopClassUids, getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) { const instance = series.getFirstInstance(); diff --git a/extensions/ohif-dicom-html-extension/src/index.js b/extensions/ohif-dicom-html-extension/src/index.js index 6a940f7b1..c49488f4c 100644 --- a/extensions/ohif-dicom-html-extension/src/index.js +++ b/extensions/ohif-dicom-html-extension/src/index.js @@ -1,3 +1,16 @@ -import OHIFDicomHtmlExtension from './OHIFDicomHtmlExtension.js'; +import OHIFDicomHtmlSopClassHandler from './OHIFDicomHtmlSopClassHandler.js'; +import OHIFDicomHtmlViewport from './OHIFDicomHtmlViewport.js'; -export default OHIFDicomHtmlExtension; +export default { + /** + * Only required property. Should be a unique value across all extensions. + */ + id: 'html', + + getViewportModule() { + return OHIFDicomHtmlViewport; + }, + getSopClassHandlerModule() { + return OHIFDicomHtmlSopClassHandler; + } +}; diff --git a/extensions/ohif-dicom-microscopy-extension/.prettierrc b/extensions/ohif-dicom-microscopy-extension/.prettierrc index 92cde390a..12348ec6a 100644 --- a/extensions/ohif-dicom-microscopy-extension/.prettierrc +++ b/extensions/ohif-dicom-microscopy-extension/.prettierrc @@ -1,3 +1,9 @@ { - "singleQuote": true -} \ No newline at end of file + "trailingComma": "es5", + "printWidth": 80, + "proseWrap": "always", + "tabWidth": 2, + "semi": true, + "singleQuote": true, + "endOfLine": "lf" +} diff --git a/extensions/ohif-dicom-microscopy-extension/README.md b/extensions/ohif-dicom-microscopy-extension/README.md new file mode 100644 index 000000000..2254292fa --- /dev/null +++ b/extensions/ohif-dicom-microscopy-extension/README.md @@ -0,0 +1,34 @@ +# @ohif/extension-dicom-microscopy + +![npm (scoped)](https://img.shields.io/npm/v/@ohif/extension-dicom-microscopy.svg?style=flat-square) + + + +#### Index + +Extension Id: `microscopy` + +- [SopClassHandler Module](#sopclasshandler-module) +- [Viewport Module](#viewport-module) + +## SopClassHandler Module + +.. + +## Viewport Module + +... + +## Resources + +### Repositories + +... + + + + + + diff --git a/extensions/ohif-dicom-microscopy-extension/package.json b/extensions/ohif-dicom-microscopy-extension/package.json index 4bd6beb86..dc472ec59 100644 --- a/extensions/ohif-dicom-microscopy-extension/package.json +++ b/extensions/ohif-dicom-microscopy-extension/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-dicom-microscopy", - "version": "0.0.6", + "version": "0.0.7", "description": "OHIF extension for Dicom Microscopy", "author": "OHIF", "license": "MIT", @@ -21,7 +21,7 @@ "peerDependencies": { "react": "^15.0.0 || ^16.0.0", "react-dom": "^15.0.0 || ^16.0.0", - "ohif-core": "^0.4.0" + "ohif-core": "^0.6.0" }, "devDependencies": { "@babel/core": "^7.2.2", diff --git a/extensions/ohif-dicom-microscopy-extension/src/DicomMicroscopySopClassHandler.js b/extensions/ohif-dicom-microscopy-extension/src/DicomMicroscopySopClassHandler.js index cbcab71c1..9196032a3 100644 --- a/extensions/ohif-dicom-microscopy-extension/src/DicomMicroscopySopClassHandler.js +++ b/extensions/ohif-dicom-microscopy-extension/src/DicomMicroscopySopClassHandler.js @@ -3,7 +3,7 @@ import OHIF from 'ohif-core'; const { utils } = OHIF; const SOP_CLASS_UIDS = { - VL_WHOLE_SLIDE_MICROSCOPY_IMAGE_STORAGE: '1.2.840.10008.5.1.4.1.1.77.1.6' + VL_WHOLE_SLIDE_MICROSCOPY_IMAGE_STORAGE: '1.2.840.10008.5.1.4.1.1.77.1.6', }; const DicomMicroscopySopClassHandler = { @@ -20,9 +20,9 @@ const DicomMicroscopySopClassHandler = { dicomWebClient, sopInstanceUid: instance.getSOPInstanceUID(), seriesInstanceUid: series.getSeriesInstanceUID(), - studyInstanceUid: study.getStudyInstanceUID() + studyInstanceUid: study.getStudyInstanceUID(), }; - } + }, }; export default DicomMicroscopySopClassHandler; diff --git a/extensions/ohif-dicom-microscopy-extension/src/DicomMicroscopyViewport.js b/extensions/ohif-dicom-microscopy-extension/src/DicomMicroscopyViewport.js index ea35325d2..f0cf85264 100644 --- a/extensions/ohif-dicom-microscopy-extension/src/DicomMicroscopyViewport.js +++ b/extensions/ohif-dicom-microscopy-extension/src/DicomMicroscopyViewport.js @@ -1,11 +1,12 @@ import React, { Component } from 'react'; + import { api } from 'dicom-microscopy-viewer'; const microscopyViewer = api.VLWholeSlideMicroscopyImageViewer; class DicomMicroscopyViewport extends Component { state = { - error: null + error: null, }; constructor(props) { @@ -21,7 +22,7 @@ class DicomMicroscopyViewport extends Component { const searchInstanceOptions = { studyInstanceUID: displaySet.studyInstanceUid, - seriesInstanceUID: displaySet.seriesInstanceUid + seriesInstanceUID: displaySet.seriesInstanceUid, }; dicomWebClient @@ -33,7 +34,7 @@ class DicomMicroscopyViewport extends Component { const retrieveInstanceOptions = { studyInstanceUID: displaySet.studyInstanceUid, seriesInstanceUID: displaySet.seriesInstanceUid, - sopInstanceUID + sopInstanceUID, }; const promise = dicomWebClient @@ -53,7 +54,7 @@ class DicomMicroscopyViewport extends Component { const viewer = new microscopyViewer({ client: dicomWebClient, - metadata + metadata, }); viewer.render({ container }); diff --git a/extensions/ohif-dicom-microscopy-extension/src/OHIFDicomMicroscopyExtension.js b/extensions/ohif-dicom-microscopy-extension/src/OHIFDicomMicroscopyExtension.js deleted file mode 100644 index 089de90fd..000000000 --- a/extensions/ohif-dicom-microscopy-extension/src/OHIFDicomMicroscopyExtension.js +++ /dev/null @@ -1,27 +0,0 @@ -import DicomMicroscopyViewport from './DicomMicroscopyViewport.js'; -import DicomMicroscopySopClassHandler from './DicomMicroscopySopClassHandler.js'; - -export default class OHIFDicomMicroscopyExtension { - /** - * Extension ID is a unique id, might be used for namespacing extension specific redux actions/reducers (?) - */ - getExtensionId() { - return 'microscopy'; - } - - getViewportModule() { - return DicomMicroscopyViewport; - } - - getSopClassHandler() { - return DicomMicroscopySopClassHandler; - } - - getPanelModule() { - return null; - } - - getToolbarModule() { - return null; - } -} diff --git a/extensions/ohif-dicom-microscopy-extension/src/index.js b/extensions/ohif-dicom-microscopy-extension/src/index.js index 8bf217ecc..2ec002069 100644 --- a/extensions/ohif-dicom-microscopy-extension/src/index.js +++ b/extensions/ohif-dicom-microscopy-extension/src/index.js @@ -1,3 +1,16 @@ -import OHIFDicomMicroscopyExtension from './OHIFDicomMicroscopyExtension.js'; +import DicomMicroscopySopClassHandler from './DicomMicroscopySopClassHandler.js'; +import DicomMicroscopyViewport from './DicomMicroscopyViewport.js'; -export default OHIFDicomMicroscopyExtension; +export default { + /** + * Only required property. Should be a unique value across all extensions. + */ + id: 'microscopy', + + getViewportModule() { + return DicomMicroscopyViewport; + }, + getSopClassHandlerModule() { + return DicomMicroscopySopClassHandler; + }, +}; diff --git a/extensions/ohif-dicom-pdf-extension/.prettierrc b/extensions/ohif-dicom-pdf-extension/.prettierrc index 92cde390a..12348ec6a 100644 --- a/extensions/ohif-dicom-pdf-extension/.prettierrc +++ b/extensions/ohif-dicom-pdf-extension/.prettierrc @@ -1,3 +1,9 @@ { - "singleQuote": true -} \ No newline at end of file + "trailingComma": "es5", + "printWidth": 80, + "proseWrap": "always", + "tabWidth": 2, + "semi": true, + "singleQuote": true, + "endOfLine": "lf" +} diff --git a/extensions/ohif-dicom-pdf-extension/package.json b/extensions/ohif-dicom-pdf-extension/package.json index afdc3dff1..67b4749b3 100644 --- a/extensions/ohif-dicom-pdf-extension/package.json +++ b/extensions/ohif-dicom-pdf-extension/package.json @@ -1,6 +1,6 @@ { - "name": "ohif-dicom-pdf-extension", - "version": "0.0.6", + "name": "@ohif/extension-dicom-pdf", + "version": "0.0.7", "description": "OHIF extension for Dicom PDF", "author": "OHIF", "license": "MIT", @@ -20,11 +20,16 @@ }, "peerDependencies": { "dicom-parser": "^1.8.3", - "ohif-core": "^0.3.4", + "ohif-core": "^0.6.0", "prop-types": "^15.6.2", "react": "^15.0.0 || ^16.0.0", "react-dom": "^15.0.0 || ^16.0.0" }, + "dependencies": { + "@babel/runtime": "^7.2.0", + "classnames": "^2.2.6", + "lodash.isequal": "^4.5.0" + }, "devDependencies": { "@babel/core": "^7.2.2", "@babel/plugin-external-helpers": "^7.2.0", @@ -80,9 +85,7 @@ "files": [ "dist" ], - "dependencies": { - "@babel/runtime": "^7.2.0", - "classnames": "^2.2.6", - "lodash.isequal": "^4.5.0" + "publishConfig": { + "access": "public" } } diff --git a/extensions/ohif-dicom-pdf-extension/src/OHIFDicomPDFExtension.js b/extensions/ohif-dicom-pdf-extension/src/OHIFDicomPDFExtension.js deleted file mode 100644 index e42e981df..000000000 --- a/extensions/ohif-dicom-pdf-extension/src/OHIFDicomPDFExtension.js +++ /dev/null @@ -1,27 +0,0 @@ -import OHIFDicomPDFViewport from './OHIFDicomPDFViewport.js'; -import OHIFDicomPDFSopClassHandler from './OHIFDicomPDFSopClassHandler.js'; - -export default class OHIFDicomPDFExtension { - /** - * Extension ID is a unique id, might be used for namespacing extension specific redux actions/reducers (?) - */ - getExtensionId() { - return 'pdf'; - } - - getViewportModule() { - return OHIFDicomPDFViewport; - } - - getSopClassHandler() { - return OHIFDicomPDFSopClassHandler; - } - - getPanelModule() { - return null; - } - - getToolbarModule() { - return null; - } -} diff --git a/extensions/ohif-dicom-pdf-extension/src/OHIFDicomPDFSopClassHandler.js b/extensions/ohif-dicom-pdf-extension/src/OHIFDicomPDFSopClassHandler.js index 4591f3498..0b8e1b7bd 100644 --- a/extensions/ohif-dicom-pdf-extension/src/OHIFDicomPDFSopClassHandler.js +++ b/extensions/ohif-dicom-pdf-extension/src/OHIFDicomPDFSopClassHandler.js @@ -1,7 +1,4 @@ -import OHIF from "ohif-core"; - -const { plugins, utils } = OHIF; -const { PLUGIN_TYPES } = plugins; +import { MODULE_TYPES, utils } from 'ohif-core'; // TODO: Should probably use dcmjs for this const SOP_CLASS_UIDS = { @@ -10,10 +7,8 @@ const SOP_CLASS_UIDS = { const OHIFDicomPDFSopClassHandler = { id: 'OHIFDicomPDFSopClassHandlerPlugin', - type: PLUGIN_TYPES.SOP_CLASS_HANDLER, - sopClassUids: [ - SOP_CLASS_UIDS.ENCAPSULATED_PDF - ], + type: MODULE_TYPES.SOP_CLASS_HANDLER, + sopClassUids: [SOP_CLASS_UIDS.ENCAPSULATED_PDF], getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) { const instance = series.getFirstInstance(); @@ -28,6 +23,6 @@ const OHIFDicomPDFSopClassHandler = { authorizationHeaders: authorizationHeaders }; } -} +}; export default OHIFDicomPDFSopClassHandler; diff --git a/extensions/ohif-dicom-pdf-extension/src/index.js b/extensions/ohif-dicom-pdf-extension/src/index.js index 74e7fff7b..b74560ec9 100644 --- a/extensions/ohif-dicom-pdf-extension/src/index.js +++ b/extensions/ohif-dicom-pdf-extension/src/index.js @@ -1,3 +1,16 @@ -import OHIFDicomPDFExtension from './OHIFDicomPDFExtension.js'; +import OHIFDicomPDFSopClassHandler from './OHIFDicomPDFSopClassHandler.js'; +import OHIFDicomPDFViewport from './OHIFDicomPDFViewport.js'; -export default OHIFDicomPDFExtension; +export default { + /** + * Only required property. Should be a unique value across all extensions. + */ + id: 'pdf', + + getViewportModule() { + return OHIFDicomPDFViewport; + }, + getSopClassHandlerModule() { + return OHIFDicomPDFSopClassHandler; + } +}; diff --git a/extensions/ohif-vtk-extension/.prettierrc b/extensions/ohif-vtk-extension/.prettierrc index 92cde390a..12348ec6a 100644 --- a/extensions/ohif-vtk-extension/.prettierrc +++ b/extensions/ohif-vtk-extension/.prettierrc @@ -1,3 +1,9 @@ { - "singleQuote": true -} \ No newline at end of file + "trailingComma": "es5", + "printWidth": 80, + "proseWrap": "always", + "tabWidth": 2, + "semi": true, + "singleQuote": true, + "endOfLine": "lf" +} diff --git a/extensions/ohif-vtk-extension/README.md b/extensions/ohif-vtk-extension/README.md index 1b4c09ef6..e21a3f0bb 100644 --- a/extensions/ohif-vtk-extension/README.md +++ b/extensions/ohif-vtk-extension/README.md @@ -1 +1,55 @@ # @ohif/extension-vtk + +![npm (scoped)](https://img.shields.io/npm/v/@ohif/extension-vtk.svg?style=flat-square) + + + +#### Index + +Extension Id: `vtk` + +- [Commands Module](#commands-module) +- [Toolbar Module](#toolbar-module) +- [Viewport Module](#viewport-module) + +## Commands Module + +| Command Name | Description | Store Contexts | +| ---------------------- | ----------- | -------------- | +| `axial` | | viewports | +| `coronal` | | viewports | +| `sagittal` | | viewports | +| `enableRotateTool` | | viewports | +| `enableCrosshairsTool` | | viewports | +| `enableLevelTool` | | viewports | +| `mpr2d` | | viewports | + +## Toolbar Module + +Our toolbar module contains definitions for: + +- `Crosshairs` +- `WWWC` +- `Rotate` + +All use the `ACTIVE_VIEWPORT::VTK` context. + +## Viewport Module + +Our Viewport wraps [OHIF/react-vtkjs-viewport][react-viewport] and is connected +the redux store. This module is the most prone to change as we hammer out our +Viewport interface. + +## Resources + +### Repositories + +- [OHIF/react-vtkjs-viewport][react-viewport] + + + + +[react-viewport]: https://github.com/OHIF/react-vtkjs-viewport + diff --git a/extensions/ohif-vtk-extension/package.json b/extensions/ohif-vtk-extension/package.json index b1728497c..4bf91f6f7 100644 --- a/extensions/ohif-vtk-extension/package.json +++ b/extensions/ohif-vtk-extension/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-vtk", - "version": "0.0.6", + "version": "0.0.7", "description": "OHIF extension for VTK.js", "author": "OHIF", "license": "MIT", @@ -27,7 +27,7 @@ "dicom-parser": "^1.8.3", "i18next": "^17.0.3", "i18next-browser-languagedetector": "^3.0.1", - "ohif-core": "^0.5.9", + "ohif-core": "^0.6.0", "prop-types": "^15.7.2", "react": "^16.8.6", "react-dom": "^16.8.6", @@ -37,6 +37,12 @@ "react-viewerbase": "^0.8.1", "redux": "^4.0.1" }, + "dependencies": { + "@babel/runtime": "^7.4.5", + "@ohif/i18n": "0.0.4", + "react-vtkjs-viewport": "0.0.9", + "vtk.js": "^8.9.1" + }, "devDependencies": { "@babel/core": "^7.4.5", "@babel/plugin-external-helpers": "^7.2.0", @@ -100,11 +106,5 @@ ], "publishConfig": { "access": "public" - }, - "dependencies": { - "@babel/runtime": "^7.4.5", - "@ohif/i18n": "0.0.4", - "react-vtkjs-viewport": "0.0.9", - "vtk.js": "^8.9.1" } } diff --git a/extensions/ohif-vtk-extension/src/ConnectedToolbarSection.js b/extensions/ohif-vtk-extension/src/ConnectedToolbarSection.js deleted file mode 100644 index 1717501fd..000000000 --- a/extensions/ohif-vtk-extension/src/ConnectedToolbarSection.js +++ /dev/null @@ -1,53 +0,0 @@ -import { connect } from 'react-redux'; -import { ToolbarSection } from 'react-viewerbase'; -import OHIF from 'ohif-core'; - -const { setToolActive } = OHIF.redux.actions; - -const mapStateToProps = state => { - return { - buttons: [ - { - command: 'Crosshairs', - type: 'tool', - text: 'Crosshairs', - icon: 'crosshairs', - active: true, - onClick: () => { - // TODO: Make these use setToolActive instead - window.commandsManager.runCommand('enableCrosshairsTool', {}, 'vtk'); - } - }, - { - command: 'WWWC', - type: 'tool', - text: 'WWWC', - icon: 'level', - active: true, - onClick: () => { - // TODO: Make these use setToolActive instead - window.commandsManager.runCommand('enableLevelTool', {}, 'vtk'); - } - }, - { - command: 'Rotate', - type: 'tool', - text: 'Rotate', - icon: '3d-rotate', - active: false, - onClick: () => { - // TODO: Make these use setToolActive instead - window.commandsManager.runCommand('enableRotateTool', {}, 'vtk'); - } - }, - ], - activeCommand: 'Crosshairs' - }; -}; - -const ConnectedToolbarSection = connect( - mapStateToProps, - null -)(ToolbarSection); - -export default ConnectedToolbarSection; diff --git a/extensions/ohif-vtk-extension/src/ConnectedVTKViewport.js b/extensions/ohif-vtk-extension/src/ConnectedVTKViewport.js index 28689a591..2eb695594 100644 --- a/extensions/ohif-vtk-extension/src/ConnectedVTKViewport.js +++ b/extensions/ohif-vtk-extension/src/ConnectedVTKViewport.js @@ -1,15 +1,10 @@ -import { connect } from 'react-redux'; -import { View2D } from 'react-vtkjs-viewport'; import OHIF from 'ohif-core'; +import { View2D } from 'react-vtkjs-viewport'; +import { connect } from 'react-redux'; -const { - setViewportActive, - setViewportSpecificData, - clearViewportSpecificData -} = OHIF.redux.actions; +const { setViewportActive, setViewportSpecificData } = OHIF.redux.actions; const mapStateToProps = (state, ownProps) => { - const activeButton = state.tools.buttons.find(tool => tool.active === true); let dataFromStore; if (state.extensions && state.extensions.vtk) { @@ -19,9 +14,6 @@ const mapStateToProps = (state, ownProps) => { // If this is the active viewport, enable prefetching. const { viewportIndex } = ownProps; const isActive = viewportIndex === state.viewports.activeViewportIndex; - const viewportSpecificData = - state.viewports.viewportSpecificData[viewportIndex] || {}; - const viewportLayout = state.viewports.layout.viewports[viewportIndex]; const pluginDetails = viewportLayout.vtk || {}; @@ -29,9 +21,10 @@ const mapStateToProps = (state, ownProps) => { layout: state.viewports.layout, isActive, ...pluginDetails, - activeTool: activeButton && activeButton.command, + // Hopefully this doesn't break anything under the hood for this one + // activeTool: activeButton && activeButton.command, ...dataFromStore, - enableStackPrefetch: isActive + enableStackPrefetch: isActive, }; }; @@ -45,13 +38,12 @@ const mapDispatchToProps = (dispatch, ownProps) => { setViewportSpecificData: data => { dispatch(setViewportSpecificData(viewportIndex, data)); - } + }, }; }; const mergeProps = (propsFromState, propsFromDispatch, ownProps) => { const { afterCreation } = propsFromState; - const { setViewportSpecificData } = propsFromDispatch; const props = { ...propsFromState, @@ -74,7 +66,7 @@ const mergeProps = (propsFromState, propsFromDispatch, ownProps) => { if (afterCreation && typeof afterCreation === 'function') { afterCreation(api); } - } + }, }; return props; }; diff --git a/extensions/ohif-vtk-extension/src/LoadingIndicator.css b/extensions/ohif-vtk-extension/src/LoadingIndicator.css index 0883f66cb..e722fdab5 100644 --- a/extensions/ohif-vtk-extension/src/LoadingIndicator.css +++ b/extensions/ohif-vtk-extension/src/LoadingIndicator.css @@ -1,5 +1,5 @@ .imageViewerLoadingIndicator { - color: #91B9CD; + color: #91b9cd; } .loadingIndicator { diff --git a/extensions/ohif-vtk-extension/src/LoadingIndicator.js b/extensions/ohif-vtk-extension/src/LoadingIndicator.js index 7dc5e4614..06bcbf4db 100644 --- a/extensions/ohif-vtk-extension/src/LoadingIndicator.js +++ b/extensions/ohif-vtk-extension/src/LoadingIndicator.js @@ -1,17 +1,18 @@ -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; - import './LoadingIndicator.css'; +import React, { PureComponent } from 'react'; + +import PropTypes from 'prop-types'; + class LoadingIndicator extends PureComponent { static propTypes = { percentComplete: PropTypes.number.isRequired, - error: PropTypes.object + error: PropTypes.object, }; static defaultProps = { percentComplete: 0, - error: null + error: null, }; render() { diff --git a/extensions/ohif-vtk-extension/src/OHIFVTKExtension.js b/extensions/ohif-vtk-extension/src/OHIFVTKExtension.js deleted file mode 100644 index d0eda51a5..000000000 --- a/extensions/ohif-vtk-extension/src/OHIFVTKExtension.js +++ /dev/null @@ -1,75 +0,0 @@ -import React, { Component } from 'react'; -import OHIFVTKViewport from './OHIFVTKViewport.js'; -import ToolbarModule from './ToolbarModule.js'; -import { definitions } from './commands'; - -/** - * Pass in 'children' to a React component. The purpose of this is to - * allow end users of this extension to pass in components which will - * be rendered on top of the base components. - * - * @param WrappedComponent - * @param children - * @return {function(*): *} - */ -function withChildren(WrappedComponent, children) { - return function(props) { - return ; - }; -} - -// Note: If you are authoring extensions which use stateful libraries (e.g. cornerstone-core, react-redux) as peerDependencies and are also duplicated at the application level, try using 'yalc' to link it to the application, rather than yarn link. This can help fix 'module not found' issues. -// https://github.com/whitecolor/yalc - -export default class OHIFVTKExtension { - constructor({ children, commandsManager }) { - this.children = children; - - _registerCommands(commandsManager, definitions, this.getExtensionId()); - } - - /** - * Extension ID is a unique id, might be used for namespacing extension specific redux actions/reducers (?) - */ - getExtensionId() { - return 'vtk'; - } - - getViewportModule() { - if (this.children && this.children.viewport) { - return withChildren(OHIFVTKViewport, this.children.viewport); - } - - return OHIFVTKViewport; - } - - getSopClassHandler() { - return null; - } - - getPanelModule() { - return null; - } - - getToolbarModule() { - return ToolbarModule; - } -} - -/** - * Register all Viewer commands - * - * @private - */ -function _registerCommands(commandsManager, definitions, commandContext) { - commandsManager.createContext(commandContext); - Object.keys(definitions).forEach(commandName => { - const commandDefinition = definitions[commandName]; - - commandsManager.registerCommand( - commandContext, - commandName, - commandDefinition - ); - }); -} diff --git a/extensions/ohif-vtk-extension/src/OHIFVTKViewport.js b/extensions/ohif-vtk-extension/src/OHIFVTKViewport.js index ff9eb3b7b..a42ac75b3 100644 --- a/extensions/ohif-vtk-extension/src/OHIFVTKViewport.js +++ b/extensions/ohif-vtk-extension/src/OHIFVTKViewport.js @@ -1,17 +1,16 @@ import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import OHIF from 'ohif-core'; -import cornerstone from 'cornerstone-core'; import { getImageData, loadImageData } from 'react-vtkjs-viewport'; +import ConnectedVTKViewport from './ConnectedVTKViewport'; +import LoadingIndicator from './LoadingIndicator.js'; +import OHIF from 'ohif-core'; +import PropTypes from 'prop-types'; +import cornerstone from 'cornerstone-core'; +import handleSegmentationStorage from './handleSegmentationStorage.js'; +import vtkDataArray from 'vtk.js/Sources/Common/Core/DataArray'; +import vtkImageData from 'vtk.js/Sources/Common/DataModel/ImageData'; import vtkVolume from 'vtk.js/Sources/Rendering/Core/Volume'; import vtkVolumeMapper from 'vtk.js/Sources/Rendering/Core/VolumeMapper'; -import vtkImageData from 'vtk.js/Sources/Common/DataModel/ImageData'; -import vtkDataArray from 'vtk.js/Sources/Common/Core/DataArray'; - -import ConnectedVTKViewport from './ConnectedVTKViewport'; -import handleSegmentationStorage from './handleSegmentationStorage.js'; -import LoadingIndicator from './LoadingIndicator.js'; const { StackManager } = OHIF.utils; @@ -25,7 +24,7 @@ cornerstone.metaData.addProvider( StackManager.setMetadataProvider(metadataProvider); const SOP_CLASSES = { - SEGMENTATION_STORAGE: '1.2.840.10008.5.1.4.1.1.66.4' + SEGMENTATION_STORAGE: '1.2.840.10008.5.1.4.1.1.66.4', }; const specialCaseHandlers = {}; @@ -51,7 +50,7 @@ function createLabelMapImageData(backgroundImageData) { const values = new Uint8Array(backgroundImageData.getNumberOfPoints()); const dataArray = vtkDataArray.newInstance({ numberOfComponents: 1, // labelmap with single component - values + values, }); labelMapData.getPointData().setScalars(dataArray); @@ -62,14 +61,14 @@ class OHIFVTKViewport extends Component { state = { volumes: null, paintFilterLabelMapImageData: null, - paintFilterBackgroundImageData: null + paintFilterBackgroundImageData: null, }; static propTypes = { studies: PropTypes.object, displaySet: PropTypes.object, viewportIndex: PropTypes.number, - children: PropTypes.node + children: PropTypes.node, }; static id = 'OHIFVTKViewport'; @@ -165,7 +164,7 @@ class OHIFVTKViewport extends Component { return loadImageData(imageDataObject).then(() => { return { data: imageDataObject.vtkImageData, - labelmap: labelmapDataObject + labelmap: labelmapDataObject, }; }); default: @@ -173,7 +172,7 @@ class OHIFVTKViewport extends Component { return loadImageData(imageDataObject).then(() => { return { - data: imageDataObject.vtkImageData + data: imageDataObject.vtkImageData, }; }); } @@ -202,7 +201,7 @@ class OHIFVTKViewport extends Component { displaySetInstanceUid, sopClassUids, sopInstanceUid, - frameIndex + frameIndex, } = displaySet; if (sopClassUids.length > 1) { @@ -231,7 +230,7 @@ class OHIFVTKViewport extends Component { this.setState({ volumes: [volumeActor], paintFilterBackgroundImageData: data, - paintFilterLabelMapImageData: labelmap + paintFilterLabelMapImageData: labelmap, }); } @@ -261,7 +260,7 @@ class OHIFVTKViewport extends Component { childrenWithProps = this.props.children.map((child, index) => { return React.cloneElement(child, { viewportIndex: this.props.viewportIndex, - key: index + key: index, }); }); } diff --git a/extensions/ohif-vtk-extension/src/ToolbarModule.js b/extensions/ohif-vtk-extension/src/ToolbarModule.js deleted file mode 100644 index 447abcab9..000000000 --- a/extensions/ohif-vtk-extension/src/ToolbarModule.js +++ /dev/null @@ -1,15 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import ConnectedToolbarSection from './ConnectedToolbarSection'; - -class ToolbarModule extends Component { - render() { - return ( -
- -
- ); - } -} - -export default ToolbarModule; diff --git a/extensions/ohif-vtk-extension/src/commands.js b/extensions/ohif-vtk-extension/src/commandsModule.js similarity index 95% rename from extensions/ohif-vtk-extension/src/commands.js rename to extensions/ohif-vtk-extension/src/commandsModule.js index 19cf396c8..b8cc692c0 100644 --- a/extensions/ohif-vtk-extension/src/commands.js +++ b/extensions/ohif-vtk-extension/src/commandsModule.js @@ -1,25 +1,22 @@ import { - vtkInteractorStyleMPRWindowLevel, - vtkInteractorStyleMPRSlice, vtkInteractorStyleMPRCrosshairs, + vtkInteractorStyleMPRSlice, + vtkInteractorStyleMPRWindowLevel, vtkSVGCrosshairsWidget, - vtkSVGWidgetManager + vtkSVGWidgetManager, } from 'react-vtkjs-viewport'; -import setViewportToVTK from './utils/setViewportToVTK.js'; import setMPRLayout from './utils/setMPRLayout.js'; +import setViewportToVTK from './utils/setViewportToVTK.js'; +import vtkCoordinate from 'vtk.js/Sources/Rendering/Core/Coordinate'; import vtkMath from 'vtk.js/Sources/Common/Core/Math'; import vtkMatrixBuilder from 'vtk.js/Sources/Common/Core/MatrixBuilder'; -import vtkCoordinate from 'vtk.js/Sources/Rendering/Core/Coordinate'; - -// TODO: Should be another way to get this -const commandsManager = window.commandsManager; // TODO: Put this somewhere else let apis = {}; function getCrosshairCallbackForIndex(index) { - return ({worldPos}) => { + return ({ worldPos }) => { // Set camera focal point to world coordinate for linked views apis.forEach((api, viewportIndex) => { if (viewportIndex !== index) { @@ -52,7 +49,10 @@ function getCrosshairCallbackForIndex(index) { const displayPosition = wPos.getComputedDisplayValue(renderer); const { svgWidgetManager } = api; - api.svgWidgets.crosshairsWidget.setPoint(displayPosition[0], displayPosition[1]); + api.svgWidgets.crosshairsWidget.setPoint( + displayPosition[0], + displayPosition[1] + ); svgWidgetManager.render(); }); }; @@ -230,7 +230,7 @@ const actions = { api.svgWidgetManager = svgWidgetManager; api.svgWidgets = { - crosshairsWidget + crosshairsWidget, }; switch (index) { @@ -255,45 +255,49 @@ const actions = { renderWindow.render(); }); - } + }, }; const definitions = { axial: { commandFn: actions.axial, storeContexts: ['viewports'], - options: {} + options: {}, }, coronal: { commandFn: actions.coronal, storeContexts: ['viewports'], - options: {} + options: {}, }, sagittal: { commandFn: actions.sagittal, storeContexts: ['viewports'], - options: {} + options: {}, }, enableRotateTool: { commandFn: actions.enableRotateTool, storeContexts: ['viewports'], - options: {} + options: {}, }, enableCrosshairsTool: { commandFn: actions.enableCrosshairsTool, storeContexts: ['viewports'], - options: {} + options: {}, }, enableLevelTool: { commandFn: actions.enableLevelTool, storeContexts: ['viewports'], - options: {} + options: {}, }, mpr2d: { commandFn: actions.mpr2d, storeContexts: ['viewports'], - options: {} - } + options: {}, + context: 'VIEWER', + }, }; -export { definitions }; +export default { + definitions, + defaultContext: 'ACTIVE_VIEWPORT::VTK', +}; diff --git a/extensions/ohif-vtk-extension/src/handleSegmentationStorage.js b/extensions/ohif-vtk-extension/src/handleSegmentationStorage.js index 6941b9712..1988456cd 100644 --- a/extensions/ohif-vtk-extension/src/handleSegmentationStorage.js +++ b/extensions/ohif-vtk-extension/src/handleSegmentationStorage.js @@ -1,5 +1,6 @@ -import OHIF from 'ohif-core'; import * as dcmjs from 'dcmjs'; + +import OHIF from 'ohif-core'; import { api } from 'dicomweb-client'; const { StackManager } = OHIF.utils; @@ -56,14 +57,14 @@ function retrieveDicomData( ) { const config = { url: wadoRoot, - headers: DICOMWeb.getAuthorizationHeader() + headers: DICOMWeb.getAuthorizationHeader(), }; const dicomWeb = new api.DICOMwebClient(config); const options = { studyInstanceUID, seriesInstanceUID, - sopInstanceUID + sopInstanceUID, }; return dicomWeb.retrieveInstance(options); @@ -136,13 +137,13 @@ async function handleSegmentationStorage( return { referenceDataObject, - labelmapDataObject + labelmapDataObject, }; return { studyInstanceUid, displaySetInstanceUid, - stack + stack, }; } diff --git a/extensions/ohif-vtk-extension/src/index.js b/extensions/ohif-vtk-extension/src/index.js index 95116b66c..726b13c7c 100644 --- a/extensions/ohif-vtk-extension/src/index.js +++ b/extensions/ohif-vtk-extension/src/index.js @@ -1,7 +1,24 @@ +import OHIFVTKViewport from './OHIFVTKViewport.js'; +import commandsModule from './commandsModule.js'; +// This feels weird import loadLocales from './loadLocales'; +import toolbarModule from './toolbarModule.js'; -import OHIFVTKExtension from './OHIFVTKExtension.js'; +export default { + /** + * Only required property. Should be a unique value across all extensions. + */ + id: 'vtk', + + getViewportModule() { + return OHIFVTKViewport; + }, + getToolbarModule() { + return toolbarModule; + }, + getCommandsModule() { + return commandsModule; + }, +}; loadLocales(); - -export default OHIFVTKExtension; diff --git a/extensions/ohif-vtk-extension/src/toolbarModule.js b/extensions/ohif-vtk-extension/src/toolbarModule.js new file mode 100644 index 000000000..fed4f54db --- /dev/null +++ b/extensions/ohif-vtk-extension/src/toolbarModule.js @@ -0,0 +1,39 @@ +const TOOLBAR_BUTTON_TYPES = { + COMMAND: 'command', + SET_TOOL_ACTIVE: 'setToolActive', +}; + +const definitions = [ + { + id: 'Crosshairs', + label: 'Crosshairs', + icon: 'crosshairs', + // + type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE, + commandName: 'enableCrosshairsTool', + commandOptions: {}, + }, + { + id: 'WWWC', + label: 'WWWC', + icon: 'level', + // + type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE, + commandName: 'enableLevelTool', + commandOptions: {}, + }, + { + id: 'Rotate', + label: 'Rotate', + icon: '3d-rotate', + // + type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE, + commandName: 'enableRotateTool', + commandOptions: {}, + }, +]; + +export default { + definitions, + defaultContext: 'ACTIVE_VIEWPORT::VTK', +}; diff --git a/extensions/ohif-vtk-extension/src/utils/setMPRLayout.js b/extensions/ohif-vtk-extension/src/utils/setMPRLayout.js index 65570887d..6de1023f4 100644 --- a/extensions/ohif-vtk-extension/src/utils/setMPRLayout.js +++ b/extensions/ohif-vtk-extension/src/utils/setMPRLayout.js @@ -11,14 +11,14 @@ export default function setMPRLayout(displaySet) { for (let i = 0; i < numViewports; i++) { viewports.push({ height: `${100 / rows}%`, - width: `${100 / columns}%` + width: `${100 / columns}%`, }); viewportSpecificData[i] = displaySet; viewportSpecificData[i].plugin = 'vtk'; } const layout = { - viewports + viewports, }; const viewportIndices = [0, 1, 2]; @@ -33,7 +33,7 @@ export default function setMPRLayout(displaySet) { }*/ const data = { - plugin: 'vtk', + // plugin: 'vtk', vtk: { mode: 'mpr', // TODO: not used afterCreation: api => { @@ -42,8 +42,8 @@ export default function setMPRLayout(displaySet) { if (apis.every(a => !!a)) { resolve(apis); } - } - } + }, + }, }; updatedViewports = setSingleLayoutData( diff --git a/extensions/ohif-vtk-extension/src/utils/setViewportToVTK.js b/extensions/ohif-vtk-extension/src/utils/setViewportToVTK.js index c018ba909..aa3dfb679 100644 --- a/extensions/ohif-vtk-extension/src/utils/setViewportToVTK.js +++ b/extensions/ohif-vtk-extension/src/utils/setViewportToVTK.js @@ -14,13 +14,13 @@ export default function setViewportToVTK( }*/ const data = { - plugin: 'vtk', + // plugin: 'vtk', vtk: { mode: 'mpr', // TODO: not used afterCreation: api => { resolve(api); - } - } + }, + }, }; const updatedViewports = setSingleLayoutData( diff --git a/package.json b/package.json index bfdaa4a08..3b64946e3 100644 --- a/package.json +++ b/package.json @@ -67,9 +67,11 @@ }, "dependencies": { "@babel/runtime": "^7.4.5", - "@ohif/extension-cornerstone": "0.0.36", - "@ohif/extension-dicom-microscopy": "0.0.6", - "@ohif/extension-vtk": "0.0.6", + "@ohif/extension-cornerstone": "0.0.37", + "@ohif/extension-dicom-html": "0.0.3", + "@ohif/extension-dicom-microscopy": "0.0.7", + "@ohif/extension-dicom-pdf": "0.0.7", + "@ohif/extension-vtk": "0.0.7", "@ohif/i18n": "0.0.4", "classnames": "^2.2.6", "cornerstone-core": "^2.2.8", @@ -84,9 +86,7 @@ "i18next-browser-languagedetector": "^3.0.1", "lodash.isequal": "4.5.0", "moment": "^2.24.0", - "ohif-core": "0.5.9", - "ohif-dicom-html-extension": "^0.0.2", - "ohif-dicom-pdf-extension": "^0.0.6", + "ohif-core": "0.6.0", "oidc-client": "1.7.x", "prop-types": "^15.7.2", "react-i18next": "^10.11.0", @@ -94,9 +94,12 @@ "react-resize-detector": "^4.2.0", "react-router": "^5.0.1", "react-router-dom": "^5.0.1", - "react-viewerbase": "0.9.0", + "react-viewerbase": "0.10.0", "redux": "^4.0.1", - "redux-oidc": "3.1.x" + "redux-logger": "^3.0.6", + "redux-oidc": "3.1.x", + "redux-thunk": "^2.3.0", + "reselect": "^4.0.0" }, "devDependencies": { "@babel/core": "^7.4.5", diff --git a/src/App.js b/src/App.js index 9d5278768..3a124728f 100644 --- a/src/App.js +++ b/src/App.js @@ -2,23 +2,21 @@ import './config'; import { CommandsManager, + ExtensionManager, HotkeysManager, - extensions, - redux, utils, } from 'ohif-core'; import React, { Component } from 'react'; import { - getDefaultToolbarButtons, getUserManagerForOpenIdConnectClient, initWebWorkers, } from './utils/index.js'; -import ConnectedToolContextMenu from './connectedComponents/ConnectedToolContextMenu'; +import { I18nextProvider } from 'react-i18next'; import OHIFCornerstoneExtension from '@ohif/extension-cornerstone'; -import OHIFDicomHtmlExtension from 'ohif-dicom-html-extension'; +import OHIFDicomHtmlExtension from '@ohif/extension-dicom-html'; import OHIFDicomMicroscopyExtension from '@ohif/extension-dicom-microscopy'; -import OHIFDicomPDFExtension from 'ohif-dicom-pdf-extension'; +import OHIFDicomPDFExtension from '@ohif/extension-dicom-pdf'; import OHIFStandaloneViewer from './OHIFStandaloneViewer'; import OHIFVTKExtension from '@ohif/extension-vtk'; import { OidcProvider } from 'redux-oidc'; @@ -27,22 +25,20 @@ 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 { getActiveContexts } from './store/layout/selectors.js'; import i18n from '@ohif/i18n'; -import { I18nextProvider } from 'react-i18next'; +import setupTools from './setupTools'; import store from './store'; // ~~~~ APP SETUP const commandsManagerConfig = { getAppState: () => store.getState(), - getActiveContexts: () => store.getState().ui.activeContexts, + getActiveContexts: () => getActiveContexts(store.getState()), }; const commandsManager = new CommandsManager(commandsManagerConfig); const hotkeysManager = new HotkeysManager(commandsManager); - -// TODO: @dannyrb will fix this -window.commandsManager = commandsManager; +const extensionManager = new ExtensionManager({ commandsManager }); // TODO: Should be done in extensions w/ commandsModule // ~~ ADD COMMANDS @@ -50,32 +46,21 @@ appCommands.init(commandsManager); if (window.config.hotkeys) { hotkeysManager.setHotkeys(window.config.hotkeys, true); } - -// 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 APP SETUP setupTools(store); -const children = { - viewport: [], -}; +// const children = { +// viewport: [], +// }; /** TODO: extensions should be passed in as prop as soon as we have the extensions as separate packages and then registered by ExtensionsManager */ -extensions.ExtensionManager.registerExtensions(store, [ - new OHIFCornerstoneExtension({ children }), - new OHIFVTKExtension({ commandsManager }), - new OHIFDicomPDFExtension(), - new OHIFDicomHtmlExtension(), - new OHIFDicomMicroscopyExtension(), +extensionManager.registerExtensions([ + OHIFCornerstoneExtension, + OHIFVTKExtension, + OHIFDicomPDFExtension, + OHIFDicomHtmlExtension, + OHIFDicomMicroscopyExtension, ]); // TODO[react] Use a provider when the whole tree is React @@ -104,12 +89,6 @@ class App extends Component { constructor(props) { super(props); - // - const defaultButtons = getDefaultToolbarButtons(this.props.routerBasename); - const buttonsAction = redux.actions.setAvailableButtons(defaultButtons); - - store.dispatch(buttonsAction); - if (this.props.oidc.length) { const firstOpenIdClient = this.props.oidc[0]; @@ -162,4 +141,5 @@ class App extends Component { export default App; -export { commandsManager, hotkeysManager }; +// Make our managers accessible +export { commandsManager, extensionManager, hotkeysManager }; diff --git a/src/appCommands/index.js b/src/appCommands/index.js index 66ab50fa6..40591d5f1 100644 --- a/src/appCommands/index.js +++ b/src/appCommands/index.js @@ -1,9 +1,7 @@ -import cornerstoneCommandDefinitions from './cornerstone.js'; import viewerCommandDefinitions from './viewer.js'; const CONTEXTS = { viewer: 'VIEWER', - cornerstone: 'VIEWER::CORNERSTONE', }; /** @@ -12,7 +10,6 @@ const CONTEXTS = { */ function init(commandsManager) { _registerViewerCommands(commandsManager); - _registerCornerstoneCommands(commandsManager); } /** @@ -35,26 +32,6 @@ function _registerViewerCommands(commandsManager) { }); } -/** - * 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/components/Header/Header.js b/src/components/Header/Header.js index 60186a517..f167c1085 100644 --- a/src/components/Header/Header.js +++ b/src/components/Header/Header.js @@ -1,15 +1,15 @@ import './Header.css'; +import './Header.css'; import { Link, withRouter } from 'react-router-dom'; import React, { Component } from 'react'; import { Dropdown } from 'react-viewerbase'; -import { withTranslation } from 'react-i18next'; -import './Header.css'; import OHIFLogo from '../OHIFLogo/OHIFLogo.js'; import PropTypes from 'prop-types'; // import { UserPreferencesModal } from 'react-viewerbase'; import { hotkeysManager } from './../../App.js'; +import { withTranslation } from 'react-i18next'; class Header extends Component { static propTypes = { @@ -25,8 +25,8 @@ class Header extends Component { }; // onSave: data => { - // const contextName = window.store.getState().commandContext.context; - // const preferences = cloneDeep(window.store.getState().preferences); + // const contextName = store.getState().commandContext.context; + // const preferences = cloneDeep(store.getState().preferences); // preferences[contextName] = data; // dispatch(setUserPreferences(preferences)); // dispatch(setUserPreferencesModalOpen(false)); diff --git a/src/config.js b/src/config.js index cd1bd8bec..fcfa138ea 100644 --- a/src/config.js +++ b/src/config.js @@ -1,7 +1,7 @@ -import dicomParser from 'dicom-parser'; +import OHIF from 'ohif-core'; import cornerstone from 'cornerstone-core'; import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader'; -import OHIF from 'ohif-core'; +import dicomParser from 'dicom-parser'; import version from './version.js'; window.info = { @@ -10,8 +10,10 @@ window.info = { }; // For debugging +//if (process.env.node_env === 'development') { window.cornerstone = cornerstone; window.cornerstoneWADOImageLoader = cornerstoneWADOImageLoader; +//} cornerstoneWADOImageLoader.external.cornerstone = cornerstone; cornerstoneWADOImageLoader.external.dicomParser = dicomParser; diff --git a/extensions/ohif-cornerstone-extension/src/ConnectedCineDialog.js b/src/connectedComponents/ConnectedCineDialog.js similarity index 95% rename from extensions/ohif-cornerstone-extension/src/ConnectedCineDialog.js rename to src/connectedComponents/ConnectedCineDialog.js index 6247f6bf2..761a79abe 100644 --- a/extensions/ohif-cornerstone-extension/src/ConnectedCineDialog.js +++ b/src/connectedComponents/ConnectedCineDialog.js @@ -20,14 +20,14 @@ const mapStateToProps = state => { const cineData = cine || { isPlaying: false, - cineFrameRate: 24 + cineFrameRate: 24, }; // New props we're creating? return { activeEnabledElement: dom, activeViewportCineData: cineData, - activeViewportIndex: state.viewports.activeViewportIndex + activeViewportIndex: state.viewports.activeViewportIndex, }; }; @@ -35,7 +35,7 @@ const mapDispatchToProps = dispatch => { return { dispatchSetViewportSpecificData: (viewportIndex, data) => { dispatch(setViewportSpecificData(viewportIndex, data)); - } + }, }; }; @@ -43,7 +43,7 @@ const mergeProps = (propsFromState, propsFromDispatch, ownProps) => { const { activeEnabledElement, activeViewportCineData, - activeViewportIndex + activeViewportIndex, } = propsFromState; return { @@ -54,7 +54,7 @@ const mergeProps = (propsFromState, propsFromDispatch, ownProps) => { cine.isPlaying = !cine.isPlaying; propsFromDispatch.dispatchSetViewportSpecificData(activeViewportIndex, { - cine + cine, }); }, onFrameRateChanged: frameRate => { @@ -62,7 +62,7 @@ const mergeProps = (propsFromState, propsFromDispatch, ownProps) => { cine.cineFrameRate = frameRate; propsFromDispatch.dispatchSetViewportSpecificData(activeViewportIndex, { - cine + cine, }); }, onClickNextButton: () => { @@ -89,7 +89,7 @@ const mergeProps = (propsFromState, propsFromDispatch, ownProps) => { if (!stackData || !stackData.data || !stackData.data.length) return; const lastIndex = stackData.data[0].imageIds.length - 1; scrollToIndex(activeEnabledElement, lastIndex); - } + }, }; }; diff --git a/src/connectedComponents/ConnectedLayoutButton.js b/src/connectedComponents/ConnectedLayoutButton.js index 23f44e70b..4842f5952 100644 --- a/src/connectedComponents/ConnectedLayoutButton.js +++ b/src/connectedComponents/ConnectedLayoutButton.js @@ -1,6 +1,6 @@ -import { connect } from 'react-redux'; import { LayoutButton } from 'react-viewerbase'; import OHIF from 'ohif-core'; +import { connect } from 'react-redux'; const { setLayout } = OHIF.redux.actions; @@ -22,7 +22,6 @@ const mapDispatchToProps = dispatch => { viewports.push({ height: `${100 / rows}%`, width: `${100 / columns}%`, - plugin: 'cornerstone', // Temporary because right now switching back from VTK breaks things }); } const layout = { diff --git a/src/connectedComponents/ConnectedLayoutManager.js b/src/connectedComponents/ConnectedLayoutManager.js index d18605ac1..564645ac0 100644 --- a/src/connectedComponents/ConnectedLayoutManager.js +++ b/src/connectedComponents/ConnectedLayoutManager.js @@ -1,32 +1,29 @@ -import { connect } from 'react-redux'; import { LayoutManager } from 'react-viewerbase'; -import OHIF from 'ohif-core'; +import { MODULE_TYPES } from 'ohif-core'; +import { connect } from 'react-redux'; +import { extensionManager } from './../App.js'; const mapStateToProps = state => { - const viewportPluginIds = state.plugins.availablePlugins - .filter(plugin => plugin.type === OHIF.plugins.PLUGIN_TYPES.VIEWPORT) - .map(plugin => plugin.id); + const availableViewportModules = {}; + const viewportModules = extensionManager.modules[MODULE_TYPES.VIEWPORT]; - const availablePlugins = {}; - viewportPluginIds.forEach(id => { - const plugin = OHIF.plugins.availablePlugins.find( - plugin => plugin.id === id - ); - if (plugin) { - availablePlugins[id] = plugin.component; - } + viewportModules.forEach(moduleDefinition => { + availableViewportModules[moduleDefinition.extensionId] = + moduleDefinition.module; }); - // TODO Use something like state.plugins.defaultPlugin[OHIF.plugins.PLUGIN_TYPES.VIEWPORT] + // TODO: Use something like state.plugins.defaultPlugin[MODULE_TYPES.VIEWPORT] let defaultPlugin; - if (viewportPluginIds && viewportPluginIds.length) { - defaultPlugin = viewportPluginIds[0]; + if (viewportModules.length) { + defaultPlugin = viewportModules[0].extensionId; } return { layout: state.viewports.layout, activeViewportIndex: state.viewports.activeViewportIndex, - availablePlugins, + // TODO: rename `availableViewportModules` + availablePlugins: availableViewportModules, + // TODO: rename `defaultViewportModule` defaultPlugin, }; }; diff --git a/src/connectedComponents/ConnectedPluginSwitch.js b/src/connectedComponents/ConnectedPluginSwitch.js index 7fd883c7f..965c05350 100644 --- a/src/connectedComponents/ConnectedPluginSwitch.js +++ b/src/connectedComponents/ConnectedPluginSwitch.js @@ -1,6 +1,7 @@ -import { connect } from 'react-redux'; -import PluginSwitch from './PluginSwitch.js'; import OHIF from 'ohif-core'; +import PluginSwitch from './PluginSwitch.js'; +import { commandsManager } from './../App.js'; +import { connect } from 'react-redux'; const { setLayout } = OHIF.redux.actions; @@ -60,7 +61,7 @@ const mergeProps = (propsFromState, propsFromDispatch, ownProps) => { icon: 'cube', active: false, onClick: () => { - window.commandsManager.runCommand('axial', {}, 'vtk'); + commandsManager.runCommand('axial'); }, }, { @@ -68,7 +69,7 @@ const mergeProps = (propsFromState, propsFromDispatch, ownProps) => { icon: 'cube', active: false, onClick: () => { - window.commandsManager.runCommand('sagittal', {}, 'vtk'); + commandsManager.runCommand('sagittal'); }, }, { @@ -76,15 +77,14 @@ const mergeProps = (propsFromState, propsFromDispatch, ownProps) => { icon: 'cube', active: false, onClick: () => { - window.commandsManager.runCommand('coronal', {}, 'vtk'); + commandsManager.runCommand('coronal'); }, },*/ { - text: '2D MPR', + label: '2D MPR', icon: 'cube', - active: false, onClick: () => { - window.commandsManager.runCommand('mpr2d', {}, 'vtk'); + commandsManager.runCommand('mpr2d'); }, }, ]; diff --git a/src/connectedComponents/ConnectedToolbarRow.js b/src/connectedComponents/ConnectedToolbarRow.js index b887ff9c4..66233bbc9 100644 --- a/src/connectedComponents/ConnectedToolbarRow.js +++ b/src/connectedComponents/ConnectedToolbarRow.js @@ -5,23 +5,11 @@ import { import ToolbarRow from './ToolbarRow'; import { connect } from 'react-redux'; - -const defaultPlugin = 'cornerstone'; +import { getActiveContexts } from './../store/layout/selectors.js'; const mapStateToProps = state => { - const { layout, viewportSpecificData, activeViewportIndex } = state.viewports; - const pluginInLayout = - layout.viewports[activeViewportIndex] && - layout.viewports[activeViewportIndex].plugin; - const pluginInViewportData = - viewportSpecificData[activeViewportIndex] && - viewportSpecificData[activeViewportIndex].plugin; - const pluginInActiveViewport = - pluginInLayout || pluginInViewportData || defaultPlugin; - // const extensionData = state.extensions[pluginInActiveViewport]; - return { - pluginId: pluginInActiveViewport, + activeContexts: getActiveContexts(state), leftSidebarOpen: state.ui.leftSidebarOpen, rightSidebarOpen: state.ui.rightSidebarOpen, }; diff --git a/src/connectedComponents/ConnectedToolbarSection.js b/src/connectedComponents/ConnectedToolbarSection.js deleted file mode 100644 index dc0f73bbc..000000000 --- a/src/connectedComponents/ConnectedToolbarSection.js +++ /dev/null @@ -1,29 +0,0 @@ -import { connect } from 'react-redux'; -import { ToolbarSection } from 'react-viewerbase'; -import OHIF from 'ohif-core'; - -const { setToolActive } = OHIF.redux.actions; - -const mapStateToProps = state => { - const activeButton = state.tools.buttons.find(tool => tool.active === true); - - return { - buttons: state.tools.buttons, - activeCommand: activeButton && activeButton.command, - }; -}; - -const mapDispatchToProps = dispatch => { - return { - setToolActive: tool => { - dispatch(setToolActive(tool.command)); - }, - }; -}; - -const ConnectedToolbarSection = connect( - mapStateToProps, - mapDispatchToProps -)(ToolbarSection); - -export default ConnectedToolbarSection; diff --git a/src/connectedComponents/ConnectedViewerMain.js b/src/connectedComponents/ConnectedViewerMain.js index c51e307f9..d0ee196e0 100644 --- a/src/connectedComponents/ConnectedViewerMain.js +++ b/src/connectedComponents/ConnectedViewerMain.js @@ -5,8 +5,6 @@ import { connect } from 'react-redux'; const { setViewportSpecificData, clearViewportSpecificData, - // setToolActive, - // setActiveViewportSpecificData, } = OHIF.redux.actions; const mapStateToProps = state => { @@ -28,12 +26,6 @@ const mapDispatchToProps = dispatch => { clearViewportSpecificData: () => { dispatch(clearViewportSpecificData()); }, - // setToolActive: tool => { - // dispatch(setToolActive(tool)); - // }, - // setActiveViewportSpecificData: viewport => { - // dispatch(setActiveViewportSpecificData(viewport)); - // }, }; }; diff --git a/src/connectedComponents/ToolbarRow.js b/src/connectedComponents/ToolbarRow.js index deda8d4c4..43a945c75 100644 --- a/src/connectedComponents/ToolbarRow.js +++ b/src/connectedComponents/ToolbarRow.js @@ -1,12 +1,14 @@ import './ToolbarRow.css'; import React, { Component } from 'react'; +import { RoundedButtonGroup, ToolbarButton } from 'react-viewerbase'; +import { commandsManager, extensionManager } from './../App.js'; +import ConnectedCineDialog from './ConnectedCineDialog'; import ConnectedLayoutButton from './ConnectedLayoutButton'; import ConnectedPluginSwitch from './ConnectedPluginSwitch.js'; -import OHIF from 'ohif-core'; +import { MODULE_TYPES } from 'ohif-core'; import PropTypes from 'prop-types'; -import { RoundedButtonGroup } from 'react-viewerbase'; class ToolbarRow extends Component { static propTypes = { @@ -14,7 +16,7 @@ class ToolbarRow extends Component { rightSidebarOpen: PropTypes.bool.isRequired, setLeftSidebarOpen: PropTypes.func, setRightSidebarOpen: PropTypes.func, - pluginId: PropTypes.string, + activeContexts: PropTypes.arrayOf(PropTypes.string).isRequired, }; static defaultProps = { @@ -22,6 +24,38 @@ class ToolbarRow extends Component { rightSidebarOpen: false, }; + constructor(props) { + super(props); + + const toolbarButtonDefinitions = _getVisibleToolbarButtons.call(this); + // TODO: + // If it's a tool that can be active... Mark it as active? + // - Tools that are on/off? + // - Tools that can be bound to multiple buttons? + + // Normal ToolbarButtons... + // Just how high do we need to hoist this state? + // Why ToolbarRow instead of just Toolbar? Do we have any others? + this.state = { + toolbarButtons: toolbarButtonDefinitions, + activeButtons: [], + isCineDialogOpen: false, + }; + + this._handleBuiltIn = _handleBuiltIn.bind(this); + } + + componentDidUpdate(prevProps) { + const activeContextsChanged = + prevProps.activeContexts !== this.props.activeContexts; + + if (activeContextsChanged) { + this.setState({ + toolbarButtons: _getVisibleToolbarButtons.call(this), + }); + } + } + onLeftSidebarValueChanged = value => { this.props.setLeftSidebarOpen(!!value); }; @@ -55,44 +89,111 @@ class ToolbarRow extends Component { ? rightSidebarToggle[0].value : null; - const currentPluginId = this.props.pluginId; + const buttonComponents = _getButtonComponents.call( + this, + this.state.toolbarButtons, + this.state.activeButtons + ); - const { PLUGIN_TYPES, availablePlugins } = OHIF.plugins; - const plugin = availablePlugins.find(entry => { - return ( - entry.type === PLUGIN_TYPES.TOOLBAR && entry.id === currentPluginId - ); - }); - - let pluginComp; - if (plugin) { - const PluginComponent = plugin.component; - - pluginComp = ; - } + const cineDialogContainerStyle = { + display: this.state.isCineDialogOpen ? 'block' : 'none', + position: 'absolute', + top: '82px', + zIndex: 999, + }; return ( -
-
- + <> +
+
+ +
+ {buttonComponents} + + +
+ +
- {pluginComp} - - -
- +
+
-
+ ); } } +/** + * Determine which extension buttons should be showing, if they're + * active, and what their onClick behavior should be. + */ +function _getButtonComponents(toolbarButtons, activeButtons) { + return toolbarButtons.map((button, index) => { + // TODO: If `button.buttons`, use `ExpandedToolMenu` + // I don't believe any extensions currently leverage this + return ( + { + if (button.commandName) { + const options = Object.assign({ evt }, button.commandOptions); + commandsManager.runCommand(button.commandName, options); + } + + // TODO: Use Types ENUM + // TODO: We can update this to be a `getter` on the extension to query + // For the active tools after we apply our updates? + if (button.type === 'setToolActive') { + this.setState({ + activeButtons: [button.id], + }); + } else if (button.type === 'builtIn') { + this._handleBuiltIn(button.options); + } + }} + isActive={activeButtons.includes(button.id)} + /> + ); + }); +} + +function _getVisibleToolbarButtons() { + const toolbarModules = extensionManager.modules[MODULE_TYPES.TOOLBAR]; + const toolbarButtonDefinitions = []; + + toolbarModules.forEach(extension => { + const { definitions, defaultContext } = extension.module; + definitions.forEach(definition => { + const context = definition.context || defaultContext; + + if (this.props.activeContexts.includes(context)) { + toolbarButtonDefinitions.push(definition); + } + }); + }); + + return toolbarButtonDefinitions; +} + +function _handleBuiltIn({ behavior } = {}) { + if (behavior === 'CINE') { + this.setState({ + isCineDialogOpen: !this.state.isCineDialogOpen, + }); + } +} + export default ToolbarRow; diff --git a/src/connectedComponents/ViewerMain.js b/src/connectedComponents/ViewerMain.js index f10d8e590..8e412def1 100644 --- a/src/connectedComponents/ViewerMain.js +++ b/src/connectedComponents/ViewerMain.js @@ -2,8 +2,7 @@ import './ViewerMain.css'; import { Component } from 'react'; import ConnectedLayoutManager from './ConnectedLayoutManager.js'; -// import { OHIF } from 'ohif-core'; -// +import ConnectedToolContextMenu from './ConnectedToolContextMenu.js'; import PropTypes from 'prop-types'; import React from 'react'; @@ -20,15 +19,6 @@ class ViewerMain extends Component { constructor(props) { super(props); - // Initialize hotkeys - // new OHIF.HotkeysUtil('viewer', { - // setViewportSpecificData: props.setViewportSpecificData, - // clearViewportSpecificData: props.clearViewportSpecificData, - // setToolActive: props.setToolActive, - // setActiveViewportSpecificData: props.setActiveViewportSpecificData, - // }); - // hotkeys.init(); - this.state = { displaySets: [], }; @@ -139,7 +129,10 @@ class ViewerMain extends Component { studies={this.props.studies} viewportData={this.getViewportData()} setViewportData={this.setViewportData} - /> + > + {/* Children to add to each viewport that support children */} + +
); } diff --git a/src/connectedComponents/ViewerRetrieveStudyData.js b/src/connectedComponents/ViewerRetrieveStudyData.js index bf9f5a08b..8622f4bd4 100644 --- a/src/connectedComponents/ViewerRetrieveStudyData.js +++ b/src/connectedComponents/ViewerRetrieveStudyData.js @@ -1,9 +1,13 @@ import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import OHIF from 'ohif-core'; -import ConnectedViewer from './ConnectedViewer.js'; +import { metadata, studies, utils } from 'ohif-core'; -const { createDisplaySets } = OHIF.utils; +import ConnectedViewer from './ConnectedViewer.js'; +import PropTypes from 'prop-types'; +import { extensionManager } from './../App.js'; + +const { OHIFStudyMetadata } = metadata; +const { retrieveStudiesMetadata } = studies; +const { studyMetadataManager, updateMetaDataManager } = utils; class ViewerRetrieveStudyData extends Component { static propTypes = { @@ -17,32 +21,55 @@ class ViewerRetrieveStudyData extends Component { error: null, }; - componentDidMount() { + async componentDidMount() { // TODO: Avoid using timepoints here //const params = { studyInstanceUids, seriesInstanceUids, timepointId, timepointsFilter={} }; const { studyInstanceUids, seriesInstanceUids, server } = this.props; - const promise = OHIF.studies.retrieveStudiesMetadata( - server, - studyInstanceUids, - seriesInstanceUids - ); - // Render the viewer when the data is ready - promise - .then(studies => { - const updatedStudies = createDisplaySets(studies); + try { + const studies = await retrieveStudiesMetadata( + server, + studyInstanceUids, + seriesInstanceUids + ); - this.setState({ - studies: updatedStudies, - }); - }) - .catch(error => { - this.setState({ - error: true, - }); + // Render the viewer when the data is ready + // TODO: CLEAR THIS SOMEWHERE ELSE + studyMetadataManager.purge(); - throw new Error(error); + // Map studies to new format, update metadata manager? + const updatedStudies = studies.map(study => { + const studyMetadata = new OHIFStudyMetadata( + study, + study.studyInstanceUid + ); + const sopClassHandlerModules = + extensionManager.modules['sopClassHandlerModule']; + + study.displaySets = + study.displaySets || + studyMetadata.createDisplaySets(sopClassHandlerModules); + studyMetadata.setDisplaySets(study.displaySets); + + // Updates WADO-RS metaDataManager + updateMetaDataManager(study); + + studyMetadataManager.add(studyMetadata); + + return study; }); + + this.setState({ + studies: updatedStudies, + }); + } catch (err) { + this.setState({ + error: true, + }); + + // TODO: Handle gracefully instead of throwing? + throw new Error(err); + } } render() { diff --git a/src/lib/jumpToRowItem.js b/src/lib/jumpToRowItem.js index fb05835e1..f9edbfd60 100644 --- a/src/lib/jumpToRowItem.js +++ b/src/lib/jumpToRowItem.js @@ -1,4 +1,7 @@ -import { OHIF } from 'ohif-core'; +import { measurements, utils } from 'ohif-core'; + +const { MeasurementApi } = measurements; +const { studyMetadataManager } = utils; // TODO: Move this function to OHIF itself so we can use it on the OHIF measurment table (when it is finished) @@ -47,9 +50,7 @@ export default function jumpToRowItem( let measurement = dataAtThisTimepoint; - const { tool } = OHIF.measurements.MeasurementApi.getToolConfiguration( - toolType - ); + const { tool } = MeasurementApi.getToolConfiguration(toolType); if (options.childToolKey) { measurement = dataAtThisTimepoint[options.childToolKey]; } else if (Array.isArray(tool.childTools)) { @@ -79,7 +80,7 @@ export default function jumpToRowItem( return; } - const study = OHIF.utils.studyMetadataManager.get(data.studyInstanceUid); + const study = studyMetadataManager.get(data.studyInstanceUid); if (!study) { throw new Error('Study not found.'); } diff --git a/src/routes/StandaloneRouting.js b/src/routes/StandaloneRouting.js index 884261d78..622f68625 100644 --- a/src/routes/StandaloneRouting.js +++ b/src/routes/StandaloneRouting.js @@ -1,10 +1,14 @@ import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import qs from 'querystring'; -import Viewer from '../connectedComponents/Viewer'; -import OHIF from 'ohif-core'; +import { log, metadata, studies, utils } from 'ohif-core'; -const { createDisplaySets } = OHIF.utils; +import PropTypes from 'prop-types'; +import Viewer from '../connectedComponents/Viewer'; +import { extensionManager } from './../App.js'; +import qs from 'querystring'; + +const { OHIFStudyMetadata } = metadata; +const { retrieveStudiesMetadata } = studies; +const { studyMetadataManager, updateMetaDataManager } = utils; class StandaloneRouting extends Component { state = { @@ -31,7 +35,7 @@ class StandaloneRouting extends Component { // Add event listeners for request failure oReq.addEventListener('error', error => { - OHIF.log.warn('An error occurred while retrieving the JSON data'); + log.warn('An error occurred while retrieving the JSON data'); reject(error); }); @@ -41,11 +45,11 @@ class StandaloneRouting extends Component { // Parse the response content // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseText if (!oReq.responseText) { - OHIF.log.warn('Response was undefined'); + log.warn('Response was undefined'); reject(new Error('Response was undefined')); } - OHIF.log.info(JSON.stringify(oReq.responseText, null, 2)); + log.info(JSON.stringify(oReq.responseText, null, 2)); const data = JSON.parse(oReq.responseText); if (data.servers && query.studyInstanceUids) { @@ -55,20 +59,18 @@ class StandaloneRouting extends Component { const studyInstanceUids = query.studyInstanceUids.split(';'); const seriesInstanceUids = []; - OHIF.studies - .retrieveStudiesMetadata( - server, - studyInstanceUids, - seriesInstanceUids - ) - .then( - studies => { - resolve(studies); - }, - error => { - reject(error); - } - ); + retrieveStudiesMetadata( + server, + studyInstanceUids, + seriesInstanceUids + ).then( + studies => { + resolve(studies); + }, + error => { + reject(error); + } + ); } else { resolve(data.studies); } @@ -77,7 +79,7 @@ class StandaloneRouting extends Component { // Open the Request to the server for the JSON data // In this case we have a server-side route called /api/ // which responds to GET requests with the study data - OHIF.log.info(`Sending Request to: ${url}`); + log.info(`Sending Request to: ${url}`); oReq.open('GET', url); oReq.setRequestHeader('Accept', 'application/json'); @@ -86,18 +88,39 @@ class StandaloneRouting extends Component { }); } - componentDidMount() { - const query = qs.parse(this.props.location.search); - StandaloneRouting.parseQueryAndFetchStudies(query).then( - studies => { - const updatedStudies = createDisplaySets(studies); + async componentDidMount() { + try { + const query = qs.parse(this.props.location.search); + const studies = await StandaloneRouting.parseQueryAndFetchStudies(query); - this.setState({ studies: updatedStudies }); - }, - error => { - this.setState({ error }); - } - ); + studyMetadataManager.purge(); + + // Map studies to new format, update metadata manager? + const updatedStudies = studies.map(study => { + const studyMetadata = new OHIFStudyMetadata( + study, + study.studyInstanceUid + ); + const sopClassHandlerModules = + extensionManager.modules['sopClassHandlerModule']; + + study.displaySets = + study.displaySets || + studyMetadata.createDisplaySets(sopClassHandlerModules); + studyMetadata.setDisplaySets(study.displaySets); + + // Updates WADO-RS metaDataManager + updateMetaDataManager(study); + + studyMetadataManager.add(studyMetadata); + + return study; + }); + + this.setState({ studies: updatedStudies }); + } catch (error) { + this.setState({ error }); + } } render() { diff --git a/src/store/index.js b/src/store/index.js index 04c81a2d6..baa63385e 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1,18 +1,28 @@ -import { combineReducers, createStore } from 'redux'; +import { applyMiddleware, combineReducers, createStore } from 'redux'; +// import { createLogger } from 'redux-logger'; import layoutReducers from './layout/reducers.js'; import { reducer as oidcReducer } from 'redux-oidc'; import { redux } from 'ohif-core'; +import thunkMiddleware from 'redux-thunk'; // Combine our ohif-core, ui, and oidc reducers // Set init data, using values found in localStorage const { reducers, localStorage } = redux; +// const loggerMiddleware = createLogger(); reducers.ui = layoutReducers; reducers.oidc = oidcReducer; -const combined = combineReducers(reducers); -const store = createStore(combined, localStorage.loadState()); +const rootReducer = combineReducers(reducers); +const store = createStore( + rootReducer, + localStorage.loadState(), // preloadedState + applyMiddleware( + thunkMiddleware // Lets us dispatch() functions + // loggerMiddleware // neat middleware that logs actions + ) +); // When the store's preferences change, // Update our cached preferences in localStorage diff --git a/src/store/layout/actions.js b/src/store/layout/actions.js index 319628c08..2b08800b1 100644 --- a/src/store/layout/actions.js +++ b/src/store/layout/actions.js @@ -1,18 +1,3 @@ -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, @@ -24,10 +9,6 @@ export const setRightSidebarOpen = state => ({ }); const actions = { - addActiveContext, - removeActiveContext, - clearActiveContexts, - // setLeftSidebarOpen, setRightSidebarOpen, }; diff --git a/src/store/layout/reducers.js b/src/store/layout/reducers.js index 6659d72a7..b292074ef 100644 --- a/src/store/layout/reducers.js +++ b/src/store/layout/reducers.js @@ -3,27 +3,10 @@ const defaultState = { rightSidebarOpen: 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 }); diff --git a/src/store/layout/selectors.js b/src/store/layout/selectors.js new file mode 100644 index 000000000..50a8ddb2e --- /dev/null +++ b/src/store/layout/selectors.js @@ -0,0 +1,28 @@ +import { createSelector } from 'reselect'; + +const getActiveViewportIndex = state => state.viewports.activeViewportIndex; +const getLayoutViewports = state => state.viewports.layout.viewports; +const getViewportSpecificData = state => state.viewports.viewportSpecificData; + +/** + * Think of this as a computed getter for our store. It lets us watch parts of + * our redux state, and only update/recalculate when those values change. + */ +export const getActiveContexts = createSelector( + [getActiveViewportIndex, getLayoutViewports, getViewportSpecificData], + (activeViewportIndex, layoutViewports, viewportSpecificData) => { + const activeContexts = ['VIEWER']; + const activeLayoutViewport = layoutViewports[activeViewportIndex] || {}; + const activeViewportSpecificData = + viewportSpecificData[activeViewportIndex] || {}; + const activeViewportPluginName = + activeLayoutViewport.plugin || activeViewportSpecificData.plugin; + + if (activeViewportPluginName) { + const activeViewportExtension = `ACTIVE_VIEWPORT::${activeViewportPluginName.toUpperCase()}`; + activeContexts.push(activeViewportExtension); + } + + return activeContexts; + } +); diff --git a/src/utils/getDefaultToolbarButtons.js b/src/utils/getDefaultToolbarButtons.js deleted file mode 100644 index 408f84c45..000000000 --- a/src/utils/getDefaultToolbarButtons.js +++ /dev/null @@ -1,60 +0,0 @@ -export default function() { - return [ - { - command: 'StackScroll', - type: 'tool', - text: 'Stack Scroll', - icon: 'bars', - active: false, - }, - { - command: 'Zoom', - type: 'tool', - text: 'Zoom', - icon: 'search-plus', - active: false, - }, - { - command: 'Wwwc', - type: 'tool', - text: 'Levels', - icon: 'level', - active: true, - }, - { - command: 'Pan', - type: 'tool', - text: 'Pan', - icon: 'arrows', - active: false, - }, - { - command: 'Length', - type: 'tool', - text: 'Length', - icon: 'measure-temp', - active: false, - }, - /*{ - command: 'Annotate', - type: 'tool', - text: 'Annotate', - icon: `icon-tools-measure-non-target`, - active: false - },*/ - { - command: 'Angle', - type: 'tool', - text: 'Angle', - icon: 'angle-left', - active: false, - }, - { - command: 'reset', - type: 'command', - text: 'Reset', - icon: 'reset', - active: false, - }, - ]; -} diff --git a/src/utils/getDefaultToolbarButtons.test.js b/src/utils/getDefaultToolbarButtons.test.js deleted file mode 100644 index 8f59f53fa..000000000 --- a/src/utils/getDefaultToolbarButtons.test.js +++ /dev/null @@ -1,9 +0,0 @@ -import getDefaultToolbarButtons from './getDefaultToolbarButtons.js'; - -describe('getDefaultToolbarButtons.js', () => { - it('returns a non-empty array', () => { - const buttons = getDefaultToolbarButtons(); - - expect(buttons.length).toBeGreaterThan(0); - }); -}); diff --git a/src/utils/index.js b/src/utils/index.js index 22478739a..efcc1628c 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -1,9 +1,4 @@ -import getDefaultToolbarButtons from './getDefaultToolbarButtons.js'; import getUserManagerForOpenIdConnectClient from './getUserManagerForOpenIdConnectClient.js'; import initWebWorkers from './initWebWorkers.js'; -export { - getDefaultToolbarButtons, - getUserManagerForOpenIdConnectClient, - initWebWorkers, -}; +export { getUserManagerForOpenIdConnectClient, initWebWorkers }; diff --git a/src/utils/index.test.js b/src/utils/index.test.js index c164ab30e..03f96fe64 100644 --- a/src/utils/index.test.js +++ b/src/utils/index.test.js @@ -5,11 +5,7 @@ describe('utils', () => { const utilExports = Object.keys(utils).sort(); expect(utilExports).toEqual( - [ - 'getDefaultToolbarButtons', - 'getUserManagerForOpenIdConnectClient', - 'initWebWorkers', - ].sort() + ['getUserManagerForOpenIdConnectClient', 'initWebWorkers'].sort() ); }); }); diff --git a/yarn.lock b/yarn.lock index b0b92cfd1..ef0f44b3c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1435,29 +1435,45 @@ universal-user-agent "^2.0.0" url-template "^2.0.8" -"@ohif/extension-cornerstone@0.0.36": - version "0.0.36" - resolved "https://registry.yarnpkg.com/@ohif/extension-cornerstone/-/extension-cornerstone-0.0.36.tgz#a09a01d152bb9f850698b537b02355456425f8d1" - integrity sha512-FrR9L74vqjNsW2wOSHeDXQo/twJAf/kM0awCn2/zGXN4tNxEaZRq56gfij86CH0OBGl1Wdzb8Zl6xe0JCtsNrA== +"@ohif/extension-cornerstone@0.0.37": + version "0.0.37" + resolved "https://registry.yarnpkg.com/@ohif/extension-cornerstone/-/extension-cornerstone-0.0.37.tgz#d9d31409ffbc773dde17767daa02b87f461cf11a" + integrity sha512-090WPGXOYIxZjQe0mIM7SipgR3ggUZpbtWpwg7huWC0PQObIM3Juy4L8mtss23OKaKsmrmVxUaFc5NRQhs+Z+w== dependencies: "@babel/runtime" "^7.2.0" classnames "^2.2.6" lodash.throttle "^4.1.1" react-cornerstone-viewport "0.1.30" -"@ohif/extension-dicom-microscopy@0.0.6": - version "0.0.6" - resolved "https://registry.yarnpkg.com/@ohif/extension-dicom-microscopy/-/extension-dicom-microscopy-0.0.6.tgz#18e0c128689695a7af505bc3dd7ede97df7f8be2" - integrity sha512-5On3GdZgeyMse2CKI0sbxJJ269IqlFXZ0Wi+8+D9lsp0Wdt2AOx15ZmmFA9MJ6GGluMHNrrTY2gZBwLyLUZyRw== +"@ohif/extension-dicom-html@0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@ohif/extension-dicom-html/-/extension-dicom-html-0.0.3.tgz#1d085f603379599cf07801ec003e3d5050e2d659" + integrity sha512-S+Mg9OFHNvllDDza55y4/NmBV1bFJ+NLbpcnsw45QyfgT3KAg8v/8ht1QYM9IcBGqhxMTJs8OqBuJI9sAiTWlw== + dependencies: + "@babel/runtime" "^7.2.0" + +"@ohif/extension-dicom-microscopy@0.0.7": + version "0.0.7" + resolved "https://registry.yarnpkg.com/@ohif/extension-dicom-microscopy/-/extension-dicom-microscopy-0.0.7.tgz#2ac78c2eef8915e43a81e82ff895c251ced0b441" + integrity sha512-QXf7+YE297owIWBMGJJsJwbMk6bfmmu0fBJQhYFwXQYBOToSiabdr21PBxNADf88ZC1kiBcULOcTBRypJtd31g== dependencies: "@babel/runtime" "^7.2.0" classnames "^2.2.6" dicom-microscopy-viewer "^0.4.3" -"@ohif/extension-vtk@0.0.6": - version "0.0.6" - resolved "https://registry.yarnpkg.com/@ohif/extension-vtk/-/extension-vtk-0.0.6.tgz#a4100963d87f268190ecac7784ff131bd4350b20" - integrity sha512-IwXIpAQEOSRbPNXW0B6XfBMrp3k3LvdD/uyrOeEjI35hVjABf/9aGwqMk9wiw672AN26w/eZ04uXeKjRBsLzsQ== +"@ohif/extension-dicom-pdf@0.0.7": + version "0.0.7" + resolved "https://registry.yarnpkg.com/@ohif/extension-dicom-pdf/-/extension-dicom-pdf-0.0.7.tgz#ba80a77235d43876e25485e1a964f36ebf104b94" + integrity sha512-uOevtxH6Vm2p+2xgo+ER3+oV1zfnUqHYoeqrICaKP3PKXGcVanWFE8W/RWiNZ+PS2/LNpSjJyLfuSxwZ3IBUVw== + dependencies: + "@babel/runtime" "^7.2.0" + classnames "^2.2.6" + lodash.isequal "^4.5.0" + +"@ohif/extension-vtk@0.0.7": + version "0.0.7" + resolved "https://registry.yarnpkg.com/@ohif/extension-vtk/-/extension-vtk-0.0.7.tgz#5bad8e35cae65957b467a12cc17e027ce80ad40a" + integrity sha512-yEdtnk7ImIoM3YrDkXXFo9ClJAXbd3rc9si3GlrOY6ImeWtwTf5LvcHG3QCe1iIfEJ2Q8wkGfERddbSw3joIYQ== dependencies: "@babel/runtime" "^7.4.5" "@ohif/i18n" "0.0.4" @@ -4503,6 +4519,11 @@ dedent@0.7.0, dedent@^0.7.0: resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= +deep-diff@^0.3.5: + version "0.3.8" + resolved "https://registry.yarnpkg.com/deep-diff/-/deep-diff-0.3.8.tgz#c01de63efb0eec9798801d40c7e0dae25b582c84" + integrity sha1-wB3mPvsO7JeYgB1Ax+Da4ltYLIQ= + deep-equal@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" @@ -10266,10 +10287,10 @@ 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.9: - version "0.5.9" - resolved "https://registry.yarnpkg.com/ohif-core/-/ohif-core-0.5.9.tgz#337835c82571bdc4935210273a6018c3af872adf" - integrity sha512-TvA/THiTpBnerRbv4FJlfwSMSyb4Tkn40ud14FP6JxLUQgCtLsTsaFhXIFBwZvBR/qVJwIlDluMxj/vqobCwkQ== +ohif-core@0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/ohif-core/-/ohif-core-0.6.0.tgz#9fea32d8d10da84b9030d2e4f76b30e431e14ac2" + integrity sha512-2Z/HTRZ21i7w056MifH1k0y4brhrZSUsmeqbMcPLSh1cztRGnLQBamBZ08SzWvktcNxLDuwJI9zNDkJ8Gw4Idw== dependencies: "@babel/runtime" "^7.2.0" ajv "^6.10.0" @@ -10280,22 +10301,6 @@ ohif-core@0.5.9: mousetrap "^1.6.3" validate.js "^0.12.0" -ohif-dicom-html-extension@^0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/ohif-dicom-html-extension/-/ohif-dicom-html-extension-0.0.2.tgz#5dd80836ee8f12f6ddf6de2b88c72af96d8c347b" - integrity sha512-7/YkaKrgnydiypFl39mvWO5vxCoGecudkdyCFedgDnG10UvkDF4nBcgNvoBupwBtGKYhXTfqye6fPWxQO0NWyw== - dependencies: - "@babel/runtime" "^7.2.0" - -ohif-dicom-pdf-extension@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/ohif-dicom-pdf-extension/-/ohif-dicom-pdf-extension-0.0.6.tgz#5c1c1bf955d160dda01c36c4ed899d8e65384d63" - integrity sha512-MRBDnC78itsos3c8QMKjgaFp8tmXyGIq5qVLREr//osqCYirhWQbCFJv73eYR5+bRCdJfUMMpvnDSChFIhcT6A== - dependencies: - "@babel/runtime" "^7.2.0" - classnames "^2.2.6" - lodash.isequal "^4.5.0" - oidc-client@1.7.x: version "1.7.1" resolved "https://registry.yarnpkg.com/oidc-client/-/oidc-client-1.7.1.tgz#8b9d8d50fd7f878968b1cda17712c1747eef9a54" @@ -12408,10 +12413,10 @@ react-transition-group@^2.0.0, react-transition-group@^2.2.0: prop-types "^15.6.2" react-lifecycles-compat "^3.0.4" -react-viewerbase@0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/react-viewerbase/-/react-viewerbase-0.9.0.tgz#947ab61bf32e1d58836a637403d128247e175d2e" - integrity sha512-VeOGzno5fg9A0Ttx+O3CQ8KXPr1XbdEOVbA+JiLwcuycX3CmDsqLzODsjeXb8npfXDbQA+n/ZPCyxsB7JDEBlA== +react-viewerbase@0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/react-viewerbase/-/react-viewerbase-0.10.0.tgz#4ac07649325759f8a58fc3be50dea4d1db8f13f8" + integrity sha512-pUt+HUGoEkDnVhxcg+gsoRzegEPVpsMPH8cnJINRoaATqbxc0jbt1N+3fTNTbPMMKZYMxC99L5bQ6OQ1QF+3qw== dependencies: "@babel/runtime" "7.2.0" "@ohif/i18n" "^0.0.4" @@ -12710,6 +12715,13 @@ redeyed@~2.1.0: dependencies: esprima "~4.0.0" +redux-logger@^3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/redux-logger/-/redux-logger-3.0.6.tgz#f7555966f3098f3c88604c449cf0baf5778274bf" + integrity sha1-91VZZvMJjzyIYExEnPC69XeCdL8= + dependencies: + deep-diff "^0.3.5" + redux-oidc@3.1.x: version "3.1.2" resolved "https://registry.yarnpkg.com/redux-oidc/-/redux-oidc-3.1.2.tgz#31a771b0e05a65879626262e1e63ba70a0936ffd" @@ -12717,6 +12729,11 @@ redux-oidc@3.1.x: optionalDependencies: immutable ">=3.6.0" +redux-thunk@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622" + integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw== + redux@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.1.tgz#436cae6cc40fbe4727689d7c8fae44808f1bfef5" @@ -13028,6 +13045,11 @@ requires-port@^1.0.0: resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= +reselect@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7" + integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA== + reserved-words@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/reserved-words/-/reserved-words-0.1.2.tgz#00a0940f98cd501aeaaac316411d9adc52b31ab1"