commit
7efdb40607
@ -1,10 +1,27 @@
|
||||
# Extensions
|
||||
|
||||
Extensions add new functionality to the viewer by registering one or more modules. They go one step further than configuration in that they allow us to inject custom React components, so long as they adhere to the module's interface. This can be something as simple as adding a new button to the toolbar, or as complex as a new viewport capable of rendering volumes in 3D.
|
||||
Extensions add new functionality to the viewer by registering one or more
|
||||
modules. They go one step further than configuration in that they allow us to
|
||||
inject custom React components, so long as they adhere to the module's
|
||||
interface. This can be something as simple as adding a new button to the
|
||||
toolbar, or as complex as a new viewport capable of rendering volumes in 3D.
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Modules](#modules)
|
||||
- [Viewport](#viewport)
|
||||
- [Toolbar](#toolbar)
|
||||
- [SOP Class Handler](#sopclasshandler)
|
||||
- [Panel](#panel)
|
||||
- [Commands](#commands)
|
||||
- [Hotkeys](#hotkeys)
|
||||
|
||||
## Overview
|
||||
|
||||
At a glance, an extension is a class or object that has a `getExtensionId()` method, and one or more "module" methods. You can find an abbreviated extension below, or [view the source](https://github.com/OHIF/Viewers/blob/react/extensions/ohif-cornerstone-extension/src/OHIFCornerstoneExtension.js#L32-L65) of our `cornerstone` viewport extension.
|
||||
At a glance, an extension is a class or object that has a `getExtensionId()`
|
||||
method, and one or more "module" methods. You can find an abbreviated extension
|
||||
below, or
|
||||
[view the source](https://github.com/OHIF/Viewers/blob/react/extensions/ohif-cornerstone-extension/src/OHIFCornerstoneExtension.js#L32-L65)
|
||||
of our `cornerstone` viewport extension.
|
||||
|
||||
```js
|
||||
class myCustomExtension {
|
||||
@ -36,11 +53,18 @@ class myCustomExtension {
|
||||
|
||||
### Modules
|
||||
|
||||
There are a few different kinds of modules. Each kind of module allows us to extend the viewer in a different way, and provides a consistent API for us to do so. You can find a full list of the [different types of modules `in ohif-core`](https://github.com/OHIF/ohif-core/blob/43c08a29eff3fb646a0e83a03a236ddd84f4a6e8/src/plugins.js#L1-L6). Information on each type of module, it's API, and how we determine when/where it should be used is included below:
|
||||
There are a few different kinds of modules. Each kind of module allows us to
|
||||
extend the viewer in a different way, and provides a consistent API for us to do
|
||||
so. You can find a full list of the
|
||||
[different types of modules `in ohif-core`](https://github.com/OHIF/ohif-core/blob/43c08a29eff3fb646a0e83a03a236ddd84f4a6e8/src/plugins.js#L1-L6).
|
||||
Information on each type of module, it's API, and how we determine when/where it
|
||||
should be used is included below:
|
||||
|
||||
#### Viewport
|
||||
|
||||
An extension can register a Viewport Module by providing a `getViewportModule()` method that returns a React Component. The React component will receive the following props:
|
||||
An extension can register a Viewport Module by providing a `getViewportModule()`
|
||||
method that returns a React Component. The React component will receive the
|
||||
following props:
|
||||
|
||||
```js
|
||||
children: PropTypes.arrayOf(PropTypes.element)
|
||||
@ -52,7 +76,8 @@ children: PropTypes.node,
|
||||
customProps: PropTypes.object
|
||||
```
|
||||
|
||||
Viewport components are managed by the `LayoutManager`. Which Viewport component is used depends on:
|
||||
Viewport components are managed by the `LayoutManager`. Which Viewport component
|
||||
is used depends on:
|
||||
|
||||
- The Layout Configuration
|
||||
- Registered SopClassHandlers
|
||||
@ -62,11 +87,15 @@ Viewport components are managed by the `LayoutManager`. Which Viewport component
|
||||
|
||||
<center><i>An example of three Viewports</i></center>
|
||||
|
||||
For a complete example implementation, [check out the OHIFCornerstoneViewport](https://github.com/OHIF/Viewers/blob/react/extensions/ohif-cornerstone-extension/src/OHIFCornerstoneViewport.js).
|
||||
For a complete example implementation,
|
||||
[check out the OHIFCornerstoneViewport](https://github.com/OHIF/Viewers/blob/react/extensions/ohif-cornerstone-extension/src/OHIFCornerstoneViewport.js).
|
||||
|
||||
#### Toolbar
|
||||
|
||||
An extension can register a Toolbar Module by providing a `getToolbarModule()` method that returns a React Component. The component does not receive any props. If you want to modify or react to state, you will need to connect to the redux store.
|
||||
An extension can register a Toolbar Module by providing a `getToolbarModule()`
|
||||
method that returns a React Component. The component does not receive any props.
|
||||
If you want to modify or react to state, you will need to connect to the redux
|
||||
store.
|
||||
|
||||

|
||||
|
||||
@ -74,7 +103,8 @@ An extension can register a Toolbar Module by providing a `getToolbarModule()` m
|
||||
|
||||
Toolbar components are rendered in the `ToolbarRow` component.
|
||||
|
||||
For a complete example implementation, [check out the OHIFCornerstoneViewport's Toolbar Module](https://github.com/OHIF/Viewers/blob/react/extensions/ohif-cornerstone-extension/src/ToolbarModule.js).
|
||||
For a complete example implementation,
|
||||
[check out the OHIFCornerstoneViewport's Toolbar Module](https://github.com/OHIF/Viewers/blob/react/extensions/ohif-cornerstone-extension/src/ToolbarModule.js).
|
||||
|
||||
#### SopClassHandler
|
||||
|
||||
@ -84,18 +114,32 @@ For a complete example implementation, [check out the OHIFCornerstoneViewport's
|
||||
|
||||
> The panel module is not yet in use.
|
||||
|
||||
#### Commands
|
||||
|
||||
...
|
||||
|
||||
#### Hotkeys
|
||||
|
||||
...
|
||||
|
||||
### Registering Extensions
|
||||
|
||||
Extensions are registered for the application at startup. The `ExtensionManager`, exposed by `ohif-core`, registers a list of extensions with our application's store. Each module provided by the extension becomes available via `state.plugins.availablePlugins`, and consists of three parts: id, type ([PLUGIN_TYPE](https://github.com/OHIF/ohif-core/blob/43c08a29eff3fb646a0e83a03a236ddd84f4a6e8/src/plugins.js#L1-L6)), and the return value of the module method.
|
||||
Extensions are registered for the application at startup. The
|
||||
`ExtensionManager`, exposed by `ohif-core`, registers a list of extensions with
|
||||
our application's store. Each module provided by the extension becomes available
|
||||
via `state.plugins.availablePlugins`, and consists of three parts: id, type
|
||||
([PLUGIN_TYPE](https://github.com/OHIF/ohif-core/blob/43c08a29eff3fb646a0e83a03a236ddd84f4a6e8/src/plugins.js#L1-L6)),
|
||||
and the return value of the module method.
|
||||
|
||||
In a future version, we will likely expose a way to provide the extensions you would like included at startup.
|
||||
In a future version, we will likely expose a way to provide the extensions you
|
||||
would like included at startup.
|
||||
|
||||
_app.js_
|
||||
|
||||
```js
|
||||
import { createStore, combineReducers } from "redux";
|
||||
import OHIF from "ohif-core";
|
||||
import OHIFCornerstoneExtension from "ohif-cornerstone-extension";
|
||||
import { createStore, combineReducers } from 'redux';
|
||||
import OHIF from 'ohif-core';
|
||||
import OHIFCornerstoneExtension from 'ohif-cornerstone-extension';
|
||||
|
||||
const combined = combineReducers(OHIF.redux.reducers);
|
||||
const store = createStore(combined);
|
||||
@ -108,6 +152,10 @@ ExtensionManager.registerExtensions(store, extensions);
|
||||
|
||||
## OHIF Maintained Extensions
|
||||
|
||||
A small number of powerful extensions for popular use cases are maintained by OHIF. They're co-located in the [`OHIF/Viewers`](https://github.com/OHIF/Viewers/tree/react/) repository, in the top level [`extensions/`](https://github.com/OHIF/Viewers/tree/react/extensions) directory.
|
||||
A small number of powerful extensions for popular use cases are maintained by
|
||||
OHIF. They're co-located in the
|
||||
[`OHIF/Viewers`](https://github.com/OHIF/Viewers/tree/react/) repository, in the
|
||||
top level [`extensions/`](https://github.com/OHIF/Viewers/tree/react/extensions)
|
||||
directory.
|
||||
|
||||
{% include "./_maintained-extensions-table.md" %}
|
||||
|
||||
@ -1 +1,54 @@
|
||||
# @ohif/extension-cornerstone
|
||||
|
||||
## Commands
|
||||
|
||||
This extensions includes the following `Commands` and `Command Definitions`. These can be registered with `@ohif/core`'s `CommandManager`. After registering the commands, they can be bound to `hotkeys` using the `HotkeysManager` and listed in the `UserPreferences` modal.
|
||||
|
||||
You can read more about [`Commands`](), [`Hotkeys`](), and the [`UserPreferences` Modal]() in their respective locations in the OHIF Viewer's documentation.
|
||||
|
||||
| Command Name | Description | Store Contexts |
|
||||
| ---------------------------- | ----------- | -------------- |
|
||||
| `rotateViewportCW` | | viewports |
|
||||
| `rotateViewportCCW` | | viewports |
|
||||
| `invertViewport` | | viewports |
|
||||
| `flipViewportVertical` | | viewports |
|
||||
| `flipViewportHorizontal` | | viewports |
|
||||
| `scaleUpViewport` | | viewports |
|
||||
| `scaleDownViewport` | | viewports |
|
||||
| `fitViewportToWindow` | | viewports |
|
||||
| `resetViewport` | | viewports |
|
||||
| clearAnnotations | TODO | |
|
||||
| next/previous Image | TODO | |
|
||||
| first/last Image | TODO | |
|
||||
| `nextViewportDisplaySet` | | none |
|
||||
| `previousViewportDisplaySet` | | none |
|
||||
|
||||
### TODO:
|
||||
|
||||
_SET TOOL_
|
||||
|
||||
- [] Default Tool
|
||||
- [] Set Zoom Tool
|
||||
- [] Set WWWC Tool
|
||||
- [] Set Pan Tool
|
||||
- [] Set Angle Measurement Tool
|
||||
- [] Set Stack Scroll Tool
|
||||
- [] Set Magnify Tool
|
||||
- [] Set Length Tool
|
||||
- [] Set Annotate Tool
|
||||
- [] Set Pixel Probe Tool
|
||||
- [] Set Elliptical ROI Tool
|
||||
- [] Set Rectangle ROI Tool
|
||||
|
||||
_OTHER_
|
||||
|
||||
- Show/Hide CINE
|
||||
- W/L Presets
|
||||
- W/L Presets config
|
||||
|
||||
<!--
|
||||
Links
|
||||
-->
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
@ -81,7 +81,7 @@
|
||||
"hammerjs": "^2.0.8",
|
||||
"lodash.isequal": "4.5.0",
|
||||
"moment": "^2.24.0",
|
||||
"ohif-core": "0.5.6",
|
||||
"ohif-core": "0.5.7",
|
||||
"ohif-dicom-html-extension": "^0.0.2",
|
||||
"ohif-dicom-pdf-extension": "^0.0.6",
|
||||
"oidc-client": "1.7.x",
|
||||
|
||||
@ -19,4 +19,55 @@ window.config = {
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
// Extensions should be able to suggest default values for these?
|
||||
// Or we can require that these be explicitly set
|
||||
hotkeys: [
|
||||
// ~ Global
|
||||
{
|
||||
commandName: 'incrementActiveViewport',
|
||||
label: 'Next Image Viewport',
|
||||
keys: ['right'],
|
||||
},
|
||||
{
|
||||
commandName: 'decrementActiveViewport',
|
||||
label: 'Previous Image Viewport',
|
||||
keys: ['left'],
|
||||
},
|
||||
// Supported Keys: https://craig.is/killing/mice
|
||||
// ~ Cornerstone Extension
|
||||
{ commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] },
|
||||
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
|
||||
{ commandName: 'invertViewport', label: 'Invert', keys: ['i'] },
|
||||
{
|
||||
commandName: 'flipViewportVertical',
|
||||
label: 'Flip Horizontally',
|
||||
keys: ['h'],
|
||||
},
|
||||
{
|
||||
commandName: 'flipViewportHorizontal',
|
||||
label: 'Flip Vertically',
|
||||
keys: ['v'],
|
||||
},
|
||||
{ commandName: 'scaleUpViewport', label: 'Zoom In', keys: ['+'] },
|
||||
{ commandName: 'scaleDownViewport', label: 'Zoom Out', keys: ['-'] },
|
||||
{ commandName: 'fitViewportToWindow', label: 'Zoom to Fit', keys: ['='] },
|
||||
{ commandName: 'resetViewport', label: 'Reset', keys: ['space'] },
|
||||
// clearAnnotations
|
||||
// nextImage
|
||||
// previousImage
|
||||
// firstImage
|
||||
// lastImage
|
||||
{
|
||||
commandName: 'nextViewportDisplaySet',
|
||||
label: 'Previous Series',
|
||||
keys: ['pagedown'],
|
||||
},
|
||||
{
|
||||
commandName: 'previousViewportDisplaySet',
|
||||
label: 'Next Series',
|
||||
keys: ['pageup'],
|
||||
},
|
||||
// ~ Cornerstone Tools
|
||||
{ commandName: 'setZoomTool', label: 'Zoom', keys: ['z'] },
|
||||
],
|
||||
};
|
||||
|
||||
@ -16,4 +16,53 @@ window.config = {
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
hotkeys: [
|
||||
// ~ Global
|
||||
{
|
||||
commandName: 'incrementActiveViewport',
|
||||
label: 'Next Image Viewport',
|
||||
keys: ['right'],
|
||||
},
|
||||
{
|
||||
commandName: 'decrementActiveViewport',
|
||||
label: 'Previous Image Viewport',
|
||||
keys: ['left'],
|
||||
},
|
||||
// Supported Keys: https://craig.is/killing/mice
|
||||
// ~ Cornerstone Extension
|
||||
{ commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] },
|
||||
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
|
||||
{ commandName: 'invertViewport', label: 'Invert', keys: ['i'] },
|
||||
{
|
||||
commandName: 'flipViewportVertical',
|
||||
label: 'Flip Horizontally',
|
||||
keys: ['h'],
|
||||
},
|
||||
{
|
||||
commandName: 'flipViewportHorizontal',
|
||||
label: 'Flip Vertically',
|
||||
keys: ['v'],
|
||||
},
|
||||
{ commandName: 'scaleUpViewport', label: 'Zoom In', keys: ['+'] },
|
||||
{ commandName: 'scaleDownViewport', label: 'Zoom Out', keys: ['-'] },
|
||||
{ commandName: 'fitViewportToWindow', label: 'Zoom to Fit', keys: ['='] },
|
||||
{ commandName: 'resetViewport', label: 'Reset', keys: ['space'] },
|
||||
// clearAnnotations
|
||||
// nextImage
|
||||
// previousImage
|
||||
// firstImage
|
||||
// lastImage
|
||||
{
|
||||
commandName: 'nextViewportDisplaySet',
|
||||
label: 'Previous Series',
|
||||
keys: ['pagedown'],
|
||||
},
|
||||
{
|
||||
commandName: 'previousViewportDisplaySet',
|
||||
label: 'Next Series',
|
||||
keys: ['pageup'],
|
||||
},
|
||||
// ~ Cornerstone Tools
|
||||
{ commandName: 'setZoomTool', label: 'Zoom', keys: ['z'] },
|
||||
],
|
||||
};
|
||||
|
||||
@ -29,12 +29,6 @@
|
||||
href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700|Sanchez&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://use.fontawesome.com/releases/v5.6.3/css/all.css"
|
||||
integrity="sha384-UHRtZLI+pbxtHCWp1t77Bi1L4ZtiqrqD80Kn4Z8NTSRyMA2Fd33n5dQ8lWUE00s/"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
@ -9,4 +9,5 @@ echo 'PUBLISHING'
|
||||
./node_modules/.bin/gh-pages \
|
||||
--silent \
|
||||
--repo https://$GITHUB_TOKEN@github.com/OHIF/Viewers.git \
|
||||
--message 'Autogenerated Message: [ci skip]' \
|
||||
--dist docs/latest/_book
|
||||
|
||||
62
src/App.js
62
src/App.js
@ -1,8 +1,13 @@
|
||||
import './config';
|
||||
|
||||
import { OidcProvider, reducer as oidcReducer } from 'redux-oidc';
|
||||
import {
|
||||
CommandsManager,
|
||||
HotkeysManager,
|
||||
extensions,
|
||||
redux,
|
||||
utils,
|
||||
} from 'ohif-core';
|
||||
import React, { Component } from 'react';
|
||||
import { combineReducers, createStore } from 'redux';
|
||||
import {
|
||||
getDefaultToolbarButtons,
|
||||
getUserManagerForOpenIdConnectClient,
|
||||
@ -10,57 +15,70 @@ import {
|
||||
} from './utils/index.js';
|
||||
|
||||
import ConnectedToolContextMenu from './connectedComponents/ConnectedToolContextMenu';
|
||||
import OHIF from 'ohif-core';
|
||||
import OHIFCornerstoneExtension from '@ohif/extension-cornerstone';
|
||||
import OHIFDicomHtmlExtension from 'ohif-dicom-html-extension';
|
||||
import OHIFDicomMicroscopyExtension from '@ohif/extension-dicom-microscopy';
|
||||
import OHIFDicomPDFExtension from 'ohif-dicom-pdf-extension';
|
||||
import OHIFStandaloneViewer from './OHIFStandaloneViewer';
|
||||
import OHIFVTKExtension from '@ohif/extension-vtk';
|
||||
import { OidcProvider } from 'redux-oidc';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Provider } from 'react-redux';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import WhiteLabellingContext from './WhiteLabellingContext';
|
||||
import appCommands from './appCommands';
|
||||
import setupTools from './setupTools';
|
||||
import ui from './redux/ui.js';
|
||||
import store from './store';
|
||||
|
||||
const { ExtensionManager } = OHIF.extensions;
|
||||
const { reducers, localStorage } = OHIF.redux;
|
||||
// ~~~~ APP SETUP
|
||||
const commandsManagerConfig = {
|
||||
getAppState: () => store.getState(),
|
||||
getActiveContexts: () => store.getState().ui.activeContexts,
|
||||
};
|
||||
|
||||
reducers.ui = ui;
|
||||
reducers.oidc = oidcReducer;
|
||||
const commandsManager = new CommandsManager(commandsManagerConfig);
|
||||
const hotkeysManager = new HotkeysManager(commandsManager);
|
||||
|
||||
const combined = combineReducers(reducers);
|
||||
const store = createStore(combined, localStorage.loadState());
|
||||
// TODO: Should be done in extensions w/ commandsModule
|
||||
// ~~ ADD COMMANDS
|
||||
appCommands.init(commandsManager);
|
||||
if (window.config.hotkeys) {
|
||||
hotkeysManager.setHotkeys(window.config.hotkeys, true);
|
||||
}
|
||||
|
||||
store.subscribe(() => {
|
||||
localStorage.saveState({
|
||||
preferences: store.getState().preferences,
|
||||
});
|
||||
// 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 />],
|
||||
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 */
|
||||
const extensions = [
|
||||
extensions.ExtensionManager.registerExtensions(store, [
|
||||
new OHIFCornerstoneExtension({ children }),
|
||||
new OHIFVTKExtension(),
|
||||
new OHIFDicomPDFExtension(),
|
||||
new OHIFDicomHtmlExtension(),
|
||||
new OHIFDicomMicroscopyExtension(),
|
||||
];
|
||||
ExtensionManager.registerExtensions(store, extensions);
|
||||
]);
|
||||
|
||||
// TODO[react] Use a provider when the whole tree is React
|
||||
window.store = store;
|
||||
|
||||
function handleServers(servers) {
|
||||
if (servers) {
|
||||
OHIF.utils.addServers(servers, store);
|
||||
utils.addServers(servers, store);
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,9 +101,7 @@ class App extends Component {
|
||||
|
||||
//
|
||||
const defaultButtons = getDefaultToolbarButtons(this.props.routerBasename);
|
||||
const buttonsAction = OHIF.redux.actions.setAvailableButtons(
|
||||
defaultButtons
|
||||
);
|
||||
const buttonsAction = redux.actions.setAvailableButtons(defaultButtons);
|
||||
|
||||
store.dispatch(buttonsAction);
|
||||
|
||||
@ -134,3 +150,5 @@ class App extends Component {
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
||||
export { commandsManager, hotkeysManager };
|
||||
|
||||
1
src/appCommands/README.md
Normal file
1
src/appCommands/README.md
Normal file
@ -0,0 +1 @@
|
||||
# Commands
|
||||
185
src/appCommands/cornerstone.js
Normal file
185
src/appCommands/cornerstone.js
Normal file
@ -0,0 +1,185 @@
|
||||
import cornerstone from 'cornerstone-core';
|
||||
import { redux } from 'ohif-core';
|
||||
import store from './../store/';
|
||||
|
||||
const { setToolActive } = redux.actions;
|
||||
|
||||
const actions = {
|
||||
rotateViewport: ({ viewports, rotation }) => {
|
||||
const enabledElement = _getActiveViewportEnabledElement(
|
||||
viewports.viewportSpecificData,
|
||||
viewports.activeViewportIndex
|
||||
);
|
||||
|
||||
if (enabledElement) {
|
||||
let viewport = cornerstone.getViewport(enabledElement);
|
||||
viewport.rotation += rotation;
|
||||
cornerstone.setViewport(enabledElement, viewport);
|
||||
}
|
||||
},
|
||||
flipViewportHorizontal: ({ viewports }) => {
|
||||
const enabledElement = _getActiveViewportEnabledElement(
|
||||
viewports.viewportSpecificData,
|
||||
viewports.activeViewportIndex
|
||||
);
|
||||
|
||||
if (enabledElement) {
|
||||
let viewport = cornerstone.getViewport(enabledElement);
|
||||
viewport.hflip = !viewport.hflip;
|
||||
cornerstone.setViewport(enabledElement, viewport);
|
||||
}
|
||||
},
|
||||
flipViewportVertical: ({ viewports }) => {
|
||||
const enabledElement = _getActiveViewportEnabledElement(
|
||||
viewports.viewportSpecificData,
|
||||
viewports.activeViewportIndex
|
||||
);
|
||||
|
||||
if (enabledElement) {
|
||||
let viewport = cornerstone.getViewport(enabledElement);
|
||||
viewport.vflip = !viewport.vflip;
|
||||
cornerstone.setViewport(enabledElement, viewport);
|
||||
}
|
||||
},
|
||||
scaleViewport: ({ viewports, direction }) => {
|
||||
const enabledElement = _getActiveViewportEnabledElement(
|
||||
viewports.viewportSpecificData,
|
||||
viewports.activeViewportIndex
|
||||
);
|
||||
const step = direction * 0.15;
|
||||
|
||||
if (enabledElement) {
|
||||
if (step) {
|
||||
let viewport = cornerstone.getViewport(enabledElement);
|
||||
viewport.scale += step;
|
||||
cornerstone.setViewport(enabledElement, viewport);
|
||||
} else {
|
||||
cornerstone.fitToWindow(enabledElement);
|
||||
}
|
||||
}
|
||||
},
|
||||
resetViewport: ({ viewports }) => {
|
||||
const enabledElement = _getActiveViewportEnabledElement(
|
||||
viewports.viewportSpecificData,
|
||||
viewports.activeViewportIndex
|
||||
);
|
||||
|
||||
if (enabledElement) {
|
||||
cornerstone.reset(enabledElement);
|
||||
}
|
||||
},
|
||||
invertViewport: ({ viewports }) => {
|
||||
const enabledElement = _getActiveViewportEnabledElement(
|
||||
viewports.viewportSpecificData,
|
||||
viewports.activeViewportIndex
|
||||
);
|
||||
|
||||
if (enabledElement) {
|
||||
let viewport = cornerstone.getViewport(enabledElement);
|
||||
viewport.invert = !viewport.invert;
|
||||
cornerstone.setViewport(enabledElement, viewport);
|
||||
}
|
||||
},
|
||||
// This has a weird hard dependency on the tools that are available as toolbar
|
||||
// buttons. You can see this in `ohif-core/src/redux/reducers/tools.js`
|
||||
// the `toolName` needs to equal the button's `command` property.
|
||||
// NOTE: It would be nice if `hotkeys` could set this, instead of creating a command per tool
|
||||
setCornerstoneToolActive: ({ toolName }) => {
|
||||
store.dispatch(setToolActive(toolName));
|
||||
},
|
||||
updateViewportDisplaySet: ({ direction }) => {
|
||||
// TODO
|
||||
console.warn('updateDisplaySet: ', direction);
|
||||
},
|
||||
clearAnnotations: () => {
|
||||
console.warn('clearAnnotations: not yet implemented');
|
||||
// const toolState =
|
||||
// cornerstoneTools.globalImageIdSpecificToolStateManager.toolState;
|
||||
// if (!toolState) return;
|
||||
// Object.keys(toolState).forEach(imageId => {
|
||||
// if (!cornerstoneImageId || cornerstoneImageId === imageId)
|
||||
// delete toolState[imageId];
|
||||
// });
|
||||
},
|
||||
};
|
||||
|
||||
const definitions = {
|
||||
rotateViewportCW: {
|
||||
commandFn: actions.rotateViewport,
|
||||
storeContexts: ['viewports'],
|
||||
options: { rotation: 90 },
|
||||
},
|
||||
rotateViewportCCW: {
|
||||
commandFn: actions.rotateViewport,
|
||||
storeContexts: ['viewports'],
|
||||
options: { rotation: -90 },
|
||||
},
|
||||
invertViewport: {
|
||||
commandFn: actions.invertViewport,
|
||||
storeContexts: ['viewports'],
|
||||
options: {},
|
||||
},
|
||||
flipViewportVertical: {
|
||||
commandFn: actions.flipViewportVertical,
|
||||
storeContexts: ['viewports'],
|
||||
options: {},
|
||||
},
|
||||
flipViewportHorizontal: {
|
||||
commandFn: actions.flipViewportHorizontal,
|
||||
storeContexts: ['viewports'],
|
||||
options: {},
|
||||
},
|
||||
scaleUpViewport: {
|
||||
keys: '',
|
||||
commandFn: actions.scaleViewport,
|
||||
storeContexts: ['viewports'],
|
||||
options: { direction: 1 },
|
||||
},
|
||||
scaleDownViewport: {
|
||||
keys: '',
|
||||
commandFn: actions.scaleViewport,
|
||||
storeContexts: ['viewports'],
|
||||
options: { direction: -1 },
|
||||
},
|
||||
fitViewportToWindow: {
|
||||
commandFn: actions.scaleViewport,
|
||||
storeContexts: ['viewports'],
|
||||
options: { direction: 0 },
|
||||
},
|
||||
resetViewport: {
|
||||
commandFn: actions.resetViewport,
|
||||
storeContexts: ['viewports'],
|
||||
options: {},
|
||||
},
|
||||
// TODO: Clear Annotations
|
||||
// TODO: Next/Previous image
|
||||
// TODO: First/Last image
|
||||
// Next/Previous series/DisplaySet
|
||||
nextViewportDisplaySet: {
|
||||
commandFn: actions.updateViewportDisplaySet,
|
||||
storeContexts: [],
|
||||
options: { direction: 1 },
|
||||
},
|
||||
previousViewportDisplaySet: {
|
||||
commandFn: actions.updateViewportDisplaySet,
|
||||
storeContexts: [],
|
||||
options: { direction: -1 },
|
||||
},
|
||||
// TOOLS
|
||||
setZoomTool: {
|
||||
commandFn: actions.setCornerstoneToolActive,
|
||||
storeContexts: [],
|
||||
options: { toolName: 'Zoom' },
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Grabs `dom` reference for the enabledElement of
|
||||
* the active viewport
|
||||
*/
|
||||
function _getActiveViewportEnabledElement(viewports, activeIndex) {
|
||||
const activeViewport = viewports[activeIndex] || {};
|
||||
return activeViewport.dom;
|
||||
}
|
||||
|
||||
export default definitions;
|
||||
60
src/appCommands/index.js
Normal file
60
src/appCommands/index.js
Normal file
@ -0,0 +1,60 @@
|
||||
import cornerstoneCommandDefinitions from './cornerstone.js';
|
||||
import viewerCommandDefinitions from './viewer.js';
|
||||
|
||||
const CONTEXTS = {
|
||||
viewer: 'VIEWER',
|
||||
cornerstone: 'VIEWER::CORNERSTONE',
|
||||
};
|
||||
|
||||
/**
|
||||
* Register all commands.
|
||||
* TODO: Extensions should self-register their commands
|
||||
*/
|
||||
function init(commandsManager) {
|
||||
_registerViewerCommands(commandsManager);
|
||||
_registerCornerstoneCommands(commandsManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register all Viewer commands
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function _registerViewerCommands(commandsManager) {
|
||||
const commandContext = CONTEXTS.viewer;
|
||||
|
||||
commandsManager.createContext(commandContext);
|
||||
Object.keys(viewerCommandDefinitions).forEach(commandName => {
|
||||
const commandDefinition = viewerCommandDefinitions[commandName];
|
||||
|
||||
commandsManager.registerCommand(
|
||||
commandContext,
|
||||
commandName,
|
||||
commandDefinition
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register all Cornerstone commands
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function _registerCornerstoneCommands(commandsManager) {
|
||||
const commandContext = CONTEXTS.cornerstone;
|
||||
|
||||
commandsManager.createContext(commandContext);
|
||||
Object.keys(cornerstoneCommandDefinitions).forEach(commandName => {
|
||||
const commandDefinition = cornerstoneCommandDefinitions[commandName];
|
||||
|
||||
commandsManager.registerCommand(
|
||||
commandContext,
|
||||
commandName,
|
||||
commandDefinition
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
init,
|
||||
};
|
||||
36
src/appCommands/viewer.js
Normal file
36
src/appCommands/viewer.js
Normal file
@ -0,0 +1,36 @@
|
||||
import { redux } from 'ohif-core';
|
||||
import store from './../store';
|
||||
const { setViewportActive } = redux.actions;
|
||||
|
||||
const actions = {
|
||||
updateViewportDisplaySet: ({ direction }) => {
|
||||
// TODO
|
||||
console.warn('updateDisplaySet: ', direction);
|
||||
},
|
||||
updateActiveViewport: ({ viewports, direction }) => {
|
||||
const { viewportSpecificData, activeViewportIndex } = viewports;
|
||||
const maxIndex = Object.keys(viewportSpecificData).length - 1;
|
||||
|
||||
let newIndex = activeViewportIndex + direction;
|
||||
newIndex = newIndex > maxIndex ? 0 : newIndex;
|
||||
newIndex = newIndex < 0 ? maxIndex : newIndex;
|
||||
|
||||
store.dispatch(setViewportActive(newIndex));
|
||||
},
|
||||
};
|
||||
|
||||
const definitions = {
|
||||
// Next/Previous active viewport
|
||||
incrementActiveViewport: {
|
||||
commandFn: actions.updateActiveViewport,
|
||||
storeContexts: ['viewports'],
|
||||
options: { direction: 1 },
|
||||
},
|
||||
decrementActiveViewport: {
|
||||
commandFn: actions.updateActiveViewport,
|
||||
storeContexts: ['viewports'],
|
||||
options: { direction: -1 },
|
||||
},
|
||||
};
|
||||
|
||||
export default definitions;
|
||||
@ -1,16 +1,18 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Link, withRouter } from 'react-router-dom';
|
||||
import { Dropdown } from 'react-viewerbase';
|
||||
import './Header.css';
|
||||
|
||||
import { Link, withRouter } from 'react-router-dom';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { Dropdown } from 'react-viewerbase';
|
||||
import OHIFLogo from '../OHIFLogo/OHIFLogo.js';
|
||||
import ConnectedUserPreferencesModal from '../../connectedComponents/ConnectedUserPreferencesModal.js';
|
||||
import PropTypes from 'prop-types';
|
||||
// import { UserPreferencesModal } from 'react-viewerbase';
|
||||
import { hotkeysManager } from './../../App.js';
|
||||
|
||||
class Header extends Component {
|
||||
static propTypes = {
|
||||
home: PropTypes.bool.isRequired,
|
||||
location: PropTypes.object.isRequired,
|
||||
openUserPreferencesModal: PropTypes.func,
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
@ -19,21 +21,34 @@ class Header extends Component {
|
||||
children: OHIFLogo(),
|
||||
};
|
||||
|
||||
// onSave: data => {
|
||||
// const contextName = window.store.getState().commandContext.context;
|
||||
// const preferences = cloneDeep(window.store.getState().preferences);
|
||||
// preferences[contextName] = data;
|
||||
// dispatch(setUserPreferences(preferences));
|
||||
// dispatch(setUserPreferencesModalOpen(false));
|
||||
// OHIF.hotkeysUtil.setHotkeys(data.hotKeysData);
|
||||
// },
|
||||
// onResetToDefaults: () => {
|
||||
// dispatch(setUserPreferences());
|
||||
// dispatch(setUserPreferencesModalOpen(false));
|
||||
// OHIF.hotkeysUtil.setHotkeys();
|
||||
// },
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { isUserPreferencesOpen: false };
|
||||
|
||||
this.state = {
|
||||
userPreferencesOpen: false,
|
||||
};
|
||||
const onClick = this.toggleUserPreferences.bind(this);
|
||||
|
||||
this.options = [
|
||||
{
|
||||
title: 'Preferences ',
|
||||
icon: {
|
||||
name: 'user',
|
||||
},
|
||||
onClick: this.props.openUserPreferencesModal,
|
||||
},
|
||||
// {
|
||||
// title: 'Preferences ',
|
||||
// icon: {
|
||||
// name: 'user',
|
||||
// },
|
||||
// onClick: onClick,
|
||||
// },
|
||||
{
|
||||
title: 'About',
|
||||
icon: {
|
||||
@ -42,6 +57,24 @@ class Header extends Component {
|
||||
link: 'http://ohif.org',
|
||||
},
|
||||
];
|
||||
|
||||
this.hotKeysData = hotkeysManager.hotkeyDefinitions;
|
||||
}
|
||||
|
||||
toggleUserPreferences() {
|
||||
const isOpen = this.state.isUserPreferencesOpen;
|
||||
|
||||
this.setState({
|
||||
isUserPreferencesOpen: !isOpen,
|
||||
});
|
||||
}
|
||||
|
||||
onUserPreferencesSave({ windowLevelData, hotKeysData }) {
|
||||
console.log(windowLevelData);
|
||||
console.log(hotKeysData);
|
||||
|
||||
// TODO: Update hotkeysManager
|
||||
// TODO: reset `this.hotKeysData`
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -75,7 +108,14 @@ class Header extends Component {
|
||||
<div className="header-menu">
|
||||
<span className="research-use">INVESTIGATIONAL USE ONLY</span>
|
||||
<Dropdown title="Options" list={this.options} align="right" />
|
||||
<ConnectedUserPreferencesModal />
|
||||
{/* <UserPreferencesModal
|
||||
isOpen={this.state.isUserPreferencesOpen}
|
||||
onCancel={this.toggleUserPreferences.bind(this)}
|
||||
onSave={this.toggleUserPreferences.bind(this)}
|
||||
onResetToDefaults={this.toggleUserPreferences.bind(this)}
|
||||
windowLevelData={{}}
|
||||
hotKeysData={this.hotKeysData}
|
||||
/> */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { connect } from 'react-redux';
|
||||
import Header from '../components/Header/Header.js';
|
||||
import { setUserPreferencesModalOpen } from '../redux/actions.js';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
@ -8,17 +7,6 @@ const mapStateToProps = state => {
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
openUserPreferencesModal: () => {
|
||||
dispatch(setUserPreferencesModalOpen(true));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const ConnectedHeader = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(Header);
|
||||
const ConnectedHeader = connect(mapStateToProps)(Header);
|
||||
|
||||
export default ConnectedHeader;
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
setLeftSidebarOpen,
|
||||
setRightSidebarOpen,
|
||||
} from './../store/layout/actions.js';
|
||||
|
||||
import ToolbarRow from './ToolbarRow';
|
||||
import { setLeftSidebarOpen, setRightSidebarOpen } from '../redux/actions.js';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
const defaultPlugin = 'cornerstone';
|
||||
|
||||
|
||||
@ -1,48 +0,0 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { UserPreferencesModal } from 'react-viewerbase';
|
||||
import OHIF from 'ohif-core';
|
||||
import { setUserPreferencesModalOpen } from '../redux/actions.js';
|
||||
import cloneDeep from 'lodash.clonedeep';
|
||||
|
||||
const { setUserPreferences } = OHIF.redux.actions;
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const contextName = window.store.getState().commandContext.context;
|
||||
return {
|
||||
isOpen: state.ui.userPreferencesModalOpen,
|
||||
windowLevelData: state.preferences[contextName]
|
||||
? state.preferences[contextName].windowLevelData
|
||||
: {},
|
||||
hotKeysData: state.preferences[contextName]
|
||||
? state.preferences[contextName].hotKeysData
|
||||
: {},
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
onCancel: () => {
|
||||
dispatch(setUserPreferencesModalOpen(false));
|
||||
},
|
||||
onSave: data => {
|
||||
const contextName = window.store.getState().commandContext.context;
|
||||
const preferences = cloneDeep(window.store.getState().preferences);
|
||||
preferences[contextName] = data;
|
||||
dispatch(setUserPreferences(preferences));
|
||||
dispatch(setUserPreferencesModalOpen(false));
|
||||
OHIF.hotkeysUtil.setHotkeys(data.hotKeysData);
|
||||
},
|
||||
onResetToDefaults: () => {
|
||||
dispatch(setUserPreferences());
|
||||
dispatch(setUserPreferencesModalOpen(false));
|
||||
OHIF.hotkeysUtil.setHotkeys();
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const ConnectedUserPreferencesModal = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(UserPreferencesModal);
|
||||
|
||||
export default ConnectedUserPreferencesModal;
|
||||
@ -1,12 +1,12 @@
|
||||
import { connect } from 'react-redux';
|
||||
import ViewerMain from './ViewerMain';
|
||||
import OHIF from 'ohif-core';
|
||||
import ViewerMain from './ViewerMain';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
const {
|
||||
setViewportSpecificData,
|
||||
clearViewportSpecificData,
|
||||
setToolActive,
|
||||
setActiveViewportSpecificData,
|
||||
// setToolActive,
|
||||
// setActiveViewportSpecificData,
|
||||
} = OHIF.redux.actions;
|
||||
|
||||
const mapStateToProps = state => {
|
||||
@ -16,6 +16,7 @@ const mapStateToProps = state => {
|
||||
activeViewportIndex,
|
||||
layout,
|
||||
viewportSpecificData,
|
||||
viewports: state.viewports,
|
||||
};
|
||||
};
|
||||
|
||||
@ -27,12 +28,12 @@ const mapDispatchToProps = dispatch => {
|
||||
clearViewportSpecificData: () => {
|
||||
dispatch(clearViewportSpecificData());
|
||||
},
|
||||
setToolActive: tool => {
|
||||
dispatch(setToolActive(tool));
|
||||
},
|
||||
setActiveViewportSpecificData: viewport => {
|
||||
dispatch(setActiveViewportSpecificData(viewport));
|
||||
},
|
||||
// setToolActive: tool => {
|
||||
// dispatch(setToolActive(tool));
|
||||
// },
|
||||
// setActiveViewportSpecificData: viewport => {
|
||||
// dispatch(setActiveViewportSpecificData(viewport));
|
||||
// },
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -1,29 +1,31 @@
|
||||
import { Component } from 'react';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { OHIF } from 'ohif-core';
|
||||
import ConnectedLayoutManager from './ConnectedLayoutManager.js';
|
||||
import './ViewerMain.css';
|
||||
|
||||
import { Component } from 'react';
|
||||
import ConnectedLayoutManager from './ConnectedLayoutManager.js';
|
||||
import { OHIF } from 'ohif-core';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
class ViewerMain extends Component {
|
||||
static propTypes = {
|
||||
studies: PropTypes.array.isRequired,
|
||||
activeViewportIndex: PropTypes.number.isRequired,
|
||||
layout: PropTypes.object,
|
||||
viewportSpecificData: PropTypes.object,
|
||||
setViewportSpecificData: PropTypes.func.isRequired,
|
||||
clearViewportSpecificData: PropTypes.func.isRequired,
|
||||
setToolActive: PropTypes.func.isRequired,
|
||||
setActiveViewportSpecificData: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
// Initialize hotkeys
|
||||
new OHIF.HotkeysUtil('viewer', {
|
||||
setViewportSpecificData: props.setViewportSpecificData,
|
||||
clearViewportSpecificData: props.clearViewportSpecificData,
|
||||
setToolActive: props.setToolActive,
|
||||
setActiveViewportSpecificData: props.setActiveViewportSpecificData,
|
||||
});
|
||||
// new OHIF.HotkeysUtil('viewer', {
|
||||
// setViewportSpecificData: props.setViewportSpecificData,
|
||||
// clearViewportSpecificData: props.clearViewportSpecificData,
|
||||
// setToolActive: props.setToolActive,
|
||||
// setActiveViewportSpecificData: props.setActiveViewportSpecificData,
|
||||
// });
|
||||
// hotkeys.init();
|
||||
|
||||
this.state = {
|
||||
displaySets: [],
|
||||
@ -147,6 +149,10 @@ class ViewerMain extends Component {
|
||||
this.props.clearViewportSpecificData(viewportIndex);
|
||||
});
|
||||
|
||||
// TODO: These don't have to be viewer specific?
|
||||
// Could qualify for other routes?
|
||||
// hotkeys.destroy();
|
||||
|
||||
// Remove beforeUnload event handler...
|
||||
//window.removeEventListener('beforeunload', unloadHandlers.beforeUnload);
|
||||
// Destroy the synchronizer used to update reference lines
|
||||
|
||||
@ -1,22 +0,0 @@
|
||||
export const setLeftSidebarOpen = state => ({
|
||||
type: 'SET_LEFT_SIDEBAR_OPEN',
|
||||
state,
|
||||
});
|
||||
|
||||
export const setRightSidebarOpen = state => ({
|
||||
type: 'SET_RIGHT_SIDEBAR_OPEN',
|
||||
state,
|
||||
});
|
||||
|
||||
export const setUserPreferencesModalOpen = state => ({
|
||||
type: 'SET_USER_PREFERENCES_MODAL_OPEN',
|
||||
state,
|
||||
});
|
||||
|
||||
const actions = {
|
||||
setLeftSidebarOpen,
|
||||
setRightSidebarOpen,
|
||||
setUserPreferencesModalOpen,
|
||||
};
|
||||
|
||||
export default actions;
|
||||
25
src/store/index.js
Normal file
25
src/store/index.js
Normal file
@ -0,0 +1,25 @@
|
||||
import { combineReducers, createStore } from 'redux';
|
||||
|
||||
import layoutReducers from './layout/reducers.js';
|
||||
import { reducer as oidcReducer } from 'redux-oidc';
|
||||
import { redux } from 'ohif-core';
|
||||
|
||||
// Combine our ohif-core, ui, and oidc reducers
|
||||
// Set init data, using values found in localStorage
|
||||
const { reducers, localStorage } = redux;
|
||||
|
||||
reducers.ui = layoutReducers;
|
||||
reducers.oidc = oidcReducer;
|
||||
|
||||
const combined = combineReducers(reducers);
|
||||
const store = createStore(combined, localStorage.loadState());
|
||||
|
||||
// When the store's preferences change,
|
||||
// Update our cached preferences in localStorage
|
||||
store.subscribe(() => {
|
||||
localStorage.saveState({
|
||||
preferences: store.getState().preferences,
|
||||
});
|
||||
});
|
||||
|
||||
export default store;
|
||||
35
src/store/layout/actions.js
Normal file
35
src/store/layout/actions.js
Normal file
@ -0,0 +1,35 @@
|
||||
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,
|
||||
});
|
||||
|
||||
export const setRightSidebarOpen = state => ({
|
||||
type: 'SET_RIGHT_SIDEBAR_OPEN',
|
||||
state,
|
||||
});
|
||||
|
||||
const actions = {
|
||||
addActiveContext,
|
||||
removeActiveContext,
|
||||
clearActiveContexts,
|
||||
//
|
||||
setLeftSidebarOpen,
|
||||
setRightSidebarOpen,
|
||||
};
|
||||
|
||||
export default actions;
|
||||
@ -1,26 +1,40 @@
|
||||
const defaultState = {
|
||||
leftSidebarOpen: true,
|
||||
rightSidebarOpen: false,
|
||||
userPreferencesModalOpen: false,
|
||||
labelling: {},
|
||||
contextMenu: {},
|
||||
activeContexts: [],
|
||||
};
|
||||
|
||||
const ui = (state = defaultState, action) => {
|
||||
switch (action.type) {
|
||||
// ~ ACTIVE CONTEXTS
|
||||
// https://redux.js.org/recipes/structuring-reducers/immutable-update-patterns#inserting-and-removing-items-in-arrays
|
||||
case 'ADD_ACTIVE_CONTEXT': {
|
||||
const shallowCopy = Object.assign({}, state);
|
||||
shallowCopy.activeContexts = [...shallowCopy.activeContexts, action.item];
|
||||
return shallowCopy;
|
||||
}
|
||||
case 'REMOVE_ACTIVE_CONTEXT': {
|
||||
const shallowCopy = Object.assign({}, state);
|
||||
shallowCopy.activeContexts = shallowCopy.activeContexts.filter(
|
||||
item => item !== action.item
|
||||
);
|
||||
return shallowCopy;
|
||||
}
|
||||
case 'CLEAR_ACTIVE_CONTEXTS':
|
||||
return Object.assign({}, state, { activeContexts: [] });
|
||||
// ~ SIDEBAR
|
||||
case 'SET_LEFT_SIDEBAR_OPEN':
|
||||
return Object.assign({}, state, { leftSidebarOpen: action.state });
|
||||
case 'SET_RIGHT_SIDEBAR_OPEN':
|
||||
return Object.assign({}, state, { rightSidebarOpen: action.state });
|
||||
case 'SET_USER_PREFERENCES_MODAL_OPEN':
|
||||
return Object.assign({}, state, {
|
||||
userPreferencesModalOpen: action.state,
|
||||
});
|
||||
case 'SET_LABELLING_FLOW_DATA':
|
||||
case 'SET_LABELLING_FLOW_DATA': {
|
||||
const labelling = Object.assign({}, action.labellingFlowData);
|
||||
|
||||
return Object.assign({}, state, { labelling });
|
||||
case 'SET_TOOL_CONTEXT_MENU_DATA':
|
||||
}
|
||||
case 'SET_TOOL_CONTEXT_MENU_DATA': {
|
||||
const contextMenu = Object.assign({}, state.contextMenu);
|
||||
|
||||
contextMenu[action.viewportIndex] = Object.assign(
|
||||
@ -29,6 +43,7 @@ const ui = (state = defaultState, action) => {
|
||||
);
|
||||
|
||||
return Object.assign({}, state, { contextMenu });
|
||||
}
|
||||
case 'RESET_LABELLING_AND_CONTEXT_MENU':
|
||||
return Object.assign({}, state, {
|
||||
labelling: defaultState.labelling,
|
||||
20
yarn.lock
20
yarn.lock
@ -7609,11 +7609,6 @@ joi@^11.1.1:
|
||||
isemail "3.x.x"
|
||||
topo "2.x.x"
|
||||
|
||||
jquery@^3.3.1:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.0.tgz#8de513fa0fa4b2c7d2e48a530e26f0596936efdf"
|
||||
integrity sha512-ggRCXln9zEqv6OqAGXFEcshF5dSBvCkzj6Gm2gzuR5fWawaX8t7cxKVkkygKODrDAzKdoYw3l/e3pm3vlT4IbQ==
|
||||
|
||||
js-levenshtein@^1.1.3:
|
||||
version "1.1.6"
|
||||
resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d"
|
||||
@ -8938,6 +8933,11 @@ moment@2.24.0, moment@>=1.6.0, moment@^2.23.0, moment@^2.24.0:
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
|
||||
integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
|
||||
|
||||
mousetrap@^1.6.3:
|
||||
version "1.6.3"
|
||||
resolved "https://registry.yarnpkg.com/mousetrap/-/mousetrap-1.6.3.tgz#80fee49665fd478bccf072c9d46bdf1bfed3558a"
|
||||
integrity sha512-bd+nzwhhs9ifsUrC2tWaSgm24/oo2c83zaRyZQF06hYA6sANfsXHtnZ19AbbbDXCDzeH5nZBSQ4NvCjgD62tJA==
|
||||
|
||||
move-concurrently@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
|
||||
@ -9702,18 +9702,18 @@ octokit-pagination-methods@^1.1.0:
|
||||
resolved "https://registry.yarnpkg.com/octokit-pagination-methods/-/octokit-pagination-methods-1.1.0.tgz#cf472edc9d551055f9ef73f6e42b4dbb4c80bea4"
|
||||
integrity sha512-fZ4qZdQ2nxJvtcasX7Ghl+WlWS/d9IgnBIwFZXVNNZUmzpno91SX5bc5vuxiuKoCtK78XxGGNuSCrDC7xYB3OQ==
|
||||
|
||||
ohif-core@0.5.6:
|
||||
version "0.5.6"
|
||||
resolved "https://registry.yarnpkg.com/ohif-core/-/ohif-core-0.5.6.tgz#b626b2fdd5bebf53f4914a8701bedf2dc2c43b71"
|
||||
integrity sha512-qe5/tHjnyPAGoNEdYZBJEejojBBOE9G5Y54vuTUnu2TUKkdbFPg2Kj/WzmLBMG/3PHcjMCqFm1JllEwcntRvOg==
|
||||
ohif-core@0.5.7:
|
||||
version "0.5.7"
|
||||
resolved "https://registry.yarnpkg.com/ohif-core/-/ohif-core-0.5.7.tgz#698ddff79f96fec68e4546e0386c850d5dccdd18"
|
||||
integrity sha512-ymxojfaTQqsRZ5X2T2zWlVlTMUOYAz0V8BFgo6aWyZTEHw2u/3GgKPZiZ/OeRNTQOmam+KQj1jncuYbOecZ4nQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.2.0"
|
||||
ajv "^6.10.0"
|
||||
dicomweb-client "^0.4.2"
|
||||
isomorphic-base64 "^1.0.2"
|
||||
jquery "^3.3.1"
|
||||
lodash.clonedeep "^4.5.0"
|
||||
lodash.merge "^4.6.1"
|
||||
mousetrap "^1.6.3"
|
||||
validate.js "^0.12.0"
|
||||
|
||||
ohif-dicom-html-extension@^0.0.2:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user