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

|
||||
|
||||
@ -74,7 +103,8 @@ An extension can register a Toolbar Module by providing a `getToolbarModule()` m
|
||||
|
||||
Toolbar components are rendered in the `ToolbarRow` component.
|
||||
|
||||
For a complete example implementation, [check out the OHIFCornerstoneViewport's Toolbar Module](https://github.com/OHIF/Viewers/blob/react/extensions/ohif-cornerstone-extension/src/ToolbarModule.js).
|
||||
For a complete example implementation,
|
||||
[check out the OHIFCornerstoneViewport's Toolbar Module](https://github.com/OHIF/Viewers/blob/react/extensions/ohif-cornerstone-extension/src/ToolbarModule.js).
|
||||
|
||||
#### SopClassHandler
|
||||
|
||||
@ -84,18 +114,32 @@ For a complete example implementation, [check out the OHIFCornerstoneViewport's
|
||||
|
||||
> The panel module is not yet in use.
|
||||
|
||||
#### Commands
|
||||
|
||||
...
|
||||
|
||||
#### Hotkeys
|
||||
|
||||
...
|
||||
|
||||
### Registering Extensions
|
||||
|
||||
Extensions are registered for the application at startup. The `ExtensionManager`, exposed by `ohif-core`, registers a list of extensions with our application's store. Each module provided by the extension becomes available via `state.plugins.availablePlugins`, and consists of three parts: id, type ([PLUGIN_TYPE](https://github.com/OHIF/ohif-core/blob/43c08a29eff3fb646a0e83a03a236ddd84f4a6e8/src/plugins.js#L1-L6)), and the return value of the module method.
|
||||
Extensions are registered for the application at startup. The
|
||||
`ExtensionManager`, exposed by `ohif-core`, registers a list of extensions with
|
||||
our application's store. Each module provided by the extension becomes available
|
||||
via `state.plugins.availablePlugins`, and consists of three parts: id, type
|
||||
([PLUGIN_TYPE](https://github.com/OHIF/ohif-core/blob/43c08a29eff3fb646a0e83a03a236ddd84f4a6e8/src/plugins.js#L1-L6)),
|
||||
and the return value of the module method.
|
||||
|
||||
In a future version, we will likely expose a way to provide the extensions you would like included at startup.
|
||||
In a future version, we will likely expose a way to provide the extensions you
|
||||
would like included at startup.
|
||||
|
||||
_app.js_
|
||||
|
||||
```js
|
||||
import { createStore, combineReducers } from "redux";
|
||||
import OHIF from "ohif-core";
|
||||
import OHIFCornerstoneExtension from "ohif-cornerstone-extension";
|
||||
import { createStore, combineReducers } from 'redux';
|
||||
import OHIF from 'ohif-core';
|
||||
import OHIFCornerstoneExtension from 'ohif-cornerstone-extension';
|
||||
|
||||
const combined = combineReducers(OHIF.redux.reducers);
|
||||
const store = createStore(combined);
|
||||
@ -108,6 +152,10 @@ ExtensionManager.registerExtensions(store, extensions);
|
||||
|
||||
## OHIF Maintained Extensions
|
||||
|
||||
A small number of powerful extensions for popular use cases are maintained by OHIF. They're co-located in the [`OHIF/Viewers`](https://github.com/OHIF/Viewers/tree/react/) repository, in the top level [`extensions/`](https://github.com/OHIF/Viewers/tree/react/extensions) directory.
|
||||
A small number of powerful extensions for popular use cases are maintained by
|
||||
OHIF. They're co-located in the
|
||||
[`OHIF/Viewers`](https://github.com/OHIF/Viewers/tree/react/) repository, in the
|
||||
top level [`extensions/`](https://github.com/OHIF/Viewers/tree/react/extensions)
|
||||
directory.
|
||||
|
||||
{% include "./_maintained-extensions-table.md" %}
|
||||
|
||||
@ -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
|
||||
|
||||
264
docs/latest/essentials/translating.md
Normal file
264
docs/latest/essentials/translating.md
Normal 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).
|
||||
@ -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 -->
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
14
extensions/ohif-i18n/.babelrc
Normal file
14
extensions/ohif-i18n/.babelrc
Normal 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"
|
||||
]
|
||||
}
|
||||
9
extensions/ohif-i18n/.editorconfig
Normal file
9
extensions/ohif-i18n/.editorconfig
Normal 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
26
extensions/ohif-i18n/.gitignore
vendored
Normal 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
|
||||
21
extensions/ohif-i18n/LICENSE
Normal file
21
extensions/ohif-i18n/LICENSE
Normal 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.
|
||||
86
extensions/ohif-i18n/package.json
Normal file
86
extensions/ohif-i18n/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
72
extensions/ohif-i18n/rollup.config.js
Normal file
72
extensions/ohif-i18n/rollup.config.js
Normal 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 || {})
|
||||
};
|
||||
24
extensions/ohif-i18n/src/config.js
Normal file
24
extensions/ohif-i18n/src/config.js
Normal 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 };
|
||||
8
extensions/ohif-i18n/src/debugger.js
Normal file
8
extensions/ohif-i18n/src/debugger.js
Normal 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);
|
||||
}
|
||||
};
|
||||
97
extensions/ohif-i18n/src/index.js
Executable file
97
extensions/ohif-i18n/src/index.js
Executable 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;
|
||||
43
extensions/ohif-i18n/src/locales/en/Buttons.json
Normal file
43
extensions/ohif-i18n/src/locales/en/Buttons.json
Normal 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"
|
||||
}
|
||||
8
extensions/ohif-i18n/src/locales/en/CineDialog.json
Normal file
8
extensions/ohif-i18n/src/locales/en/CineDialog.json
Normal 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)"
|
||||
}
|
||||
10
extensions/ohif-i18n/src/locales/en/Common.json
Executable file
10
extensions/ohif-i18n/src/locales/en/Common.json
Executable file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"Reset": "Reset",
|
||||
"Previous": "Previous",
|
||||
"Next": "Next",
|
||||
"Play": "Play",
|
||||
"Stop": "Stop",
|
||||
"Layout": "Layout",
|
||||
"More": "More",
|
||||
"Image": "Image"
|
||||
}
|
||||
8
extensions/ohif-i18n/src/locales/en/Header.json
Normal file
8
extensions/ohif-i18n/src/locales/en/Header.json
Normal 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"
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
{
|
||||
"Criteria nonconformities": "Criteria nonconformities",
|
||||
"Relabel": "Relabel",
|
||||
"Description": "Description",
|
||||
"Delete": "Delete",
|
||||
"Targets": "Targets",
|
||||
"NonTargets": "NonTargets",
|
||||
"MAX": "MAX"
|
||||
}
|
||||
3
extensions/ohif-i18n/src/locales/en/UK/Header.json
Normal file
3
extensions/ohif-i18n/src/locales/en/UK/Header.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"About": "Info"
|
||||
}
|
||||
3
extensions/ohif-i18n/src/locales/en/US/Header.json
Normal file
3
extensions/ohif-i18n/src/locales/en/US/Header.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"About": "About"
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
{
|
||||
"User Preferences": "User Preferences",
|
||||
"Save": "$t(Buttons:Save)",
|
||||
"Reset to Defaults": "$t(Buttons:Reset to Defaults)",
|
||||
"Cancel": "$t(Buttons:Cancel)"
|
||||
}
|
||||
3
extensions/ohif-i18n/src/locales/es/AR/Header.json
Normal file
3
extensions/ohif-i18n/src/locales/es/AR/Header.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"INVESTIGATIONAL USE ONLY": "SOLO USO DE DESAROLLO"
|
||||
}
|
||||
42
extensions/ohif-i18n/src/locales/es/Buttons.json
Normal file
42
extensions/ohif-i18n/src/locales/es/Buttons.json
Normal 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"
|
||||
}
|
||||
8
extensions/ohif-i18n/src/locales/es/CineDialog.json
Normal file
8
extensions/ohif-i18n/src/locales/es/CineDialog.json
Normal 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)"
|
||||
}
|
||||
10
extensions/ohif-i18n/src/locales/es/Common.json
Executable file
10
extensions/ohif-i18n/src/locales/es/Common.json
Executable file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"reset": "Reiniciar",
|
||||
"Previous": "Anterior",
|
||||
"Next": "Próximo",
|
||||
"Play": "Play",
|
||||
"Stop": "Stop",
|
||||
"Layout": "Esquema",
|
||||
"More": "Más",
|
||||
"Image": "Imagen"
|
||||
}
|
||||
8
extensions/ohif-i18n/src/locales/es/Header.json
Normal file
8
extensions/ohif-i18n/src/locales/es/Header.json
Normal 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"
|
||||
}
|
||||
3
extensions/ohif-i18n/src/locales/es/MX/Header.json
Normal file
3
extensions/ohif-i18n/src/locales/es/MX/Header.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"INVESTIGATIONAL USE ONLY": "SOLO USO DE INVESTIGACIÓN"
|
||||
}
|
||||
11
extensions/ohif-i18n/src/locales/es/MeasurementTable.json
Normal file
11
extensions/ohif-i18n/src/locales/es/MeasurementTable.json
Normal 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"
|
||||
}
|
||||
@ -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)"
|
||||
}
|
||||
5219
extensions/ohif-i18n/yarn.lock
Normal file
5219
extensions/ohif-i18n/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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/**'],
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@ -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}
|
||||
</>
|
||||
);
|
||||
|
||||
299
extensions/ohif-vtk-extension/src/commands.js
Normal file
299
extensions/ohif-vtk-extension/src/commands.js
Normal 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 };
|
||||
@ -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,
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
import loadLocales from './loadLocales';
|
||||
|
||||
import OHIFVTKExtension from './OHIFVTKExtension.js';
|
||||
|
||||
loadLocales();
|
||||
|
||||
export default OHIFVTKExtension;
|
||||
|
||||
8
extensions/ohif-vtk-extension/src/loadLocales.js
Normal file
8
extensions/ohif-vtk-extension/src/loadLocales.js
Normal file
@ -0,0 +1,8 @@
|
||||
import { addLocales } from '@ohif/i18n';
|
||||
|
||||
function loadLocales() {
|
||||
const context = require.context(`./locales`, true, /\.json$/);
|
||||
addLocales(context);
|
||||
}
|
||||
|
||||
export default loadLocales;
|
||||
@ -0,0 +1,3 @@
|
||||
{
|
||||
"Rotate": "Rotate"
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
{
|
||||
"Rotate": "Rotacionar"
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
61
extensions/ohif-vtk-extension/src/utils/setMPRLayout.js
Normal file
61
extensions/ohif-vtk-extension/src/utils/setMPRLayout.js
Normal 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
|
||||
);
|
||||
});
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
39
extensions/ohif-vtk-extension/src/utils/setViewportToVTK.js
Normal file
39
extensions/ohif-vtk-extension/src/utils/setViewportToVTK.js
Normal 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
|
||||
);
|
||||
});
|
||||
}
|
||||
108
extensions/ohif-vtk-extension/webpack.config.js
Normal file
108
extensions/ohif-vtk-extension/webpack.config.js
Normal 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
@ -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"
|
||||
|
||||
98
package.json
98
package.json
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,4 +19,55 @@ window.config = {
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
// Extensions should be able to suggest default values for these?
|
||||
// Or we can require that these be explicitly set
|
||||
hotkeys: [
|
||||
// ~ Global
|
||||
{
|
||||
commandName: 'incrementActiveViewport',
|
||||
label: 'Next Image Viewport',
|
||||
keys: ['right'],
|
||||
},
|
||||
{
|
||||
commandName: 'decrementActiveViewport',
|
||||
label: 'Previous Image Viewport',
|
||||
keys: ['left'],
|
||||
},
|
||||
// Supported Keys: https://craig.is/killing/mice
|
||||
// ~ Cornerstone Extension
|
||||
{ commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] },
|
||||
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
|
||||
{ commandName: 'invertViewport', label: 'Invert', keys: ['i'] },
|
||||
{
|
||||
commandName: 'flipViewportVertical',
|
||||
label: 'Flip Horizontally',
|
||||
keys: ['h'],
|
||||
},
|
||||
{
|
||||
commandName: 'flipViewportHorizontal',
|
||||
label: 'Flip Vertically',
|
||||
keys: ['v'],
|
||||
},
|
||||
{ commandName: 'scaleUpViewport', label: 'Zoom In', keys: ['+'] },
|
||||
{ commandName: 'scaleDownViewport', label: 'Zoom Out', keys: ['-'] },
|
||||
{ commandName: 'fitViewportToWindow', label: 'Zoom to Fit', keys: ['='] },
|
||||
{ commandName: 'resetViewport', label: 'Reset', keys: ['space'] },
|
||||
// clearAnnotations
|
||||
// nextImage
|
||||
// previousImage
|
||||
// firstImage
|
||||
// lastImage
|
||||
{
|
||||
commandName: 'nextViewportDisplaySet',
|
||||
label: 'Previous Series',
|
||||
keys: ['pagedown'],
|
||||
},
|
||||
{
|
||||
commandName: 'previousViewportDisplaySet',
|
||||
label: 'Next Series',
|
||||
keys: ['pageup'],
|
||||
},
|
||||
// ~ Cornerstone Tools
|
||||
{ commandName: 'setZoomTool', label: 'Zoom', keys: ['z'] },
|
||||
],
|
||||
};
|
||||
|
||||
@ -16,4 +16,53 @@ window.config = {
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
hotkeys: [
|
||||
// ~ Global
|
||||
{
|
||||
commandName: 'incrementActiveViewport',
|
||||
label: 'Next Image Viewport',
|
||||
keys: ['right'],
|
||||
},
|
||||
{
|
||||
commandName: 'decrementActiveViewport',
|
||||
label: 'Previous Image Viewport',
|
||||
keys: ['left'],
|
||||
},
|
||||
// Supported Keys: https://craig.is/killing/mice
|
||||
// ~ Cornerstone Extension
|
||||
{ commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] },
|
||||
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
|
||||
{ commandName: 'invertViewport', label: 'Invert', keys: ['i'] },
|
||||
{
|
||||
commandName: 'flipViewportVertical',
|
||||
label: 'Flip Horizontally',
|
||||
keys: ['h'],
|
||||
},
|
||||
{
|
||||
commandName: 'flipViewportHorizontal',
|
||||
label: 'Flip Vertically',
|
||||
keys: ['v'],
|
||||
},
|
||||
{ commandName: 'scaleUpViewport', label: 'Zoom In', keys: ['+'] },
|
||||
{ commandName: 'scaleDownViewport', label: 'Zoom Out', keys: ['-'] },
|
||||
{ commandName: 'fitViewportToWindow', label: 'Zoom to Fit', keys: ['='] },
|
||||
{ commandName: 'resetViewport', label: 'Reset', keys: ['space'] },
|
||||
// clearAnnotations
|
||||
// nextImage
|
||||
// previousImage
|
||||
// firstImage
|
||||
// lastImage
|
||||
{
|
||||
commandName: 'nextViewportDisplaySet',
|
||||
label: 'Previous Series',
|
||||
keys: ['pagedown'],
|
||||
},
|
||||
{
|
||||
commandName: 'previousViewportDisplaySet',
|
||||
label: 'Next Series',
|
||||
keys: ['pageup'],
|
||||
},
|
||||
// ~ Cornerstone Tools
|
||||
{ commandName: 'setZoomTool', label: 'Zoom', keys: ['z'] },
|
||||
],
|
||||
};
|
||||
|
||||
@ -29,12 +29,6 @@
|
||||
href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700|Sanchez&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://use.fontawesome.com/releases/v5.6.3/css/all.css"
|
||||
integrity="sha384-UHRtZLI+pbxtHCWp1t77Bi1L4ZtiqrqD80Kn4Z8NTSRyMA2Fd33n5dQ8lWUE00s/"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
@ -9,4 +9,5 @@ echo 'PUBLISHING'
|
||||
./node_modules/.bin/gh-pages \
|
||||
--silent \
|
||||
--repo https://$GITHUB_TOKEN@github.com/OHIF/Viewers.git \
|
||||
--message 'Autogenerated Message: [ci skip]' \
|
||||
--dist docs/latest/_book
|
||||
|
||||
@ -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(),
|
||||
|
||||
99
src/App.js
99
src/App.js
@ -1,8 +1,13 @@
|
||||
import './config';
|
||||
|
||||
import { OidcProvider, reducer as oidcReducer } from 'redux-oidc';
|
||||
import {
|
||||
CommandsManager,
|
||||
HotkeysManager,
|
||||
extensions,
|
||||
redux,
|
||||
utils,
|
||||
} from 'ohif-core';
|
||||
import React, { Component } from 'react';
|
||||
import { combineReducers, createStore } from 'redux';
|
||||
import {
|
||||
getDefaultToolbarButtons,
|
||||
getUserManagerForOpenIdConnectClient,
|
||||
@ -10,57 +15,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 };
|
||||
|
||||
1
src/appCommands/README.md
Normal file
1
src/appCommands/README.md
Normal file
@ -0,0 +1 @@
|
||||
# Commands
|
||||
185
src/appCommands/cornerstone.js
Normal file
185
src/appCommands/cornerstone.js
Normal file
@ -0,0 +1,185 @@
|
||||
import cornerstone from 'cornerstone-core';
|
||||
import { redux } from 'ohif-core';
|
||||
import store from './../store/';
|
||||
|
||||
const { setToolActive } = redux.actions;
|
||||
|
||||
const actions = {
|
||||
rotateViewport: ({ viewports, rotation }) => {
|
||||
const enabledElement = _getActiveViewportEnabledElement(
|
||||
viewports.viewportSpecificData,
|
||||
viewports.activeViewportIndex
|
||||
);
|
||||
|
||||
if (enabledElement) {
|
||||
let viewport = cornerstone.getViewport(enabledElement);
|
||||
viewport.rotation += rotation;
|
||||
cornerstone.setViewport(enabledElement, viewport);
|
||||
}
|
||||
},
|
||||
flipViewportHorizontal: ({ viewports }) => {
|
||||
const enabledElement = _getActiveViewportEnabledElement(
|
||||
viewports.viewportSpecificData,
|
||||
viewports.activeViewportIndex
|
||||
);
|
||||
|
||||
if (enabledElement) {
|
||||
let viewport = cornerstone.getViewport(enabledElement);
|
||||
viewport.hflip = !viewport.hflip;
|
||||
cornerstone.setViewport(enabledElement, viewport);
|
||||
}
|
||||
},
|
||||
flipViewportVertical: ({ viewports }) => {
|
||||
const enabledElement = _getActiveViewportEnabledElement(
|
||||
viewports.viewportSpecificData,
|
||||
viewports.activeViewportIndex
|
||||
);
|
||||
|
||||
if (enabledElement) {
|
||||
let viewport = cornerstone.getViewport(enabledElement);
|
||||
viewport.vflip = !viewport.vflip;
|
||||
cornerstone.setViewport(enabledElement, viewport);
|
||||
}
|
||||
},
|
||||
scaleViewport: ({ viewports, direction }) => {
|
||||
const enabledElement = _getActiveViewportEnabledElement(
|
||||
viewports.viewportSpecificData,
|
||||
viewports.activeViewportIndex
|
||||
);
|
||||
const step = direction * 0.15;
|
||||
|
||||
if (enabledElement) {
|
||||
if (step) {
|
||||
let viewport = cornerstone.getViewport(enabledElement);
|
||||
viewport.scale += step;
|
||||
cornerstone.setViewport(enabledElement, viewport);
|
||||
} else {
|
||||
cornerstone.fitToWindow(enabledElement);
|
||||
}
|
||||
}
|
||||
},
|
||||
resetViewport: ({ viewports }) => {
|
||||
const enabledElement = _getActiveViewportEnabledElement(
|
||||
viewports.viewportSpecificData,
|
||||
viewports.activeViewportIndex
|
||||
);
|
||||
|
||||
if (enabledElement) {
|
||||
cornerstone.reset(enabledElement);
|
||||
}
|
||||
},
|
||||
invertViewport: ({ viewports }) => {
|
||||
const enabledElement = _getActiveViewportEnabledElement(
|
||||
viewports.viewportSpecificData,
|
||||
viewports.activeViewportIndex
|
||||
);
|
||||
|
||||
if (enabledElement) {
|
||||
let viewport = cornerstone.getViewport(enabledElement);
|
||||
viewport.invert = !viewport.invert;
|
||||
cornerstone.setViewport(enabledElement, viewport);
|
||||
}
|
||||
},
|
||||
// This has a weird hard dependency on the tools that are available as toolbar
|
||||
// buttons. You can see this in `ohif-core/src/redux/reducers/tools.js`
|
||||
// the `toolName` needs to equal the button's `command` property.
|
||||
// NOTE: It would be nice if `hotkeys` could set this, instead of creating a command per tool
|
||||
setCornerstoneToolActive: ({ toolName }) => {
|
||||
store.dispatch(setToolActive(toolName));
|
||||
},
|
||||
updateViewportDisplaySet: ({ direction }) => {
|
||||
// TODO
|
||||
console.warn('updateDisplaySet: ', direction);
|
||||
},
|
||||
clearAnnotations: () => {
|
||||
console.warn('clearAnnotations: not yet implemented');
|
||||
// const toolState =
|
||||
// cornerstoneTools.globalImageIdSpecificToolStateManager.toolState;
|
||||
// if (!toolState) return;
|
||||
// Object.keys(toolState).forEach(imageId => {
|
||||
// if (!cornerstoneImageId || cornerstoneImageId === imageId)
|
||||
// delete toolState[imageId];
|
||||
// });
|
||||
},
|
||||
};
|
||||
|
||||
const definitions = {
|
||||
rotateViewportCW: {
|
||||
commandFn: actions.rotateViewport,
|
||||
storeContexts: ['viewports'],
|
||||
options: { rotation: 90 },
|
||||
},
|
||||
rotateViewportCCW: {
|
||||
commandFn: actions.rotateViewport,
|
||||
storeContexts: ['viewports'],
|
||||
options: { rotation: -90 },
|
||||
},
|
||||
invertViewport: {
|
||||
commandFn: actions.invertViewport,
|
||||
storeContexts: ['viewports'],
|
||||
options: {},
|
||||
},
|
||||
flipViewportVertical: {
|
||||
commandFn: actions.flipViewportVertical,
|
||||
storeContexts: ['viewports'],
|
||||
options: {},
|
||||
},
|
||||
flipViewportHorizontal: {
|
||||
commandFn: actions.flipViewportHorizontal,
|
||||
storeContexts: ['viewports'],
|
||||
options: {},
|
||||
},
|
||||
scaleUpViewport: {
|
||||
keys: '',
|
||||
commandFn: actions.scaleViewport,
|
||||
storeContexts: ['viewports'],
|
||||
options: { direction: 1 },
|
||||
},
|
||||
scaleDownViewport: {
|
||||
keys: '',
|
||||
commandFn: actions.scaleViewport,
|
||||
storeContexts: ['viewports'],
|
||||
options: { direction: -1 },
|
||||
},
|
||||
fitViewportToWindow: {
|
||||
commandFn: actions.scaleViewport,
|
||||
storeContexts: ['viewports'],
|
||||
options: { direction: 0 },
|
||||
},
|
||||
resetViewport: {
|
||||
commandFn: actions.resetViewport,
|
||||
storeContexts: ['viewports'],
|
||||
options: {},
|
||||
},
|
||||
// TODO: Clear Annotations
|
||||
// TODO: Next/Previous image
|
||||
// TODO: First/Last image
|
||||
// Next/Previous series/DisplaySet
|
||||
nextViewportDisplaySet: {
|
||||
commandFn: actions.updateViewportDisplaySet,
|
||||
storeContexts: [],
|
||||
options: { direction: 1 },
|
||||
},
|
||||
previousViewportDisplaySet: {
|
||||
commandFn: actions.updateViewportDisplaySet,
|
||||
storeContexts: [],
|
||||
options: { direction: -1 },
|
||||
},
|
||||
// TOOLS
|
||||
setZoomTool: {
|
||||
commandFn: actions.setCornerstoneToolActive,
|
||||
storeContexts: [],
|
||||
options: { toolName: 'Zoom' },
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Grabs `dom` reference for the enabledElement of
|
||||
* the active viewport
|
||||
*/
|
||||
function _getActiveViewportEnabledElement(viewports, activeIndex) {
|
||||
const activeViewport = viewports[activeIndex] || {};
|
||||
return activeViewport.dom;
|
||||
}
|
||||
|
||||
export default definitions;
|
||||
60
src/appCommands/index.js
Normal file
60
src/appCommands/index.js
Normal file
@ -0,0 +1,60 @@
|
||||
import cornerstoneCommandDefinitions from './cornerstone.js';
|
||||
import viewerCommandDefinitions from './viewer.js';
|
||||
|
||||
const CONTEXTS = {
|
||||
viewer: 'VIEWER',
|
||||
cornerstone: 'VIEWER::CORNERSTONE',
|
||||
};
|
||||
|
||||
/**
|
||||
* Register all commands.
|
||||
* TODO: Extensions should self-register their commands
|
||||
*/
|
||||
function init(commandsManager) {
|
||||
_registerViewerCommands(commandsManager);
|
||||
_registerCornerstoneCommands(commandsManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register all Viewer commands
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function _registerViewerCommands(commandsManager) {
|
||||
const commandContext = CONTEXTS.viewer;
|
||||
|
||||
commandsManager.createContext(commandContext);
|
||||
Object.keys(viewerCommandDefinitions).forEach(commandName => {
|
||||
const commandDefinition = viewerCommandDefinitions[commandName];
|
||||
|
||||
commandsManager.registerCommand(
|
||||
commandContext,
|
||||
commandName,
|
||||
commandDefinition
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register all Cornerstone commands
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function _registerCornerstoneCommands(commandsManager) {
|
||||
const commandContext = CONTEXTS.cornerstone;
|
||||
|
||||
commandsManager.createContext(commandContext);
|
||||
Object.keys(cornerstoneCommandDefinitions).forEach(commandName => {
|
||||
const commandDefinition = cornerstoneCommandDefinitions[commandName];
|
||||
|
||||
commandsManager.registerCommand(
|
||||
commandContext,
|
||||
commandName,
|
||||
commandDefinition
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
init,
|
||||
};
|
||||
36
src/appCommands/viewer.js
Normal file
36
src/appCommands/viewer.js
Normal file
@ -0,0 +1,36 @@
|
||||
import { redux } from 'ohif-core';
|
||||
import store from './../store';
|
||||
const { setViewportActive } = redux.actions;
|
||||
|
||||
const actions = {
|
||||
updateViewportDisplaySet: ({ direction }) => {
|
||||
// TODO
|
||||
console.warn('updateDisplaySet: ', direction);
|
||||
},
|
||||
updateActiveViewport: ({ viewports, direction }) => {
|
||||
const { viewportSpecificData, activeViewportIndex } = viewports;
|
||||
const maxIndex = Object.keys(viewportSpecificData).length - 1;
|
||||
|
||||
let newIndex = activeViewportIndex + direction;
|
||||
newIndex = newIndex > maxIndex ? 0 : newIndex;
|
||||
newIndex = newIndex < 0 ? maxIndex : newIndex;
|
||||
|
||||
store.dispatch(setViewportActive(newIndex));
|
||||
},
|
||||
};
|
||||
|
||||
const definitions = {
|
||||
// Next/Previous active viewport
|
||||
incrementActiveViewport: {
|
||||
commandFn: actions.updateActiveViewport,
|
||||
storeContexts: ['viewports'],
|
||||
options: { direction: 1 },
|
||||
},
|
||||
decrementActiveViewport: {
|
||||
commandFn: actions.updateActiveViewport,
|
||||
storeContexts: ['viewports'],
|
||||
options: { direction: -1 },
|
||||
},
|
||||
};
|
||||
|
||||
export default definitions;
|
||||
@ -1,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));
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
setLeftSidebarOpen,
|
||||
setRightSidebarOpen,
|
||||
} from './../store/layout/actions.js';
|
||||
|
||||
import ToolbarRow from './ToolbarRow';
|
||||
import { setLeftSidebarOpen, setRightSidebarOpen } from '../redux/actions.js';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
const defaultPlugin = 'cornerstone';
|
||||
|
||||
|
||||
@ -1,48 +0,0 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { UserPreferencesModal } from 'react-viewerbase';
|
||||
import OHIF from 'ohif-core';
|
||||
import { setUserPreferencesModalOpen } from '../redux/actions.js';
|
||||
import cloneDeep from 'lodash.clonedeep';
|
||||
|
||||
const { setUserPreferences } = OHIF.redux.actions;
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const contextName = window.store.getState().commandContext.context;
|
||||
return {
|
||||
isOpen: state.ui.userPreferencesModalOpen,
|
||||
windowLevelData: state.preferences[contextName]
|
||||
? state.preferences[contextName].windowLevelData
|
||||
: {},
|
||||
hotKeysData: state.preferences[contextName]
|
||||
? state.preferences[contextName].hotKeysData
|
||||
: {},
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
onCancel: () => {
|
||||
dispatch(setUserPreferencesModalOpen(false));
|
||||
},
|
||||
onSave: data => {
|
||||
const contextName = window.store.getState().commandContext.context;
|
||||
const preferences = cloneDeep(window.store.getState().preferences);
|
||||
preferences[contextName] = data;
|
||||
dispatch(setUserPreferences(preferences));
|
||||
dispatch(setUserPreferencesModalOpen(false));
|
||||
OHIF.hotkeysUtil.setHotkeys(data.hotKeysData);
|
||||
},
|
||||
onResetToDefaults: () => {
|
||||
dispatch(setUserPreferences());
|
||||
dispatch(setUserPreferencesModalOpen(false));
|
||||
OHIF.hotkeysUtil.setHotkeys();
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const ConnectedUserPreferencesModal = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(UserPreferencesModal);
|
||||
|
||||
export default ConnectedUserPreferencesModal;
|
||||
@ -1,12 +1,12 @@
|
||||
import { connect } from 'react-redux';
|
||||
import ViewerMain from './ViewerMain';
|
||||
import OHIF from 'ohif-core';
|
||||
import ViewerMain from './ViewerMain';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
const {
|
||||
setViewportSpecificData,
|
||||
clearViewportSpecificData,
|
||||
setToolActive,
|
||||
setActiveViewportSpecificData,
|
||||
// setToolActive,
|
||||
// setActiveViewportSpecificData,
|
||||
} = OHIF.redux.actions;
|
||||
|
||||
const mapStateToProps = state => {
|
||||
@ -16,6 +16,7 @@ const mapStateToProps = state => {
|
||||
activeViewportIndex,
|
||||
layout,
|
||||
viewportSpecificData,
|
||||
viewports: state.viewports,
|
||||
};
|
||||
};
|
||||
|
||||
@ -27,12 +28,12 @@ const mapDispatchToProps = dispatch => {
|
||||
clearViewportSpecificData: () => {
|
||||
dispatch(clearViewportSpecificData());
|
||||
},
|
||||
setToolActive: tool => {
|
||||
dispatch(setToolActive(tool));
|
||||
},
|
||||
setActiveViewportSpecificData: viewport => {
|
||||
dispatch(setActiveViewportSpecificData(viewport));
|
||||
},
|
||||
// setToolActive: tool => {
|
||||
// dispatch(setToolActive(tool));
|
||||
// },
|
||||
// setActiveViewportSpecificData: viewport => {
|
||||
// dispatch(setActiveViewportSpecificData(viewport));
|
||||
// },
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
import loglevel from 'loglevel';
|
||||
|
||||
const log = loglevel.getLogger('OHIFViewer');
|
||||
log.setLevel('info');
|
||||
|
||||
export default log;
|
||||
@ -1,22 +0,0 @@
|
||||
export const setLeftSidebarOpen = state => ({
|
||||
type: 'SET_LEFT_SIDEBAR_OPEN',
|
||||
state,
|
||||
});
|
||||
|
||||
export const setRightSidebarOpen = state => ({
|
||||
type: 'SET_RIGHT_SIDEBAR_OPEN',
|
||||
state,
|
||||
});
|
||||
|
||||
export const setUserPreferencesModalOpen = state => ({
|
||||
type: 'SET_USER_PREFERENCES_MODAL_OPEN',
|
||||
state,
|
||||
});
|
||||
|
||||
const actions = {
|
||||
setLeftSidebarOpen,
|
||||
setRightSidebarOpen,
|
||||
setUserPreferencesModalOpen,
|
||||
};
|
||||
|
||||
export default actions;
|
||||
25
src/store/index.js
Normal file
25
src/store/index.js
Normal file
@ -0,0 +1,25 @@
|
||||
import { combineReducers, createStore } from 'redux';
|
||||
|
||||
import layoutReducers from './layout/reducers.js';
|
||||
import { reducer as oidcReducer } from 'redux-oidc';
|
||||
import { redux } from 'ohif-core';
|
||||
|
||||
// Combine our ohif-core, ui, and oidc reducers
|
||||
// Set init data, using values found in localStorage
|
||||
const { reducers, localStorage } = redux;
|
||||
|
||||
reducers.ui = layoutReducers;
|
||||
reducers.oidc = oidcReducer;
|
||||
|
||||
const combined = combineReducers(reducers);
|
||||
const store = createStore(combined, localStorage.loadState());
|
||||
|
||||
// When the store's preferences change,
|
||||
// Update our cached preferences in localStorage
|
||||
store.subscribe(() => {
|
||||
localStorage.saveState({
|
||||
preferences: store.getState().preferences,
|
||||
});
|
||||
});
|
||||
|
||||
export default store;
|
||||
35
src/store/layout/actions.js
Normal file
35
src/store/layout/actions.js
Normal file
@ -0,0 +1,35 @@
|
||||
export const addActiveContext = state => ({
|
||||
type: 'ADD_ACTIVE_CONTEXT',
|
||||
state,
|
||||
});
|
||||
|
||||
export const removeActiveContext = state => ({
|
||||
type: 'REMOVE_ACTIVE_CONTEXT',
|
||||
state,
|
||||
});
|
||||
|
||||
export const clearActiveContexts = state => ({
|
||||
type: 'CLEAR_ACTIVE_CONTEXTS',
|
||||
state,
|
||||
});
|
||||
|
||||
export const setLeftSidebarOpen = state => ({
|
||||
type: 'SET_LEFT_SIDEBAR_OPEN',
|
||||
state,
|
||||
});
|
||||
|
||||
export const setRightSidebarOpen = state => ({
|
||||
type: 'SET_RIGHT_SIDEBAR_OPEN',
|
||||
state,
|
||||
});
|
||||
|
||||
const actions = {
|
||||
addActiveContext,
|
||||
removeActiveContext,
|
||||
clearActiveContexts,
|
||||
//
|
||||
setLeftSidebarOpen,
|
||||
setRightSidebarOpen,
|
||||
};
|
||||
|
||||
export default actions;
|
||||
@ -1,26 +1,40 @@
|
||||
const defaultState = {
|
||||
leftSidebarOpen: true,
|
||||
rightSidebarOpen: false,
|
||||
userPreferencesModalOpen: false,
|
||||
labelling: {},
|
||||
contextMenu: {},
|
||||
activeContexts: [],
|
||||
};
|
||||
|
||||
const ui = (state = defaultState, action) => {
|
||||
switch (action.type) {
|
||||
// ~ ACTIVE CONTEXTS
|
||||
// https://redux.js.org/recipes/structuring-reducers/immutable-update-patterns#inserting-and-removing-items-in-arrays
|
||||
case 'ADD_ACTIVE_CONTEXT': {
|
||||
const shallowCopy = Object.assign({}, state);
|
||||
shallowCopy.activeContexts = [...shallowCopy.activeContexts, action.item];
|
||||
return shallowCopy;
|
||||
}
|
||||
case 'REMOVE_ACTIVE_CONTEXT': {
|
||||
const shallowCopy = Object.assign({}, state);
|
||||
shallowCopy.activeContexts = shallowCopy.activeContexts.filter(
|
||||
item => item !== action.item
|
||||
);
|
||||
return shallowCopy;
|
||||
}
|
||||
case 'CLEAR_ACTIVE_CONTEXTS':
|
||||
return Object.assign({}, state, { activeContexts: [] });
|
||||
// ~ SIDEBAR
|
||||
case 'SET_LEFT_SIDEBAR_OPEN':
|
||||
return Object.assign({}, state, { leftSidebarOpen: action.state });
|
||||
case 'SET_RIGHT_SIDEBAR_OPEN':
|
||||
return Object.assign({}, state, { rightSidebarOpen: action.state });
|
||||
case 'SET_USER_PREFERENCES_MODAL_OPEN':
|
||||
return Object.assign({}, state, {
|
||||
userPreferencesModalOpen: action.state,
|
||||
});
|
||||
case 'SET_LABELLING_FLOW_DATA':
|
||||
case 'SET_LABELLING_FLOW_DATA': {
|
||||
const labelling = Object.assign({}, action.labellingFlowData);
|
||||
|
||||
return Object.assign({}, state, { labelling });
|
||||
case 'SET_TOOL_CONTEXT_MENU_DATA':
|
||||
}
|
||||
case 'SET_TOOL_CONTEXT_MENU_DATA': {
|
||||
const contextMenu = Object.assign({}, state.contextMenu);
|
||||
|
||||
contextMenu[action.viewportIndex] = Object.assign(
|
||||
@ -29,6 +43,7 @@ const ui = (state = defaultState, action) => {
|
||||
);
|
||||
|
||||
return Object.assign({}, state, { contextMenu });
|
||||
}
|
||||
case 'RESET_LABELLING_AND_CONTEXT_MENU':
|
||||
return Object.assign({}, state, {
|
||||
labelling: defaultState.labelling,
|
||||
@ -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',
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user