update branch

This commit is contained in:
Rodrigo Antinarelli 2019-06-12 13:22:46 -03:00
commit 8c608dc05b
25 changed files with 749 additions and 199 deletions

View File

@ -142,7 +142,17 @@ jobs:
- ~/.cache/yarn
key: yarn-packages-v1-{{ .Branch }}-{{ checksum "yarn.lock" }}
# Build & Test
- run: yarn build:demo:ci
- run:
name: 'Build Demo Site, Upload SourceMaps, Send Deploy Notification'
command: |
yarn build:demo:ci
export FILE_1=$(find ./build/static/js -type f -name "2.*.js" -exec basename {} \;)
export FILE_MAIN=$(find ./build/static/js -type f -name "main.*.js" -exec basename {} \;)
export FILE_RUNTIME_MAIN=$(find ./build/static/js -type f -name "runtime~main.*.js" -exec basename {} \;)
curl https://api.rollbar.com/api/1/sourcemap -F source_map=@build/static/js/$FILE_1.map -F access_token=$ROLLBAR_TOKEN -F version=$CIRCLE_SHA1 -F minified_url=https://$GOOGLE_STORAGE_BUCKET/static/js/$FILE_1
curl https://api.rollbar.com/api/1/sourcemap -F source_map=@build/static/js/$FILE_MAIN.map -F access_token=$ROLLBAR_TOKEN -F version=$CIRCLE_SHA1 -F minified_url=https://$GOOGLE_STORAGE_BUCKET/static/js/$FILE_MAIN
curl https://api.rollbar.com/api/1/sourcemap -F source_map=@build/static/js/$FILE_RUNTIME_MAIN.map -F access_token=$ROLLBAR_TOKEN -F version=$CIRCLE_SHA1 -F minified_url=https://$GOOGLE_STORAGE_BUCKET/static/js/$FILE_RUNTIME_MAIN
curl --request POST https://api.rollbar.com/api/1/deploy/ -F access_token=$ROLLBAR_TOKEN -F environment=$GOOGLE_STORAGE_BUCKET -F revision=$CIRCLE_SHA1 -F local_username=CircleCI
# Persist :+1:
- persist_to_workspace:
root: ~/repo

View File

@ -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.
![Toolbar Extension](../assets/img/extensions-toolbar.gif)
@ -74,7 +103,8 @@ An extension can register a Toolbar Module by providing a `getToolbarModule()` m
Toolbar components are rendered in the `ToolbarRow` component.
For a complete example implementation, [check out the OHIFCornerstoneViewport's Toolbar Module](https://github.com/OHIF/Viewers/blob/react/extensions/ohif-cornerstone-extension/src/ToolbarModule.js).
For a complete example implementation,
[check out the OHIFCornerstoneViewport's Toolbar Module](https://github.com/OHIF/Viewers/blob/react/extensions/ohif-cornerstone-extension/src/ToolbarModule.js).
#### SopClassHandler
@ -84,18 +114,32 @@ For a complete example implementation, [check out the OHIFCornerstoneViewport's
> The panel module is not yet in use.
#### Commands
...
#### Hotkeys
...
### Registering Extensions
Extensions are registered for the application at startup. The `ExtensionManager`, exposed by `ohif-core`, registers a list of extensions with our application's store. Each module provided by the extension becomes available via `state.plugins.availablePlugins`, and consists of three parts: id, type ([PLUGIN_TYPE](https://github.com/OHIF/ohif-core/blob/43c08a29eff3fb646a0e83a03a236ddd84f4a6e8/src/plugins.js#L1-L6)), and the return value of the module method.
Extensions are registered for the application at startup. The
`ExtensionManager`, exposed by `ohif-core`, registers a list of extensions with
our application's store. Each module provided by the extension becomes available
via `state.plugins.availablePlugins`, and consists of three parts: id, type
([PLUGIN_TYPE](https://github.com/OHIF/ohif-core/blob/43c08a29eff3fb646a0e83a03a236ddd84f4a6e8/src/plugins.js#L1-L6)),
and the return value of the module method.
In a future version, we will likely expose a way to provide the extensions you would like included at startup.
In a future version, we will likely expose a way to provide the extensions you
would like included at startup.
_app.js_
```js
import { createStore, combineReducers } from "redux";
import OHIF from "ohif-core";
import OHIFCornerstoneExtension from "ohif-cornerstone-extension";
import { createStore, combineReducers } from 'redux';
import OHIF from 'ohif-core';
import OHIFCornerstoneExtension from 'ohif-cornerstone-extension';
const combined = combineReducers(OHIF.redux.reducers);
const store = createStore(combined);
@ -108,6 +152,10 @@ ExtensionManager.registerExtensions(store, extensions);
## OHIF Maintained Extensions
A small number of powerful extensions for popular use cases are maintained by OHIF. They're co-located in the [`OHIF/Viewers`](https://github.com/OHIF/Viewers/tree/react/) repository, in the top level [`extensions/`](https://github.com/OHIF/Viewers/tree/react/extensions) directory.
A small number of powerful extensions for popular use cases are maintained by
OHIF. They're co-located in the
[`OHIF/Viewers`](https://github.com/OHIF/Viewers/tree/react/) repository, in the
top level [`extensions/`](https://github.com/OHIF/Viewers/tree/react/extensions)
directory.
{% include "./_maintained-extensions-table.md" %}

View File

@ -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 -->

View File

@ -14,6 +14,11 @@
YARN_FLAGS = "--no-ignore-optional --pure-lockfile"
# COMMENT: This a rule for Single Page Applications
[[redirects]]
from = "/demo/*"
to = "/demo/index.html"
status = 200
[[redirects]]
from = "/*"
to = "/index.html"

View File

@ -84,7 +84,7 @@
"i18next-browser-languagedetector": "^3.0.1",
"lodash.isequal": "4.5.0",
"moment": "^2.24.0",
"ohif-core": "0.5.6",
"ohif-core": "0.5.8",
"ohif-dicom-html-extension": "^0.0.2",
"ohif-dicom-pdf-extension": "^0.0.6",
"oidc-client": "1.7.x",
@ -97,7 +97,7 @@
"react-resize-detector": "^3.4.0",
"react-router": "^4.3.1",
"react-router-dom": "^4.3.1",
"react-viewerbase": "0.7.0",
"react-viewerbase": "0.7.1",
"react-vtkjs-viewport": "^0.0.7",
"redux": "^4.0.1",
"redux-oidc": "3.1.x"

View File

@ -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'] },
],
};

View File

@ -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'] },
],
};

View File

@ -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>

View File

@ -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

View File

@ -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,59 +15,73 @@ 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 i18n from '@ohif/i18n';
import { I18nextProvider } from 'react-i18next';
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);
}
}
@ -85,9 +104,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);
@ -142,3 +159,5 @@ class App extends Component {
}
export default App;
export { commandsManager, hotkeysManager };

View File

@ -0,0 +1 @@
# Commands

View 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
View 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
View 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;

View File

@ -1,17 +1,20 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
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 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,
t: PropTypes.func.isRequired,
};
@ -21,12 +24,25 @@ class Header extends Component {
children: OHIFLogo(),
};
// onSave: data => {
// const contextName = window.store.getState().commandContext.context;
// const preferences = cloneDeep(window.store.getState().preferences);
// preferences[contextName] = data;
// dispatch(setUserPreferences(preferences));
// dispatch(setUserPreferencesModalOpen(false));
// OHIF.hotkeysUtil.setHotkeys(data.hotKeysData);
// },
// onResetToDefaults: () => {
// dispatch(setUserPreferences());
// dispatch(setUserPreferencesModalOpen(false));
// OHIF.hotkeysUtil.setHotkeys();
// },
constructor(props) {
super(props);
this.state = { isUserPreferencesOpen: false };
this.state = {
userPreferencesOpen: false,
};
// const onClick = this.toggleUserPreferences.bind(this);
this.loadOptions();
}
@ -34,11 +50,11 @@ class Header extends Component {
loadOptions() {
const { t } = this.props;
this.options = [
{
title: t('Preferences'),
icon: { name: 'user' },
onClick: this.props.openUserPreferencesModal,
},
// {
// title: t('Preferences'),
// icon: { name: 'user' },
// onClick: onClick,
// },
{
title: t('About'),
icon: {
@ -47,6 +63,23 @@ 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() {
@ -81,7 +114,7 @@ class Header extends Component {
<div className="header-menu">
<span className="research-use">{t('INVESTIGATIONAL USE ONLY')}</span>
<Dropdown title={t('Options')} list={this.options} align="right" />
<ConnectedUserPreferencesModal />
{/* <ConnectedUserPreferencesModal /> */}
</div>
</div>
);

View File

@ -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;

View File

@ -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';

View File

@ -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;

View File

@ -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));
// },
};
};

View File

@ -1,29 +1,32 @@
import { Component } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import { OHIF } from 'ohif-core';
import ConnectedLayoutManager from './ConnectedLayoutManager.js';
import './ViewerMain.css';
import { Component } from 'react';
import ConnectedLayoutManager from './ConnectedLayoutManager.js';
// import { OHIF } from 'ohif-core';
//
import PropTypes from 'prop-types';
import React from 'react';
class ViewerMain extends Component {
static propTypes = {
studies: PropTypes.array.isRequired,
activeViewportIndex: PropTypes.number.isRequired,
layout: PropTypes.object,
viewportSpecificData: PropTypes.object,
setViewportSpecificData: PropTypes.func.isRequired,
clearViewportSpecificData: PropTypes.func.isRequired,
setToolActive: PropTypes.func.isRequired,
setActiveViewportSpecificData: PropTypes.func.isRequired,
};
constructor(props) {
super(props);
// Initialize hotkeys
new OHIF.HotkeysUtil('viewer', {
setViewportSpecificData: props.setViewportSpecificData,
clearViewportSpecificData: props.clearViewportSpecificData,
setToolActive: props.setToolActive,
setActiveViewportSpecificData: props.setActiveViewportSpecificData,
});
// new OHIF.HotkeysUtil('viewer', {
// setViewportSpecificData: props.setViewportSpecificData,
// clearViewportSpecificData: props.clearViewportSpecificData,
// setToolActive: props.setToolActive,
// setActiveViewportSpecificData: props.setActiveViewportSpecificData,
// });
// hotkeys.init();
this.state = {
displaySets: [],
@ -147,6 +150,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

View File

@ -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
View 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;

View 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;

View File

@ -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,

View File

@ -7634,11 +7634,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"
@ -8963,6 +8958,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"
@ -9727,18 +9727,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.8:
version "0.5.8"
resolved "https://registry.yarnpkg.com/ohif-core/-/ohif-core-0.5.8.tgz#d2feab1c2c8440464123318aca8d55e9f228d51e"
integrity sha512-yI4nq1Vxi8CVno6codLJttjiyPkmjjYIl12QcAGDGmpYcLt2LopOCLZ2M2eeJbxt8yfh5nwSYMcjC3cYIr6C9A==
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:
@ -11565,14 +11565,6 @@ react-dev-utils@^8.0.0:
strip-ansi "5.0.0"
text-table "0.2.0"
react-dnd-html5-backend@7.0.2:
version "7.0.2"
resolved "https://registry.yarnpkg.com/react-dnd-html5-backend/-/react-dnd-html5-backend-7.0.2.tgz#f74181ca0ff05be13eb6094629c5ad560f558a7e"
integrity sha512-BPhmHeQjvhPXRykHvFLbM+TJFrrarcuf/mIArNEmXzZuNhLfbOnHtMSzR8lPwodABcDAYj7hEF7vTABXX298vA==
dependencies:
dnd-core "^7.0.2"
lodash "^4.17.11"
react-dnd-html5-backend@^7.0.2:
version "7.4.4"
resolved "https://registry.yarnpkg.com/react-dnd-html5-backend/-/react-dnd-html5-backend-7.4.4.tgz#3372e5af4af24f680cd75900c782606539c01220"
@ -11580,6 +11572,14 @@ react-dnd-html5-backend@^7.0.2:
dependencies:
dnd-core "^7.4.4"
react-dnd-touch-backend@^0.8.3:
version "0.8.3"
resolved "https://registry.yarnpkg.com/react-dnd-touch-backend/-/react-dnd-touch-backend-0.8.3.tgz#85d961ec8578dc9efa5b78000ea904d37e01e60f"
integrity sha512-LLJlpeJftRreP9ixm3yfd1JEXf+6BeKo5dkfJFHvNSOEOu+FDSwNl5l8uAG0FeD4z2ZVi7RDQ/jUxdjkBV3zBg==
dependencies:
dnd-core "^7.4.4"
invariant "^2.2.4"
react-dnd@7.0.2:
version "7.0.2"
resolved "https://registry.yarnpkg.com/react-dnd/-/react-dnd-7.0.2.tgz#8f5611a6e877592932c082d6280c64d1c817f420"
@ -11793,10 +11793,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.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/react-viewerbase/-/react-viewerbase-0.7.0.tgz#b0f48a0f003b849c280b8ca67927d4780c68b0dc"
integrity sha512-1VQXREKSsqxtOSSHRou0gjBODrSOjE8SBJ6/dHG0gPgdmeotOg68xPMKYf9K7fYyGZ6CDA7oVCAmdTE4SVYP5Q==
react-viewerbase@0.7.1:
version "0.7.1"
resolved "https://registry.yarnpkg.com/react-viewerbase/-/react-viewerbase-0.7.1.tgz#5a27f2f7d327dfd8f8dfe26e6d5d3a911deeee85"
integrity sha512-xQVK8s3Etk51zR0mW8eVh49emWM6X8sgwnVO3WXtdWf6rewePYqFcFTZ2n4iV/Ru+NeZHNlC4lpOq9wN8SjcpA==
dependencies:
"@babel/runtime" "7.2.0"
classnames "2.2.6"
@ -11806,7 +11806,7 @@ react-viewerbase@^0.7.0:
react-bootstrap-modal "4.2.0"
react-dates "18.4.1"
react-dnd "7.0.2"
react-dnd-html5-backend "7.0.2"
react-dnd-touch-backend "^0.8.3"
react-with-direction "1.3.0"
react-vtkjs-viewport@^0.0.7: