Compare commits
23
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a52d9f05d | ||
|
|
d5e21bb5bb | ||
|
|
04ffe3ec76 | ||
|
|
9cdf8b690b | ||
|
|
8d49b31d03 | ||
|
|
e4cfae7ad9 | ||
|
|
e53041f630 | ||
|
|
35c54eac56 | ||
|
|
e7be573328 | ||
|
|
8a7706b286 | ||
|
|
c217b8bbde | ||
|
|
74f3cbc4b2 | ||
|
|
d091cd604c | ||
|
|
3a4280163d | ||
|
|
2d75e01ea0 | ||
|
|
5c5a49486d | ||
|
|
a8a57132a7 | ||
|
|
92c8996ed4 | ||
|
|
f07dca5fa4 | ||
|
|
c99e0d8a23 | ||
|
|
84144bc3ef | ||
|
|
5cbdd0c616 | ||
|
|
fc015326c7 |
No files matched your search
@@ -315,6 +315,7 @@ workflows:
|
|||||||
working_directory: platform/viewer
|
working_directory: platform/viewer
|
||||||
build: npx cross-env QUICK_BUILD=true yarn run build:package
|
build: npx cross-env QUICK_BUILD=true yarn run build:package
|
||||||
start: yarn run test:e2e:serve
|
start: yarn run test:e2e:serve
|
||||||
|
spec: 'cypress/integration/common/**/*'
|
||||||
wait-on: 'http://localhost:3000'
|
wait-on: 'http://localhost:3000'
|
||||||
cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
|
cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
|
||||||
no-workspace: true # Don't persist workspace
|
no-workspace: true # Don't persist workspace
|
||||||
@@ -344,6 +345,7 @@ workflows:
|
|||||||
build: npx cross-env QUICK_BUILD=true yarn run build
|
build: npx cross-env QUICK_BUILD=true yarn run build
|
||||||
start: yarn run test:e2e:serve
|
start: yarn run test:e2e:serve
|
||||||
wait-on: 'http://localhost:3000'
|
wait-on: 'http://localhost:3000'
|
||||||
|
command: 'yarn run test:e2e:ci'
|
||||||
cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
|
cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
|
||||||
no-workspace: true # Don't persist workspace
|
no-workspace: true # Don't persist workspace
|
||||||
post-steps:
|
post-steps:
|
||||||
@@ -364,6 +366,7 @@ workflows:
|
|||||||
working_directory: platform/viewer
|
working_directory: platform/viewer
|
||||||
build: npx cross-env QUICK_BUILD=true yarn run build:package
|
build: npx cross-env QUICK_BUILD=true yarn run build:package
|
||||||
start: yarn run test:e2e:serve
|
start: yarn run test:e2e:serve
|
||||||
|
spec: 'cypress/integration/common/**/*'
|
||||||
wait-on: 'http://localhost:3000'
|
wait-on: 'http://localhost:3000'
|
||||||
cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
|
cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
|
||||||
no-workspace: true # Don't persist workspace
|
no-workspace: true # Don't persist workspace
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
PERCY_TOKEN=<your token here>
|
||||||
@@ -21,6 +21,7 @@ npm-debug.log
|
|||||||
package-lock.json
|
package-lock.json
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.env
|
||||||
|
|
||||||
# Common Example Data Directories
|
# Common Example Data Directories
|
||||||
sampledata/
|
sampledata/
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
|
// ~~ ENV
|
||||||
|
const dotenv = require('dotenv');
|
||||||
|
//
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
|
const PACKAGE = require('../platform/viewer/package.json');
|
||||||
// ~~ RULES
|
// ~~ RULES
|
||||||
const loadShadersRule = require('./rules/loadShaders.js');
|
const loadShadersRule = require('./rules/loadShaders.js');
|
||||||
const loadWebWorkersRule = require('./rules/loadWebWorkers.js');
|
const loadWebWorkersRule = require('./rules/loadWebWorkers.js');
|
||||||
@@ -9,6 +13,10 @@ const TerserJSPlugin = require('terser-webpack-plugin');
|
|||||||
// ~~ ENV VARS
|
// ~~ ENV VARS
|
||||||
const NODE_ENV = process.env.NODE_ENV;
|
const NODE_ENV = process.env.NODE_ENV;
|
||||||
const QUICK_BUILD = process.env.QUICK_BUILD;
|
const QUICK_BUILD = process.env.QUICK_BUILD;
|
||||||
|
const BUILD_NUM = process.env.CIRCLE_BUILD_NUM || '0';
|
||||||
|
|
||||||
|
//
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
module.exports = (env, argv, { SRC_DIR, DIST_DIR }) => {
|
module.exports = (env, argv, { SRC_DIR, DIST_DIR }) => {
|
||||||
if (!process.env.NODE_ENV) {
|
if (!process.env.NODE_ENV) {
|
||||||
@@ -68,6 +76,8 @@ module.exports = (env, argv, { SRC_DIR, DIST_DIR }) => {
|
|||||||
'process.env.DEBUG': JSON.stringify(process.env.DEBUG),
|
'process.env.DEBUG': JSON.stringify(process.env.DEBUG),
|
||||||
'process.env.APP_CONFIG': JSON.stringify(process.env.APP_CONFIG || ''),
|
'process.env.APP_CONFIG': JSON.stringify(process.env.APP_CONFIG || ''),
|
||||||
'process.env.PUBLIC_URL': JSON.stringify(process.env.PUBLIC_URL || '/'),
|
'process.env.PUBLIC_URL': JSON.stringify(process.env.PUBLIC_URL || '/'),
|
||||||
|
'process.env.VERSION_NUMBER': JSON.stringify(PACKAGE.version || ''),
|
||||||
|
'process.env.BUILD_NUM': JSON.stringify(BUILD_NUM),
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
// Fix: https://github.com/webpack-contrib/css-loader/issues/447#issuecomment-285598881
|
// Fix: https://github.com/webpack-contrib/css-loader/issues/447#issuecomment-285598881
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
[![Netlify Status][netlify-image]][netlify-url]
|
[![Netlify Status][netlify-image]][netlify-url]
|
||||||
[![CircleCI][circleci-image]][circleci-url]
|
[![CircleCI][circleci-image]][circleci-url]
|
||||||
[![codecov][codecov-image]][codecov-url]
|
[![codecov][codecov-image]][codecov-url]
|
||||||
|
[![This project is using Percy.io for visual regression testing.][percy-image]](percy-url)
|
||||||
[](#contributors)
|
[](#contributors)
|
||||||
<!-- prettier-ignore-end -->
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
@@ -288,6 +289,8 @@ MIT © [OHIF](https://github.com/OHIF)
|
|||||||
[docker-image-url]: https://hub.docker.com/r/ohif/viewer
|
[docker-image-url]: https://hub.docker.com/r/ohif/viewer
|
||||||
[license-image]: https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square
|
[license-image]: https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square
|
||||||
[license-url]: LICENSE
|
[license-url]: LICENSE
|
||||||
|
[percy-image]: https://percy.io/static/images/percy-badge.svg
|
||||||
|
[percy-url]: https://percy.io/Open-Health-Imaging-Foundation/OHIF-Viewer
|
||||||
<!-- Links -->
|
<!-- Links -->
|
||||||
[monorepo]: https://en.wikipedia.org/wiki/Monorepo
|
[monorepo]: https://en.wikipedia.org/wiki/Monorepo
|
||||||
[how-to-fork]: https://help.github.com/en/articles/fork-a-repo
|
[how-to-fork]: https://help.github.com/en/articles/fork-a-repo
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ If everything worked as expected, you should have a new `dist/` directory in the
|
|||||||
project's folder. It should roughly resemble the following:
|
project's folder. It should roughly resemble the following:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
dist/
|
<root>platform/viewer/dist/
|
||||||
├── app-config.js
|
├── app-config.js
|
||||||
├── app.bundle.js
|
├── app.bundle.js
|
||||||
├── app.css
|
├── app.css
|
||||||
|
|||||||
@@ -3,6 +3,54 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [0.52.13](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.12...@ohif/extension-vtk@0.52.13) (2019-10-18)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.52.12](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.11...@ohif/extension-vtk@0.52.12) (2019-10-18)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.52.11](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.10...@ohif/extension-vtk@0.52.11) (2019-10-15)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.52.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.9...@ohif/extension-vtk@0.52.10) (2019-10-15)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.52.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.8...@ohif/extension-vtk@0.52.9) (2019-10-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.52.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.7...@ohif/extension-vtk@0.52.8) (2019-10-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [0.52.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.6...@ohif/extension-vtk@0.52.7) (2019-10-11)
|
## [0.52.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.6...@ohif/extension-vtk@0.52.7) (2019-10-11)
|
||||||
|
|
||||||
**Note:** Version bump only for package @ohif/extension-vtk
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/extension-vtk",
|
"name": "@ohif/extension-vtk",
|
||||||
"version": "0.52.7",
|
"version": "0.52.13",
|
||||||
"description": "OHIF extension for VTK.js",
|
"description": "OHIF extension for VTK.js",
|
||||||
"author": "OHIF",
|
"author": "OHIF",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -48,11 +48,11 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.5.5",
|
"@babel/runtime": "^7.5.5",
|
||||||
"react-vtkjs-viewport": "^0.1.6"
|
"react-vtkjs-viewport": "^0.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ohif/core": "^1.3.1",
|
"@ohif/core": "^1.4.0",
|
||||||
"@ohif/ui": "^0.53.4",
|
"@ohif/ui": "^0.55.0",
|
||||||
"cornerstone-tools": "^4.0.9",
|
"cornerstone-tools": "^4.0.9",
|
||||||
"cornerstone-wado-image-loader": "^3.0.0",
|
"cornerstone-wado-image-loader": "^3.0.0",
|
||||||
"dcmjs": "^0.6.1",
|
"dcmjs": "^0.6.1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import OHIF from "@ohif/core";
|
import OHIF from '@ohif/core';
|
||||||
import { View2D } from "react-vtkjs-viewport";
|
import { View2D } from 'react-vtkjs-viewport';
|
||||||
import { connect } from "react-redux";
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
const { setViewportActive, setViewportSpecificData } = OHIF.redux.actions;
|
const { setViewportActive, setViewportSpecificData } = OHIF.redux.actions;
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
// Hopefully this doesn't break anything under the hood for this one
|
// Hopefully this doesn't break anything under the hood for this one
|
||||||
// activeTool: activeButton && activeButton.command,
|
// activeTool: activeButton && activeButton.command,
|
||||||
...dataFromStore,
|
...dataFromStore,
|
||||||
enableStackPrefetch: isActive
|
enableStackPrefetch: isActive,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ const mapDispatchToProps = (dispatch, ownProps) => {
|
|||||||
|
|
||||||
setViewportSpecificData: data => {
|
setViewportSpecificData: data => {
|
||||||
dispatch(setViewportSpecificData(viewportIndex, data));
|
dispatch(setViewportSpecificData(viewportIndex, data));
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -63,10 +63,10 @@ const mergeProps = (propsFromState, propsFromDispatch, ownProps) => {
|
|||||||
// Store the API details for later
|
// Store the API details for later
|
||||||
//setViewportSpecificData({ vtkApi: api });
|
//setViewportSpecificData({ vtkApi: api });
|
||||||
|
|
||||||
if (afterCreation && typeof afterCreation === "function") {
|
if (afterCreation && typeof afterCreation === 'function') {
|
||||||
afterCreation(api);
|
afterCreation(api);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
return props;
|
return props;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,15 +3,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.loadingIndicator {
|
.loadingIndicator {
|
||||||
background-color: rgba(0, 0, 0, 0.75);
|
background-color: rgba(0, 0, 0, 0);
|
||||||
font-size: 18px;
|
font-size: 8px;
|
||||||
height: 100%;
|
height: 20%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|
||||||
/* Necessary for click-through to cornerstone element below */
|
/* Necessary for click-through to vtkjs element below */
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import './LoadingIndicator.css';
|
import './LoadingIndicator.css';
|
||||||
|
|
||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
|
import { withTranslation } from 'react-i18next';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
class LoadingIndicator extends PureComponent {
|
class LoadingIndicator extends PureComponent {
|
||||||
@@ -35,7 +35,8 @@ class LoadingIndicator extends PureComponent {
|
|||||||
<div className="imageViewerLoadingIndicator loadingIndicator">
|
<div className="imageViewerLoadingIndicator loadingIndicator">
|
||||||
<div className="indicatorContents">
|
<div className="indicatorContents">
|
||||||
<p>
|
<p>
|
||||||
Loading... <i className="fa fa-spin fa-circle-o-notch fa-fw" />{' '}
|
{this.props.t('Reformatting')}...
|
||||||
|
<i className="fa fa-spin fa-circle-o-notch fa-fw" />
|
||||||
{percComplete}
|
{percComplete}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -46,4 +47,4 @@ class LoadingIndicator extends PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LoadingIndicator;
|
export default withTranslation('Common')(LoadingIndicator);
|
||||||
@@ -11,7 +11,6 @@ import vtkDataArray from 'vtk.js/Sources/Common/Core/DataArray';
|
|||||||
import vtkImageData from 'vtk.js/Sources/Common/DataModel/ImageData';
|
import vtkImageData from 'vtk.js/Sources/Common/DataModel/ImageData';
|
||||||
import vtkVolume from 'vtk.js/Sources/Rendering/Core/Volume';
|
import vtkVolume from 'vtk.js/Sources/Rendering/Core/Volume';
|
||||||
import vtkVolumeMapper from 'vtk.js/Sources/Rendering/Core/VolumeMapper';
|
import vtkVolumeMapper from 'vtk.js/Sources/Rendering/Core/VolumeMapper';
|
||||||
import vtkViewportSubscriptionManager from './utils/vtkViewportSubscriptionManager.js';
|
|
||||||
|
|
||||||
const { StackManager } = OHIF.utils;
|
const { StackManager } = OHIF.utils;
|
||||||
|
|
||||||
@@ -172,47 +171,45 @@ class OHIFVTKViewport extends Component {
|
|||||||
default:
|
default:
|
||||||
imageDataObject = getImageData(stack.imageIds, displaySetInstanceUid);
|
imageDataObject = getImageData(stack.imageIds, displaySetInstanceUid);
|
||||||
|
|
||||||
const loadImageDataPromise = loadImageData(imageDataObject);
|
return imageDataObject;
|
||||||
|
|
||||||
return loadImageDataPromise.then(() => {
|
|
||||||
return {
|
|
||||||
data: imageDataObject.vtkImageData,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
getOrCreateVolume(data, displaySetInstanceUid) {
|
getOrCreateVolume(imageDataObject, displaySetInstanceUid) {
|
||||||
if (volumeCache[displaySetInstanceUid]) {
|
if (volumeCache[displaySetInstanceUid]) {
|
||||||
return volumeCache[displaySetInstanceUid];
|
return volumeCache[displaySetInstanceUid];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { vtkImageData, imageMetaData0 } = imageDataObject;
|
||||||
|
const { windowWidth, windowCenter, modality } = imageMetaData0;
|
||||||
|
|
||||||
|
let lower;
|
||||||
|
let upper;
|
||||||
|
|
||||||
|
if (modality === 'PT') {
|
||||||
|
// For PET just set the range to 0-5 SUV
|
||||||
|
lower = 0;
|
||||||
|
upper = 5;
|
||||||
|
} else {
|
||||||
|
lower = windowCenter - windowWidth / 2.0;
|
||||||
|
upper = windowCenter + windowWidth / 2.0;
|
||||||
|
}
|
||||||
|
|
||||||
const volumeActor = vtkVolume.newInstance();
|
const volumeActor = vtkVolume.newInstance();
|
||||||
const volumeMapper = vtkVolumeMapper.newInstance();
|
const volumeMapper = vtkVolumeMapper.newInstance();
|
||||||
|
|
||||||
volumeActor.setMapper(volumeMapper);
|
volumeActor.setMapper(volumeMapper);
|
||||||
volumeMapper.setInputData(data);
|
volumeMapper.setInputData(vtkImageData);
|
||||||
|
|
||||||
const range = data
|
|
||||||
.getPointData()
|
|
||||||
.getScalars()
|
|
||||||
.getRange();
|
|
||||||
|
|
||||||
// TODO: For PET we might want to just set this to 0-5 SUV
|
|
||||||
volumeActor
|
volumeActor
|
||||||
.getProperty()
|
.getProperty()
|
||||||
.getRGBTransferFunction(0)
|
.getRGBTransferFunction(0)
|
||||||
.setRange(range[0], range[1]);
|
.setRange(lower, upper);
|
||||||
|
|
||||||
// TODO: Should look into implementing autoAdjustSampleDistance in vtk
|
const spacing = vtkImageData.getSpacing();
|
||||||
const sampleDistance =
|
// Set the sample distance to half the mean length of one side. This is where the divide by 6 comes from.
|
||||||
1.2 *
|
// https://github.com/Kitware/VTK/blob/6b559c65bb90614fb02eb6d1b9e3f0fca3fe4b0b/Rendering/VolumeOpenGL2/vtkSmartVolumeMapper.cxx#L344
|
||||||
Math.sqrt(
|
const sampleDistance = (spacing[0] + spacing[1] + spacing[2]) / 6;
|
||||||
data
|
|
||||||
.getSpacing()
|
|
||||||
.map(v => v * v)
|
|
||||||
.reduce((a, b) => a + b, 0)
|
|
||||||
);
|
|
||||||
|
|
||||||
volumeMapper.setSampleDistance(sampleDistance);
|
volumeMapper.setSampleDistance(sampleDistance);
|
||||||
|
|
||||||
@@ -221,7 +218,7 @@ class OHIFVTKViewport extends Component {
|
|||||||
return volumeActor;
|
return volumeActor;
|
||||||
}
|
}
|
||||||
|
|
||||||
async setStateFromProps() {
|
setStateFromProps() {
|
||||||
const { studies, displaySet } = this.props.viewportData;
|
const { studies, displaySet } = this.props.viewportData;
|
||||||
const {
|
const {
|
||||||
studyInstanceUid,
|
studyInstanceUid,
|
||||||
@@ -238,8 +235,7 @@ class OHIFVTKViewport extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sopClassUid = sopClassUids[0];
|
const sopClassUid = sopClassUids[0];
|
||||||
|
const imageDataObject = this.getViewportData(
|
||||||
let { data, labelmap } = await this.getViewportData(
|
|
||||||
studies,
|
studies,
|
||||||
studyInstanceUid,
|
studyInstanceUid,
|
||||||
displaySetInstanceUid,
|
displaySetInstanceUid,
|
||||||
@@ -248,29 +244,51 @@ class OHIFVTKViewport extends Component {
|
|||||||
frameIndex
|
frameIndex
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.imageDataObject = imageDataObject;
|
||||||
|
|
||||||
// TODO: Temporarily disabling this since it is not yet
|
// TODO: Temporarily disabling this since it is not yet
|
||||||
// being used and hurts performance significantly.
|
// being used and hurts performance significantly.
|
||||||
/*if (!labelmap) {
|
/*if (!labelmap) {
|
||||||
labelmap = createLabelMapImageData(data);
|
labelmap = createLabelMapImageData(data);
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
const volumeActor = this.getOrCreateVolume(data, displaySetInstanceUid);
|
const volumeActor = this.getOrCreateVolume(
|
||||||
|
imageDataObject,
|
||||||
|
displaySetInstanceUid
|
||||||
|
);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
volumes: [volumeActor],
|
paintFilterBackgroundImageData: imageDataObject.vtkImageData,
|
||||||
paintFilterBackgroundImageData: data,
|
paintFilterLabelMapImageData: null, // TODO
|
||||||
paintFilterLabelMapImageData: labelmap,
|
percentComplete: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
paintFilterBackgroundImageData: imageDataObject.vtkImageData,
|
||||||
|
paintFilterLabelMapImageData: null, // TODO
|
||||||
|
percentComplete: 0,
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
this.loadProgressively(imageDataObject);
|
||||||
|
|
||||||
|
// TODO: There must be a better way to do this.
|
||||||
|
// We do this so that if all the data is available the react-vtkjs-viewport
|
||||||
|
// Will render _something_ before the volumes are set and the volume
|
||||||
|
// Construction that happens in react-vtkjs-viewport locks up the CPU.
|
||||||
|
setTimeout(() => {
|
||||||
|
this.setState({
|
||||||
|
volumes: [volumeActor],
|
||||||
|
});
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.setStateFromProps();
|
this.setStateFromProps();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
vtkViewportSubscriptionManager.unsubscribe(this.props.viewportIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
const { studies, displaySet } = this.props.viewportData;
|
const { studies, displaySet } = this.props.viewportData;
|
||||||
const prevDisplaySet = prevProps.viewportData.displaySet;
|
const prevDisplaySet = prevProps.viewportData.displaySet;
|
||||||
@@ -285,6 +303,37 @@ class OHIFVTKViewport extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadProgressively(imageDataObject) {
|
||||||
|
loadImageData(imageDataObject);
|
||||||
|
|
||||||
|
const { isLoading, insertPixelDataPromises } = imageDataObject;
|
||||||
|
|
||||||
|
const numberOfFrames = insertPixelDataPromises.length;
|
||||||
|
|
||||||
|
if (!isLoading) {
|
||||||
|
this.setState({ isLoaded: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
insertPixelDataPromises.forEach(promise => {
|
||||||
|
promise.then(numberProcessed => {
|
||||||
|
const percentComplete = Math.floor(
|
||||||
|
(numberProcessed * 100) / numberOfFrames
|
||||||
|
);
|
||||||
|
|
||||||
|
if (percentComplete !== this.state.percentComplete) {
|
||||||
|
this.setState({
|
||||||
|
percentComplete,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Promise.all(insertPixelDataPromises).then(() => {
|
||||||
|
this.setState({ isLoaded: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let childrenWithProps = null;
|
let childrenWithProps = null;
|
||||||
|
|
||||||
@@ -302,21 +351,23 @@ class OHIFVTKViewport extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{this.state.volumes ? (
|
<div style={style}>
|
||||||
<ConnectedVTKViewport
|
{!this.state.isLoaded && (
|
||||||
volumes={this.state.volumes}
|
<LoadingIndicator percentComplete={this.state.percentComplete} />
|
||||||
paintFilterLabelMapImageData={
|
)}
|
||||||
this.state.paintFilterLabelMapImageData
|
{this.state.volumes && (
|
||||||
}
|
<ConnectedVTKViewport
|
||||||
paintFilterBackgroundImageData={
|
volumes={this.state.volumes}
|
||||||
this.state.paintFilterBackgroundImageData
|
paintFilterLabelMapImageData={
|
||||||
}
|
this.state.paintFilterLabelMapImageData
|
||||||
viewportIndex={this.props.viewportIndex}
|
}
|
||||||
/>
|
paintFilterBackgroundImageData={
|
||||||
) : (
|
this.state.paintFilterBackgroundImageData
|
||||||
<div style={style}>
|
}
|
||||||
<LoadingIndicator />
|
viewportIndex={this.props.viewportIndex}
|
||||||
</div>
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
{childrenWithProps}
|
{childrenWithProps}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -4,66 +4,16 @@ import {
|
|||||||
vtkInteractorStyleMPRSlice,
|
vtkInteractorStyleMPRSlice,
|
||||||
vtkInteractorStyleMPRRotate,
|
vtkInteractorStyleMPRRotate,
|
||||||
vtkSVGCrosshairsWidget,
|
vtkSVGCrosshairsWidget,
|
||||||
vtkSVGWidgetManager,
|
|
||||||
} from 'react-vtkjs-viewport';
|
} from 'react-vtkjs-viewport';
|
||||||
|
|
||||||
import setMPRLayout from './utils/setMPRLayout.js';
|
import setMPRLayout from './utils/setMPRLayout.js';
|
||||||
import setViewportToVTK from './utils/setViewportToVTK.js';
|
import setViewportToVTK from './utils/setViewportToVTK.js';
|
||||||
import vtkViewportSubscriptionManager from './utils/vtkViewportSubscriptionManager.js';
|
|
||||||
import vtkCoordinate from 'vtk.js/Sources/Rendering/Core/Coordinate';
|
|
||||||
import vtkMath from 'vtk.js/Sources/Common/Core/Math';
|
|
||||||
import vtkMatrixBuilder from 'vtk.js/Sources/Common/Core/MatrixBuilder';
|
|
||||||
import Constants from 'vtk.js/Sources/Rendering/Core/VolumeMapper/Constants.js';
|
import Constants from 'vtk.js/Sources/Rendering/Core/VolumeMapper/Constants.js';
|
||||||
|
|
||||||
const { BlendMode } = Constants;
|
const { BlendMode } = Constants;
|
||||||
|
|
||||||
// TODO: Put this somewhere else
|
// TODO: Put this somewhere else
|
||||||
let apis = {};
|
let apis = {};
|
||||||
let currentSlabThickness = 0.1;
|
|
||||||
let defaultSlabThickness = 0.1;
|
|
||||||
|
|
||||||
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) {
|
async function _getActiveViewportVTKApi(viewports) {
|
||||||
const {
|
const {
|
||||||
@@ -105,8 +55,6 @@ async function _getActiveViewportVTKApi(viewports) {
|
|||||||
|
|
||||||
function _setView(api, sliceNormal, viewUp) {
|
function _setView(api, sliceNormal, viewUp) {
|
||||||
const renderWindow = api.genericRenderWindow.getRenderWindow();
|
const renderWindow = api.genericRenderWindow.getRenderWindow();
|
||||||
const renderer = api.genericRenderWindow.getRenderer();
|
|
||||||
const camera = renderer.getActiveCamera();
|
|
||||||
const istyle = renderWindow.getInteractor().getInteractorStyle();
|
const istyle = renderWindow.getInteractor().getInteractorStyle();
|
||||||
istyle.setSliceNormal(...sliceNormal);
|
istyle.setSliceNormal(...sliceNormal);
|
||||||
istyle.setViewUp(...viewUp);
|
istyle.setViewUp(...viewUp);
|
||||||
@@ -114,41 +62,6 @@ function _setView(api, sliceNormal, viewUp) {
|
|||||||
renderWindow.render();
|
renderWindow.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
function switchMPRInteractors(api, istyle) {
|
|
||||||
const renderWindow = api.genericRenderWindow.getRenderWindow();
|
|
||||||
const renderer = api.genericRenderWindow.getRenderer();
|
|
||||||
const camera = renderer.getActiveCamera();
|
|
||||||
const currentIStyle = renderWindow.getInteractor().getInteractorStyle();
|
|
||||||
|
|
||||||
let currentViewport;
|
|
||||||
if (currentIStyle.getViewport && istyle.getViewport) {
|
|
||||||
currentViewport = currentIStyle.getViewport();
|
|
||||||
}
|
|
||||||
|
|
||||||
let currentSlabThickness;
|
|
||||||
if (currentIStyle.getSlabThickness && istyle.getSlabThickness) {
|
|
||||||
currentSlabThickness = currentIStyle.getSlabThickness();
|
|
||||||
defaultSlabThickness = currentSlabThickness;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderWindow.getInteractor().setInteractorStyle(istyle);
|
|
||||||
|
|
||||||
// TODO: Not sure why this is required the second time this function is called
|
|
||||||
istyle.setInteractor(renderWindow.getInteractor());
|
|
||||||
|
|
||||||
if (currentViewport) {
|
|
||||||
istyle.setViewport(currentViewport);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (istyle.getVolumeMapper() !== api.volumes[0]) {
|
|
||||||
if (currentSlabThickness) {
|
|
||||||
istyle.setSlabThickness(currentSlabThickness);
|
|
||||||
}
|
|
||||||
|
|
||||||
istyle.setVolumeMapper(api.volumes[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
axial: async ({ viewports }) => {
|
axial: async ({ viewports }) => {
|
||||||
const api = await _getActiveViewportVTKApi(viewports);
|
const api = await _getActiveViewportVTKApi(viewports);
|
||||||
@@ -175,52 +88,48 @@ const actions = {
|
|||||||
apis.forEach(api => {
|
apis.forEach(api => {
|
||||||
const istyle = vtkInteractorStyleMPRRotate.newInstance();
|
const istyle = vtkInteractorStyleMPRRotate.newInstance();
|
||||||
|
|
||||||
switchMPRInteractors(api, istyle);
|
api.setInteractorStyle({ istyle });
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
enableCrosshairsTool: () => {
|
enableCrosshairsTool: () => {
|
||||||
apis.forEach((api, index) => {
|
apis.forEach((api, apiIndex) => {
|
||||||
const istyle = vtkInteractorStyleMPRCrosshairs.newInstance();
|
const istyle = vtkInteractorStyleMPRCrosshairs.newInstance();
|
||||||
|
|
||||||
switchMPRInteractors(api, istyle);
|
api.setInteractorStyle({
|
||||||
|
istyle,
|
||||||
istyle.setCallback(getCrosshairCallbackForIndex(index));
|
configuration: { apis, apiIndex },
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
enableLevelTool: () => {
|
enableLevelTool: () => {
|
||||||
|
const callbacks = {
|
||||||
|
setOnLevelsChanged: ({ windowCenter, windowWidth }) => {
|
||||||
|
apis.forEach(api => {
|
||||||
|
const renderWindow = api.genericRenderWindow.getRenderWindow();
|
||||||
|
|
||||||
|
renderWindow.render();
|
||||||
|
|
||||||
|
api.updateVOI(windowWidth, windowCenter);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
apis.forEach(api => {
|
apis.forEach(api => {
|
||||||
const istyle = vtkInteractorStyleMPRWindowLevel.newInstance();
|
const istyle = vtkInteractorStyleMPRWindowLevel.newInstance();
|
||||||
|
|
||||||
switchMPRInteractors(api, istyle);
|
api.setInteractorStyle({ istyle, callbacks });
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
setSlabThickness: ({ slabThickness }) => {
|
setSlabThickness: ({ slabThickness }) => {
|
||||||
currentSlabThickness = slabThickness;
|
|
||||||
|
|
||||||
apis.forEach(api => {
|
apis.forEach(api => {
|
||||||
const renderWindow = api.genericRenderWindow.getRenderWindow();
|
api.setSlabThickness(slabThickness);
|
||||||
const istyle = renderWindow.getInteractor().getInteractorStyle();
|
|
||||||
|
|
||||||
if (istyle.setSlabThickness) {
|
|
||||||
istyle.setSlabThickness(currentSlabThickness);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderWindow.render();
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
changeSlabThickness: ({ change }) => {
|
changeSlabThickness: ({ change }) => {
|
||||||
currentSlabThickness += change;
|
|
||||||
currentSlabThickness = Math.max(currentSlabThickness, 0.1);
|
|
||||||
|
|
||||||
apis.forEach(api => {
|
apis.forEach(api => {
|
||||||
const renderWindow = api.genericRenderWindow.getRenderWindow();
|
const slabThickness = Math.max(api.getSlabThickness() + change, 0.1);
|
||||||
const istyle = renderWindow.getInteractor().getInteractorStyle();
|
|
||||||
|
|
||||||
if (istyle.setSlabThickness) {
|
api.setSlabThickness(slabThickness);
|
||||||
istyle.setSlabThickness(currentSlabThickness);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderWindow.render();
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
setBlendModeToComposite: () => {
|
setBlendModeToComposite: () => {
|
||||||
@@ -228,13 +137,15 @@ const actions = {
|
|||||||
const renderWindow = api.genericRenderWindow.getRenderWindow();
|
const renderWindow = api.genericRenderWindow.getRenderWindow();
|
||||||
const istyle = renderWindow.getInteractor().getInteractorStyle();
|
const istyle = renderWindow.getInteractor().getInteractorStyle();
|
||||||
|
|
||||||
|
const slabThickness = api.getSlabThickness();
|
||||||
|
|
||||||
const mapper = api.volumes[0].getMapper();
|
const mapper = api.volumes[0].getMapper();
|
||||||
if (mapper.setBlendModeToComposite) {
|
if (mapper.setBlendModeToComposite) {
|
||||||
mapper.setBlendModeToComposite();
|
mapper.setBlendModeToComposite();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (istyle.setSlabThickness) {
|
if (istyle.setSlabThickness) {
|
||||||
istyle.setSlabThickness(defaultSlabThickness);
|
istyle.setSlabThickness(slabThickness);
|
||||||
}
|
}
|
||||||
renderWindow.render();
|
renderWindow.render();
|
||||||
});
|
});
|
||||||
@@ -252,7 +163,6 @@ const actions = {
|
|||||||
setBlendMode: ({ blendMode }) => {
|
setBlendMode: ({ blendMode }) => {
|
||||||
apis.forEach(api => {
|
apis.forEach(api => {
|
||||||
const renderWindow = api.genericRenderWindow.getRenderWindow();
|
const renderWindow = api.genericRenderWindow.getRenderWindow();
|
||||||
const istyle = renderWindow.getInteractor().getInteractorStyle();
|
|
||||||
|
|
||||||
api.volumes[0].getMapper().setBlendMode(blendMode);
|
api.volumes[0].getMapper().setBlendMode(blendMode);
|
||||||
|
|
||||||
@@ -260,78 +170,60 @@ const actions = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
mpr2d: async ({ viewports }) => {
|
mpr2d: async ({ viewports }) => {
|
||||||
|
// TODO push a lot of this backdoor logic lower down to the library level.
|
||||||
const displaySet =
|
const displaySet =
|
||||||
viewports.viewportSpecificData[viewports.activeViewportIndex];
|
viewports.viewportSpecificData[viewports.activeViewportIndex];
|
||||||
|
|
||||||
let apiByViewport;
|
// Get current VOI if cornerstone viewport.
|
||||||
|
const cornerstoneVOI = getVOIFromCornerstoneViewport(displaySet);
|
||||||
|
|
||||||
|
const viewportProps = [
|
||||||
|
{
|
||||||
|
//Axial
|
||||||
|
orientation: {
|
||||||
|
sliceNormal: [0, 0, 1],
|
||||||
|
viewUp: [0, -1, 0],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Sagital
|
||||||
|
orientation: {
|
||||||
|
sliceNormal: [1, 0, 0],
|
||||||
|
viewUp: [0, 0, 1],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Coronal
|
||||||
|
orientation: {
|
||||||
|
sliceNormal: [0, 1, 0],
|
||||||
|
viewUp: [0, 0, 1],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
apiByViewport = await setMPRLayout(displaySet);
|
apis = await setMPRLayout(displaySet, viewportProps, 1, 3);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
apis = apiByViewport;
|
if (cornerstoneVOI) {
|
||||||
|
setVOI(cornerstoneVOI);
|
||||||
|
}
|
||||||
|
|
||||||
const rgbTransferFunction = apiByViewport[0].volumes[0]
|
// Add widgets and set default interactorStyle of each viewport.
|
||||||
.getProperty()
|
apis.forEach((api, apiIndex) => {
|
||||||
.getRGBTransferFunction(0);
|
api.addSVGWidget(
|
||||||
|
vtkSVGCrosshairsWidget.newInstance(),
|
||||||
const onModifiedSubscription = rgbTransferFunction.onModified(() => {
|
'crosshairsWidget'
|
||||||
apiByViewport.forEach(a => {
|
);
|
||||||
const renderWindow = a.genericRenderWindow.getRenderWindow();
|
|
||||||
|
|
||||||
renderWindow.render();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
vtkViewportSubscriptionManager.pushSubscription(0, onModifiedSubscription);
|
|
||||||
|
|
||||||
apiByViewport.forEach((api, index) => {
|
|
||||||
const renderWindow = api.genericRenderWindow.getRenderWindow();
|
|
||||||
const renderer = api.genericRenderWindow.getRenderer();
|
|
||||||
const camera = renderer.getActiveCamera();
|
|
||||||
|
|
||||||
const istyle = vtkInteractorStyleMPRCrosshairs.newInstance();
|
const istyle = vtkInteractorStyleMPRCrosshairs.newInstance();
|
||||||
renderWindow.getInteractor().setInteractorStyle(istyle);
|
|
||||||
|
|
||||||
istyle.setVolumeMapper(api.volumes[0]);
|
api.setInteractorStyle({
|
||||||
istyle.setCallback(getCrosshairCallbackForIndex(index));
|
istyle,
|
||||||
|
configuration: { apis, apiIndex },
|
||||||
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);
|
|
||||||
istyle.setViewUp(0, -1, 0);
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
// sagittal
|
|
||||||
istyle.setSliceNormal(1, 0, 0);
|
|
||||||
istyle.setViewUp(0, 0, 1);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
// Coronal
|
|
||||||
istyle.setSliceNormal(0, 1, 0);
|
|
||||||
istyle.setViewUp(0, 0, 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderWindow.render();
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -421,3 +313,41 @@ export default {
|
|||||||
definitions,
|
definitions,
|
||||||
defaultContext: 'ACTIVE_VIEWPORT::VTK',
|
defaultContext: 'ACTIVE_VIEWPORT::VTK',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getVOIFromCornerstoneViewport(displaySet) {
|
||||||
|
const cornerstoneElement = cornerstone.getEnabledElement(displaySet.dom);
|
||||||
|
|
||||||
|
if (cornerstoneElement) {
|
||||||
|
const imageId = cornerstoneElement.image.imageId;
|
||||||
|
|
||||||
|
const { modality } = cornerstone.metaData.get(
|
||||||
|
'generalSeriesModule',
|
||||||
|
imageId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (modality !== 'PT') {
|
||||||
|
const { windowWidth, windowCenter } = cornerstoneElement.viewport.voi;
|
||||||
|
|
||||||
|
return {
|
||||||
|
windowWidth,
|
||||||
|
windowCenter,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setVOI(voi) {
|
||||||
|
const { windowWidth, windowCenter } = voi;
|
||||||
|
const lower = windowCenter - windowWidth / 2.0;
|
||||||
|
const upper = windowCenter + windowWidth / 2.0;
|
||||||
|
|
||||||
|
const rgbTransferFunction = apis[0].volumes[0]
|
||||||
|
.getProperty()
|
||||||
|
.getRGBTransferFunction(0);
|
||||||
|
|
||||||
|
rgbTransferFunction.setRange(lower, upper);
|
||||||
|
|
||||||
|
apis.forEach(api => {
|
||||||
|
api.updateVOI(windowWidth, windowCenter);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,11 +1,23 @@
|
|||||||
import setLayoutAndViewportData from './setLayoutAndViewportData.js';
|
import setLayoutAndViewportData from './setLayoutAndViewportData.js';
|
||||||
|
|
||||||
export default function setMPRLayout(displaySet) {
|
export default function setMPRLayout(
|
||||||
|
displaySet,
|
||||||
|
viewportPropsArray,
|
||||||
|
numRows = 1,
|
||||||
|
numColumns = 1
|
||||||
|
) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const viewports = [];
|
const viewports = [];
|
||||||
const numRows = 1;
|
|
||||||
const numColumns = 3;
|
|
||||||
const numViewports = numRows * numColumns;
|
const numViewports = numRows * numColumns;
|
||||||
|
|
||||||
|
if (viewportPropsArray && viewportPropsArray.length !== numViewports) {
|
||||||
|
reject(
|
||||||
|
new Error(
|
||||||
|
'viewportProps is supplied but its length is not equal to numViewports'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const viewportSpecificData = {};
|
const viewportSpecificData = {};
|
||||||
|
|
||||||
for (let i = 0; i < numViewports; i++) {
|
for (let i = 0; i < numViewports; i++) {
|
||||||
@@ -17,8 +29,8 @@ export default function setMPRLayout(displaySet) {
|
|||||||
const apis = [];
|
const apis = [];
|
||||||
viewports.forEach((viewport, index) => {
|
viewports.forEach((viewport, index) => {
|
||||||
apis[index] = null;
|
apis[index] = null;
|
||||||
|
const viewportProps = viewportPropsArray[index];
|
||||||
viewports[index] = Object.assign({}, viewports[index], {
|
viewports[index] = Object.assign({}, viewports[index], {
|
||||||
// plugin: 'vtk',
|
|
||||||
vtk: {
|
vtk: {
|
||||||
mode: 'mpr', // TODO: not used
|
mode: 'mpr', // TODO: not used
|
||||||
afterCreation: api => {
|
afterCreation: api => {
|
||||||
@@ -28,6 +40,7 @@ export default function setMPRLayout(displaySet) {
|
|||||||
resolve(apis);
|
resolve(apis);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
...viewportProps,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
const subscriptions = [];
|
|
||||||
|
|
||||||
// This is pretty hacky right now, but it makes sure we don't keep adding subscriptions.
|
|
||||||
// TODO -> Nuke this and move it up a layer once we have more vigorous layout support.
|
|
||||||
|
|
||||||
const vtkViewportSubscriptionManager = {
|
|
||||||
subscriptions,
|
|
||||||
pushSubscription(viewportIndex, subscription) {
|
|
||||||
if (!Array.isArray(subscriptions[viewportIndex])) {
|
|
||||||
subscriptions[viewportIndex] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
subscriptions[viewportIndex].push(subscription);
|
|
||||||
},
|
|
||||||
unsubscribe(viewportIndex) {
|
|
||||||
if (!subscriptions[viewportIndex]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (subscriptions[viewportIndex].length) {
|
|
||||||
subscriptions[viewportIndex].pop().unsubscribe();
|
|
||||||
}
|
|
||||||
|
|
||||||
subscriptions[viewportIndex] = null;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default vtkViewportSubscriptionManager;
|
|
||||||
@@ -26,6 +26,7 @@
|
|||||||
"test:unit": "jest --collectCoverage",
|
"test:unit": "jest --collectCoverage",
|
||||||
"test:unit:ci": "lerna run test:unit:ci --parallel --stream",
|
"test:unit:ci": "lerna run test:unit:ci --parallel --stream",
|
||||||
"test:e2e": "lerna run test:e2e --stream",
|
"test:e2e": "lerna run test:e2e --stream",
|
||||||
|
"test:e2e:ci": "lerna run test:e2e:ci --stream",
|
||||||
"test:e2e:dist": "lerna run test:e2e:dist --stream",
|
"test:e2e:dist": "lerna run test:e2e:dist --stream",
|
||||||
"test:e2e:serve": "lerna run test:e2e:serve --stream",
|
"test:e2e:serve": "lerna run test:e2e:serve --stream",
|
||||||
"see-changed": "lerna changed",
|
"see-changed": "lerna changed",
|
||||||
@@ -49,6 +50,7 @@
|
|||||||
"@babel/plugin-transform-runtime": "^7.5.0",
|
"@babel/plugin-transform-runtime": "^7.5.0",
|
||||||
"@babel/preset-env": "^7.5.0",
|
"@babel/preset-env": "^7.5.0",
|
||||||
"@babel/preset-react": "^7.0.0",
|
"@babel/preset-react": "^7.0.0",
|
||||||
|
"@percy/cypress": "^2.2.0",
|
||||||
"babel-eslint": "9.x",
|
"babel-eslint": "9.x",
|
||||||
"babel-loader": "^8.0.6",
|
"babel-loader": "^8.0.6",
|
||||||
"babel-plugin-inline-react-svg": "1.1.0",
|
"babel-plugin-inline-react-svg": "1.1.0",
|
||||||
@@ -57,6 +59,7 @@
|
|||||||
"copy-webpack-plugin": "^5.0.3",
|
"copy-webpack-plugin": "^5.0.3",
|
||||||
"cross-env": "^5.2.0",
|
"cross-env": "^5.2.0",
|
||||||
"css-loader": "^3.2.0",
|
"css-loader": "^3.2.0",
|
||||||
|
"dotenv": "^8.1.0",
|
||||||
"eslint": "5.16.0",
|
"eslint": "5.16.0",
|
||||||
"eslint-config-react-app": "^4.0.1",
|
"eslint-config-react-app": "^4.0.1",
|
||||||
"eslint-plugin-flowtype": "2.x",
|
"eslint-plugin-flowtype": "2.x",
|
||||||
|
|||||||
@@ -3,6 +3,28 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.4.0](https://github.com/OHIF/Viewers/compare/@ohif/core@1.3.2...@ohif/core@1.4.0) (2019-10-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 🎸 Only allow reconstruction of datasets that make sense ([#1010](https://github.com/OHIF/Viewers/issues/1010)) ([2d75e01](https://github.com/OHIF/Viewers/commit/2d75e01)), closes [#561](https://github.com/OHIF/Viewers/issues/561)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.3.2](https://github.com/OHIF/Viewers/compare/@ohif/core@1.3.1...@ohif/core@1.3.2) (2019-10-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Return display sets in StudyMetadata._createDisplaySetsForSeries ([#1042](https://github.com/OHIF/Viewers/issues/1042)) ([fc01532](https://github.com/OHIF/Viewers/commit/fc01532))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [1.3.1](https://github.com/OHIF/Viewers/compare/@ohif/core@1.3.0...@ohif/core@1.3.1) (2019-10-11)
|
## [1.3.1](https://github.com/OHIF/Viewers/compare/@ohif/core@1.3.0...@ohif/core@1.3.1) (2019-10-11)
|
||||||
|
|
||||||
**Note:** Version bump only for package @ohif/core
|
**Note:** Version bump only for package @ohif/core
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/core",
|
"name": "@ohif/core",
|
||||||
"version": "1.3.1",
|
"version": "1.4.0",
|
||||||
"description": "Generic business logic for web-based medical imaging applications",
|
"description": "Generic business logic for web-based medical imaging applications",
|
||||||
"author": "OHIF Core Team",
|
"author": "OHIF Core Team",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { SeriesMetadata } from './SeriesMetadata';
|
|||||||
import { api } from 'dicomweb-client';
|
import { api } from 'dicomweb-client';
|
||||||
// - createStacks
|
// - createStacks
|
||||||
import { isImage } from '../../utils/isImage';
|
import { isImage } from '../../utils/isImage';
|
||||||
|
import isDisplaySetReconstructable from '../../utils/isDisplaySetReconstructable';
|
||||||
import isLowPriorityModality from '../../utils/isLowPriorityModality';
|
import isLowPriorityModality from '../../utils/isLowPriorityModality';
|
||||||
|
|
||||||
export class StudyMetadata extends Metadata {
|
export class StudyMetadata extends Metadata {
|
||||||
@@ -121,7 +122,7 @@ export class StudyMetadata extends Metadata {
|
|||||||
if (displaySet) {
|
if (displaySet) {
|
||||||
displaySet.sopClassModule = true;
|
displaySet.sopClassModule = true;
|
||||||
displaySets.push(displaySet);
|
displaySets.push(displaySet);
|
||||||
return;
|
return displaySets;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -620,6 +621,16 @@ const makeDisplaySet = (series, instances) => {
|
|||||||
imageSet.getImage(0).getRawValue('x00200013')
|
imageSet.getImage(0).getRawValue('x00200013')
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isReconstructable = isDisplaySetReconstructable(series, instances);
|
||||||
|
|
||||||
|
imageSet.isReconstructable = isReconstructable.value;
|
||||||
|
|
||||||
|
if (isReconstructable.missingFrames) {
|
||||||
|
// TODO -> This is currently unused, but may be used for reconstructing
|
||||||
|
// Volumes with gaps later on.
|
||||||
|
imageSet.missingFrames = isReconstructable.missingFrames;
|
||||||
|
}
|
||||||
|
|
||||||
return imageSet;
|
return imageSet;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,157 @@
|
|||||||
|
/**
|
||||||
|
* Checks if a series is reconstructable to a 3D volume.
|
||||||
|
*
|
||||||
|
* @param {Object} series The `OHIFSeriesMetadata` object.
|
||||||
|
* @param {Object[]} instances The `OHIFInstanceMetadata` object
|
||||||
|
*/
|
||||||
|
export default function isDisplaySetReconstructable(series, instances) {
|
||||||
|
// Can't reconstruct if we only have one image.
|
||||||
|
|
||||||
|
const modality = series._data.modality; // TODO -> Is there a better way to get this?
|
||||||
|
const isMultiframe = instances[0].getRawValue('x00280008') > 1;
|
||||||
|
|
||||||
|
if (!constructableModalities.includes(modality)) {
|
||||||
|
return { value: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isMultiframe && instances.length === 1) {
|
||||||
|
return { values: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isMultiframe) {
|
||||||
|
return processMultiframe(instances[0]);
|
||||||
|
} else {
|
||||||
|
return processSingleframe(instances);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function processMultiframe(instance) {
|
||||||
|
//TODO: deal with multriframe checks! return true for now.
|
||||||
|
return { value: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
function processSingleframe(instances) {
|
||||||
|
const firstImage = instances[0];
|
||||||
|
const firstImageRows = firstImage.getTagValue('x00280010');
|
||||||
|
const firstImageColumns = firstImage.getTagValue('x00280011');
|
||||||
|
const firstImageSamplesPerPixel = firstImage.getTagValue('x00280002');
|
||||||
|
// Note: No need to unpack iop, can compare string form.
|
||||||
|
const firstImageOrientationPatient = firstImage.getTagValue('x00200037');
|
||||||
|
|
||||||
|
// Can't reconstruct if we:
|
||||||
|
// -- Have a different dimensions within a displaySet.
|
||||||
|
// -- Have a different number of components within a displaySet.
|
||||||
|
// -- Have different orientations within a displaySet.
|
||||||
|
for (let i = 1; i < instances.length; i++) {
|
||||||
|
const instance = instances[i];
|
||||||
|
const rows = instance.getTagValue('x00280010');
|
||||||
|
const columns = instance.getTagValue('x00280011');
|
||||||
|
const samplesPerPixel = instance.getTagValue('x00280002');
|
||||||
|
const imageOrientationPatient = instance.getTagValue('x00200037');
|
||||||
|
|
||||||
|
if (
|
||||||
|
rows !== firstImageRows ||
|
||||||
|
columns !== firstImageColumns ||
|
||||||
|
samplesPerPixel !== firstImageSamplesPerPixel ||
|
||||||
|
imageOrientationPatient !== firstImageOrientationPatient
|
||||||
|
) {
|
||||||
|
return { value: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let missingFrames = 0;
|
||||||
|
|
||||||
|
// Check if frame spacing is approximately equal within a tolerance.
|
||||||
|
// If spacing is on a uniform grid but we are missing frames,
|
||||||
|
// Allow reconstruction, but pass back the number of missing frames.
|
||||||
|
if (instances.length > 2) {
|
||||||
|
const firstIpp = _getImagePositionPatient(firstImage);
|
||||||
|
const lastIpp = _getImagePositionPatient(instances[instances.length - 1]);
|
||||||
|
const averageSpacingBetweenFrames =
|
||||||
|
_getPerpendicularDistance(firstIpp, lastIpp) / (instances.length - 1);
|
||||||
|
|
||||||
|
let previousIpp = firstIpp;
|
||||||
|
|
||||||
|
for (let i = 1; i < instances.length; i++) {
|
||||||
|
const instance = instances[i];
|
||||||
|
const ipp = _getImagePositionPatient(instance);
|
||||||
|
|
||||||
|
const spacingBetweenFrames = _getPerpendicularDistance(ipp, previousIpp);
|
||||||
|
const spacingIssue = _getSpacingIssue(
|
||||||
|
spacingBetweenFrames,
|
||||||
|
averageSpacingBetweenFrames
|
||||||
|
);
|
||||||
|
|
||||||
|
if (spacingIssue) {
|
||||||
|
const issue = spacingIssue.issue;
|
||||||
|
|
||||||
|
if (issue === reconstructionIssues.MISSING_FRAMES) {
|
||||||
|
missingFrames += spacingIssue.missingFrames;
|
||||||
|
} else if (issue === reconstructionIssues.IRREGULAR_SPACING) {
|
||||||
|
return { value: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
previousIpp = ipp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { value: true, missingFrames };
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Is 10% a reasonable tolerance for spacing?
|
||||||
|
const tolerance = 0.1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks for spacing issues.
|
||||||
|
*
|
||||||
|
* @param {number} spacing The spacing between two frames.
|
||||||
|
* @param {number} averageSpacing The average spacing between all frames.
|
||||||
|
*
|
||||||
|
* @returns {Object} An object containing the issue and extra information if necessary.
|
||||||
|
*/
|
||||||
|
function _getSpacingIssue(spacing, averageSpacing) {
|
||||||
|
const equalWithinTolerance =
|
||||||
|
Math.abs(spacing - averageSpacing) < averageSpacing * tolerance;
|
||||||
|
|
||||||
|
if (equalWithinTolerance) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const multipleOfAverageSpacing = spacing / averageSpacing;
|
||||||
|
|
||||||
|
const numberOfSpacings = Math.round(multipleOfAverageSpacing);
|
||||||
|
|
||||||
|
const errorForEachSpacing =
|
||||||
|
Math.abs(spacing - numberOfSpacings * averageSpacing) / numberOfSpacings;
|
||||||
|
|
||||||
|
if (errorForEachSpacing < tolerance * averageSpacing) {
|
||||||
|
return {
|
||||||
|
issue: reconstructionIssues.MISSING_FRAMES,
|
||||||
|
missingFrames: numberOfSpacings - 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { issue: reconstructionIssues.IRREGULAR_SPACING };
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getImagePositionPatient(instance) {
|
||||||
|
return instance
|
||||||
|
.getTagValue('x00200032')
|
||||||
|
.split('\\')
|
||||||
|
.map(element => Number(element));
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getPerpendicularDistance(a, b) {
|
||||||
|
return Math.sqrt(
|
||||||
|
Math.pow(a[0] - b[0], 2) +
|
||||||
|
Math.pow(a[1] - b[1], 2) +
|
||||||
|
Math.pow(a[2] - b[2], 2)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const constructableModalities = ['MR', 'CT', 'PT', 'NM'];
|
||||||
|
const reconstructionIssues = {
|
||||||
|
MISSING_FRAMES: 'missingframes',
|
||||||
|
IRREGULAR_SPACING: 'irregularspacing',
|
||||||
|
};
|
||||||
@@ -3,6 +3,17 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [0.51.0](https://github.com/OHIF/Viewers/compare/@ohif/i18n@0.50.5...@ohif/i18n@0.51.0) (2019-10-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Add browser info and app version ([#1046](https://github.com/OHIF/Viewers/issues/1046)) ([c217b8b](https://github.com/OHIF/Viewers/commit/c217b8b))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [0.50.5](https://github.com/OHIF/Viewers/compare/@ohif/i18n@0.50.4...@ohif/i18n@0.50.5) (2019-10-04)
|
## [0.50.5](https://github.com/OHIF/Viewers/compare/@ohif/i18n@0.50.4...@ohif/i18n@0.50.5) (2019-10-04)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/i18n",
|
"name": "@ohif/i18n",
|
||||||
"version": "0.50.5",
|
"version": "0.51.0",
|
||||||
"description": "Internationalization library for The OHIF Viewer",
|
"description": "Internationalization library for The OHIF Viewer",
|
||||||
"author": "OHIF",
|
"author": "OHIF",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"Browser": "Browser",
|
||||||
|
"Build Number": "Build Number",
|
||||||
|
"Latest Master Commits": "Latest Master Commits",
|
||||||
|
"More details": "More details",
|
||||||
|
"Name": "Name",
|
||||||
|
"OHIF Viewer - About": "OHIF Viewer - About",
|
||||||
|
"OS": "OS",
|
||||||
|
"Report an issue": "Report an issue",
|
||||||
|
"Repository URL": "Repository URL",
|
||||||
|
"Value": "Value",
|
||||||
|
"Version Information": "Version Information",
|
||||||
|
"Visit the forum": "Visit the forum"
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import AboutModal from "./AboutModal.json";
|
||||||
import Buttons from "./Buttons.json";
|
import Buttons from "./Buttons.json";
|
||||||
import CineDialog from "./CineDialog.json";
|
import CineDialog from "./CineDialog.json";
|
||||||
import Common from "./Common.json";
|
import Common from "./Common.json";
|
||||||
@@ -8,6 +9,7 @@ import UserPreferencesModal from "./UserPreferencesModal.json";
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
'en-US': {
|
'en-US': {
|
||||||
|
AboutModal,
|
||||||
Buttons,
|
Buttons,
|
||||||
CineDialog,
|
CineDialog,
|
||||||
Common,
|
Common,
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"Browser": "Navegador",
|
||||||
|
"Build Number": "Número da compilação",
|
||||||
|
"Latest Master Commits": "Últimos Commits na Master",
|
||||||
|
"More details": "Mais detalhes",
|
||||||
|
"Name": "Nome",
|
||||||
|
"OHIF Viewer - About": "OHIF Viewer - Sobre",
|
||||||
|
"OS": "SO",
|
||||||
|
"Report an issue": "Informar um problema",
|
||||||
|
"Repository URL": "URL do Repositório",
|
||||||
|
"Value": "Valor",
|
||||||
|
"Version Information": "Informação da Versão",
|
||||||
|
"Visit the forum": "Visite o fórum"
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import AboutModal from "./AboutModal.json";
|
||||||
import Buttons from "./Buttons.json";
|
import Buttons from "./Buttons.json";
|
||||||
import CineDialog from "./CineDialog.json";
|
import CineDialog from "./CineDialog.json";
|
||||||
import Common from "./Common.json";
|
import Common from "./Common.json";
|
||||||
@@ -6,6 +7,7 @@ import UserPreferencesModal from "./UserPreferencesModal.json";
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
'pt-BR': {
|
'pt-BR': {
|
||||||
|
AboutModal,
|
||||||
Buttons,
|
Buttons,
|
||||||
CineDialog,
|
CineDialog,
|
||||||
Common,
|
Common,
|
||||||
|
|||||||
@@ -3,6 +3,28 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [0.55.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.54.0...@ohif/ui@0.55.0) (2019-10-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Add browser info and app version ([#1046](https://github.com/OHIF/Viewers/issues/1046)) ([c217b8b](https://github.com/OHIF/Viewers/commit/c217b8b))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [0.54.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.53.4...@ohif/ui@0.54.0) (2019-10-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Notification Service ([#1011](https://github.com/OHIF/Viewers/issues/1011)) ([92c8996](https://github.com/OHIF/Viewers/commit/92c8996))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [0.53.4](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.53.3...@ohif/ui@0.53.4) (2019-10-11)
|
## [0.53.4](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.53.3...@ohif/ui@0.53.4) (2019-10-11)
|
||||||
|
|
||||||
**Note:** Version bump only for package @ohif/ui
|
**Note:** Version bump only for package @ohif/ui
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/ui",
|
"name": "@ohif/ui",
|
||||||
"version": "0.53.4",
|
"version": "0.55.0",
|
||||||
"description": "A set of React components for Medical Imaging Viewers",
|
"description": "A set of React components for Medical Imaging Viewers",
|
||||||
"author": "OHIF Contributors",
|
"author": "OHIF Contributors",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -39,6 +39,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.5.5",
|
"@babel/runtime": "^7.5.5",
|
||||||
"@ohif/i18n": "^0.2.3",
|
"@ohif/i18n": "^0.2.3",
|
||||||
|
"browser-detect": "^0.2.28",
|
||||||
"classnames": "2.2.6",
|
"classnames": "2.2.6",
|
||||||
"i18next": "^17.0.3",
|
"i18next": "^17.0.3",
|
||||||
"i18next-browser-languagedetector": "^3.0.1",
|
"i18next-browser-languagedetector": "^3.0.1",
|
||||||
|
|||||||
@@ -0,0 +1,196 @@
|
|||||||
|
.sb-container {
|
||||||
|
position: fixed;
|
||||||
|
width: var(--snackbar-size);
|
||||||
|
padding: 20px;
|
||||||
|
z-index: var(--snackbar-zIndex);
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sb-topLeft {
|
||||||
|
top: 0;
|
||||||
|
bottom: auto;
|
||||||
|
left: 0;
|
||||||
|
right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sb-topCenter {
|
||||||
|
left: 50%;
|
||||||
|
top: 0;
|
||||||
|
bottom: auto;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sb-topRight {
|
||||||
|
top: 0;
|
||||||
|
bottom: auto;
|
||||||
|
left: auto;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sb-bottomLeft {
|
||||||
|
top: auto;
|
||||||
|
bottom: 0px;
|
||||||
|
left: 0px;
|
||||||
|
right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sb-bottomCenter {
|
||||||
|
left: 50%;
|
||||||
|
bottom: 0;
|
||||||
|
top: auto;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sb-bottomRight {
|
||||||
|
top: auto;
|
||||||
|
bottom: 0px;
|
||||||
|
left: auto;
|
||||||
|
right: 0px;
|
||||||
|
margin: 10px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sb-topLeft .sb-item,
|
||||||
|
.sb-topCenter .sb-item,
|
||||||
|
.sb-topRight .sb-item {
|
||||||
|
margin: 10px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sb-bottomLeft .sb-item,
|
||||||
|
.sb-bottomCenter .sb-item,
|
||||||
|
.sb-bottomRight .sb-item {
|
||||||
|
margin: 0 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sb-closeBtn {
|
||||||
|
height: 20px;
|
||||||
|
opacity: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 2px;
|
||||||
|
text-align: center;
|
||||||
|
text-shadow: none;
|
||||||
|
width: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
right: 5px;
|
||||||
|
top: 5px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
background: rgba(255, 255, 255, 0.6);
|
||||||
|
border-radius: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sb-closeBtn:hover {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sb-closeIcon {
|
||||||
|
display: block;
|
||||||
|
font-size: 0;
|
||||||
|
height: 100%;
|
||||||
|
line-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sb-closeIcon:after,
|
||||||
|
.sb-closeIcon:before {
|
||||||
|
content: ' ';
|
||||||
|
display: block;
|
||||||
|
height: 2px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
width: 12px;
|
||||||
|
background-color: #222;
|
||||||
|
opacity: 1;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sb-closeIcon:before {
|
||||||
|
left: 4px;
|
||||||
|
top: 3px;
|
||||||
|
|
||||||
|
transform: rotate(45deg);
|
||||||
|
transform-origin: 0px 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sb-closeIcon:after {
|
||||||
|
right: 3px;
|
||||||
|
top: 5px;
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
transform-origin: calc(100% - 3px) 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sb-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sb-message {
|
||||||
|
font-size: 14px;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sb-item {
|
||||||
|
position: relative;
|
||||||
|
transition: height 300ms ease;
|
||||||
|
animation: fadein 1s;
|
||||||
|
padding: 20px;
|
||||||
|
color: white;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 6px 10px 0 rgba(0, 0, 0, 0.2), 0 1px 18px 0 rgba(0, 0, 0, 0.12),
|
||||||
|
0 3px 5px -1px rgba(0, 0, 0, 0.14);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sb-item a {
|
||||||
|
color: white;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sb-container .sb-hidden {
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
height: 0;
|
||||||
|
opacity: 0;
|
||||||
|
transition: all 300ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sb-success {
|
||||||
|
background-color: var(--snackbar-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sb-error {
|
||||||
|
background-color: var(--snackbar-error);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sb-warning {
|
||||||
|
background-color: var(--snackbar-warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sb-info {
|
||||||
|
background-color: var(--snackbar-info);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadein {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
top: 30px;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Internet Explorer */
|
||||||
|
@-ms-keyframes fadein {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
top: 30px;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import SnackbarItem from './SnackbarItem';
|
||||||
|
import './Snackbar.css';
|
||||||
|
import { useSnackbarContext } from '../../utils/SnackbarProvider';
|
||||||
|
|
||||||
|
const SnackbarContainer = () => {
|
||||||
|
const { snackbarItems, hide } = useSnackbarContext();
|
||||||
|
|
||||||
|
const renderItem = item => {
|
||||||
|
return <SnackbarItem key={item.itemId} options={item} onClose={hide} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!snackbarItems) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderItems = () => {
|
||||||
|
const items = {
|
||||||
|
topLeft: [],
|
||||||
|
topCenter: [],
|
||||||
|
topRight: [],
|
||||||
|
bottomLeft: [],
|
||||||
|
bottomCenter: [],
|
||||||
|
bottomRight: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
snackbarItems.map(item => {
|
||||||
|
items[item.position].push(item);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{Object.keys(items).map(pos => {
|
||||||
|
if (!items[pos].length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={pos} className={`sb-container sb-${pos}`}>
|
||||||
|
{items[pos].map(item => (
|
||||||
|
<div key={item.id}>{renderItem(item)}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return <>{renderItems()}</>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SnackbarContainer;
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
const SnackbarItem = ({ options, onClose }) => {
|
||||||
|
const handleClose = () => {
|
||||||
|
onClose(options.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (options.autoClose) {
|
||||||
|
setTimeout(() => {
|
||||||
|
handleClose();
|
||||||
|
}, options.duration);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`${options.visible ? '' : 'sb-hidden'} sb-${
|
||||||
|
options.type
|
||||||
|
} sb-item`}
|
||||||
|
>
|
||||||
|
<span className="sb-closeBtn" onClick={handleClose}>
|
||||||
|
<span className="sb-closeIcon">x</span>
|
||||||
|
</span>
|
||||||
|
{options.title && <div className="sb-title">{options.title}</div>}
|
||||||
|
{options.message && <div className="sb-message">{options.message}</div>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SnackbarItem;
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export default {
|
||||||
|
INFO: 'info',
|
||||||
|
WARNING: 'warning',
|
||||||
|
SUCCESS: 'success',
|
||||||
|
ERROR: 'error',
|
||||||
|
};
|
||||||
@@ -1,14 +1,16 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Modal from 'react-bootstrap-modal';
|
import Modal from 'react-bootstrap-modal';
|
||||||
|
import detect from 'browser-detect';
|
||||||
import './AboutModal.styl';
|
import './AboutModal.styl';
|
||||||
|
import { withTranslation } from '../../utils/LanguageProvider';
|
||||||
|
|
||||||
import 'react-bootstrap-modal/lib/css/rbm-patch.css';
|
import 'react-bootstrap-modal/lib/css/rbm-patch.css';
|
||||||
|
|
||||||
// TODO: Is this the only component importing these?
|
// TODO: Is this the only component importing these?
|
||||||
import './../../design/styles/common/modal.styl';
|
import './../../design/styles/common/modal.styl';
|
||||||
|
|
||||||
export class AboutModal extends Component {
|
class AboutModal extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
}
|
}
|
||||||
@@ -18,16 +20,41 @@ export class AboutModal extends Component {
|
|||||||
onCancel: PropTypes.func,
|
onCancel: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
static itemsPreset = [
|
itemsPreset() {
|
||||||
{
|
const { t } = this.props;
|
||||||
name: 'Repository URL',
|
const browser = detect();
|
||||||
value: 'https://github.com/OHIF/Viewers/',
|
const capitalize = s =>
|
||||||
},
|
s.substr(0, 1).toUpperCase() + s.substr(1).toLowerCase();
|
||||||
{
|
|
||||||
name: 'Latest Master Commits',
|
return [
|
||||||
value: 'https://github.com/OHIF/Viewers/commits/master',
|
{
|
||||||
},
|
name: t('Repository URL'),
|
||||||
];
|
value: 'https://github.com/OHIF/Viewers/',
|
||||||
|
link: 'https://github.com/OHIF/Viewers/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: t('Latest Master Commits'),
|
||||||
|
value: 'https://github.com/OHIF/Viewers/commits/master',
|
||||||
|
link: 'https://github.com/OHIF/Viewers/commits/master',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Version Number',
|
||||||
|
value: process.env.VERSION_NUMBER,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: t('Build Number'),
|
||||||
|
value: process.env.BUILD_NUM,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: t('Browser'),
|
||||||
|
value: `${capitalize(browser.name)} ${browser.version}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: t('OS'),
|
||||||
|
value: browser.os,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
@@ -38,15 +65,20 @@ export class AboutModal extends Component {
|
|||||||
<tr key={item.name}>
|
<tr key={item.name}>
|
||||||
<td>{item.name}</td>
|
<td>{item.name}</td>
|
||||||
<td>
|
<td>
|
||||||
<a target="_blank" href={item.value}>
|
{item.link ? (
|
||||||
{item.value}
|
<a target="_blank" href={item.link}>
|
||||||
</a>
|
{item.value}
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
item.value
|
||||||
|
)}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { t } = this.props;
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
show={this.props.isOpen}
|
show={this.props.isOpen}
|
||||||
@@ -58,7 +90,7 @@ export class AboutModal extends Component {
|
|||||||
keyboard={false}
|
keyboard={false}
|
||||||
>
|
>
|
||||||
<Modal.Header closeButton>
|
<Modal.Header closeButton>
|
||||||
<Modal.Title>About</Modal.Title>
|
<Modal.Title>{t('OHIF Viewer - About')}</Modal.Title>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
<div className="btn-group">
|
<div className="btn-group">
|
||||||
@@ -67,34 +99,36 @@ export class AboutModal extends Component {
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
href="https://groups.google.com/forum/#!forum/cornerstone-platform"
|
href="https://groups.google.com/forum/#!forum/cornerstone-platform"
|
||||||
>
|
>
|
||||||
Visit the forum
|
{t('Visit the forum')}
|
||||||
</a>
|
</a>
|
||||||
|
{` `}
|
||||||
<a
|
<a
|
||||||
className="btn btn-default"
|
className="btn btn-default"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href="https://github.com/OHIF/Viewers/issues"
|
href="https://github.com/OHIF/Viewers/issues/new/choose"
|
||||||
>
|
>
|
||||||
Report an issue
|
{t('Report an issue')}
|
||||||
</a>
|
</a>
|
||||||
|
{` `}
|
||||||
<a
|
<a
|
||||||
className="btn btn-default"
|
className="btn btn-default"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href="http://ohif.org"
|
href="http://ohif.org"
|
||||||
>
|
>
|
||||||
More details
|
{t('More details')}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3>Version Information</h3>
|
<h3>{t('Version Information')}</h3>
|
||||||
<table className="table table-responsive">
|
<table className="table table-responsive">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>{t('Name')}</th>
|
||||||
<th>Value</th>
|
<th>{t('Value')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{AboutModal.itemsPreset.map(item => this.renderTableRow(item))}
|
{this.itemsPreset().map(item => this.renderTableRow(item))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@@ -103,3 +137,7 @@ export class AboutModal extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const connectedComponent = withTranslation('AboutModal')(AboutModal);
|
||||||
|
export { connectedComponent as AboutModal };
|
||||||
|
export default connectedComponent;
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
z-index: 1050;
|
z-index: 1050;
|
||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
outline: 0;
|
outline: 0;
|
||||||
|
background: rgba(0,0,0,0.5);
|
||||||
|
|
||||||
.modal-dialog
|
.modal-dialog
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ import { ScrollableArea } from './ScrollableArea/ScrollableArea.js';
|
|||||||
import Toolbar from './viewer/Toolbar.js';
|
import Toolbar from './viewer/Toolbar.js';
|
||||||
import ToolbarButton from './viewer/ToolbarButton.js';
|
import ToolbarButton from './viewer/ToolbarButton.js';
|
||||||
import ViewerbaseDragDropContext from './utils/viewerbaseDragDropContext.js';
|
import ViewerbaseDragDropContext from './utils/viewerbaseDragDropContext.js';
|
||||||
|
import SnackbarProvider, {
|
||||||
|
useSnackbarContext,
|
||||||
|
withSnackbar,
|
||||||
|
} from './utils/SnackbarProvider';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
ICONS,
|
ICONS,
|
||||||
@@ -68,4 +72,7 @@ export {
|
|||||||
UserPreferences,
|
UserPreferences,
|
||||||
UserPreferencesModal,
|
UserPreferencesModal,
|
||||||
ViewerbaseDragDropContext,
|
ViewerbaseDragDropContext,
|
||||||
|
SnackbarProvider,
|
||||||
|
useSnackbarContext,
|
||||||
|
withSnackbar,
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
import React, { useState, createContext, useContext } from 'react';
|
||||||
|
import SnackbarContainer from '../components/snackbar/SnackbarContainer';
|
||||||
|
import SnackbarTypes from '../components/snackbar/SnackbarTypes';
|
||||||
|
|
||||||
|
const SnackbarContext = createContext(null);
|
||||||
|
|
||||||
|
export const useSnackbarContext = () => useContext(SnackbarContext);
|
||||||
|
|
||||||
|
const SnackbarProvider = ({ children }) => {
|
||||||
|
const DEFAULT_OPTIONS = {
|
||||||
|
title: '',
|
||||||
|
message: '',
|
||||||
|
duration: 5000,
|
||||||
|
autoClose: true,
|
||||||
|
position: 'bottomRight',
|
||||||
|
type: SnackbarTypes.INFO,
|
||||||
|
};
|
||||||
|
|
||||||
|
const [count, setCount] = useState(1);
|
||||||
|
const [snackbarItems, setSnackbarItems] = useState([]);
|
||||||
|
|
||||||
|
const show = options => {
|
||||||
|
if (!options || (!options.title && !options.message)) {
|
||||||
|
console.warn(
|
||||||
|
'Snackbar cannot be rendered without required parameters: title | message'
|
||||||
|
);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newItem = {
|
||||||
|
...DEFAULT_OPTIONS,
|
||||||
|
...options,
|
||||||
|
id: count,
|
||||||
|
visible: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
setSnackbarItems(state => [...state, newItem]);
|
||||||
|
setCount(count + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const hide = id => {
|
||||||
|
const hideItem = items => {
|
||||||
|
const newItems = items.map(item => {
|
||||||
|
if (item.id === id) {
|
||||||
|
item.visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
|
||||||
|
return newItems;
|
||||||
|
};
|
||||||
|
|
||||||
|
setSnackbarItems(state => hideItem(state));
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setSnackbarItems(state => [...state.filter(item => item.id !== id)]);
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const hideAll = () => {
|
||||||
|
// reset count
|
||||||
|
setCount(1);
|
||||||
|
|
||||||
|
// remove all items from array
|
||||||
|
setSnackbarItems(() => []);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* expose snackbar methods to window for debug purposes
|
||||||
|
* TODO: Check if it's really necessary
|
||||||
|
*/
|
||||||
|
window.snackbar = {
|
||||||
|
show,
|
||||||
|
hide,
|
||||||
|
hideAll,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SnackbarContext.Provider value={{ show, hide, hideAll, snackbarItems }}>
|
||||||
|
{!!snackbarItems && <SnackbarContainer />}
|
||||||
|
{children}
|
||||||
|
</SnackbarContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* High Order Component to use the snackbar methods through a Class Component
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const withSnackbar = Component => {
|
||||||
|
return function WrappedComponent(props) {
|
||||||
|
const snackbarContext = {
|
||||||
|
...useSnackbarContext(),
|
||||||
|
};
|
||||||
|
return <Component {...props} snackbarContext={snackbarContext} />;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SnackbarProvider;
|
||||||
@@ -3,6 +3,93 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.10.3](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.10.2...@ohif/viewer@1.10.3) (2019-10-18)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.10.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.10.1...@ohif/viewer@1.10.2) (2019-10-18)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.10.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.10.0...@ohif/viewer@1.10.1) (2019-10-16)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [1.10.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.9.1...@ohif/viewer@1.10.0) (2019-10-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Add browser info and app version ([#1046](https://github.com/OHIF/Viewers/issues/1046)) ([c217b8b](https://github.com/OHIF/Viewers/commit/c217b8b))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.9.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.9.0...@ohif/viewer@1.9.1) (2019-10-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 Remove debugger statement left in from last PR ([#1052](https://github.com/OHIF/Viewers/issues/1052)) ([d091cd6](https://github.com/OHIF/Viewers/commit/d091cd6))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [1.9.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.8.0...@ohif/viewer@1.9.0) (2019-10-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 🎸 Only allow reconstruction of datasets that make sense ([#1010](https://github.com/OHIF/Viewers/issues/1010)) ([2d75e01](https://github.com/OHIF/Viewers/commit/2d75e01)), closes [#561](https://github.com/OHIF/Viewers/issues/561)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [1.8.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.7.0...@ohif/viewer@1.8.0) (2019-10-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Notification Service ([#1011](https://github.com/OHIF/Viewers/issues/1011)) ([92c8996](https://github.com/OHIF/Viewers/commit/92c8996))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [1.7.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.6.3...@ohif/viewer@1.7.0) (2019-10-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Implement a 'Exit 2D MPR' button in the toolbar ([c99e0d8](https://github.com/OHIF/Viewers/commit/c99e0d8))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.6.3](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.6.2...@ohif/viewer@1.6.3) (2019-10-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [1.6.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.6.1...@ohif/viewer@1.6.2) (2019-10-11)
|
## [1.6.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.6.1...@ohif/viewer@1.6.2) (2019-10-11)
|
||||||
|
|
||||||
**Note:** Version bump only for package @ohif/viewer
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ describe('OHIF Cornerstone Toolbar', () => {
|
|||||||
before(() => {
|
before(() => {
|
||||||
cy.openStudy('MISTER^MR');
|
cy.openStudy('MISTER^MR');
|
||||||
cy.waitDicomImage();
|
cy.waitDicomImage();
|
||||||
|
cy.expectMinimumThumbnails(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.initCornerstoneToolsAliases();
|
cy.initCornerstoneToolsAliases();
|
||||||
cy.initCommonElementsAliases();
|
cy.initCommonElementsAliases();
|
||||||
//Following best practices, reset should be done before each test
|
|
||||||
cy.resetViewport();
|
cy.resetViewport();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -42,9 +42,6 @@ describe('OHIF Cornerstone Toolbar', () => {
|
|||||||
cy.get('@moreBtn')
|
cy.get('@moreBtn')
|
||||||
.should('be.visible')
|
.should('be.visible')
|
||||||
.contains('More');
|
.contains('More');
|
||||||
cy.get('@twodmprBtn')
|
|
||||||
.should('be.visible')
|
|
||||||
.contains('2D MPR');
|
|
||||||
cy.get('@layoutBtn')
|
cy.get('@layoutBtn')
|
||||||
.should('be.visible')
|
.should('be.visible')
|
||||||
.contains('Layout');
|
.contains('Layout');
|
||||||
@@ -148,7 +145,7 @@ describe('OHIF Cornerstone Toolbar', () => {
|
|||||||
//Add annotation on the viewport
|
//Add annotation on the viewport
|
||||||
const firstClick = [150, 100];
|
const firstClick = [150, 100];
|
||||||
const secondClick = [130, 170];
|
const secondClick = [130, 170];
|
||||||
cy.addLine('.cornerstone-canvas', firstClick, secondClick);
|
cy.addLine('@viewport', firstClick, secondClick);
|
||||||
|
|
||||||
//Verify if measurement annotation was added into the measurements panel
|
//Verify if measurement annotation was added into the measurements panel
|
||||||
cy.get('@measurementsBtn')
|
cy.get('@measurementsBtn')
|
||||||
@@ -176,7 +173,7 @@ describe('OHIF Cornerstone Toolbar', () => {
|
|||||||
const initPos = [180, 390];
|
const initPos = [180, 390];
|
||||||
const midPos = [300, 410];
|
const midPos = [300, 410];
|
||||||
const finalPos = [180, 450];
|
const finalPos = [180, 450];
|
||||||
cy.addAngle('.cornerstone-canvas', initPos, midPos, finalPos);
|
cy.addAngle('@viewport', initPos, midPos, finalPos);
|
||||||
|
|
||||||
//Verify if measurement annotation was added into the measurements panel
|
//Verify if measurement annotation was added into the measurements panel
|
||||||
cy.get('@measurementsBtn')
|
cy.get('@measurementsBtn')
|
||||||
@@ -361,33 +358,20 @@ describe('OHIF Cornerstone Toolbar', () => {
|
|||||||
.should('be.eq', 9);
|
.should('be.eq', 9);
|
||||||
});
|
});
|
||||||
|
|
||||||
//Commented this to avoid throwing an wrong exception at the end of "Layout button" test
|
|
||||||
//this commented section should be uncommented once issue #999 is fixed. (https://github.com/OHIF/Viewers/issues/999)
|
|
||||||
|
|
||||||
//verify if layout has changed to 1 viewport
|
//verify if layout has changed to 1 viewport
|
||||||
// cy.get('@layoutBtn').click();
|
cy.get('@layoutBtn').click();
|
||||||
// cy.get('tbody > :nth-child(1) > :nth-child(1)').click();
|
cy.get('tbody > :nth-child(1) > :nth-child(1)').click();
|
||||||
// cy.get('.viewport-container').then(($viewport) =>{
|
cy.get('.viewport-container').then($viewport => {
|
||||||
// cy.wrap($viewport)
|
cy.wrap($viewport)
|
||||||
// .its('length')
|
.its('length')
|
||||||
// .should('be.eq', 1);
|
.should('be.eq', 1);
|
||||||
// })
|
});
|
||||||
cy.reload();
|
|
||||||
cy.waitDicomImage();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('checks if Clear tool will delete all measurements added in the viewport', () => {
|
it('checks if Clear tool will delete all measurements added in the viewport', () => {
|
||||||
//Add measurements in the viewport
|
//Add measurements in the viewport
|
||||||
cy.get('@lengthBtn').click();
|
cy.addLengthMeasurement();
|
||||||
const firstClick = [150, 100];
|
cy.addAngleMeasurement();
|
||||||
const secondClick = [130, 170];
|
|
||||||
cy.addLine('.cornerstone-canvas', firstClick, secondClick);
|
|
||||||
|
|
||||||
cy.get('@angleBtn').click();
|
|
||||||
const initPos = [180, 390];
|
|
||||||
const midPos = [300, 410];
|
|
||||||
const finalPos = [180, 450];
|
|
||||||
cy.addAngle('.cornerstone-canvas', initPos, midPos, finalPos);
|
|
||||||
|
|
||||||
//Verify if measurement annotation was added into the measurements panel
|
//Verify if measurement annotation was added into the measurements panel
|
||||||
cy.get('@measurementsBtn').click();
|
cy.get('@measurementsBtn').click();
|
||||||
@@ -419,23 +403,18 @@ describe('OHIF Cornerstone Toolbar', () => {
|
|||||||
|
|
||||||
it('checks if Eraser tool will remove the measurements added in the viewport', () => {
|
it('checks if Eraser tool will remove the measurements added in the viewport', () => {
|
||||||
//Add measurements in the viewport
|
//Add measurements in the viewport
|
||||||
cy.get('@lengthBtn').click();
|
cy.addLengthMeasurement();
|
||||||
const firstClick = [150, 100];
|
cy.addAngleMeasurement();
|
||||||
const secondClick = [130, 170];
|
|
||||||
cy.addLine('.cornerstone-canvas', firstClick, secondClick);
|
|
||||||
|
|
||||||
cy.get('@angleBtn').click();
|
|
||||||
const initPos = [180, 390];
|
|
||||||
const midPos = [300, 410];
|
|
||||||
const finalPos = [180, 450];
|
|
||||||
cy.addAngle('.cornerstone-canvas', initPos, midPos, finalPos);
|
|
||||||
|
|
||||||
//Verify if measurement annotation was added into the measurements panel
|
//Verify if measurement annotation was added into the measurements panel
|
||||||
cy.get('@measurementsBtn').click();
|
cy.get('@measurementsBtn').click();
|
||||||
cy.get('.measurementItem')
|
cy.get('.measurementItem')
|
||||||
.its('length')
|
.its('length')
|
||||||
.should('be.eq', 2);
|
.should('be.eq', 2);
|
||||||
cy.get('@measurementsBtn').click();
|
cy.get('@measurementsBtn')
|
||||||
|
.click()
|
||||||
|
.wait(2000);
|
||||||
|
//cy.isNotInViewport('@measurementsPanel'); //TO DO: check this intermittent behaviour
|
||||||
|
|
||||||
//Click More button
|
//Click More button
|
||||||
cy.get('@moreBtn').click();
|
cy.get('@moreBtn').click();
|
||||||
@@ -443,14 +422,14 @@ describe('OHIF Cornerstone Toolbar', () => {
|
|||||||
cy.get('.tooltip-inner > :nth-child(12)').click();
|
cy.get('.tooltip-inner > :nth-child(12)').click();
|
||||||
|
|
||||||
//Erase measurement #1 and Verify if it was removed from the measurements panel
|
//Erase measurement #1 and Verify if it was removed from the measurements panel
|
||||||
const [x1, y1] = firstClick;
|
const [x1, y1] = [150, 100];
|
||||||
cy.get('@viewport').click(x1, y1, { force: true });
|
cy.get('@viewport').click(x1, y1, { force: true });
|
||||||
cy.get('.measurementItem')
|
cy.get('.measurementItem')
|
||||||
.its('length')
|
.its('length')
|
||||||
.should('be.eq', 1);
|
.should('be.eq', 1);
|
||||||
|
|
||||||
//Erase measurement #2 and Verify if it was removed from the measurements panel
|
//Erase measurement #2 and Verify if it was removed from the measurements panel
|
||||||
const [x2, y2] = initPos;
|
const [x2, y2] = [180, 390];
|
||||||
cy.get('@viewport').click(x2, y2, { force: true });
|
cy.get('@viewport').click(x2, y2, { force: true });
|
||||||
cy.get('.measurementItem').should('not.exist');
|
cy.get('.measurementItem').should('not.exist');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ describe('OHIFStandaloneViewer', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('loads route with at least 2 rows', () => {
|
it('loads route with at least 2 rows', () => {
|
||||||
|
cy.screenshot();
|
||||||
|
cy.percySnapshot();
|
||||||
|
|
||||||
cy.get('#studyListData tr')
|
cy.get('#studyListData tr')
|
||||||
.its('length')
|
.its('length')
|
||||||
.should('be.gt', 2);
|
.should('be.gt', 2);
|
||||||
|
|||||||
@@ -2,15 +2,18 @@ describe('OHIF Study Viewer Page', () => {
|
|||||||
before(() => {
|
before(() => {
|
||||||
cy.openStudy('MISTER^MR');
|
cy.openStudy('MISTER^MR');
|
||||||
cy.waitDicomImage();
|
cy.waitDicomImage();
|
||||||
|
cy.expectMinimumThumbnails(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.initCommonElementsAliases();
|
cy.initCommonElementsAliases();
|
||||||
//Following best practices, reset should be done before each test
|
|
||||||
cy.resetViewport();
|
cy.resetViewport();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('checks if series thumbnails are being displayed', () => {
|
it('checks if series thumbnails are being displayed', () => {
|
||||||
|
cy.screenshot();
|
||||||
|
cy.percySnapshot();
|
||||||
|
|
||||||
cy.get('.ThumbnailEntryContainer')
|
cy.get('.ThumbnailEntryContainer')
|
||||||
.its('length')
|
.its('length')
|
||||||
.should('be.gt', 1);
|
.should('be.gt', 1);
|
||||||
@@ -62,8 +65,11 @@ describe('OHIF Study Viewer Page', () => {
|
|||||||
// Confirm Selection
|
// Confirm Selection
|
||||||
cy.get('.checkIconWrapper').click();
|
cy.get('.checkIconWrapper').click();
|
||||||
|
|
||||||
//Verify if 'Bone' label was added
|
// Verify if 'Bone' label was added
|
||||||
cy.get('.measurementLocation').should('contain.text', 'Bone');
|
cy.get('.measurementLocation').should('contain.text', 'Bone');
|
||||||
|
// Close panel
|
||||||
|
cy.get('@measurementsBtn').click();
|
||||||
|
cy.get('@measurementsPanel').should('not.be.enabled');
|
||||||
});
|
});
|
||||||
|
|
||||||
//TO-DO: Test case will fail due to issue #1013: https://github.com/OHIF/Viewers/issues/1013
|
//TO-DO: Test case will fail due to issue #1013: https://github.com/OHIF/Viewers/issues/1013
|
||||||
@@ -90,4 +96,114 @@ describe('OHIF Study Viewer Page', () => {
|
|||||||
// cy.get('.measurementLocation')
|
// cy.get('.measurementLocation')
|
||||||
// .should('contain.text', descriptionText);
|
// .should('contain.text', descriptionText);
|
||||||
// });
|
// });
|
||||||
|
|
||||||
|
it('checks if measurement item can be deleted through the context menu on the viewport', () => {
|
||||||
|
cy.addLengthMeasurement([100, 100], [200, 100]); //Adding measurement in the viewport
|
||||||
|
|
||||||
|
//Right click on measurement annotation
|
||||||
|
const [x1, y1] = [150, 100];
|
||||||
|
cy.get('@viewport')
|
||||||
|
.trigger('mousedown', x1, y1, {
|
||||||
|
which: 3,
|
||||||
|
})
|
||||||
|
.trigger('mouseup', x1, y1, {
|
||||||
|
which: 3,
|
||||||
|
});
|
||||||
|
|
||||||
|
//Contextmenu is visible
|
||||||
|
cy.get('.ToolContextMenu').should('be.visible');
|
||||||
|
|
||||||
|
//Click "Delete measurement"
|
||||||
|
cy.get('.form-action')
|
||||||
|
.contains('Delete measurement')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
//Open measurements menu
|
||||||
|
cy.get('@measurementsBtn').click();
|
||||||
|
|
||||||
|
//Verify measurements was removed from panel
|
||||||
|
cy.get('.measurementItem')
|
||||||
|
.should('not.exist')
|
||||||
|
.log('Annotation removed with success');
|
||||||
|
|
||||||
|
//Close panel
|
||||||
|
cy.get('@measurementsBtn').click();
|
||||||
|
cy.get('@measurementsPanel').should('not.be.enabled');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds relabel and description to measurement item through the context menu on the viewport', () => {
|
||||||
|
cy.addLengthMeasurement([100, 100], [200, 100]); //Adding measurement in the viewport
|
||||||
|
|
||||||
|
// Relabel
|
||||||
|
// Right click on measurement annotation
|
||||||
|
const [x1, y1] = [150, 100];
|
||||||
|
cy.get('@viewport')
|
||||||
|
.trigger('mousedown', x1, y1, {
|
||||||
|
which: 3,
|
||||||
|
})
|
||||||
|
.trigger('mouseup', x1, y1, {
|
||||||
|
which: 3,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Contextmenu is visible
|
||||||
|
cy.get('.ToolContextMenu').should('be.visible');
|
||||||
|
|
||||||
|
// Click "Relabel"
|
||||||
|
cy.get('.form-action')
|
||||||
|
.contains('Relabel')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
// Search for "Brain"
|
||||||
|
cy.get('.searchInput').type('Brain');
|
||||||
|
|
||||||
|
// Select "Brain" Result
|
||||||
|
cy.get('.treeInputs > .wrapperLabel')
|
||||||
|
.contains('Brain')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
// Confirm Selection
|
||||||
|
cy.get('.checkIconWrapper').click();
|
||||||
|
|
||||||
|
// Description
|
||||||
|
// Right click on measurement annotation
|
||||||
|
cy.get('@viewport')
|
||||||
|
.trigger('mousedown', x1, y1, {
|
||||||
|
which: 3,
|
||||||
|
})
|
||||||
|
.trigger('mouseup', x1, y1, {
|
||||||
|
which: 3,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Contextmenu is visible
|
||||||
|
cy.get('.ToolContextMenu').should('be.visible');
|
||||||
|
|
||||||
|
// Click "Description"
|
||||||
|
cy.get('.form-action')
|
||||||
|
.contains('Add Description')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
// Enter description text
|
||||||
|
const descriptionText = 'Adding text for description test';
|
||||||
|
cy.get('#description').type(descriptionText);
|
||||||
|
|
||||||
|
// Confirm
|
||||||
|
cy.get('.btn-confirm').click();
|
||||||
|
|
||||||
|
//Open measurements menu
|
||||||
|
cy.get('@measurementsBtn').click();
|
||||||
|
|
||||||
|
// Verify if label was added
|
||||||
|
cy.get('.measurementLocation')
|
||||||
|
.should('contain.text', 'Brain')
|
||||||
|
.log('Relabel added with success');
|
||||||
|
|
||||||
|
//Verify if descriptionText was added
|
||||||
|
cy.get('.measurementLocation')
|
||||||
|
.should('contain.text', descriptionText)
|
||||||
|
.log('Description added with success');
|
||||||
|
|
||||||
|
// Close panel
|
||||||
|
cy.get('@measurementsBtn').click();
|
||||||
|
cy.get('@measurementsPanel').should('not.be.enabled');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
describe('OHIF HTML Extension', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.openStudy('Dummy');
|
||||||
|
cy.expectMinimumThumbnails(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks if series thumbnails are being displayed', () => {
|
||||||
|
cy.get('[data-cy="thumbnail-list"]')
|
||||||
|
.contains('SR')
|
||||||
|
.its('length')
|
||||||
|
.should('to.be.at.least', 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('drags and drop a SR thumbnail into viewport', () => {
|
||||||
|
cy.get('[data-cy="thumbnail-list"]')
|
||||||
|
.contains('SR')
|
||||||
|
.drag('.viewport-drop-target');
|
||||||
|
|
||||||
|
cy.get(':nth-child(2) > h1').should(
|
||||||
|
'contain.text',
|
||||||
|
'Imaging Measurement Report'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
describe('OHIF PDF Extension', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.openStudy('Dummy');
|
||||||
|
cy.expectMinimumThumbnails(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks if series thumbnails are being displayed', () => {
|
||||||
|
cy.get('[data-cy="thumbnail-list"]')
|
||||||
|
.contains('DOC')
|
||||||
|
.its('length')
|
||||||
|
.should('to.be.at.least', 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('drags and drop a PDF thumbnail into viewport', () => {
|
||||||
|
cy.get('[data-cy="thumbnail-list"]')
|
||||||
|
.contains('DOC')
|
||||||
|
.drag('.viewport-drop-target');
|
||||||
|
|
||||||
|
cy.get('.DicomPDFViewport')
|
||||||
|
.its('length')
|
||||||
|
.should('be.eq', 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
describe('OHIF VTK Extension', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.openStudy('Juno');
|
||||||
|
cy.waitDicomImage();
|
||||||
|
cy.expectMinimumThumbnails(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// TODO: We shouldn't have to drag the thumbnail
|
||||||
|
// This is a known bug; 2D MPR button does not show until viewport
|
||||||
|
// has data from a drag-n-drop
|
||||||
|
// Drag and drop first thumbnail into first viewport
|
||||||
|
cy.get('[data-cy="thumbnail-list"]:nth-child(3)').drag(
|
||||||
|
'.cornerstone-canvas'
|
||||||
|
);
|
||||||
|
|
||||||
|
cy.get('.PluginSwitch > .toolbar-button')
|
||||||
|
.as('twodmprBtn')
|
||||||
|
.should('be.visible')
|
||||||
|
.then(btn => {
|
||||||
|
if (!btn.text().includes('Exit')) {
|
||||||
|
btn.click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.initVTKToolsAliases();
|
||||||
|
cy.wait(1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks if VTK buttons are displayed on the toolbar', () => {
|
||||||
|
cy.get('@crosshairsBtn')
|
||||||
|
.should('be.visible')
|
||||||
|
.contains('Crosshairs');
|
||||||
|
cy.get('@wwwcBtn')
|
||||||
|
.should('be.visible')
|
||||||
|
.contains('WWWC');
|
||||||
|
cy.get('@rotateBtn')
|
||||||
|
.should('be.visible')
|
||||||
|
.contains('Rotate');
|
||||||
|
cy.get('@slabSlider')
|
||||||
|
.should('be.visible')
|
||||||
|
.contains('Slab Thickness');
|
||||||
|
cy.get('@modeDropdown')
|
||||||
|
.should('be.visible')
|
||||||
|
.contains('MIP');
|
||||||
|
cy.get('@modeCheckbox').should('be.visible');
|
||||||
|
cy.get('@layoutBtn')
|
||||||
|
.should('be.visible')
|
||||||
|
.contains('Layout');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -7,10 +7,10 @@
|
|||||||
// You can read more here:
|
// You can read more here:
|
||||||
// https://on.cypress.io/plugins-guide
|
// https://on.cypress.io/plugins-guide
|
||||||
// ***********************************************************
|
// ***********************************************************
|
||||||
|
let percyHealthCheck = require('@percy/cypress/task');
|
||||||
|
|
||||||
// This function is called when a project is opened or re-opened (e.g. due to
|
// This function is called when a project is opened or re-opened (e.g. due to
|
||||||
// the project's config changing)
|
// the project's config changing)
|
||||||
|
|
||||||
module.exports = (on, config) => {
|
module.exports = (on, config) => {
|
||||||
// `on` is used to hook into various events Cypress emits
|
// `on` is used to hook into various events Cypress emits
|
||||||
// `config` is the resolved Cypress config
|
// `config` is the resolved Cypress config
|
||||||
@@ -24,4 +24,6 @@ module.exports = (on, config) => {
|
|||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
on('task', percyHealthCheck);
|
||||||
};
|
};
|
||||||
@@ -10,7 +10,6 @@ export function initCornerstoneToolsAliases() {
|
|||||||
cy.get('.ToolbarRow > :nth-child(9)').as('resetBtn');
|
cy.get('.ToolbarRow > :nth-child(9)').as('resetBtn');
|
||||||
cy.get('.ToolbarRow > :nth-child(10)').as('cineBtn');
|
cy.get('.ToolbarRow > :nth-child(10)').as('cineBtn');
|
||||||
cy.get('.expandableToolMenu').as('moreBtn');
|
cy.get('.expandableToolMenu').as('moreBtn');
|
||||||
cy.get('.PluginSwitch > .toolbar-button').as('twodmprBtn');
|
|
||||||
cy.get('.btn-group > .toolbar-button').as('layoutBtn');
|
cy.get('.btn-group > .toolbar-button').as('layoutBtn');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,3 +31,21 @@ export function initCommonElementsAliases() {
|
|||||||
'viewportInfoBottomRight'
|
'viewportInfoBottomRight'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Creating aliases for Routes
|
||||||
|
export function initRouteAliases() {
|
||||||
|
cy.server();
|
||||||
|
cy.route('GET', '/dcm4chee-arc/**/series').as('getStudySeries');
|
||||||
|
}
|
||||||
|
|
||||||
|
//Creating aliases for VTK tools buttons
|
||||||
|
export function initVTKToolsAliases() {
|
||||||
|
cy.get('.PluginSwitch > .toolbar-button').as('twodmprBtn');
|
||||||
|
cy.get('.ToolbarRow > :nth-child(2)').as('crosshairsBtn');
|
||||||
|
cy.get('.ToolbarRow > :nth-child(3)').as('wwwcBtn');
|
||||||
|
cy.get('.ToolbarRow > :nth-child(4)').as('rotateBtn');
|
||||||
|
cy.get('.slab-thickness').as('slabSlider');
|
||||||
|
cy.get('.select-ohif').as('modeDropdown');
|
||||||
|
cy.get('.ohif-check-label').as('modeCheckbox');
|
||||||
|
cy.get('.btn-group > .toolbar-button').as('layoutBtn');
|
||||||
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
|
import '@percy/cypress';
|
||||||
import { DragSimulator } from '../helpers/DragSimulator.js';
|
import { DragSimulator } from '../helpers/DragSimulator.js';
|
||||||
import {
|
import {
|
||||||
initCornerstoneToolsAliases,
|
initCornerstoneToolsAliases,
|
||||||
initCommonElementsAliases,
|
initCommonElementsAliases,
|
||||||
|
initRouteAliases,
|
||||||
|
initVTKToolsAliases,
|
||||||
} from './aliases.js';
|
} from './aliases.js';
|
||||||
|
|
||||||
// ***********************************************
|
// ***********************************************
|
||||||
@@ -36,14 +39,19 @@ import {
|
|||||||
* @param {string} PatientName - Patient name that we would like to search for
|
* @param {string} PatientName - Patient name that we would like to search for
|
||||||
*/
|
*/
|
||||||
Cypress.Commands.add('openStudy', patientName => {
|
Cypress.Commands.add('openStudy', patientName => {
|
||||||
|
cy.initRouteAliases();
|
||||||
cy.visit('/');
|
cy.visit('/');
|
||||||
cy.get('#patientName').type(patientName);
|
cy.get('#patientName').type(patientName);
|
||||||
|
|
||||||
cy.get('.studylistStudy > .patientName')
|
cy.get('.studylistStudy > .patientName', { timeout: 5000 })
|
||||||
.as('patientResult')
|
.contains(patientName)
|
||||||
.then({ timeout: 5000 }, $patientResult => {
|
.click();
|
||||||
cy.contains(patientName).click();
|
|
||||||
});
|
// cy.get('.studylistStudy > .patientName')
|
||||||
|
// .as('patientResult')
|
||||||
|
// .should({ timeout: 3000 }, () => {
|
||||||
|
// cy.contains(patientName).click();
|
||||||
|
// });
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -103,6 +111,12 @@ Cypress.Commands.add(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Cypress.Commands.add('expectMinimumThumbnails', (seriesToWait = 1) => {
|
||||||
|
cy.get('[data-cy=thumbnail-list]', { timeout: 10000 }).should($itemList => {
|
||||||
|
expect($itemList.length >= seriesToWait).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
//Command to wait DICOM image to load into the viewport
|
//Command to wait DICOM image to load into the viewport
|
||||||
Cypress.Commands.add('waitDicomImage', (timeout = 20000) => {
|
Cypress.Commands.add('waitDicomImage', (timeout = 20000) => {
|
||||||
cy.window()
|
cy.window()
|
||||||
@@ -182,21 +196,82 @@ Cypress.Commands.add('initCommonElementsAliases', () => {
|
|||||||
initCommonElementsAliases();
|
initCommonElementsAliases();
|
||||||
});
|
});
|
||||||
|
|
||||||
//Add measurements in the viewport
|
//Initialize aliases for Routes
|
||||||
Cypress.Commands.add('addLengthMeasurement', () => {
|
Cypress.Commands.add('initRouteAliases', () => {
|
||||||
cy.initCornerstoneToolsAliases();
|
initRouteAliases();
|
||||||
cy.get('@lengthBtn').click();
|
});
|
||||||
const firstClick = [150, 100];
|
|
||||||
const secondClick = [130, 170];
|
//Initialize aliases for VTK tools
|
||||||
cy.addLine('.cornerstone-canvas', firstClick, secondClick);
|
Cypress.Commands.add('initVTKToolsAliases', () => {
|
||||||
|
initVTKToolsAliases();
|
||||||
});
|
});
|
||||||
|
|
||||||
//Add measurements in the viewport
|
//Add measurements in the viewport
|
||||||
Cypress.Commands.add('addAngleMeasurement', () => {
|
Cypress.Commands.add(
|
||||||
cy.initCornerstoneToolsAliases();
|
'addLengthMeasurement',
|
||||||
cy.get('@angleBtn').click();
|
(firstClick = [150, 100], secondClick = [130, 170]) => {
|
||||||
const initPos = [180, 390];
|
cy.initCornerstoneToolsAliases();
|
||||||
const midPos = [300, 410];
|
cy.get('@lengthBtn').click();
|
||||||
const finalPos = [180, 450];
|
cy.addLine('@viewport', firstClick, secondClick);
|
||||||
cy.addAngle('.cornerstone-canvas', initPos, midPos, finalPos);
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
//Add measurements in the viewport
|
||||||
|
Cypress.Commands.add(
|
||||||
|
'addAngleMeasurement',
|
||||||
|
(initPos = [180, 390], midPos = [300, 410], finalPos = [180, 450]) => {
|
||||||
|
cy.initCornerstoneToolsAliases();
|
||||||
|
cy.get('@angleBtn').click();
|
||||||
|
cy.addAngle('@viewport', initPos, midPos, finalPos);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests if element is NOT in viewport, or does not exist in DOM
|
||||||
|
*
|
||||||
|
* @param {string} element - element selector string or alias
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
Cypress.Commands.add('isNotInViewport', element => {
|
||||||
|
cy.get(element, { timeout: 3000 }).should($el => {
|
||||||
|
const bottom = Cypress.$(cy.state('window')).height() - 50;
|
||||||
|
const right = Cypress.$(cy.state('window')).width() - 50;
|
||||||
|
|
||||||
|
// If it's not visible, it's not in the viewport
|
||||||
|
if ($el) {
|
||||||
|
const rect = $el[0].getBoundingClientRect();
|
||||||
|
|
||||||
|
// TODO: support leftOf, above
|
||||||
|
const isBeneath = rect.top >= bottom && rect.bottom >= bottom;
|
||||||
|
const isRightOf = rect.left >= right && rect.right >= right;
|
||||||
|
const isNotInViewport = isBeneath && isRightOf;
|
||||||
|
|
||||||
|
expect(isNotInViewport).to.be.true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests if element is in viewport, or it does exist in DOM
|
||||||
|
*
|
||||||
|
* @param {string} element - element selector string or alias
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
Cypress.Commands.add('isInViewport', element => {
|
||||||
|
cy.get(element, { timeout: 3000 }).should($el => {
|
||||||
|
const bottom = Cypress.$(cy.state('window')).height();
|
||||||
|
const right = Cypress.$(cy.state('window')).width();
|
||||||
|
|
||||||
|
// If it's not visible, it's not in the viewport
|
||||||
|
if ($el) {
|
||||||
|
const rect = $el[0].getBoundingClientRect();
|
||||||
|
|
||||||
|
// TODO: support leftOf, above
|
||||||
|
const isBeneath = rect.top < bottom && rect.bottom < bottom;
|
||||||
|
const isRightOf = rect.left < right && rect.right < right;
|
||||||
|
const isInViewport = isBeneath && isRightOf;
|
||||||
|
|
||||||
|
expect(isInViewport).to.be.true;
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/viewer",
|
"name": "@ohif/viewer",
|
||||||
"version": "1.6.2",
|
"version": "1.10.3",
|
||||||
"description": "OHIF Viewer",
|
"description": "OHIF Viewer",
|
||||||
"author": "OHIF Contributors",
|
"author": "OHIF Contributors",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -27,9 +27,9 @@
|
|||||||
"dev:viewer": "yarn run dev",
|
"dev:viewer": "yarn run dev",
|
||||||
"start": "yarn run dev",
|
"start": "yarn run dev",
|
||||||
"test:e2e": "cypress open",
|
"test:e2e": "cypress open",
|
||||||
"test:e2e:ci": "cypress run",
|
"test:e2e:ci": "percy exec -- cypress run",
|
||||||
"test:e2e:dist": "start-server-and-test test:e2e:serve http://localhost:3000 test:e2e:ci",
|
"test:e2e:dist": "start-server-and-test test:e2e:serve http://localhost:3000 test:e2e:ci",
|
||||||
"test:e2e:serve": "serve -l 3000 -s dist",
|
"test:e2e:serve": "serve -n -l 3000 -s dist",
|
||||||
"test:unit": "jest --watchAll",
|
"test:unit": "jest --watchAll",
|
||||||
"test:unit:ci": "jest --ci --runInBand --collectCoverage",
|
"test:unit:ci": "jest --ci --runInBand --collectCoverage",
|
||||||
"prepublishOnly": "yarn run build:package",
|
"prepublishOnly": "yarn run build:package",
|
||||||
@@ -45,14 +45,14 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.5.5",
|
"@babel/runtime": "^7.5.5",
|
||||||
"@ohif/core": "^1.3.1",
|
"@ohif/core": "^1.4.0",
|
||||||
"@ohif/extension-cornerstone": "^2.0.0",
|
"@ohif/extension-cornerstone": "^2.0.0",
|
||||||
"@ohif/extension-dicom-html": "^1.0.0",
|
"@ohif/extension-dicom-html": "^1.0.0",
|
||||||
"@ohif/extension-dicom-microscopy": "^0.50.6",
|
"@ohif/extension-dicom-microscopy": "^0.50.6",
|
||||||
"@ohif/extension-dicom-pdf": "^1.0.0",
|
"@ohif/extension-dicom-pdf": "^1.0.0",
|
||||||
"@ohif/extension-vtk": "^0.52.7",
|
"@ohif/extension-vtk": "^0.52.13",
|
||||||
"@ohif/i18n": "^0.50.5",
|
"@ohif/i18n": "^0.51.0",
|
||||||
"@ohif/ui": "^0.53.4",
|
"@ohif/ui": "^0.55.0",
|
||||||
"@tanem/react-nprogress": "^1.1.25",
|
"@tanem/react-nprogress": "^1.1.25",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"core-js": "^3.2.1",
|
"core-js": "^3.2.1",
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
"redux-oidc": "3.1.x",
|
"redux-oidc": "3.1.x",
|
||||||
"redux-thunk": "^2.3.0",
|
"redux-thunk": "^2.3.0",
|
||||||
"reselect": "^4.0.0",
|
"reselect": "^4.0.0",
|
||||||
"vtk.js": "^11.0.1"
|
"vtk.js": "^11.6.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"cypress": "^3.3.1",
|
"cypress": "^3.3.1",
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import { BrowserRouter as Router } from 'react-router-dom';
|
|||||||
import { getActiveContexts } from './store/layout/selectors.js';
|
import { getActiveContexts } from './store/layout/selectors.js';
|
||||||
import i18n from '@ohif/i18n';
|
import i18n from '@ohif/i18n';
|
||||||
import store from './store';
|
import store from './store';
|
||||||
|
import { SnackbarProvider } from '@ohif/ui';
|
||||||
|
|
||||||
// Contexts
|
// Contexts
|
||||||
import WhiteLabellingContext from './context/WhiteLabellingContext';
|
import WhiteLabellingContext from './context/WhiteLabellingContext';
|
||||||
@@ -99,7 +100,9 @@ class App extends Component {
|
|||||||
<WhiteLabellingContext.Provider
|
<WhiteLabellingContext.Provider
|
||||||
value={this.props.whiteLabelling}
|
value={this.props.whiteLabelling}
|
||||||
>
|
>
|
||||||
<OHIFStandaloneViewer userManager={userManager} />
|
<SnackbarProvider>
|
||||||
|
<OHIFStandaloneViewer userManager={userManager} />
|
||||||
|
</SnackbarProvider>
|
||||||
</WhiteLabellingContext.Provider>
|
</WhiteLabellingContext.Provider>
|
||||||
</Router>
|
</Router>
|
||||||
</UserManagerContext.Provider>
|
</UserManagerContext.Provider>
|
||||||
@@ -116,7 +119,9 @@ class App extends Component {
|
|||||||
<I18nextProvider i18n={i18n}>
|
<I18nextProvider i18n={i18n}>
|
||||||
<Router basename={this.props.routerBasename}>
|
<Router basename={this.props.routerBasename}>
|
||||||
<WhiteLabellingContext.Provider value={this.props.whiteLabelling}>
|
<WhiteLabellingContext.Provider value={this.props.whiteLabelling}>
|
||||||
<OHIFStandaloneViewer />
|
<SnackbarProvider>
|
||||||
|
<OHIFStandaloneViewer />
|
||||||
|
</SnackbarProvider>
|
||||||
</WhiteLabellingContext.Provider>
|
</WhiteLabellingContext.Provider>
|
||||||
</Router>
|
</Router>
|
||||||
</I18nextProvider>
|
</I18nextProvider>
|
||||||
|
|||||||
@@ -96,3 +96,27 @@
|
|||||||
color: #91b9cd;
|
color: #91b9cd;
|
||||||
margin-right: 1rem;
|
margin-right: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-versionInfo {
|
||||||
|
display: inline-block;
|
||||||
|
color: black;
|
||||||
|
background: #9ccef9;
|
||||||
|
padding: 0px 8px;
|
||||||
|
border-radius: 16px;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-left: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-versionInfoHome {
|
||||||
|
display: block;
|
||||||
|
color: black;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 16px;
|
||||||
|
right: 0;
|
||||||
|
background: #9ccef9;
|
||||||
|
padding: 0px 8px;
|
||||||
|
border-radius: 16px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
@@ -101,6 +101,14 @@ class Header extends Component {
|
|||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<span
|
||||||
|
className={`${
|
||||||
|
this.props.home ? 'header-versionInfoHome' : 'header-versionInfo'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
v{process.env.VERSION_NUMBER}
|
||||||
|
</span>
|
||||||
|
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
|
|
||||||
{showStudyList && !this.props.home && (
|
{showStudyList && !this.props.home && (
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
// import OHIF from '@ohif/core';
|
import OHIF from '@ohif/core';
|
||||||
|
import React from 'react';
|
||||||
import PluginSwitch from './PluginSwitch.js';
|
import PluginSwitch from './PluginSwitch.js';
|
||||||
import { commandsManager } from './../App.js';
|
import { commandsManager } from './../App.js';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
// const { setLayout } = OHIF.redux.actions;
|
const { setLayout } = OHIF.redux.actions;
|
||||||
|
|
||||||
|
const ConnectedPluginSwitch = props => {
|
||||||
|
return <PluginSwitch {...props} />;
|
||||||
|
};
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = state => {
|
||||||
const { activeViewportIndex, layout, viewportSpecificData } = state.viewports;
|
const { activeViewportIndex, layout, viewportSpecificData } = state.viewports;
|
||||||
@@ -15,13 +20,13 @@ const mapStateToProps = state => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// const mapDispatchToProps = dispatch => {
|
const mapDispatchToProps = dispatch => {
|
||||||
// return {
|
return {
|
||||||
// setLayout: data => {
|
setLayout: data => {
|
||||||
// dispatch(setLayout(data));
|
dispatch(setLayout(data));
|
||||||
// }
|
},
|
||||||
// };
|
};
|
||||||
// };
|
};
|
||||||
|
|
||||||
/*function setSingleLayoutData(originalArray, viewportIndex, data) {
|
/*function setSingleLayoutData(originalArray, viewportIndex, data) {
|
||||||
const viewports = originalArray.slice();
|
const viewports = originalArray.slice();
|
||||||
@@ -33,23 +38,35 @@ const mapStateToProps = state => {
|
|||||||
}*/
|
}*/
|
||||||
|
|
||||||
const mergeProps = (propsFromState, propsFromDispatch, ownProps) => {
|
const mergeProps = (propsFromState, propsFromDispatch, ownProps) => {
|
||||||
//const { activeViewportIndex, layout } = propsFromState;
|
const { activeViewportIndex, viewportSpecificData } = propsFromState;
|
||||||
//const { setLayout } = propsFromDispatch;
|
const { studies } = ownProps;
|
||||||
|
const { setLayout } = propsFromDispatch;
|
||||||
|
|
||||||
// TODO: Do not display certain options if the current display set
|
|
||||||
// cannot be displayed using these view types
|
|
||||||
const mpr = () => {
|
const mpr = () => {
|
||||||
commandsManager.runCommand('mpr2d');
|
commandsManager.runCommand('mpr2d');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const exitMpr = () => {
|
||||||
|
const layout = {
|
||||||
|
numRows: 1,
|
||||||
|
numColumns: 1,
|
||||||
|
viewports: [{ plugin: 'cornerstone' }],
|
||||||
|
};
|
||||||
|
|
||||||
|
setLayout(layout);
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
mpr,
|
mpr,
|
||||||
|
exitMpr,
|
||||||
|
activeViewportIndex,
|
||||||
|
viewportSpecificData,
|
||||||
|
studies,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const ConnectedPluginSwitch = connect(
|
export default connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
null, // mapDispatchToProps
|
mapDispatchToProps,
|
||||||
mergeProps
|
mergeProps
|
||||||
)(PluginSwitch);
|
)(ConnectedPluginSwitch);
|
||||||
|
|
||||||
export default ConnectedPluginSwitch;
|
|
||||||
@@ -6,17 +6,101 @@ import './PluginSwitch.css';
|
|||||||
class PluginSwitch extends Component {
|
class PluginSwitch extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
mpr: PropTypes.func,
|
mpr: PropTypes.func,
|
||||||
|
activeViewportIndex: PropTypes.number,
|
||||||
|
viewportSpecificData: PropTypes.object,
|
||||||
|
studies: PropTypes.array,
|
||||||
|
exitMpr: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {};
|
static defaultProps = {};
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isPlugSwitchOn: false,
|
||||||
|
label: '2D MPR',
|
||||||
|
icon: 'cube',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClick = () => {
|
||||||
|
if (this.state.isPlugSwitchOn) {
|
||||||
|
this.setState({
|
||||||
|
isPlugSwitchOn: false,
|
||||||
|
label: '2D MPR',
|
||||||
|
icon: 'cube',
|
||||||
|
});
|
||||||
|
this.props.exitMpr();
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
isPlugSwitchOn: true,
|
||||||
|
label: 'Exit 2D MPR',
|
||||||
|
icon: 'times',
|
||||||
|
});
|
||||||
|
this.props.mpr();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { label, icon } = this.state;
|
||||||
|
|
||||||
|
// Render exit mpr if switched on, otherwise check if mpr button should be displayed.
|
||||||
|
const shouldRender =
|
||||||
|
this.state.isPlugSwitchOn || _shouldRenderMpr2DButton.call(this);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="PluginSwitch">
|
<>
|
||||||
<ToolbarButton label="2D MPR" icon="cube" onClick={this.props.mpr} />
|
{shouldRender && (
|
||||||
</div>
|
<div className="PluginSwitch">
|
||||||
|
<ToolbarButton
|
||||||
|
label={label}
|
||||||
|
icon={icon}
|
||||||
|
onClick={this.handleClick}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _shouldRenderMpr2DButton() {
|
||||||
|
const { viewportSpecificData, studies, activeViewportIndex } = this.props;
|
||||||
|
|
||||||
|
if (!viewportSpecificData[activeViewportIndex]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { displaySetInstanceUid, studyInstanceUid } = viewportSpecificData[
|
||||||
|
activeViewportIndex
|
||||||
|
];
|
||||||
|
|
||||||
|
const displaySet = _getDisplaySet(
|
||||||
|
studies,
|
||||||
|
studyInstanceUid,
|
||||||
|
displaySetInstanceUid
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!displaySet) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return displaySet.isReconstructable;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getDisplaySet(studies, studyInstanceUid, displaySetInstanceUid) {
|
||||||
|
const study = studies.find(
|
||||||
|
study => study.studyInstanceUid === studyInstanceUid
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!study) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const displaySet = study.displaySets.find(set => {
|
||||||
|
return set.displaySetInstanceUid === displaySetInstanceUid;
|
||||||
|
});
|
||||||
|
|
||||||
|
return displaySet;
|
||||||
|
}
|
||||||
|
|
||||||
export default PluginSwitch;
|
export default PluginSwitch;
|
||||||
@@ -25,6 +25,7 @@ class ToolbarRow extends Component {
|
|||||||
selectedRightSidePanel: PropTypes.string.isRequired,
|
selectedRightSidePanel: PropTypes.string.isRequired,
|
||||||
handleSidePanelChange: PropTypes.func,
|
handleSidePanelChange: PropTypes.func,
|
||||||
activeContexts: PropTypes.arrayOf(PropTypes.string).isRequired,
|
activeContexts: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||||
|
studies: PropTypes.array,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -128,7 +129,7 @@ class ToolbarRow extends Component {
|
|||||||
</div>
|
</div>
|
||||||
{buttonComponents}
|
{buttonComponents}
|
||||||
<ConnectedLayoutButton />
|
<ConnectedLayoutButton />
|
||||||
<ConnectedPluginSwitch />
|
<ConnectedPluginSwitch studies={this.props.studies} />
|
||||||
<div
|
<div
|
||||||
className="pull-right m-t-1 rm-x-1"
|
className="pull-right m-t-1 rm-x-1"
|
||||||
style={{ marginLeft: 'auto' }}
|
style={{ marginLeft: 'auto' }}
|
||||||
@@ -229,7 +230,6 @@ function _getButtonComponents(toolbarButtons, activeButtons) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A handy way for us to handle different button types. IE. firing commands for
|
* A handy way for us to handle different button types. IE. firing commands for
|
||||||
* buttons, or initiation built in behavior.
|
* buttons, or initiation built in behavior.
|
||||||
|
|||||||
@@ -279,6 +279,7 @@ class Viewer extends Component {
|
|||||||
|
|
||||||
this.setState(updatedState);
|
this.setState(updatedState);
|
||||||
}}
|
}}
|
||||||
|
studies={this.props.studies}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/*<ConnectedStudyLoadingMonitor studies={this.props.studies} />*/}
|
{/*<ConnectedStudyLoadingMonitor studies={this.props.studies} />*/}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* Sizes */
|
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
|
/* Sizes */
|
||||||
|
|
||||||
--top-bar-height: 40px;
|
--top-bar-height: 40px;
|
||||||
--top-bar-expanded-height: 160px;
|
--top-bar-expanded-height: 160px;
|
||||||
--toolbar-height: 78px;
|
--toolbar-height: 78px;
|
||||||
@@ -9,19 +9,26 @@
|
|||||||
--right-sidepanel-menu-width: 323px;
|
--right-sidepanel-menu-width: 323px;
|
||||||
--study-list-padding: 8%;
|
--study-list-padding: 8%;
|
||||||
--study-list-padding-medium-screen: 10px;
|
--study-list-padding-medium-screen: 10px;
|
||||||
}
|
--snackbar-size: 344px;
|
||||||
|
|
||||||
/* Transitions */
|
/* Transitions */
|
||||||
|
|
||||||
:root {
|
|
||||||
--transition-duration: 0.3s;
|
--transition-duration: 0.3s;
|
||||||
--transition-effect: ease;
|
--transition-effect: ease;
|
||||||
--sidepanel-transition: all 0.3s ease;
|
--sidepanel-transition: all 0.3s ease;
|
||||||
}
|
|
||||||
|
|
||||||
/* Thicknesses */
|
/* Thicknesses */
|
||||||
|
|
||||||
:root {
|
|
||||||
--viewport-border-thickness: 1px;
|
--viewport-border-thickness: 1px;
|
||||||
--ui-border-thickness: 1px;
|
--ui-border-thickness: 1px;
|
||||||
|
|
||||||
|
/** Colors **/
|
||||||
|
|
||||||
|
--snackbar-success: rgba(94, 164, 0, 0.9);
|
||||||
|
--snackbar-error: rgba(236, 61, 61, 0.9);
|
||||||
|
--snackbar-warning: rgba(235, 173, 23, 0.9);
|
||||||
|
--snackbar-info: rgba(54, 156, 199, 0.9);
|
||||||
|
|
||||||
|
/** z-Index **/
|
||||||
|
--snackbar-zIndex: 10;
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user