Merge branch 'master' into dev-react-proxy

This commit is contained in:
Danny Brown 2019-06-18 21:05:13 -04:00 committed by GitHub
commit 39d844f106
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
86 changed files with 12853 additions and 6202 deletions

View File

@ -121,6 +121,66 @@ jobs:
docker push ohif/$IMAGE_NAME:$IMAGE_VERSION_FULL
docker push ohif/$IMAGE_NAME:latest
fi
build_demo_site:
<<: *defaults
steps:
# Download and cache dependencies
- checkout
- restore_cache:
name: Restore Yarn Package Cache
keys:
# when lock file changes, use increasingly general patterns to restore cache
- yarn-packages-v1-{{ .Branch }}-{{ checksum "yarn.lock" }}
- yarn-packages-v1-{{ .Branch }}-
- yarn-packages-v1-
- run:
name: Install Dependencies
command: yarn install --frozen-lockfile
- save_cache:
name: Save Yarn Package Cache
paths:
- ~/.cache/yarn
key: yarn-packages-v1-{{ .Branch }}-{{ checksum "yarn.lock" }}
# Build & Test
- 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
paths: .
demo_site_publish:
working_directory: ~/repo
docker:
- image: google/cloud-sdk
steps:
- attach_workspace:
at: ~/repo
- setup_remote_docker:
docker_layer_caching: true
- run:
name: Deploy latest version to viewer.ohif.org
command: |
# This file will exist if a new version was published by
# our `semantic-release` command in the previous job
#if [[ ! -e tmp/updated-version.txt ]]; then
# exit 0
#else
echo $GCLOUD_SERVICE_KEY | gcloud auth activate-service-account --key-file=-
gcloud --quiet config set project ${GOOGLE_PROJECT_ID}
gcloud --quiet config set compute/zone ${GOOGLE_COMPUTE_ZONE}
gsutil -m rm gs://$GOOGLE_STORAGE_BUCKET/**
gsutil -m rsync -R build gs://$GOOGLE_STORAGE_BUCKET
#fi
workflows:
version: 2
@ -153,3 +213,15 @@ workflows:
requires:
- build_and_test
- npm_publish
- build_demo_site:
requires:
- build_and_test
filters:
branches:
only: master
- demo_site_publish:
requires:
- build_demo_site
filters:
branches:
only: master

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text eol=lf

View File

@ -6,6 +6,7 @@
- [Data Source](essentials/data-source.md)
- [Configuration](essentials/configuration.md)
- [Themeing](essentials/themeing.md)
- [Translating](essentials/translating.md)
- [Troubleshooting](essentials/troubleshooting.md)
- [Scope of Project](essentials/scope-of-project.md)

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

@ -21,11 +21,6 @@ include tags. Here's how it works:
<code>Google Fonts, Sanchez & Roboto</code>
</a>
</li>
<li>
<a href="https://use.fontawesome.com/releases/v5.7.2/css/all.css">
<code>fontawesome@5.7.2</code>
</a>
</li>
<li>
<a href="https://unpkg.com/react@16/umd/react.production.min.js">
<code>react@16.8.6</code>
@ -73,7 +68,7 @@ window.config = {
},
],
},
}
};
```
<ol start="5"><li>
@ -82,10 +77,10 @@ window.config = {
```js
// Made available by the `ohif-viewer` script included in step 1
var Viewer = window.OHIFStandaloneViewer.App
var app = React.createElement(Viewer, window.config, null)
var Viewer = window.OHIFStandaloneViewer.App;
var app = React.createElement(Viewer, window.config, null);
ReactDOM.render(app, document.getElementById('ohif-viewer-target'))
ReactDOM.render(app, document.getElementById('ohif-viewer-target'));
```
#### Tips & Tricks

View File

@ -0,0 +1,264 @@
# Translating
OHIF supports internationalization using [i18next](https://www.i18next.com/)
through the npm package [@ohif/i18n](https://www.npmjs.com/package/@ohif/i18n),
where is the main instance of i18n containing several languages and tools.
### Installing
```bash
yarn add @ohif/i18n
# OR
npm install --save @ohif/i18n
```
### How it works
After installing `@ohif/i18n` npm package, the translation function
[t](https://www.i18next.com/overview/api#t) can be used [with](#with-react) or
[without](#without-react) React.
A translation will occur every time a text match happens in a
[t](https://www.i18next.com/overview/api#t) function.
The [t](https://www.i18next.com/overview/api#t) function is responsible for
getting translations using all the power of i18next.
E.g.
Before:
```html
<div>my translated text</div>
```
After:
```html
<div>{t('my translated text')}</div>
```
If the translation.json file contains a key that matches the HTML content e.g.
`my translated text`, it will be replaced automatically by the
[t](https://www.i18next.com/overview/api#t) function.
---
#### With React
This section will introduce you to [react-i18next](https://react.i18next.com/)
basics and show how to implement the [t](https://www.i18next.com/overview/api#t)
function easily.
##### Using HOCs
In most cases we used
[High Order Components](https://react.i18next.com/latest/withtranslation-hoc) to
share the `t` function among OHIF's components.
E.g.
```js
import React from 'react';
import { withTranslation } from '@ohif/i18n';
function MyComponent({ t, i18n }) {
return <p>{t('my translated text')}</p>;
}
export default withTranslation('MyNameSpace')(MyComponent);
```
> Important: if you are using React outside the OHIF Viewer, check the
> [I18nextProvider](#using-outside-of-ohif-viewer) section, `withTranslation`
> HOC doesnt works without a I18nextProvider
##### Using Hooks
Also, it's possible to get the `t` tool using
[React Hooks](https://react.i18next.com/latest/usetranslation-hook), but it
requires at least React > 16.8 😉
#### Using outside of OHIF viewer
OHIF Viewer already sets a main
[I18nextProvider](https://react.i18next.com/latest/i18nextprovider) connected to
the shared i18n instance from `@ohif/i18n`, all extensions inside OHIF Viewer
will share this same provider at the end, you don't need to set new providers at all.
But, if you need to use it completely outside of OHIF viewer, you can set the
I18nextProvider this way:
```js
import i18n, { I18nextProvider } from '@ohif/i18n';
import App from './App';
<I18nextProvider i18n={i18n}>
<App />
</I18nextProvider>;
```
After setting `I18nextProvider` in your React App, all translations from
`@ohif/i18n` should be available following the basic [With React](#with-react) usage.
---
#### Without React
When needed, you can also use available translations _without React_.
E.g.
```js
import { t } from '@ohif/i18n';
console.log(t('my translated text'));
console.log(t('$t(Common:Play) my translated text'));
```
---
# Main Concepts While Translating
## - Namespaces
Namespaces are being used to organize translations in smaller portions, combined
semantically or by use. Each `.json` file inside `@ohif/i18n` npm package
becomes a new namespace automatically.
- Buttons: All buttons translations
- CineDialog: Translations for the toll tips inside the Cine Player Dialog
- Common: all common jargons that can be reused like `t('$t(common:image)')`
- Header: translations related to OHIF's Header Top Bar
- MeasurementTable - Translations for the react-viewerbase Measurement Table
- UserPreferencesModal - Translations for the react-viewerbase Preferences Modal
### How to use another NameSpace inside the current NameSpace?
i18next provides a parsing feature able to get translations strings from any NameSpace,
like this following example getting data from `Common` NameSpace:
```
$t(Common:Reset)
```
## - Extending Languages in @ohif/i18n
Sometimes, even using the same language, some nouns or jargons can change according to
the country, states or even from Hospital to Hospital.
In this cases, you don't need to set an entire language again, you can extend languages creating a new folder inside a pre existent language folder and @ohif/i18n will do the hard work.
This new folder must to be called with a double character name, like the `UK` in the following file tree:
```bash
|-- src
|-- locales
|-- en
|-- Buttons.json
| UK
|-- Buttons.js
| US
|-- Buttons.js
...
```
All properties inside a Namespace will be merged in the new sub language, e.g `en-US` and `en-UK` will merge the props with `en`.
This feature is based on i18next's fallback languages tool.
### - Extending languages dynamically
Once you have access to the i18n instance, you can use the
[addResourceBundle](https://www.i18next.com/how-to/add-or-load-translations#add-after-init)
method to add and change language resources.
E.g.
```js
import { i18n } from '@ohif/i18n';
i18next.addResourceBundle('pt-BR', 'Buttons', {
Angle: 'Ângulo',
});
```
---
### How to set a whole new language
To set a brand new language you can do it in two different ways:
- Opening a pull request for `@ohif/i18n` and sharing the translation with the
community. 😍 Please see [Contributing](#contributing-with-new-languages) section
for further information.
- Setting it only in your project or extension:
You'll need a folder structure like the following, which you can load using the `node context` and send it to `addLocales` method.
Folder structure:
```bash
|-- ...
|-- src
|-- locales
|-- en
|-- Buttons.json
|-- es
| CO
|-- Buttons.js
|-- Buttons.json
...
```
E.g. of `addLocales` usage
```js
import { addLocales } from '@ohif/i18n';
const localesPath = './locales';
const context = require.context(localesPath, true, /\.json$/);
addLocales(context);
```
Also, [i18next](https://www.i18next.com/how-to/add-or-load-translations#add-after-init) provides a few methods to deal with languages, you have access to it's instance importing the default of @ohif/i18n;
Fell fre to play around with i18next like this:
```
import i18next from '@ohif/i18n';
i18next.addResourceBundle('en', 'namespace1', {
key: 'hello from namespace 1'
});
```
---
## language Detections
@ohif/i18n uses [i18next-browser-languageDetector](https://github.com/i18next/i18next-browser-languageDetector) to manage detections, also exports a method called initI18n that accepts a new detector config as parameter.
### Changing the language
OHIF Viewer accepts a query param called `lng` in the url to change the language.
E.g.
```
https://docs.ohif.org/demo/?lng=es-MX
```
### Language Persistence
The user's language preference is kept automatically by the detector and stored at a cookie called 'i18next', and in a localstorage key called 'i18nextLng'.
These names can be changed with a new [Detector Config](https://github.com/i18next/i18next-browser-languageDetector).
## Debugging translations
There is an environment variable responsible for debugging the translations, called `REACT_APP_I18N_DEBUG`.
Run the project as following to get full debug information:
```bash
REACT_APP_I18N_DEBUG=true yarn run dev
```
### Contributing with new languages
Contributions of any kind are welcome! Please check the
[instructions](https://docs.ohif.org/contributing.html).

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

@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone",
"version": "0.0.34",
"version": "0.0.36",
"description": "OHIF extension for Cornerstone",
"author": "OHIF",
"license": "MIT",

View File

@ -47,10 +47,6 @@ const mapDispatchToProps = (dispatch, ownProps) => {
dispatch(setViewportSpecificData(viewportIndex, data));
},
clearViewportSpecificData: () => {
dispatch(clearViewportSpecificData(viewportIndex));
},
/**
* Our component "enables" the underlying dom element on "componentDidMount"
* It listens for that event, and then emits the enabledElement. We can grab

View File

@ -54,31 +54,60 @@ class OHIFCornerstoneViewport extends Component {
StackManager.clearStacks();
}
/**
* Obtain the CornerstoneTools Stack for the specified display set.
*
* @param {Object[]} studies
* @param {String} studyInstanceUid
* @param {String} displaySetInstanceUid
* @param {String} [sopInstanceUid]
* @param {Number} [frameIndex=1]
* @return {Object} CornerstoneTools Stack
*/
static getCornerstoneStack(
studies,
studyInstanceUid,
displaySetInstanceUid,
sopInstanceUid,
frameIndex
frameIndex = 0
) {
if (!studies || !studies.length) {
throw new Error('Studies not provided.');
}
if (!studyInstanceUid) {
throw new Error('StudyInstanceUID not provided.')
}
if (!displaySetInstanceUid) {
throw new Error('StudyInstanceUID not provided.')
}
// Create shortcut to displaySet
const study = studies.find(
study => study.studyInstanceUid === studyInstanceUid
);
if (!study) {
throw new Error('Study not found.');
}
const displaySet = study.displaySets.find(set => {
return set.displaySetInstanceUid === displaySetInstanceUid;
});
if (!displaySet) {
throw new Error('Display Set not found.');
}
// Get stack from Stack Manager
const storedStack = StackManager.findOrCreateStack(study, displaySet);
// Clone the stack here so we don't mutate it
const stack = Object.assign({}, storedStack);
stack.currentImageIdIndex = frameIndex;
if (frameIndex !== undefined) {
stack.currentImageIdIndex = frameIndex;
} else if (sopInstanceUid) {
if (sopInstanceUid) {
const index = stack.imageIds.findIndex(imageId => {
const sopCommonModule = cornerstone.metaData.get(
'sopCommonModule',
@ -94,10 +123,8 @@ class OHIFCornerstoneViewport extends Component {
if (index > -1) {
stack.currentImageIdIndex = index;
} else {
stack.currentImageIdIndex = 0;
console.warn('SOPInstanceUID provided was not found in specified DisplaySet');
}
} else {
stack.currentImageIdIndex = 0;
}
return stack;

View File

@ -789,6 +789,7 @@ any-observable@^0.3.0:
argparse@^1.0.7:
version "1.0.10"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==
dependencies:
sprintf-js "~1.0.2"
@ -1947,6 +1948,7 @@ espree@^5.0.0:
esprima@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
esquery@^1.0.1:
version "1.0.1"
@ -2974,8 +2976,9 @@ js-levenshtein@^1.1.3:
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
js-yaml@^3.12.0, js-yaml@^3.9.0:
version "3.12.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1"
version "3.13.1"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==
dependencies:
argparse "^1.0.7"
esprima "^4.0.0"
@ -5047,6 +5050,7 @@ split-string@^3.0.1, split-string@^3.0.2:
sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
stable@~0.1.6:
version "0.1.8"

View File

@ -1501,7 +1501,7 @@ async@^1.5.2:
version "1.5.2"
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
async@^2.1.4, async@^2.5.0, async@^2.6.1:
async@^2.1.4, async@^2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610"
dependencies:
@ -2501,6 +2501,11 @@ commander@~2.13.0:
version "2.13.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c"
commander@~2.20.0:
version "2.20.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"
integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==
common-tags@^1.4.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937"
@ -4425,10 +4430,11 @@ handle-thing@^1.2.5:
resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4"
handlebars@^4.0.3:
version "4.0.12"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.12.tgz#2c15c8a96d46da5e266700518ba8cb8d919d5bc5"
version "4.1.2"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.2.tgz#b6b37c1ced0306b221e094fc7aca3ec23b131b67"
integrity sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==
dependencies:
async "^2.5.0"
neo-async "^2.6.0"
optimist "^0.6.1"
source-map "^0.6.1"
optionalDependencies:
@ -6519,6 +6525,7 @@ minimist@^1.1.1, minimist@^1.2.0:
minimist@~0.0.1:
version "0.0.10"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=
minipass@^2.2.1, minipass@^2.3.4:
version "2.3.5"
@ -6657,9 +6664,10 @@ negotiator@0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
neo-async@^2.5.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.0.tgz#b9d15e4d71c6762908654b5183ed38b753340835"
neo-async@^2.5.0, neo-async@^2.6.0:
version "2.6.1"
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==
nice-try@^1.0.4:
version "1.0.5"
@ -6963,6 +6971,7 @@ opn@5.4.0, opn@^5.1.0:
optimist@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
integrity sha1-2j6nRob6IaGaERwybpDrFaAZZoY=
dependencies:
minimist "~0.0.1"
wordwrap "~0.0.2"
@ -9442,6 +9451,7 @@ source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7:
source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
sourcemap-codec@^1.4.1:
version "1.4.4"
@ -10131,13 +10141,21 @@ uglify-es@^3.3.4:
commander "~2.13.0"
source-map "~0.6.1"
uglify-js@3.4.x, uglify-js@^3.1.4:
uglify-js@3.4.x:
version "3.4.9"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3"
dependencies:
commander "~2.17.1"
source-map "~0.6.1"
uglify-js@^3.1.4:
version "3.6.0"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.6.0.tgz#704681345c53a8b2079fb6cec294b05ead242ff5"
integrity sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==
dependencies:
commander "~2.20.0"
source-map "~0.6.1"
uglifyjs-webpack-plugin@^1.2.4:
version "1.3.0"
resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.3.0.tgz#75f548160858163a08643e086d5fefe18a5d67de"
@ -10621,6 +10639,7 @@ wide-align@^1.1.0:
wordwrap@~0.0.2:
version "0.0.3"
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc=
wordwrap@~1.0.0:
version "1.0.0"

View File

@ -1501,7 +1501,7 @@ async@^1.5.2:
version "1.5.2"
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
async@^2.1.4, async@^2.5.0, async@^2.6.1:
async@^2.1.4, async@^2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610"
dependencies:
@ -2505,6 +2505,11 @@ commander@~2.13.0:
version "2.13.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c"
commander@~2.20.0:
version "2.20.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"
integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==
common-tags@^1.4.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937"
@ -4429,10 +4434,11 @@ handle-thing@^1.2.5:
resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4"
handlebars@^4.0.3:
version "4.0.12"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.12.tgz#2c15c8a96d46da5e266700518ba8cb8d919d5bc5"
version "4.1.2"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.2.tgz#b6b37c1ced0306b221e094fc7aca3ec23b131b67"
integrity sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==
dependencies:
async "^2.5.0"
neo-async "^2.6.0"
optimist "^0.6.1"
source-map "^0.6.1"
optionalDependencies:
@ -6528,6 +6534,7 @@ minimist@^1.1.1, minimist@^1.2.0:
minimist@~0.0.1:
version "0.0.10"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=
minipass@^2.2.1, minipass@^2.3.4:
version "2.3.5"
@ -6666,9 +6673,10 @@ negotiator@0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
neo-async@^2.5.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.0.tgz#b9d15e4d71c6762908654b5183ed38b753340835"
neo-async@^2.5.0, neo-async@^2.6.0:
version "2.6.1"
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==
nice-try@^1.0.4:
version "1.0.5"
@ -6972,6 +6980,7 @@ opn@5.4.0, opn@^5.1.0:
optimist@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
integrity sha1-2j6nRob6IaGaERwybpDrFaAZZoY=
dependencies:
minimist "~0.0.1"
wordwrap "~0.0.2"
@ -9451,6 +9460,7 @@ source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7:
source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
sourcemap-codec@^1.4.1:
version "1.4.4"
@ -10140,13 +10150,21 @@ uglify-es@^3.3.4:
commander "~2.13.0"
source-map "~0.6.1"
uglify-js@3.4.x, uglify-js@^3.1.4:
uglify-js@3.4.x:
version "3.4.9"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3"
dependencies:
commander "~2.17.1"
source-map "~0.6.1"
uglify-js@^3.1.4:
version "3.6.0"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.6.0.tgz#704681345c53a8b2079fb6cec294b05ead242ff5"
integrity sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==
dependencies:
commander "~2.20.0"
source-map "~0.6.1"
uglifyjs-webpack-plugin@^1.2.4:
version "1.3.0"
resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.3.0.tgz#75f548160858163a08643e086d5fefe18a5d67de"
@ -10630,6 +10648,7 @@ wide-align@^1.1.0:
wordwrap@~0.0.2:
version "0.0.3"
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc=
wordwrap@~1.0.0:
version "1.0.0"

View File

@ -0,0 +1,14 @@
{
"presets": [
["@babel/preset-env", {
"targets": {
"ie": "11"
}
}],
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-proposal-class-properties",
"@babel/plugin-transform-runtime"
]
}

View File

@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

26
extensions/ohif-i18n/.gitignore vendored Normal file
View File

@ -0,0 +1,26 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
node_modules
# builds
build
dist
.rpt2_cache
# misc
.DS_Store
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.idea
yalc.lock
.yalc

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Open Health Imaging Foundation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,86 @@
{
"name": "@ohif/i18n",
"version": "0.0.4",
"description": "OHIF extension for internationalization",
"author": "OHIF",
"license": "MIT",
"repository": "OHIF/Viewers",
"main": "dist/index.umd.js",
"module": "dist/index.es.js",
"jsnext:main": "dist/index.es.js",
"engines": {
"node": ">=8",
"npm": ">=5"
},
"scripts": {
"build": "rollup -c",
"prepublishOnly": "npm run build"
},
"peerDependencies": {
"i18next": "^17.0.3",
"i18next-browser-languagedetector": "^3.0.1",
"react": "^16.0.0",
"react-dom": "^16.0.0",
"react-i18next": "^10.11.0"
},
"devDependencies": {
"@babel/core": "^7.2.2",
"@babel/plugin-external-helpers": "^7.2.0",
"@babel/plugin-proposal-class-properties": "^7.2.3",
"@babel/plugin-transform-runtime": "^7.2.0",
"@babel/preset-env": "^7.2.3",
"@babel/preset-react": "^7.0.0",
"babel-eslint": "^10.0.1",
"cross-env": "^5.2.0",
"eslint": "5.13.0",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-node": "^8.0.0",
"eslint-plugin-promise": "^4.0.1",
"eslint-plugin-react": "^7.11.1",
"husky": "^1.3.1",
"i18next": "^15.1.3",
"i18next-browser-languagedetector": "^3.0.1",
"lint-staged": "^8.1.0",
"prettier": "^1.15.3",
"react": "^16.0.0",
"react-dom": "^16.0.0",
"react-i18next": "^10.11.0",
"rollup": "^1.1.2",
"rollup-plugin-babel": "^4.2.0",
"rollup-plugin-commonjs": "^9.2.0",
"rollup-plugin-copy": "^2.0.1",
"rollup-plugin-node-builtins": "^2.1.2",
"rollup-plugin-node-resolve": "^4.0.0",
"rollup-plugin-peer-deps-external": "^2.2.0",
"rollup-plugin-postcss": "^2.0.3",
"rollup-plugin-url": "^2.1.0"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"src/**/*.{js,jsx,json,css}": [
"prettier --single-quote --write",
"git add"
]
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
],
"files": [
"dist"
],
"publishConfig": {
"access": "public"
},
"dependencies": {
"@babel/runtime": "^7.2.0",
"classnames": "^2.2.6",
"rollup-plugin-json": "^4.0.0"
}
}

View File

@ -0,0 +1,72 @@
import babel from 'rollup-plugin-babel';
import commonjs from 'rollup-plugin-commonjs';
import external from 'rollup-plugin-peer-deps-external';
import postcss from 'rollup-plugin-postcss';
import resolve from 'rollup-plugin-node-resolve';
import url from 'rollup-plugin-url';
import pkg from './package.json';
// Deal with https://github.com/rollup/rollup-plugin-commonjs/issues/297
import builtins from 'rollup-plugin-node-builtins';
import copy from 'rollup-plugin-copy';
import json from 'rollup-plugin-json';
const globals = {
react: 'React',
'react-dom': 'ReactDOM',
'react-redux': 'ReactRedux',
'react-resize-detector': 'ReactResizeDetector',
'prop-types': 'PropTypes',
'i18next': 'i18next',
'react-i18next': 'react-i18next',
'i18next-browser-languagedetector': 'LngDetector'
};
export default {
input: 'src/index.js',
output: [
{
file: pkg.main,
format: 'umd',
name: 'ohif-i18n',
sourcemap: true,
globals,
exports: 'named',
},
{
file: pkg.module,
format: 'es',
sourcemap: true,
globals,
exports: 'named',
},
],
plugins: [
builtins(),
external(),
postcss({
modules: false,
}),
copy({
targets: ['src/locales'],
outputFolder: 'dist',
}),
json({
// ignores indent and generates the smallest code
compact: true, // Default: false
// generate a named export for every property of the JSON object
namedExports: true // Default: true
}),
url(),
babel({
exclude: 'node_modules/**',
externalHelpers: true,
runtimeHelpers: true,
}),
resolve(),
commonjs({
include: ['node_modules/**', '.yalc/**'],
}),
],
external: Object.keys(pkg.peerDependencies || {})
};

View File

@ -0,0 +1,24 @@
const debugMode = !!(
process.env.NODE_ENV !== 'production' && process.env.REACT_APP_I18N_DEBUG
);
const detectionOptions = {
// order and from where user language should be detected
order: ['querystring', 'cookie', 'localStorage', 'navigator', 'htmlTag', 'path', 'subdomain'],
// keys or params to lookup language from
lookupQuerystring: 'lng',
lookupCookie: 'i18next',
lookupLocalStorage: 'i18nextLng',
lookupFromPathIndex: 0,
lookupFromSubdomainIndex: 0,
// cache user language on
caches: ['localStorage', 'cookie'],
excludeCacheFor: ['cimode'], // languages to not persist (cookie, localStorage)
// optional htmlTag with lang attribute, the default is:
htmlTag: document.documentElement
};
export { debugMode, detectionOptions };

View File

@ -0,0 +1,8 @@
import { debugMode } from './config';
export default (message, level = 'log') => {
if (debugMode) {
// eslint-disable-next-line
console[level]('@ohif/i18n: ', message);
}
};

View File

@ -0,0 +1,97 @@
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LngDetector from 'i18next-browser-languagedetector';
import customDebug from './debugger';
import pkg from '../package.json';
import { debugMode, detectionOptions } from './config';
let translate;
function getNameSpaceString(key) {
const nameSpaceMatcher = key.match(/[^/]+$/g);
let finalNameSpace;
if (nameSpaceMatcher !== null) {
finalNameSpace = nameSpaceMatcher[0].replace('.json', '');
}
return finalNameSpace;
}
function getKeyForNameSpaces(key) {
const cleanedKey = key.match(/[/\\].+(?=[/\\])/);
let finalKey;
if (cleanedKey !== null) {
finalKey = cleanedKey[0].replace(/[/\\]/, '');
finalKey = finalKey.replace(/[/\\]/, '-');
}
return finalKey;
}
function getLocales() {
var isTestEnvironment = process.env.NODE_ENV === 'test';
// require.context is exclusive from webpack. This conditional is needed to escape while running tests
if (isTestEnvironment) {
return {};
}
const context = require.context(`./locales`, true, /\.json$/);
const locales = {};
context.keys().forEach(key => {
locales[getKeyForNameSpaces(key)] = {
...locales[getKeyForNameSpaces(key)],
[getNameSpaceString(key)]: context(key),
};
});
return locales;
}
function addLocales(context) {
context.keys().forEach(key => {
i18n.addResourceBundle(
getKeyForNameSpaces(key),
getNameSpaceString(key),
context(key),
true,
true
);
});
customDebug(`Locales added successfully`, 'info');
}
function initI18n(detection = detectionOptions) {
i18n
.use(LngDetector)
.use(initReactI18next)
.init({
resources: getLocales(),
debug: debugMode,
keySeparator: false,
interpolation: {
escapeValue: false,
},
detection,
fallbackNS: ['Common'],
defaultNS: 'Common',
react: {
wait: true,
},
})
.then(function(t) {
translate = t;
customDebug(`t function available.`, 'info');
});
}
customDebug(`version ${pkg.version} loaded.`, 'info');
initI18n();
export { translate as t, addLocales, initI18n };
export default i18n;

View File

@ -0,0 +1,43 @@
{
"Themes": "Themes",
"Previous": "$t(Common:Previous)",
"Next": "$t(Common:Next)",
"Play": "$t(Common:Play)",
"Stop": "$t(Common:Stop)",
"Layout": "$t(Common:Layout)",
"More": "$t(Common:More)",
"Crosshairs": "Crosshairs",
"Magnify": "Magnify",
"ROI Window": "ROI Window",
"Probe": "Probe",
"Ellipse": "Ellipse",
"Rectangle": "Rectangle",
"Invert": "Invert",
"Rotate Right": "Rotate Right",
"Flip H": "Flip H",
"Flip V": "Flip V",
"Clear": "Clear",
"Brush": "Brush",
"Coronal": "Coronal",
"Stack Scroll": "Stack Scroll",
"Measurements": "Measurements",
"Zoom": "Zoom",
"Levels": "Levels",
"Pan": "Pan",
"Length": "Length",
"Angle": "Angle",
"Bidirectional": "Bidirectional",
"Freehand": "Freehand",
"Elliptical": "Elliptical",
"Circle": "Circle",
"Rectangle": "Rectangle",
"Reset": "$t(Common:Reset)",
"CINE": "CINE",
"Acquired": "Acquired",
"Sagittal": "Sagittal",
"Axial": "Axial",
"Manual": "Manual",
"Save": "Save",
"Reset to Defaults": "$t(Common:Reset) to Defaults",
"Cancel": "Cancel"
}

View File

@ -0,0 +1,8 @@
{
"fps": "fps",
"Skip to first image": "Skip to first $t(Common:Image)",
"Previous image": "$t(Common:Previous) $t(Common:Image)",
"Play / Stop": "$t(Common:Play) / $t(Common:Stop)",
"Next image": "$t(Common:Play) $t(Common:Image)",
"Skip to last image": "Skip, to last $t(Common:Image)"
}

View File

@ -0,0 +1,10 @@
{
"Reset": "Reset",
"Previous": "Previous",
"Next": "Next",
"Play": "Play",
"Stop": "Stop",
"Layout": "Layout",
"More": "More",
"Image": "Image"
}

View File

@ -0,0 +1,8 @@
{
"INVESTIGATIONAL USE ONLY": "INVESTIGATIONAL USE ONLY",
"Options": "Options",
"About": "About",
"Preferences": "Preferences",
"Study list": "Study list",
"Back to Viewer": "Back to Viewer"
}

View File

@ -0,0 +1,9 @@
{
"Criteria nonconformities": "Criteria nonconformities",
"Relabel": "Relabel",
"Description": "Description",
"Delete": "Delete",
"Targets": "Targets",
"NonTargets": "NonTargets",
"MAX": "MAX"
}

View File

@ -0,0 +1,3 @@
{
"About": "Info"
}

View File

@ -0,0 +1,3 @@
{
"About": "About"
}

View File

@ -0,0 +1,6 @@
{
"User Preferences": "User Preferences",
"Save": "$t(Buttons:Save)",
"Reset to Defaults": "$t(Buttons:Reset to Defaults)",
"Cancel": "$t(Buttons:Cancel)"
}

View File

@ -0,0 +1,3 @@
{
"INVESTIGATIONAL USE ONLY": "SOLO USO DE DESAROLLO"
}

View File

@ -0,0 +1,42 @@
{
"Themes": "Temas",
"Previous": "$t(Common:Previous)",
"Next": "$t(Common:Next)",
"Play": "$t(Common:Play)",
"Stop": "$t(Common:Stop)",
"Layout": "$t(Common:Layout)",
"More": "$t(Common:More)",
"Crosshairs": "Cruces",
"Magnify": "Lupa",
"ROI Window": "Ventana ROI",
"Probe": "Probar",
"Ellipse": "Elipse",
"Rectangle": "Rectángulo",
"Invert": "Invertido",
"Rotate Right": "Rotar ->",
"Flip H": "Espejo Hor.",
"Flip V": "Espejo Ver.",
"Clear": "Limpiar",
"Brush": "Escoba",
"Coronal": "Coronal",
"Stack Scroll": "Avance X slice",
"Measurements": "Medidas",
"Zoom": "Zoom",
"Levels": "Niveles",
"Pan": "Mover",
"Length": "Medición",
"Angle": "Ángulo",
"Bidirectional": "Bidirectional",
"Freehand": "Freehand",
"Elliptical": "Elliptical",
"Circle": "Circle",
"Reset": "$t(Common:Reset)",
"CINE": "CINE",
"Acquired": "Acquired",
"Sagittal": "Sagittal",
"Axial": "Axial",
"Manual": "Manual",
"Save": "Guardar",
"Reset to Defaults": "$t(Common:reset) por defectos",
"Cancel": "Cancelar"
}

View File

@ -0,0 +1,8 @@
{
"fps": "fps",
"Skip to first image": "Avanza para la primera $t(Common:Image)",
"Previous image": "$t(Common:Previous) $t(Common:Image)",
"Play / Stop": "$t(Common:Play) / $t(Common:Stop)",
"Next image": "$t(Common:Play) $t(Common:Image)",
"Skip to last image": "Pular para la ultima $t(Common:Image)"
}

View File

@ -0,0 +1,10 @@
{
"reset": "Reiniciar",
"Previous": "Anterior",
"Next": "Próximo",
"Play": "Play",
"Stop": "Stop",
"Layout": "Esquema",
"More": "Más",
"Image": "Imagen"
}

View File

@ -0,0 +1,8 @@
{
"INVESTIGATIONAL USE ONLY": "SOLO USO DE INVESTIGACIÓN",
"Options": "Opciones",
"About": "Sobre",
"Preferences": "Preferencias",
"Study list": "Lista de estudio",
"Back to Viewer": "Back to Viewer"
}

View File

@ -0,0 +1,3 @@
{
"INVESTIGATIONAL USE ONLY": "SOLO USO DE INVESTIGACIÓN"
}

View File

@ -0,0 +1,11 @@
{
"Criteria nonconformities": "Criterios de no conformidades",
"Relabel": "Reetiquetar",
"Description": "Descripción",
"Delete": "Borrar",
"Targets": "Objetivos",
"NonTargets": "NonObjetivos",
"MAX": "máximo",
"Chest Wall Posterior": "Pared pectoral posterior",
"Bone Extremity": "Extremidad ósea"
}

View File

@ -0,0 +1,6 @@
{
"User Preferences": "Preferencias de usuario",
"Save": "$t(Buttons:Save)",
"Reset to Defaults": "$t(Buttons:Reset to Defaults)",
"Cancel": "$t(Buttons:Cancel)"
}

File diff suppressed because it is too large Load Diff

View File

@ -1,69 +1,81 @@
{
"name": "@ohif/extension-vtk",
"version": "0.0.2",
"version": "0.0.6",
"description": "OHIF extension for VTK.js",
"author": "OHIF",
"license": "MIT",
"repository": "OHIF/Viewers",
"main": "dist/index.umd.js",
"module": "dist/index.es.js",
"jsnext:main": "dist/index.es.js",
"engines": {
"node": ">=8",
"npm": ">=5"
},
"scripts": {
"build": "rollup -c",
"prepublishOnly": "npm run build",
"start": "rollup -c -w",
"build": "webpack --progress --colors --mode development",
"build:release": "webpack --progress --colors --mode production",
"start": "webpack --watch --progress --colors --mode development",
"prepare": "yarn run build:release",
"predeploy": "cd example && yarn install && yarn run build:release",
"prepublishOnly": "yarn run build:release",
"lint": "eslint -c .eslintrc --fix src && prettier --single-quote --write src/**/*.{js,jsx,json,css}"
},
"peerDependencies": {
"@ohif/i18n": "0.0.4",
"cornerstone-core": "^2.2.8",
"cornerstone-wado-image-loader": "^2.2.3",
"dcmjs": "^0.3.6",
"dcmjs": "^0.4.7",
"dicom-parser": "^1.8.3",
"hammerjs": "^2.0.8",
"ohif-core": "^0.3.3",
"prop-types": "^15.6.2",
"react": "^15.0.0 || ^16.0.0",
"react-dom": "^15.0.0 || ^16.0.0",
"react-redux": "^6.0.0",
"react-resize-detector": "^3.4.0",
"react-viewerbase": "^0.6.0",
"i18next": "^17.0.3",
"i18next-browser-languagedetector": "^3.0.1",
"ohif-core": "^0.5.9",
"prop-types": "^15.7.2",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-i18next": "^10.11.0",
"react-redux": "^7.1.0",
"react-resize-detector": "^4.2.0",
"react-viewerbase": "^0.8.1",
"redux": "^4.0.1"
},
"devDependencies": {
"@babel/core": "^7.2.2",
"@babel/core": "^7.4.5",
"@babel/plugin-external-helpers": "^7.2.0",
"@babel/plugin-proposal-class-properties": "^7.2.3",
"@babel/plugin-transform-runtime": "^7.2.0",
"@babel/preset-env": "^7.2.3",
"@babel/plugin-proposal-class-properties": "^7.4.4",
"@babel/plugin-transform-runtime": "^7.4.4",
"@babel/preset-env": "^7.4.5",
"@babel/preset-react": "^7.0.0",
"babel-eslint": "^10.0.1",
"babel-loader": "^8.0.6",
"cornerstone-tools": "^3.13.0",
"cornerstone-wado-image-loader": "^2.2.3",
"cross-env": "^5.2.0",
"eslint": "5.13.0",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-node": "^8.0.0",
"eslint-plugin-promise": "^4.0.1",
"eslint-plugin-react": "^7.11.1",
"dcmjs": "^0.4.7",
"dicom-parser": "^1.8.3",
"eslint": "5.16.0",
"eslint-plugin-import": "^2.17.3",
"eslint-plugin-node": "^9.1.0",
"eslint-plugin-promise": "^4.1.1",
"eslint-plugin-react": "^7.13.0",
"gh-pages": "^2.0.1",
"husky": "^1.3.1",
"lint-staged": "^8.1.0",
"prettier": "^1.15.3",
"husky": "^2.4.1",
"i18next": "^17.0.3",
"i18next-browser-languagedetector": "^3.0.1",
"lint-staged": "^8.2.0",
"ohif-core": "^0.5.9",
"prettier": "^1.18.2",
"react": "^16.6.3",
"react-dom": "^16.6.3",
"rollup": "^1.1.2",
"rollup-plugin-babel": "^4.2.0",
"rollup-plugin-commonjs": "^9.2.0",
"rollup-plugin-node-builtins": "^2.1.2",
"rollup-plugin-node-resolve": "^4.0.0",
"rollup-plugin-peer-deps-external": "^2.2.0",
"rollup-plugin-postcss": "^2.0.3",
"rollup-plugin-url": "^2.1.0",
"stylelint": "^9.9.0",
"stylelint-config-recommended": "^2.1.0",
"stylus": "^0.54.5"
"react-i18next": "^10.11.0",
"react-redux": "^7.1.0",
"react-viewerbase": "^0.8.1",
"redux": "^4.0.1",
"shader-loader": "^1.3.1",
"stylelint": "^10.1.0",
"stylelint-config-recommended": "^2.2.0",
"stylus": "^0.54.5",
"webpack": "^4.33.0",
"webpack-cli": "^3.3.4",
"worker-loader": "^2.0.0"
},
"husky": {
"hooks": {
@ -90,8 +102,9 @@
"access": "public"
},
"dependencies": {
"@babel/runtime": "^7.2.0",
"classnames": "^2.2.6",
"react-vtkjs-viewport": "^0.0.7"
"@babel/runtime": "^7.4.5",
"@ohif/i18n": "0.0.4",
"react-vtkjs-viewport": "0.0.9",
"vtk.js": "^8.9.1"
}
}

View File

@ -6,6 +6,7 @@ import pkg from './package.json';
import postcss from 'rollup-plugin-postcss';
import resolve from 'rollup-plugin-node-resolve';
import url from 'rollup-plugin-url';
import copy from 'rollup-plugin-copy';
// Deal with https://github.com/rollup/rollup-plugin-commonjs/issues/297
@ -24,7 +25,8 @@ const globals = {
dcmjs: 'dcmjs',
'dicom-parser': 'dicomParser',
'ohif-core': 'OHIF',
hammerjs: 'Hammer'
hammerjs: 'Hammer',
'@ohif/i18n': 'i18n'
};
export default {
@ -56,6 +58,10 @@ export default {
externalHelpers: true,
runtimeHelpers: true
}),
copy({
targets: ['src/locales'],
outputFolder: 'dist',
}),
resolve(),
commonjs({
include: ['node_modules/**', '.yalc/**'],

View File

@ -5,33 +5,49 @@ 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: [
{
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: true
}
active: false,
onClick: () => {
// TODO: Make these use setToolActive instead
window.commandsManager.runCommand('enableRotateTool', {}, 'vtk');
}
},
],
activeCommand: 'Rotate'
};
};
const mapDispatchToProps = dispatch => {
return {
setToolActive: tool => {
//dispatch(setToolActive(tool.command))
}
activeCommand: 'Crosshairs'
};
};
const ConnectedToolbarSection = connect(
mapStateToProps,
mapDispatchToProps
null
)(ToolbarSection);
export default ConnectedToolbarSection;

View File

@ -1,5 +1,5 @@
import { connect } from 'react-redux';
import { VTKMPRViewport } from 'react-vtkjs-viewport';
import { View2D } from 'react-vtkjs-viewport';
import OHIF from 'ohif-core';
const {
@ -17,7 +17,7 @@ const mapStateToProps = (state, ownProps) => {
}
// If this is the active viewport, enable prefetching.
const { viewportIndex } = ownProps; //.viewportData;
const { viewportIndex } = ownProps;
const isActive = viewportIndex === state.viewports.activeViewportIndex;
const viewportSpecificData =
state.viewports.viewportSpecificData[viewportIndex] || {};
@ -31,7 +31,7 @@ const mapStateToProps = (state, ownProps) => {
...pluginDetails,
activeTool: activeButton && activeButton.command,
...dataFromStore,
enableStackPrefetch: isActive,
enableStackPrefetch: isActive
};
};
@ -45,17 +45,44 @@ const mapDispatchToProps = (dispatch, ownProps) => {
setViewportSpecificData: data => {
dispatch(setViewportSpecificData(viewportIndex, data));
},
clearViewportSpecificData: () => {
dispatch(clearViewportSpecificData(viewportIndex));
}
};
};
const mergeProps = (propsFromState, propsFromDispatch, ownProps) => {
const { afterCreation } = propsFromState;
const { setViewportSpecificData } = propsFromDispatch;
const props = {
...propsFromState,
...propsFromDispatch,
...ownProps,
/**
* Our component sets up the underlying dom element on "componentDidMount"
* for use with VTK.
*
* The onCreated prop passes back an Object containing many of the internal
* components of the VTK scene. We can grab a reference to these here, to
* make playing with VTK's native methods easier.
*
* A similar approach is taken with the Cornerstone extension.
*/
onCreated: api => {
// Store the API details for later
//setViewportSpecificData({ vtkApi: api });
if (afterCreation && typeof afterCreation === 'function') {
afterCreation(api);
}
}
};
return props;
};
const ConnectedVTKViewport = connect(
mapStateToProps,
//mapDispatchToProps
)(VTKMPRViewport);
mapDispatchToProps,
mergeProps
)(View2D);
export default ConnectedVTKViewport;

View File

@ -1,6 +1,7 @@
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
@ -13,7 +14,7 @@ import ToolbarModule from './ToolbarModule.js';
*/
function withChildren(WrappedComponent, children) {
return function(props) {
return <WrappedComponent children={children} { ...props } />;
return <WrappedComponent children={children} {...props} />;
};
}
@ -21,8 +22,10 @@ function withChildren(WrappedComponent, children) {
// https://github.com/whitecolor/yalc
export default class OHIFVTKExtension {
constructor(children) {
constructor({ children, commandsManager }) {
this.children = children;
_registerCommands(commandsManager, definitions, this.getExtensionId());
}
/**
@ -52,3 +55,21 @@ export default class OHIFVTKExtension {
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
);
});
}

View File

@ -1,10 +1,16 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import OHIF from 'ohif-core';
import ConnectedVTKViewport from './ConnectedVTKViewport';
import cornerstone from 'cornerstone-core';
import handleSegmentationStorage from './handleSegmentationStorage.js';
import { getImageData, loadImageData } from 'react-vtkjs-viewport';
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;
@ -27,9 +33,36 @@ specialCaseHandlers[
SOP_CLASSES.SEGMENTATION_STORAGE
] = handleSegmentationStorage;
// TODO: Figure out where we plan to put this long term
const volumeCache = {};
/**
* Create a labelmap image with the same dimensions as our background volume.
*
* @param backgroundImageData vtkImageData
*/
function createLabelMapImageData(backgroundImageData) {
const labelMapData = vtkImageData.newInstance(
backgroundImageData.get('spacing', 'origin', 'direction')
);
labelMapData.setDimensions(backgroundImageData.getDimensions());
labelMapData.computeTransforms();
const values = new Uint8Array(backgroundImageData.getNumberOfPoints());
const dataArray = vtkDataArray.newInstance({
numberOfComponents: 1, // labelmap with single component
values
});
labelMapData.getPointData().setScalars(dataArray);
return labelMapData;
}
class OHIFVTKViewport extends Component {
state = {
viewportData: null
volumes: null,
paintFilterLabelMapImageData: null,
paintFilterBackgroundImageData: null
};
static propTypes = {
@ -39,14 +72,14 @@ class OHIFVTKViewport extends Component {
children: PropTypes.node
};
static id = 'OHIFCornerstoneViewport';
static id = 'OHIFVTKViewport';
static init() {
console.log('OHIFCornerstoneViewport init()');
console.log('OHIFVTKViewport init()');
}
static destroy() {
console.log('OHIFCornerstoneViewport destroy()');
console.log('OHIFVTKViewport destroy()');
StackManager.clearStacks();
}
@ -97,22 +130,6 @@ class OHIFVTKViewport extends Component {
return stack;
}
static getViewportData = (
studies,
studyInstanceUid,
displaySetInstanceUid,
sopInstanceUid,
frameIndex
) => {
return OHIFVTKViewport.getCornerstoneStack(
studies,
studyInstanceUid,
displaySetInstanceUid,
sopInstanceUid,
frameIndex
);
};
getViewportData = (
studies,
studyInstanceUid,
@ -121,63 +138,64 @@ class OHIFVTKViewport extends Component {
sopInstanceUid,
frameIndex
) => {
return new Promise((resolve, reject) => {
const stack = OHIFVTKViewport.getViewportData(
studies,
studyInstanceUid,
displaySetInstanceUid,
sopClassUid,
sopInstanceUid,
frameIndex
);
const stack = OHIFVTKViewport.getCornerstoneStack(
studies,
studyInstanceUid,
displaySetInstanceUid,
sopClassUid,
sopInstanceUid,
frameIndex
);
let imageDataObject;
let labelmapDataObject;
let doneLoadingCallback;
let callbacks;
let imageDataObject;
let labelmapDataObject;
switch (sopClassUid) {
case SOP_CLASSES.SEGMENTATION_STORAGE:
throw new Error("Not yet implemented");
switch (sopClassUid) {
case SOP_CLASSES.SEGMENTATION_STORAGE:
throw new Error('Not yet implemented');
const data = handleSegmentationStorage(stack.imageIds, displaySetInstanceUid, cornerstone);
imageDataObject = data.referenceDataObject;
labelmapDataObject = data.labelmapDataObject;
const data = handleSegmentationStorage(
stack.imageIds,
displaySetInstanceUid
);
doneLoadingCallback = () => {
resolve({
data: imageDataObject.vtkImageData,
labelmap: labelmapDataObject
});
imageDataObject = data.referenceDataObject;
labelmapDataObject = data.labelmapDataObject;
return loadImageData(imageDataObject).then(() => {
return {
data: imageDataObject.vtkImageData,
labelmap: labelmapDataObject
};
});
default:
imageDataObject = getImageData(stack.imageIds, displaySetInstanceUid);
callbacks = [
doneLoadingCallback
];
loadImageData(imageDataObject, callbacks, cornerstone);
break;
default:
imageDataObject = getImageData(stack.imageIds, displaySetInstanceUid, cornerstone);
doneLoadingCallback = () => {
resolve({
data: imageDataObject.vtkImageData,
});
return loadImageData(imageDataObject).then(() => {
return {
data: imageDataObject.vtkImageData
};
callbacks = [
doneLoadingCallback
];
loadImageData(imageDataObject, callbacks, cornerstone);
break;
}
});
});
}
};
setStateFromProps() {
getOrCreateVolume(data, displaySetInstanceUid) {
if (volumeCache[displaySetInstanceUid]) {
return volumeCache[displaySetInstanceUid];
}
const volumeActor = vtkVolume.newInstance();
const volumeMapper = vtkVolumeMapper.newInstance();
volumeActor.setMapper(volumeMapper);
volumeMapper.setInputData(data);
volumeCache[displaySetInstanceUid] = volumeActor;
return volumeActor;
}
async setStateFromProps() {
const { studies, displaySet } = this.props.viewportData;
const {
studyInstanceUid,
@ -195,18 +213,25 @@ class OHIFVTKViewport extends Component {
const sopClassUid = sopClassUids[0];
this.getViewportData(
let { data, labelmap } = await this.getViewportData(
studies,
studyInstanceUid,
displaySetInstanceUid,
sopClassUid,
sopInstanceUid,
frameIndex
).then(({ data, labelmap })=> {
this.setState({
viewportData: data,
labelmap
});
);
if (!labelmap) {
labelmap = createLabelMapImageData(data);
}
const volumeActor = this.getOrCreateVolume(data, displaySetInstanceUid);
this.setState({
volumes: [volumeActor],
paintFilterBackgroundImageData: data,
paintFilterLabelMapImageData: labelmap
});
}
@ -245,15 +270,22 @@ class OHIFVTKViewport extends Component {
return (
<>
{this.state.viewportData ? (
{this.state.volumes ? (
<ConnectedVTKViewport
data={this.state.viewportData}
labelmap={this.state.labelmap}
volumes={this.state.volumes}
paintFilterLabelMapImageData={
this.state.paintFilterLabelMapImageData
}
paintFilterBackgroundImageData={
this.state.paintFilterBackgroundImageData
}
viewportIndex={this.props.viewportIndex}
/>
) : <div style={style}>
<LoadingIndicator/>
</div>}
) : (
<div style={style}>
<LoadingIndicator />
</div>
)}
{childrenWithProps}
</>
);

View File

@ -0,0 +1,299 @@
import {
vtkInteractorStyleMPRWindowLevel,
vtkInteractorStyleMPRSlice,
vtkInteractorStyleMPRCrosshairs,
vtkSVGCrosshairsWidget,
vtkSVGWidgetManager
} from 'react-vtkjs-viewport';
import setViewportToVTK from './utils/setViewportToVTK.js';
import setMPRLayout from './utils/setMPRLayout.js';
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}) => {
// Set camera focal point to world coordinate for linked views
apis.forEach((api, viewportIndex) => {
if (viewportIndex !== index) {
// We are basically doing the same as getSlice but with the world coordinate
// that we want to jump to instead of the camera focal point.
// I would rather do the camera adjustment directly but I keep
// doing it wrong and so this is good enough for now.
const renderWindow = api.genericRenderWindow.getRenderWindow();
const istyle = renderWindow.getInteractor().getInteractorStyle();
const sliceNormal = istyle.getSliceNormal();
const transform = vtkMatrixBuilder
.buildFromDegree()
.identity()
.rotateFromDirections(sliceNormal, [1, 0, 0]);
const mutatedWorldPos = worldPos.slice();
transform.apply(mutatedWorldPos);
const slice = mutatedWorldPos[0];
istyle.setSlice(slice);
renderWindow.render();
}
const renderer = api.genericRenderWindow.getRenderer();
const wPos = vtkCoordinate.newInstance();
wPos.setCoordinateSystemToWorld();
wPos.setValue(worldPos);
const displayPosition = wPos.getComputedDisplayValue(renderer);
const { svgWidgetManager } = api;
api.svgWidgets.crosshairsWidget.setPoint(displayPosition[0], displayPosition[1]);
svgWidgetManager.render();
});
};
}
async function _getActiveViewportVTKApi(viewports) {
const { layout, viewportSpecificData, activeViewportIndex } = viewports;
const currentData = layout.viewports[activeViewportIndex];
if (currentData && currentData.plugin === 'vtk') {
// TODO: I was storing/pulling this from Redux but ran into weird issues
if (apis[activeViewportIndex]) {
return apis[activeViewportIndex];
}
}
const displaySet = viewportSpecificData[activeViewportIndex];
let api;
if (!api) {
try {
api = await setViewportToVTK(
displaySet,
activeViewportIndex,
layout,
viewportSpecificData
);
} catch (error) {
throw new Error(error);
}
}
return api;
}
function _setView(api, sliceNormal, viewUp) {
const renderWindow = api.genericRenderWindow.getRenderWindow();
const renderer = api.genericRenderWindow.getRenderer();
const camera = renderer.getActiveCamera();
const istyle = renderWindow.getInteractor().getInteractorStyle();
istyle.setSliceNormal(...sliceNormal);
camera.setViewUp(...viewUp);
api.volumes[0].getMapper().setSampleDistance(5.0);
api.volumes[0].getMapper().setMaximumSamplesPerRay(2000);
renderWindow.render();
}
function switchMPRInteractors(api, istyle) {
const renderWindow = api.genericRenderWindow.getRenderWindow();
let currentNormal;
if (istyle.getSliceNormal) {
currentNormal = istyle.getSliceNormal();
}
// TODO: This is a hacky workaround because disabling the vtkInteractorStyleMPRSlice is currently
// broken. The camera.onModified is never removed. (https://github.com/Kitware/vtk-js/issues/1110)
renderWindow
.getInteractor()
.getInteractorStyle()
.setInteractor(null);
renderWindow.getInteractor().setInteractorStyle(istyle);
// TODO: Not sure why this is required the second time this function is called
istyle.setInteractor(renderWindow.getInteractor());
if (istyle.getVolumeMapper() !== api.volumes[0]) {
if (currentNormal) {
istyle.setSliceNormal(currentNormal);
}
istyle.setVolumeMapper(api.volumes[0]);
}
}
const actions = {
axial: async ({ viewports }) => {
const api = await _getActiveViewportVTKApi(viewports);
apis[viewports.activeViewportIndex] = api;
_setView(api, [0, 0, 1], [0, -1, 0]);
},
sagittal: async ({ viewports }) => {
const api = await _getActiveViewportVTKApi(viewports);
apis[viewports.activeViewportIndex] = api;
_setView(api, [1, 0, 0], [0, 0, 1]);
},
coronal: async ({ viewports }) => {
const api = await _getActiveViewportVTKApi(viewports);
apis[viewports.activeViewportIndex] = api;
_setView(api, [0, 1, 0], [0, 0, 1]);
},
enableRotateTool: async ({ viewports }) => {
apis.forEach(api => {
const istyle = vtkInteractorStyleMPRSlice.newInstance();
switchMPRInteractors(api, istyle);
});
},
enableCrosshairsTool: async ({ viewports }) => {
apis.forEach((api, index) => {
const istyle = vtkInteractorStyleMPRCrosshairs.newInstance();
switchMPRInteractors(api, istyle);
istyle.setCallback(getCrosshairCallbackForIndex(index));
});
},
enableLevelTool: async ({ viewports }) => {
apis.forEach(api => {
const istyle = vtkInteractorStyleMPRWindowLevel.newInstance();
switchMPRInteractors(api, istyle);
});
},
mpr2d: async ({ viewports }) => {
const displaySet =
viewports.viewportSpecificData[viewports.activeViewportIndex];
let apiByViewport;
try {
apiByViewport = await setMPRLayout(displaySet);
} catch (error) {
throw new Error(error);
}
apis = apiByViewport;
/*const rgbTransferFunction = apiByViewport[0].volumes[0]
.getProperty()
.getRGBTransferFunction(0);
rgbTransferFunction.onModified(() => {
apiByViewport.forEach(a => {
const renderWindow = a.genericRenderWindow.getRenderWindow();
renderWindow.render();
});
});*/
apis[0].volumes[0].getMapper().setSampleDistance(1.5);
apiByViewport.forEach((api, index) => {
const renderWindow = api.genericRenderWindow.getRenderWindow();
const renderer = api.genericRenderWindow.getRenderer();
const camera = renderer.getActiveCamera();
// TODO: This is a hacky workaround because disabling the vtkInteractorStyleMPRSlice is currently
// broken. The camera.onModified is never removed. (https://github.com/Kitware/vtk-js/issues/1110)
renderWindow
.getInteractor()
.getInteractorStyle()
.setInteractor(null);
const istyle = vtkInteractorStyleMPRCrosshairs.newInstance();
renderWindow.getInteractor().setInteractorStyle(istyle);
istyle.setVolumeMapper(api.volumes[0]);
istyle.setCallback(getCrosshairCallbackForIndex(index));
const svgWidgetManager = vtkSVGWidgetManager.newInstance();
svgWidgetManager.setRenderer(renderer);
svgWidgetManager.setScale(1);
const crosshairsWidget = vtkSVGCrosshairsWidget.newInstance();
svgWidgetManager.addWidget(crosshairsWidget);
svgWidgetManager.render();
api.svgWidgetManager = svgWidgetManager;
api.svgWidgets = {
crosshairsWidget
};
switch (index) {
default:
case 0:
//Axial
istyle.setSliceNormal(0, 0, 1);
camera.setViewUp(0, -1, 0);
break;
case 1:
// sagittal
istyle.setSliceNormal(1, 0, 0);
camera.setViewUp(0, 0, 1);
break;
case 2:
// Coronal
istyle.setSliceNormal(0, 1, 0);
camera.setViewUp(0, 0, 1);
break;
}
renderWindow.render();
});
}
};
const definitions = {
axial: {
commandFn: actions.axial,
storeContexts: ['viewports'],
options: {}
},
coronal: {
commandFn: actions.coronal,
storeContexts: ['viewports'],
options: {}
},
sagittal: {
commandFn: actions.sagittal,
storeContexts: ['viewports'],
options: {}
},
enableRotateTool: {
commandFn: actions.enableRotateTool,
storeContexts: ['viewports'],
options: {}
},
enableCrosshairsTool: {
commandFn: actions.enableCrosshairsTool,
storeContexts: ['viewports'],
options: {}
},
enableLevelTool: {
commandFn: actions.enableLevelTool,
storeContexts: ['viewports'],
options: {}
},
mpr2d: {
commandFn: actions.mpr2d,
storeContexts: ['viewports'],
options: {}
}
};
export { definitions };

View File

@ -1,5 +1,6 @@
import OHIF from 'ohif-core';
import * as dcmjs from 'dcmjs';
import { api } from 'dicomweb-client';
const { StackManager } = OHIF.utils;
@ -47,12 +48,25 @@ function getCornerstoneStack(studies, studyInstanceUid, displaySetInstanceUid) {
return stackClone;
}
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()
}).then(response => response.arrayBuffer());
function retrieveDicomData(
studyInstanceUID,
seriesInstanceUID,
sopInstanceUID,
wadoRoot
) {
const config = {
url: wadoRoot,
headers: DICOMWeb.getAuthorizationHeader()
};
const dicomWeb = new api.DICOMwebClient(config);
const options = {
studyInstanceUID,
seriesInstanceUID,
sopInstanceUID
};
return dicomWeb.retrieveInstance(options);
}
async function handleSegmentationStorage(
@ -68,8 +82,22 @@ async function handleSegmentationStorage(
studyInstanceUid,
displaySetInstanceUid
);
const segWadoUri = displaySet.images[0].getData().wadouri;
const arrayBuffer = await retrieveDicomData(segWadoUri);
// TODO: This is terrible but we need to use WADO-RS or we can't retrieve the SEG
// from google cloud
const wadoRoot = displaySet.images[0].getData().wadoRoot;
const StudyInstanceUID = displaySet.images[0].getStudyInstanceUID();
const SeriesInstanceUID = displaySet.images[0].getSeriesInstanceUID();
const SOPInstanceUID = displaySet.images[0].getSOPInstanceUID();
const arrayBuffer = await retrieveDicomData(
StudyInstanceUID,
SeriesInstanceUID,
SOPInstanceUID,
wadoRoot
);
const dicomData = dcmjs.data.DicomMessage.readFile(arrayBuffer);
const dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
dicomData.dict
@ -109,7 +137,7 @@ async function handleSegmentationStorage(
return {
referenceDataObject,
labelmapDataObject
}
};
return {
studyInstanceUid,

View File

@ -1,3 +1,7 @@
import loadLocales from './loadLocales';
import OHIFVTKExtension from './OHIFVTKExtension.js';
loadLocales();
export default OHIFVTKExtension;

View File

@ -0,0 +1,8 @@
import { addLocales } from '@ohif/i18n';
function loadLocales() {
const context = require.context(`./locales`, true, /\.json$/);
addLocales(context);
}
export default loadLocales;

View File

@ -0,0 +1,3 @@
{
"Rotate": "Rotate"
}

View File

@ -0,0 +1,3 @@
{
"Rotate": "Rotacionar"
}

View File

@ -0,0 +1,10 @@
import { redux } from 'ohif-core';
const { setViewportLayoutAndData } = redux.actions;
// TODO: Should not be getting dispatch from the window, but I'm not sure how else to do it cleanly
export default function setLayoutAndViewportData(layout, viewportSpecificData) {
const action = setViewportLayoutAndData(layout, viewportSpecificData);
window.store.dispatch(action);
}

View File

@ -0,0 +1,61 @@
import setLayoutAndViewportData from './setLayoutAndViewportData.js';
import setSingleLayoutData from './setSingleLayoutData.js';
export default function setMPRLayout(displaySet) {
return new Promise((resolve, reject) => {
let viewports = [];
const rows = 1;
const columns = 3;
const numViewports = rows * columns;
const viewportSpecificData = {};
for (let i = 0; i < numViewports; i++) {
viewports.push({
height: `${100 / rows}%`,
width: `${100 / columns}%`
});
viewportSpecificData[i] = displaySet;
viewportSpecificData[i].plugin = 'vtk';
}
const layout = {
viewports
};
const viewportIndices = [0, 1, 2];
let updatedViewports = layout.viewports;
const apis = [];
viewportIndices.forEach(viewportIndex => {
apis[viewportIndex] = null;
/*const currentData = layout.viewports[viewportIndex];
if (currentData && currentData.plugin === 'vtk') {
reject(new Error('Should not have reached this point??'));
}*/
const data = {
plugin: 'vtk',
vtk: {
mode: 'mpr', // TODO: not used
afterCreation: api => {
apis[viewportIndex] = api;
if (apis.every(a => !!a)) {
resolve(apis);
}
}
}
};
updatedViewports = setSingleLayoutData(
updatedViewports,
viewportIndex,
data
);
});
setLayoutAndViewportData(
{ viewports: updatedViewports },
viewportSpecificData
);
});
}

View File

@ -0,0 +1,12 @@
export default function setSingleLayoutData(
originalArray,
viewportIndex,
data
) {
const viewports = originalArray.slice();
const layoutData = Object.assign({}, viewports[viewportIndex], data);
viewports[viewportIndex] = layoutData;
return viewports;
}

View File

@ -0,0 +1,39 @@
import setLayoutAndViewportData from './setLayoutAndViewportData.js';
import setSingleLayoutData from './setSingleLayoutData.js';
export default function setViewportToVTK(
displaySet,
viewportIndex,
layout,
viewportSpecificData
) {
return new Promise((resolve, reject) => {
/*const currentData = layout.viewports[viewportIndex];
if (currentData && currentData.plugin === 'vtk') {
reject(new Error('Should not have reached this point??'));
}*/
const data = {
plugin: 'vtk',
vtk: {
mode: 'mpr', // TODO: not used
afterCreation: api => {
resolve(api);
}
}
};
const updatedViewports = setSingleLayoutData(
layout.viewports,
viewportIndex,
data
);
const updatedViewportData = viewportSpecificData;
setLayoutAndViewportData(
{ viewports: updatedViewports },
updatedViewportData
);
});
}

View File

@ -0,0 +1,108 @@
var path = require('path')
var webpack = require('webpack')
const autoprefixer = require('autoprefixer');
const cssRules = [
{
test: /\.css$/,
exclude: /\.module\.css$/,
use: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: () => [autoprefixer('last 2 version', 'ie >= 10')],
},
},
],
},
{
test: /\.glsl$/i,
include: /vtk\.js[\/\\]Sources/,
loader: 'shader-loader',
},
{
test: /\.worker\.js$/,
include: /vtk\.js[\/\\]Sources/,
use: [
{
loader: 'worker-loader',
options: { inline: true, fallback: false },
},
],
},
{
test: /\.css$/,
include: /\.module\.css$/,
use: [
{ loader: 'style-loader' },
{
loader: 'css-loader',
options: {
localIdentName: '[name]-[local]_[sha512:hash:base64:5]',
modules: true,
},
},
{
loader: 'postcss-loader',
options: {
plugins: () => [autoprefixer('last 2 version', 'ie >= 10')],
},
},
],
},
];
var entry = path.join(__dirname, './src/index.js')
const sourcePath = path.join(__dirname, './src')
const outputPath = path.join(__dirname, './dist')
module.exports = {
entry,
output: {
path: outputPath,
filename: 'index.umd.js',
library: '@ohif/extension-vtk',
libraryTarget: 'umd',
globalObject: 'this',
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: ['babel-loader'],
}
].concat(cssRules),
},
resolve: {
modules: [path.resolve(__dirname, 'node_modules'), sourcePath],
},
externals: [{
'cornerstone-core': {
commonjs: 'cornerstone-core',
commonjs2: 'cornerstone-core',
amd: 'cornerstone-core',
root: 'cornerstone',
},
'cornerstone-math': {
commonjs: 'cornerstone-math',
commonjs2: 'cornerstone-math',
amd: 'cornerstone-math',
root: 'cornerstoneMath',
},
},
'ohif-core',
'dcmjs',
'react-viewerbase',
'react',//: 'React',
'react-dom',//: 'ReactDOM',
'react-redux',//: 'ReactRedux',
'react-resize-detector',//: 'ReactResizeDetector',
'react-viewerbase',//: 'reactViewerbase',
'prop-types'//: 'PropTypes'
/*/\b(vtk.js)/*/
],
}

File diff suppressed because it is too large Load Diff

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

@ -1,6 +1,6 @@
{
"name": "ohif-viewer",
"version": "0.0.20",
"version": "0.0.21",
"description": "OHIF Viewer",
"author": "OHIF Contributors",
"license": "MIT",
@ -22,6 +22,7 @@
"build:package:ci": "yarn run preBuild && node --max-old-space-size=4096 node_modules/rollup/bin/rollup -c",
"build:web": "yarn run preBuild && react-scripts --max_old_space_size=4096 build",
"build:web:ci": "yarn run preBuild && cross-env PUBLIC_URL=/demo REACT_APP_CONFIG=config/netlify.js react-scripts --max_old_space_size=4096 build && cpx 'build/**/*' docs/latest/_book/demo --verbose",
"build:demo:ci": "yarn run preBuild && cross-env PUBLIC_URL=/ REACT_APP_CONFIG=config/public_dicomweb.js react-scripts --max_old_space_size=4096 build",
"lint": "eslint -c .eslintrc --fix src && prettier --single-quote --write src/**/*.{js,jsx,json,css}",
"test": "jest",
"test:ci": "jest --ci --runInBand --collectCoverage --reporters=default --reporters=jest-junit && codecov",
@ -61,89 +62,84 @@
}
},
"peerDependencies": {
"react": "^15.0.0 || ^16.0.0",
"react-dom": "^15.0.0 || ^16.0.0"
"react": "^16.8.6",
"react-dom": "^16.8.6"
},
"dependencies": {
"@babel/polyfill": "^7.2.5",
"@babel/runtime": "^7.2.0",
"@ohif/extension-cornerstone": "0.0.34",
"@babel/runtime": "^7.4.5",
"@ohif/extension-cornerstone": "0.0.36",
"@ohif/extension-dicom-microscopy": "0.0.6",
"@ohif/extension-vtk": "0.0.2",
"@ohif/extension-vtk": "0.0.6",
"@ohif/i18n": "0.0.4",
"classnames": "^2.2.6",
"cornerstone-core": "^2.2.8",
"cornerstone-math": "^0.1.8",
"cornerstone-tools": "^3.11.0",
"cornerstone-tools": "^3.13.0",
"cornerstone-wado-image-loader": "^2.2.3",
"dcmjs": "^0.3.8",
"dcmjs": "^0.4.7",
"dicom-parser": "^1.8.3",
"dicomweb-client": "^0.4.2",
"dicomweb-client": "^0.4.4",
"hammerjs": "^2.0.8",
"i18next": "^17.0.3",
"i18next-browser-languagedetector": "^3.0.1",
"lodash.isequal": "4.5.0",
"moment": "^2.24.0",
"ohif-core": "0.5.5",
"ohif-core": "0.5.9",
"ohif-dicom-html-extension": "^0.0.2",
"ohif-dicom-pdf-extension": "^0.0.6",
"oidc-client": "1.7.x",
"prop-types": "^15.6.2",
"react-bootstrap-modal": "^4.2.0",
"react-dnd": "^7.0.2",
"react-dnd-html5-backend": "^7.0.2",
"react-redux": "^6.0.0",
"react-resize-detector": "^3.4.0",
"react-router": "^4.3.1",
"react-router-dom": "^4.3.1",
"react-viewerbase": "0.6.1",
"react-vtkjs-viewport": "^0.0.7",
"prop-types": "^15.7.2",
"react-i18next": "^10.11.0",
"react-redux": "^7.1.0",
"react-resize-detector": "^4.2.0",
"react-router": "^5.0.1",
"react-router-dom": "^5.0.1",
"react-viewerbase": "0.9.0",
"redux": "^4.0.1",
"redux-oidc": "3.1.x"
},
"devDependencies": {
"@babel/core": "^7.2.2",
"@babel/plugin-external-helpers": "^7.2.0",
"@babel/plugin-proposal-class-properties": "^7.2.3",
"@babel/core": "^7.4.5",
"@babel/plugin-proposal-class-properties": "^7.4.4",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/plugin-transform-runtime": "^7.2.0",
"@babel/preset-env": "^7.2.3",
"@babel/plugin-transform-runtime": "^7.4.4",
"@babel/preset-env": "^7.4.5",
"@babel/preset-react": "^7.0.0",
"@semantic-release/exec": "3.3.2",
"@svgr/rollup": "^4.1.0",
"babel-eslint": "^9.0.0",
"codecov": "3.3.0",
"@semantic-release/exec": "3.3.3",
"@svgr/rollup": "^4.3.0",
"babel-eslint": "^10.0.1",
"codecov": "3.5.0",
"commitizen": "3.1.x",
"cpx": "1.5.0",
"cross-env": "^5.2.0",
"cz-conventional-changelog": "2.1.0",
"eslint": "5.12.0",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-node": "^8.0.0",
"eslint-plugin-promise": "^4.0.1",
"eslint-plugin-react": "^7.11.1",
"eslint": "5.16.0",
"eslint-plugin-import": "^2.17.3",
"eslint-plugin-node": "^9.1.0",
"eslint-plugin-promise": "^4.1.1",
"eslint-plugin-react": "^7.13.0",
"gh-pages": "2.0.1",
"husky": "1.3.x",
"husky": "2.4.x",
"identity-obj-proxy": "3.0.x",
"jest-canvas-mock": "2.0.0",
"jest-junit": "6.3.x",
"lint-staged": "^8.1.0",
"jest-canvas-mock": "2.1.0",
"jest-junit": "6.4.x",
"lint-staged": "^8.2.1",
"lodash": "4.17.11",
"lodash.clonedeep": "4.5.0",
"prettier": "1.15.x",
"prettier": "1.18.x",
"react": "^16.7.0",
"react-dom": "^16.7.0",
"react-scripts": "^2.1.5",
"react-test-renderer": "^16.8.6",
"rollup": "^1.1.2",
"rollup-plugin-babel": "^4.2.0",
"rollup-plugin-commonjs": "^9.2.0",
"rollup-plugin-json": "^3.1.0",
"react-scripts": "^3.0.1",
"rollup": "^1.15.5",
"rollup-plugin-babel": "^4.3.2",
"rollup-plugin-commonjs": "^10.0.0",
"rollup-plugin-json": "^4.0.0",
"rollup-plugin-node-builtins": "^2.1.2",
"rollup-plugin-node-resolve": "^4.0.0",
"rollup-plugin-node-resolve": "^5.0.2",
"rollup-plugin-peer-deps-external": "^2.2.0",
"rollup-plugin-postcss": "^2.0.3",
"rollup-plugin-url": "^2.1.0",
"rollup-plugin-url": "^2.2.2",
"semantic-release": "15.13.x",
"stylelint": "^9.9.0",
"stylelint-config-recommended": "^2.1.0",
"stylus": "^0.54.5"
"stylelint": "^10.1.0"
}
}

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

@ -43,7 +43,7 @@ export default {
url(),
svgr(),
json(),
resolve(),
resolve({preferBuiltins: true}),
babel({
exclude: 'node_modules/**',
runtimeHelpers: true,
@ -66,6 +66,7 @@ export default {
'cornerstoneTools',
],
'node_modules/dcmjs/build/dcmjs.js': ['data', 'adapters'],
'node_modules/prop-types/index.js': ['bool', 'number', 'string', 'shape', 'func', 'any', 'node']
},
}),
builtins(),

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,57 +15,75 @@ 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: @dannyrb will fix this
window.commandsManager = commandsManager;
store.subscribe(() => {
localStorage.saveState({
preferences: store.getState().preferences,
});
// TODO: Should be done in extensions w/ commandsModule
// ~~ ADD COMMANDS
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 />],
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 OHIFVTKExtension({ commandsManager }),
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 +106,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);
@ -110,27 +131,35 @@ class App extends Component {
if (userManager) {
return (
<Provider store={store}>
<OidcProvider store={store} userManager={userManager}>
<Router basename={this.props.routerBasename}>
<WhiteLabellingContext.Provider value={this.props.whiteLabelling}>
<OHIFStandaloneViewer userManager={userManager} />
</WhiteLabellingContext.Provider>
</Router>
</OidcProvider>
<I18nextProvider i18n={i18n}>
<OidcProvider store={store} userManager={userManager}>
<Router basename={this.props.routerBasename}>
<WhiteLabellingContext.Provider
value={this.props.whiteLabelling}
>
<OHIFStandaloneViewer userManager={userManager} />
</WhiteLabellingContext.Provider>
</Router>
</OidcProvider>
</I18nextProvider>
</Provider>
);
}
return (
<Provider store={store}>
<Router basename={this.props.routerBasename}>
<WhiteLabellingContext.Provider value={this.props.whiteLabelling}>
<OHIFStandaloneViewer />
</WhiteLabellingContext.Provider>
</Router>
<I18nextProvider i18n={i18n}>
<Router basename={this.props.routerBasename}>
<WhiteLabellingContext.Provider value={this.props.whiteLabelling}>
<OHIFStandaloneViewer />
</WhiteLabellingContext.Provider>
</Router>
</I18nextProvider>
</Provider>
);
}
}
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,22 @@
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,
};
static defaultProps = {
@ -19,32 +24,66 @@ 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();
}
loadOptions() {
const { t } = this.props;
this.options = [
// {
// title: t('Preferences'),
// icon: { name: 'user' },
// onClick: onClick,
// },
{
title: 'Preferences ',
icon: {
name: 'user',
},
onClick: this.props.openUserPreferencesModal,
},
{
title: 'About',
title: t('About'),
icon: {
name: 'info',
},
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() {
const { t } = this.props;
return (
<div className={`entry-header ${this.props.home ? 'header-big' : ''}`}>
<div className="header-left-box">
@ -53,7 +92,7 @@ class Header extends Component {
to={this.props.location.studyLink}
className="header-btn header-viewerLink"
>
Back to Viewer
{t('Back to Viewer')}
</Link>
)}
@ -67,19 +106,19 @@ class Header extends Component {
state: { studyLink: this.props.location.pathname },
}}
>
Study list
{t('Study list')}
</Link>
)}
</div>
<div className="header-menu">
<span className="research-use">INVESTIGATIONAL USE ONLY</span>
<Dropdown title="Options" list={this.options} align="right" />
<ConnectedUserPreferencesModal />
<span className="research-use">{t('INVESTIGATIONAL USE ONLY')}</span>
<Dropdown title={t('Options')} list={this.options} align="right" />
{/* <ConnectedUserPreferencesModal /> */}
</div>
</div>
);
}
}
export default withRouter(Header);
export default withTranslation('Header')(withRouter(Header));

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

@ -22,6 +22,7 @@ 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 = {

View File

@ -5,10 +5,11 @@ import OHIF from 'ohif-core';
const { setLayout } = OHIF.redux.actions;
const mapStateToProps = state => {
const { activeViewportIndex, layout } = state.viewports;
const { activeViewportIndex, layout, viewportSpecificData } = state.viewports;
return {
activeViewportIndex,
viewportSpecificData,
layout,
};
};
@ -21,23 +22,23 @@ const mapDispatchToProps = dispatch => {
};
};
function setSingleLayoutData(originalArray, viewportIndex, data) {
/*function setSingleLayoutData(originalArray, viewportIndex, data) {
const viewports = originalArray.slice();
const layoutData = Object.assign({}, viewports[viewportIndex], data);
viewports[viewportIndex] = layoutData;
return viewports;
}
}*/
const mergeProps = (propsFromState, propsFromDispatch, ownProps) => {
const { activeViewportIndex, layout } = propsFromState;
const { setLayout } = propsFromDispatch;
//const { activeViewportIndex, layout } = propsFromState;
//const { setLayout } = propsFromDispatch;
// TODO: Do not display certain options if the current display set
// cannot be displayed using these view types
const buttons = [
{
/*{
text: 'Acquired',
type: 'command',
icon: 'bars',
@ -59,22 +60,7 @@ const mergeProps = (propsFromState, propsFromDispatch, ownProps) => {
icon: 'cube',
active: false,
onClick: () => {
console.warn('Axial');
const data = {
plugin: 'vtk',
vtk: {
mode: 'mpr',
sliceNormal: [0, 0, 1],
},
};
const layoutData = setSingleLayoutData(
layout.viewports,
activeViewportIndex,
data
);
setLayout({ viewports: layoutData });
window.commandsManager.runCommand('axial', {}, 'vtk');
},
},
{
@ -82,22 +68,7 @@ const mergeProps = (propsFromState, propsFromDispatch, ownProps) => {
icon: 'cube',
active: false,
onClick: () => {
console.warn('Sagittal');
const data = {
plugin: 'vtk',
vtk: {
mode: 'mpr',
sliceNormal: [1, 0, 0],
},
};
const layoutData = setSingleLayoutData(
layout.viewports,
activeViewportIndex,
data
);
setLayout({ viewports: layoutData });
window.commandsManager.runCommand('sagittal', {}, 'vtk');
},
},
{
@ -105,41 +76,17 @@ const mergeProps = (propsFromState, propsFromDispatch, ownProps) => {
icon: 'cube',
active: false,
onClick: () => {
console.warn('Coronal');
const data = {
plugin: 'vtk',
vtk: {
mode: 'mpr',
sliceNormal: [0, 1, 0],
},
};
const layoutData = setSingleLayoutData(
layout.viewports,
activeViewportIndex,
data
);
setLayout({ viewports: layoutData });
window.commandsManager.runCommand('coronal', {}, 'vtk');
},
},*/
{
text: '2D MPR',
icon: 'cube',
active: false,
onClick: () => {
window.commandsManager.runCommand('mpr2d', {}, 'vtk');
},
},
/*{
text: '3D',
icon: `#cube`,
onClick: (click) => {
console.warn('3D Perspective');
const data = {
plugin: 'vtk',
vtk: {
mode: '3d',
}
};
const layoutData = setSingleLayoutData(layout.viewports, activeViewportIndex, data);
setLayout({ viewports: layoutData });
}
}*/
];
return {

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

@ -10,8 +10,6 @@
.sidebar-menu {
height: 100%;
/* required transformation to make inner fixed elements relative to this one*/
transform: scale(1);
transition: var(--sidebar-transition);
}

View File

@ -1,29 +1,33 @@
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 = {
activeViewportIndex: PropTypes.number.isRequired,
studies: PropTypes.array.isRequired,
viewportSpecificData: PropTypes.object.isRequired,
layout: PropTypes.object.isRequired,
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 +151,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,6 +0,0 @@
import loglevel from 'loglevel';
const log = loglevel.getLogger('OHIFViewer');
log.setLevel('info');
export default log;

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

@ -49,48 +49,6 @@ export default function() {
icon: 'angle-left',
active: false,
},
{
command: 'Bidirectional',
type: 'tool',
text: 'Bidirectional',
icon: 'measure-target',
active: false,
},
{
command: 'Brush',
type: 'tool',
text: 'Brush',
icon: 'circle',
active: false,
},
{
command: 'FreehandMouse',
type: 'tool',
text: 'Freehand',
icon: 'star',
active: false,
},
{
command: 'EllipticalRoi',
type: 'tool',
text: 'EllipticalRoi',
icon: 'oval',
active: false,
},
{
command: 'CircleRoi',
type: 'tool',
text: 'CircleRoi',
icon: 'dot-circle',
active: false,
},
{
command: 'RectangleRoi',
type: 'tool',
text: 'RectangleRoi',
icon: 'square-o',
active: false,
},
{
command: 'reset',
type: 'command',

View File

@ -5,13 +5,19 @@ import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
* @param {String} baseDirectory
* @param {String} webWorkScriptsPath
*/
export default function(baseDirectory, webWorkScriptsPath) {
export default function initWebWorkers(
baseDirectory = '/',
webWorkScriptsPath = ''
) {
let scriptsPath = `${window.location.protocol}//${
window.location.host
}${baseDirectory}`;
if (webWorkScriptsPath) {
scriptsPath += `/${webWorkScriptsPath}/`;
}${baseDirectory}${webWorkScriptsPath}`;
// Ensure the last character is a slash
if (scriptsPath[scriptsPath.length - 1] !== '/') {
scriptsPath += '/';
}
const config = {
maxWebWorkers: Math.max(navigator.hardwareConcurrency - 1, 1),
startWebWorkersOnDemand: true,

4482
yarn.lock

File diff suppressed because it is too large Load Diff