Merge pull request #549 from dannyrb/feature/extensions-panels-and-docs
Feature/extensions panels and docs
This commit is contained in:
commit
f7ba7621ef
@ -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" %}
|
||||
|
||||
<!--
|
||||
Links
|
||||
-->
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
[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
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
69
extensions/_ohif-example-extension/src/index.js
Normal file
69
extensions/_ohif-example-extension/src/index.js
Normal file
@ -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,
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -1,3 +1,9 @@
|
||||
{
|
||||
"singleQuote": true
|
||||
}
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 80,
|
||||
"proseWrap": "always",
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
|
||||
@ -1,54 +1,89 @@
|
||||
# @ohif/extension-cornerstone
|
||||
|
||||
## Commands
|
||||

|
||||
|
||||
This extensions includes the following `Commands` and `Command Definitions`. These can be registered with `@ohif/core`'s `CommandManager`. After registering the commands, they can be bound to `hotkeys` using the `HotkeysManager` and listed in the `UserPreferences` modal.
|
||||
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.
|
||||
<!-- TODO: Simple image or GIF? -->
|
||||
|
||||
| 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]
|
||||
|
||||
<!--
|
||||
Links
|
||||
-->
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
[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
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -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;
|
||||
@ -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 <WrappedComponent {...mergedProps} />;
|
||||
};
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
@ -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'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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 (
|
||||
<div className="ToolbarModule">
|
||||
<ConnectedToolbarSection />
|
||||
<ToolbarButton
|
||||
active={this.state.cineDialogOpen}
|
||||
onClick={this.onClickCineToolbarButton}
|
||||
text="CINE"
|
||||
icon="youtube"
|
||||
/>
|
||||
<div className="CineDialogContainer" style={cineDialogContainerStyle}>
|
||||
<ConnectedCineDialog />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ToolbarModule;
|
||||
@ -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',
|
||||
};
|
||||
@ -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;
|
||||
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
},
|
||||
};
|
||||
|
||||
106
extensions/ohif-cornerstone-extension/src/toolbarModule.js
Normal file
106
extensions/ohif-cornerstone-extension/src/toolbarModule.js
Normal file
@ -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',
|
||||
};
|
||||
@ -1,3 +1,9 @@
|
||||
{
|
||||
"singleQuote": true
|
||||
}
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 80,
|
||||
"proseWrap": "always",
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
|
||||
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
{
|
||||
"singleQuote": true
|
||||
}
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 80,
|
||||
"proseWrap": "always",
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
|
||||
34
extensions/ohif-dicom-microscopy-extension/README.md
Normal file
34
extensions/ohif-dicom-microscopy-extension/README.md
Normal file
@ -0,0 +1,34 @@
|
||||
# @ohif/extension-dicom-microscopy
|
||||
|
||||

|
||||
|
||||
<!-- TODO: Simple image or GIF? -->
|
||||
|
||||
#### Index
|
||||
|
||||
Extension Id: `microscopy`
|
||||
|
||||
- [SopClassHandler Module](#sopclasshandler-module)
|
||||
- [Viewport Module](#viewport-module)
|
||||
|
||||
## SopClassHandler Module
|
||||
|
||||
..
|
||||
|
||||
## Viewport Module
|
||||
|
||||
...
|
||||
|
||||
## Resources
|
||||
|
||||
### Repositories
|
||||
|
||||
...
|
||||
|
||||
<!--
|
||||
Links
|
||||
-->
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
@ -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",
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 });
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
},
|
||||
};
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
{
|
||||
"singleQuote": true
|
||||
}
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 80,
|
||||
"proseWrap": "always",
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
{
|
||||
"singleQuote": true
|
||||
}
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 80,
|
||||
"proseWrap": "always",
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
|
||||
@ -1 +1,55 @@
|
||||
# @ohif/extension-vtk
|
||||
|
||||

|
||||
|
||||
<!-- TODO: Simple image or GIF? -->
|
||||
|
||||
#### 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]
|
||||
|
||||
<!--
|
||||
Links
|
||||
-->
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
[react-viewport]: https://github.com/OHIF/react-vtkjs-viewport
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
@ -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;
|
||||
};
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
.imageViewerLoadingIndicator {
|
||||
color: #91B9CD;
|
||||
color: #91b9cd;
|
||||
}
|
||||
|
||||
.loadingIndicator {
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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 <WrappedComponent children={children} {...props} />;
|
||||
};
|
||||
}
|
||||
|
||||
// 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
|
||||
);
|
||||
});
|
||||
}
|
||||
@ -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,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ConnectedToolbarSection from './ConnectedToolbarSection';
|
||||
|
||||
class ToolbarModule extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="ToolbarModule">
|
||||
<ConnectedToolbarSection />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ToolbarModule;
|
||||
@ -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',
|
||||
};
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
39
extensions/ohif-vtk-extension/src/toolbarModule.js
Normal file
39
extensions/ohif-vtk-extension/src/toolbarModule.js
Normal file
@ -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',
|
||||
};
|
||||
@ -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(
|
||||
|
||||
@ -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(
|
||||
|
||||
19
package.json
19
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",
|
||||
|
||||
58
src/App.js
58
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: [<ConnectedToolContextMenu key="tool-context" />],
|
||||
};
|
||||
// const children = {
|
||||
// viewport: [<ConnectedToolContextMenu key="tool-context" />],
|
||||
// };
|
||||
|
||||
/** 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 };
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
@ -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');
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -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;
|
||||
@ -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));
|
||||
// },
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -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 = <PluginComponent />;
|
||||
}
|
||||
const cineDialogContainerStyle = {
|
||||
display: this.state.isCineDialogOpen ? 'block' : 'none',
|
||||
position: 'absolute',
|
||||
top: '82px',
|
||||
zIndex: 999,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="ToolbarRow">
|
||||
<div className="pull-left m-t-1 p-y-1" style={{ padding: '10px' }}>
|
||||
<RoundedButtonGroup
|
||||
options={leftSidebarToggle}
|
||||
value={leftSidebarValue}
|
||||
onValueChanged={this.onLeftSidebarValueChanged}
|
||||
/>
|
||||
<>
|
||||
<div className="ToolbarRow">
|
||||
<div className="pull-left m-t-1 p-y-1" style={{ padding: '10px' }}>
|
||||
<RoundedButtonGroup
|
||||
options={leftSidebarToggle}
|
||||
value={leftSidebarValue}
|
||||
onValueChanged={this.onLeftSidebarValueChanged}
|
||||
/>
|
||||
</div>
|
||||
{buttonComponents}
|
||||
<ConnectedLayoutButton />
|
||||
<ConnectedPluginSwitch />
|
||||
<div
|
||||
className="pull-right m-t-1 rm-x-1"
|
||||
style={{ marginLeft: 'auto' }}
|
||||
>
|
||||
<RoundedButtonGroup
|
||||
options={rightSidebarToggle}
|
||||
value={rightSidebarValue}
|
||||
onValueChanged={this.onRightSidebarValueChanged}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{pluginComp}
|
||||
<ConnectedLayoutButton />
|
||||
<ConnectedPluginSwitch />
|
||||
<div className="pull-right m-t-1 rm-x-1" style={{ marginLeft: 'auto' }}>
|
||||
<RoundedButtonGroup
|
||||
options={rightSidebarToggle}
|
||||
value={rightSidebarValue}
|
||||
onValueChanged={this.onRightSidebarValueChanged}
|
||||
/>
|
||||
<div className="CineDialogContainer" style={cineDialogContainerStyle}>
|
||||
<ConnectedCineDialog />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (
|
||||
<ToolbarButton
|
||||
key={button.id}
|
||||
label={button.label}
|
||||
icon={button.icon}
|
||||
onClick={(evt, props) => {
|
||||
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;
|
||||
|
||||
@ -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 */}
|
||||
<ConnectedToolContextMenu />
|
||||
</ConnectedLayoutManager>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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.');
|
||||
}
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -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 });
|
||||
|
||||
28
src/store/layout/selectors.js
Normal file
28
src/store/layout/selectors.js
Normal file
@ -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;
|
||||
}
|
||||
);
|
||||
@ -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,
|
||||
},
|
||||
];
|
||||
}
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
@ -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 };
|
||||
|
||||
@ -5,11 +5,7 @@ describe('utils', () => {
|
||||
const utilExports = Object.keys(utils).sort();
|
||||
|
||||
expect(utilExports).toEqual(
|
||||
[
|
||||
'getDefaultToolbarButtons',
|
||||
'getUserManagerForOpenIdConnectClient',
|
||||
'initWebWorkers',
|
||||
].sort()
|
||||
['getUserManagerForOpenIdConnectClient', 'initWebWorkers'].sort()
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
94
yarn.lock
94
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"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user