Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f26940005 | ||
|
|
9d08e81aa5 | ||
|
|
1a539d04c2 | ||
|
|
5cc78c867f | ||
|
|
cefa480979 | ||
|
|
66537bf1b9 | ||
|
|
862d426c71 | ||
|
|
e62f403fe9 | ||
|
|
dea0ceab57 | ||
|
|
a6c1e6c717 | ||
|
|
b03739428f | ||
|
|
2a52d9f05d | ||
|
|
d5e21bb5bb |
No files matched your search
+63
-4
@@ -13,7 +13,7 @@ version: 2.1
|
|||||||
##
|
##
|
||||||
orbs:
|
orbs:
|
||||||
codecov: codecov/codecov@1.0.5
|
codecov: codecov/codecov@1.0.5
|
||||||
cypress: cypress-io/cypress@1.10.0
|
cypress: cypress-io/cypress@1.11.0
|
||||||
|
|
||||||
defaults: &defaults
|
defaults: &defaults
|
||||||
docker:
|
docker:
|
||||||
@@ -198,7 +198,7 @@ jobs:
|
|||||||
- run: chmod +x ~/repo/.circleci/build-and-publish-docs.sh
|
- run: chmod +x ~/repo/.circleci/build-and-publish-docs.sh
|
||||||
- run: ~/repo/.circleci/build-and-publish-docs.sh
|
- run: ~/repo/.circleci/build-and-publish-docs.sh
|
||||||
|
|
||||||
DOCKER_PUBLISH:
|
DOCKER_MASTER_PUBLISH:
|
||||||
<<: *defaults
|
<<: *defaults
|
||||||
steps:
|
steps:
|
||||||
- attach_workspace:
|
- attach_workspace:
|
||||||
@@ -227,6 +227,43 @@ jobs:
|
|||||||
docker push ohif/$IMAGE_NAME:latest
|
docker push ohif/$IMAGE_NAME:latest
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
DOCKER_PR_PUBLISH:
|
||||||
|
<<: *defaults
|
||||||
|
steps:
|
||||||
|
# Enable yarn workspaces
|
||||||
|
- run: yarn config set workspaces-experimental true
|
||||||
|
|
||||||
|
# Checkout code and ALL Git Tags
|
||||||
|
- checkout:
|
||||||
|
post:
|
||||||
|
- git fetch --all
|
||||||
|
|
||||||
|
- restore_cache:
|
||||||
|
name: Restore Yarn and Cypress Package Cache
|
||||||
|
keys:
|
||||||
|
# when lock file changes, use increasingly general patterns to restore cache
|
||||||
|
- yarn-packages-{{ checksum "yarn.lock" }}
|
||||||
|
- yarn-packages-
|
||||||
|
|
||||||
|
- run:
|
||||||
|
name: Install Dependencies
|
||||||
|
command: yarn install --frozen-lockfile
|
||||||
|
|
||||||
|
- setup_remote_docker:
|
||||||
|
docker_layer_caching: false
|
||||||
|
|
||||||
|
- run:
|
||||||
|
name: Build and push Docker image
|
||||||
|
command: |
|
||||||
|
# Remove npm config
|
||||||
|
rm -f ./.npmrc
|
||||||
|
# Set our version number using vars
|
||||||
|
echo $CIRCLE_BUILD_NUM
|
||||||
|
# Build our image, auth, and push
|
||||||
|
docker build --tag ohif/viewer:PR_BUILD-$CIRCLE_BUILD_NUM .
|
||||||
|
echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin
|
||||||
|
docker push ohif/viewer:PR_BUILD-$CIRCLE_BUILD_NUM
|
||||||
|
|
||||||
VIEWER_DEMO_BUILD:
|
VIEWER_DEMO_BUILD:
|
||||||
<<: *defaults
|
<<: *defaults
|
||||||
steps:
|
steps:
|
||||||
@@ -288,10 +325,13 @@ workflows:
|
|||||||
# E2E: PWA
|
# E2E: PWA
|
||||||
- cypress/run:
|
- cypress/run:
|
||||||
name: 'E2E: PWA'
|
name: 'E2E: PWA'
|
||||||
|
executor: cypress/browsers-chrome76
|
||||||
|
browser: chrome
|
||||||
pre-steps:
|
pre-steps:
|
||||||
- run: 'rm -rf ~/.yarn && npm i -g yarn && yarn -v && yarn global
|
- run: 'rm -rf ~/.yarn && npm i -g yarn && yarn -v && yarn global
|
||||||
add wait-on' # Use yarn latest
|
add wait-on' # Use yarn latest
|
||||||
yarn: true
|
yarn: true
|
||||||
|
record: false
|
||||||
store_artifacts: false
|
store_artifacts: false
|
||||||
working_directory: platform/viewer
|
working_directory: platform/viewer
|
||||||
build: npx cross-env QUICK_BUILD=true yarn run build
|
build: npx cross-env QUICK_BUILD=true yarn run build
|
||||||
@@ -307,10 +347,13 @@ workflows:
|
|||||||
# E2E: script-tag
|
# E2E: script-tag
|
||||||
- cypress/run:
|
- cypress/run:
|
||||||
name: 'E2E: Script Tag'
|
name: 'E2E: Script Tag'
|
||||||
|
executor: cypress/browsers-chrome76
|
||||||
|
browser: chrome
|
||||||
pre-steps:
|
pre-steps:
|
||||||
- run: 'rm -rf ~/.yarn && npm i -g yarn && yarn -v && yarn global
|
- run: 'rm -rf ~/.yarn && npm i -g yarn && yarn -v && yarn global
|
||||||
add wait-on' # Use yarn latest
|
add wait-on' # Use yarn latest
|
||||||
yarn: true
|
yarn: true
|
||||||
|
record: false
|
||||||
store_artifacts: false
|
store_artifacts: false
|
||||||
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
|
||||||
@@ -325,6 +368,17 @@ workflows:
|
|||||||
requires:
|
requires:
|
||||||
- UNIT_TESTS
|
- UNIT_TESTS
|
||||||
|
|
||||||
|
PR_OPTIONAL_DOCKER_PUBLISH:
|
||||||
|
jobs:
|
||||||
|
# https://circleci.com/docs/2.0/workflows/#holding-a-workflow-for-a-manual-approval
|
||||||
|
- AWAIT_APPROVAL:
|
||||||
|
type: approval
|
||||||
|
# Update hub.docker.org
|
||||||
|
- DOCKER_PR_PUBLISH:
|
||||||
|
context: Docker Hub
|
||||||
|
requires:
|
||||||
|
- AWAIT_APPROVAL
|
||||||
|
|
||||||
# MERGE TO MASTER
|
# MERGE TO MASTER
|
||||||
RELEASE:
|
RELEASE:
|
||||||
jobs:
|
jobs:
|
||||||
@@ -335,11 +389,13 @@ workflows:
|
|||||||
# E2E: PWA + Persist
|
# E2E: PWA + Persist
|
||||||
- cypress/run:
|
- cypress/run:
|
||||||
name: 'E2E: PWA'
|
name: 'E2E: PWA'
|
||||||
|
executor: cypress/browsers-chrome76
|
||||||
|
browser: chrome
|
||||||
pre-steps:
|
pre-steps:
|
||||||
- run: 'rm -rf ~/.yarn && npm i -g yarn && yarn -v && yarn global
|
- run: 'rm -rf ~/.yarn && npm i -g yarn && yarn -v && yarn global
|
||||||
add wait-on' # Use yarn latest
|
add wait-on' # Use yarn latest
|
||||||
yarn: true
|
yarn: true
|
||||||
record: true
|
record: false
|
||||||
store_artifacts: true
|
store_artifacts: true
|
||||||
working_directory: platform/viewer
|
working_directory: platform/viewer
|
||||||
build: npx cross-env QUICK_BUILD=true yarn run build
|
build: npx cross-env QUICK_BUILD=true yarn run build
|
||||||
@@ -358,10 +414,13 @@ workflows:
|
|||||||
# E2E: script-tag
|
# E2E: script-tag
|
||||||
- cypress/run:
|
- cypress/run:
|
||||||
name: 'E2E: Script Tag'
|
name: 'E2E: Script Tag'
|
||||||
|
executor: cypress/browsers-chrome76
|
||||||
|
browser: chrome
|
||||||
pre-steps:
|
pre-steps:
|
||||||
- run: 'rm -rf ~/.yarn && npm i -g yarn && yarn -v && yarn global
|
- run: 'rm -rf ~/.yarn && npm i -g yarn && yarn -v && yarn global
|
||||||
add wait-on' # Use yarn latest
|
add wait-on' # Use yarn latest
|
||||||
yarn: true
|
yarn: true
|
||||||
|
record: false
|
||||||
store_artifacts: false
|
store_artifacts: false
|
||||||
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
|
||||||
@@ -396,6 +455,6 @@ workflows:
|
|||||||
requires:
|
requires:
|
||||||
- VIEWER_DEMO_BUILD
|
- VIEWER_DEMO_BUILD
|
||||||
# Update hub.docker.org
|
# Update hub.docker.org
|
||||||
- DOCKER_PUBLISH:
|
- DOCKER_MASTER_PUBLISH:
|
||||||
requires:
|
requires:
|
||||||
- NPM_PUBLISH
|
- NPM_PUBLISH
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
### Request Checklist
|
||||||
|
|
||||||
|
- [] Brief description of changes
|
||||||
|
- [] Links to any relevant issues
|
||||||
|
- [] Required status checks are passing
|
||||||
|
- [] `@mention` a maintainer to request a review
|
||||||
|
|
||||||
|
#### Special
|
||||||
|
|
||||||
|
> If this PR is not on a branch for this repo, it is "untrusted" and does not
|
||||||
|
> have access to env vars. Reviewers can kick off a trusted docker deploy by:
|
||||||
|
>
|
||||||
|
> 1. Verifying there are no malicious changes to CI build
|
||||||
|
> 2. Using the [git-push-fork-to-upstream-branch][script] script
|
||||||
|
> 3. Executing the manual approval step in the CI workflow
|
||||||
|
|
||||||
|
- [] Request docker publish of PR
|
||||||
|
|
||||||
|
_Want to improve this process? Consider PR'ing the ability for reviewers to add
|
||||||
|
a label to trigger this process._
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Links
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
|
[blog]: https://circleci.com/blog/triggering-trusted-ci-jobs-on-untrusted-forks/
|
||||||
|
[script]: https://github.com/jklukas/git-push-fork-to-upstream-branch
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
@@ -44,6 +44,7 @@ RUN yarn config set workspaces-experimental true
|
|||||||
RUN yarn install
|
RUN yarn install
|
||||||
|
|
||||||
ENV PATH /usr/src/app/node_modules/.bin:$PATH
|
ENV PATH /usr/src/app/node_modules/.bin:$PATH
|
||||||
|
ENV QUICK_BUILD true
|
||||||
# ENV GENERATE_SOURCEMAP=false
|
# ENV GENERATE_SOURCEMAP=false
|
||||||
# ENV REACT_APP_CONFIG=config/default.js
|
# ENV REACT_APP_CONFIG=config/default.js
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,33 @@
|
|||||||
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.15](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.14...@ohif/extension-vtk@0.52.15) (2019-10-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* ⚡️ Throttle UI refresh of synced WL value in vtk port ([#1070](https://github.com/OHIF/Viewers/issues/1070)) ([9d08e81](https://github.com/OHIF/Viewers/commit/9d08e81aa5def63a5e34f464aae099f7c29f0ac5))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.52.14](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.13...@ohif/extension-vtk@0.52.14) (2019-10-22)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [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)
|
## [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
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/extension-vtk",
|
"name": "@ohif/extension-vtk",
|
||||||
"version": "0.52.12",
|
"version": "0.52.15",
|
||||||
"description": "OHIF extension for VTK.js",
|
"description": "OHIF extension for VTK.js",
|
||||||
"author": "OHIF",
|
"author": "OHIF",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -48,11 +48,12 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.5.5",
|
"@babel/runtime": "^7.5.5",
|
||||||
"react-vtkjs-viewport": "^0.3.0"
|
"lodash.throttle": "^4.1.1",
|
||||||
|
"react-vtkjs-viewport": "^0.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ohif/core": "^1.4.0",
|
"@ohif/core": "^1.4.0",
|
||||||
"@ohif/ui": "^0.55.0",
|
"@ohif/ui": "^0.56.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",
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
@@ -182,7 +181,6 @@ class OHIFVTKViewport extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { vtkImageData, imageMetaData0 } = imageDataObject;
|
const { vtkImageData, imageMetaData0 } = imageDataObject;
|
||||||
|
|
||||||
const { windowWidth, windowCenter, modality } = imageMetaData0;
|
const { windowWidth, windowCenter, modality } = imageMetaData0;
|
||||||
|
|
||||||
let lower;
|
let lower;
|
||||||
@@ -208,18 +206,17 @@ class OHIFVTKViewport extends Component {
|
|||||||
.getRGBTransferFunction(0)
|
.getRGBTransferFunction(0)
|
||||||
.setRange(lower, upper);
|
.setRange(lower, upper);
|
||||||
|
|
||||||
// TODO: Should look into implementing autoAdjustSampleDistance in vtk
|
|
||||||
// Manually changing the sample distance here by a couple orders of
|
|
||||||
// Magnitude in either direction makes no difference.
|
|
||||||
// Also setting volumeMapper.setMaximumSamplesPerRay(10000)
|
|
||||||
// Prevents the warning of too many samples, but the resolution isn't improved.
|
|
||||||
|
|
||||||
const spacing = vtkImageData.getSpacing();
|
const spacing = vtkImageData.getSpacing();
|
||||||
|
// Set the sample distance to half the mean length of one side. This is where the divide by 6 comes from.
|
||||||
// https://github.com/Kitware/VTK/blob/6b559c65bb90614fb02eb6d1b9e3f0fca3fe4b0b/Rendering/VolumeOpenGL2/vtkSmartVolumeMapper.cxx#L344
|
// https://github.com/Kitware/VTK/blob/6b559c65bb90614fb02eb6d1b9e3f0fca3fe4b0b/Rendering/VolumeOpenGL2/vtkSmartVolumeMapper.cxx#L344
|
||||||
const sampleDistance = (spacing[0] + spacing[1] + spacing[2]) / 6.0;
|
const sampleDistance = (spacing[0] + spacing[1] + spacing[2]) / 6;
|
||||||
|
|
||||||
volumeMapper.setSampleDistance(sampleDistance);
|
volumeMapper.setSampleDistance(sampleDistance);
|
||||||
|
|
||||||
|
// Be generous to surpress warnings, as the logging really hurts performance.
|
||||||
|
// TODO: maybe we should auto adjust samples to 1000.
|
||||||
|
volumeMapper.setMaximumSamplesPerRay(4000);
|
||||||
|
|
||||||
volumeCache[displaySetInstanceUid] = volumeActor;
|
volumeCache[displaySetInstanceUid] = volumeActor;
|
||||||
|
|
||||||
return volumeActor;
|
return volumeActor;
|
||||||
@@ -251,6 +248,8 @@ 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) {
|
||||||
@@ -294,10 +293,6 @@ class OHIFVTKViewport extends Component {
|
|||||||
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;
|
||||||
|
|||||||
@@ -4,66 +4,17 @@ 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';
|
||||||
|
import throttle from 'lodash.throttle';
|
||||||
|
|
||||||
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 +56,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 +63,6 @@ function _setView(api, sliceNormal, viewUp) {
|
|||||||
renderWindow.render();
|
renderWindow.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
function switchMPRInteractors(api, istyle) {
|
|
||||||
const renderWindow = api.genericRenderWindow.getRenderWindow();
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
const interactor = renderWindow.getInteractor();
|
|
||||||
|
|
||||||
interactor.setInteractorStyle(istyle);
|
|
||||||
|
|
||||||
// TODO: Not sure why this is required the second time this function is called
|
|
||||||
istyle.setInteractor(interactor);
|
|
||||||
|
|
||||||
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 +89,56 @@ 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: () => {
|
||||||
|
function updateVOI(apis, windowWidth, windowCenter) {
|
||||||
|
apis.forEach(api => {
|
||||||
|
api.updateVOI(windowWidth, windowCenter);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const throttledUpdateVOIs = throttle(updateVOI, 16, { trailing: true }); // ~ 60 fps
|
||||||
|
|
||||||
|
const callbacks = {
|
||||||
|
setOnLevelsChanged: ({ windowCenter, windowWidth }) => {
|
||||||
|
apis.forEach(api => {
|
||||||
|
const renderWindow = api.genericRenderWindow.getRenderWindow();
|
||||||
|
|
||||||
|
renderWindow.render();
|
||||||
|
});
|
||||||
|
|
||||||
|
throttledUpdateVOIs(apis, 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 +146,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 +172,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);
|
||||||
|
|
||||||
@@ -264,28 +183,8 @@ const actions = {
|
|||||||
const displaySet =
|
const displaySet =
|
||||||
viewports.viewportSpecificData[viewports.activeViewportIndex];
|
viewports.viewportSpecificData[viewports.activeViewportIndex];
|
||||||
|
|
||||||
// TODO -> Clean this logic up a bit.
|
// Get current VOI if cornerstone viewport.
|
||||||
const cornerstoneElement = cornerstone.getEnabledElement(displaySet.dom);
|
const cornerstoneVOI = getVOIFromCornerstoneViewport(displaySet);
|
||||||
|
|
||||||
let cornerstoneVOI;
|
|
||||||
|
|
||||||
if (cornerstoneElement) {
|
|
||||||
const imageId = cornerstoneElement.image.imageId;
|
|
||||||
|
|
||||||
const { modality } = cornerstone.metaData.get(
|
|
||||||
'generalSeriesModule',
|
|
||||||
imageId
|
|
||||||
);
|
|
||||||
|
|
||||||
if (modality !== 'PT') {
|
|
||||||
const { windowWidth, windowCenter } = cornerstoneElement.viewport.voi;
|
|
||||||
|
|
||||||
cornerstoneVOI = {
|
|
||||||
windowWidth,
|
|
||||||
windowCenter,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const viewportProps = [
|
const viewportProps = [
|
||||||
{
|
{
|
||||||
@@ -295,7 +194,6 @@ const actions = {
|
|||||||
viewUp: [0, -1, 0],
|
viewUp: [0, -1, 0],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
// Sagital
|
// Sagital
|
||||||
orientation: {
|
orientation: {
|
||||||
@@ -312,77 +210,29 @@ const actions = {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
let apiByViewport;
|
|
||||||
try {
|
try {
|
||||||
apiByViewport = await setMPRLayout(displaySet, viewportProps);
|
apis = await setMPRLayout(displaySet, viewportProps, 1, 3);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
apis = apiByViewport;
|
|
||||||
|
|
||||||
const rgbTransferFunction = apiByViewport[0].volumes[0]
|
|
||||||
.getProperty()
|
|
||||||
.getRGBTransferFunction(0);
|
|
||||||
|
|
||||||
if (cornerstoneVOI) {
|
if (cornerstoneVOI) {
|
||||||
const { windowWidth, windowCenter } = cornerstoneVOI;
|
setVOI(cornerstoneVOI);
|
||||||
const lower = windowCenter - windowWidth / 2.0;
|
|
||||||
const upper = windowCenter + windowWidth / 2.0;
|
|
||||||
|
|
||||||
rgbTransferFunction.setRange(lower, upper);
|
|
||||||
|
|
||||||
apiByViewport.forEach(api => {
|
|
||||||
api.updateVOI(windowWidth, windowCenter);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const onModifiedSubscription = rgbTransferFunction.onModified(() => {
|
// Add widgets and set default interactorStyle of each viewport.
|
||||||
const range = rgbTransferFunction.getMappingRange();
|
apis.forEach((api, apiIndex) => {
|
||||||
const windowWidth = Math.abs(range[1] - range[0]);
|
api.addSVGWidget(
|
||||||
const windowCenter = range[0] + windowWidth / 2;
|
vtkSVGCrosshairsWidget.newInstance(),
|
||||||
|
'crosshairsWidget'
|
||||||
apiByViewport.forEach(api => {
|
);
|
||||||
const renderWindow = api.genericRenderWindow.getRenderWindow();
|
|
||||||
|
|
||||||
renderWindow.render();
|
|
||||||
|
|
||||||
api.updateVOI(windowWidth, windowCenter);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
vtkViewportSubscriptionManager.pushSubscription(0, onModifiedSubscription);
|
|
||||||
|
|
||||||
apiByViewport.forEach((api, index) => {
|
|
||||||
const renderWindow = api.genericRenderWindow.getRenderWindow();
|
|
||||||
const renderer = api.genericRenderWindow.getRenderer();
|
|
||||||
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
|
|
||||||
const orientation = api.getOrientation();
|
|
||||||
|
|
||||||
istyle.setViewUp(...orientation.viewUp);
|
|
||||||
istyle.setSliceNormal(...orientation.sliceNormal);
|
|
||||||
|
|
||||||
renderWindow.render();
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -472,3 +322,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);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ export default function setMPRLayout(
|
|||||||
displaySet,
|
displaySet,
|
||||||
viewportPropsArray,
|
viewportPropsArray,
|
||||||
numRows = 1,
|
numRows = 1,
|
||||||
numColumns = 3
|
numColumns = 1
|
||||||
) {
|
) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const viewports = [];
|
const viewports = [];
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -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.56.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.55.0...@ohif/ui@0.56.0) (2019-10-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 🎸 Load spinner when selecting gcloud store. Add key on td ([#1034](https://github.com/OHIF/Viewers/issues/1034)) ([e62f403](https://github.com/OHIF/Viewers/commit/e62f403fe9e3df56713128e3d59045824b086d8d)), closes [#1057](https://github.com/OHIF/Viewers/issues/1057)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [0.55.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.54.0...@ohif/ui@0.55.0) (2019-10-15)
|
# [0.55.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.54.0...@ohif/ui@0.55.0) (2019-10-15)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/ui",
|
"name": "@ohif/ui",
|
||||||
"version": "0.55.0",
|
"version": "0.56.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",
|
||||||
|
|||||||
@@ -11,11 +11,79 @@ import { StudylistToolbar } from './StudyListToolbar.js';
|
|||||||
import { isInclusivelyBeforeDay } from 'react-dates';
|
import { isInclusivelyBeforeDay } from 'react-dates';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import debounce from 'lodash.debounce';
|
import debounce from 'lodash.debounce';
|
||||||
|
import isEqual from 'lodash.isequal';
|
||||||
import { withTranslation } from '../../utils/LanguageProvider';
|
import { withTranslation } from '../../utils/LanguageProvider';
|
||||||
|
|
||||||
const today = moment();
|
const today = moment();
|
||||||
const lastWeek = moment().subtract(7, 'day');
|
const lastWeek = moment().subtract(7, 'day');
|
||||||
const lastMonth = moment().subtract(1, 'month');
|
const lastMonth = moment().subtract(1, 'month');
|
||||||
|
function getPaginationFragment(
|
||||||
|
props,
|
||||||
|
searchData,
|
||||||
|
nextPageCb,
|
||||||
|
prevPageCb,
|
||||||
|
changeRowsPerPageCb
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<PaginationArea
|
||||||
|
pageOptions={props.pageOptions}
|
||||||
|
currentPage={searchData.currentPage}
|
||||||
|
nextPageFunc={nextPageCb}
|
||||||
|
prevPageFunc={prevPageCb}
|
||||||
|
onRowsPerPageChange={changeRowsPerPageCb}
|
||||||
|
rowsPerPage={searchData.rowsPerPage}
|
||||||
|
recordCount={props.studies.length}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTableMeta(translate) {
|
||||||
|
return {
|
||||||
|
patientName: {
|
||||||
|
displayText: translate('PatientName'),
|
||||||
|
sort: 0,
|
||||||
|
},
|
||||||
|
patientId: {
|
||||||
|
displayText: translate('MRN'),
|
||||||
|
sort: 0,
|
||||||
|
},
|
||||||
|
accessionNumber: {
|
||||||
|
displayText: translate('AccessionNumber'),
|
||||||
|
sort: 0,
|
||||||
|
},
|
||||||
|
studyDate: {
|
||||||
|
displayText: translate('StudyDate'),
|
||||||
|
inputType: 'date-range',
|
||||||
|
sort: 0,
|
||||||
|
},
|
||||||
|
modalities: {
|
||||||
|
displayText: translate('Modality'),
|
||||||
|
sort: 0,
|
||||||
|
},
|
||||||
|
studyDescription: {
|
||||||
|
displayText: translate('StudyDescription'),
|
||||||
|
sort: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNoListFragment(translate, studies, error, loading) {
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="loading">
|
||||||
|
<StudyListLoadingText />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else if (error) {
|
||||||
|
return (
|
||||||
|
<div className="notFound">
|
||||||
|
{translate('There was an error fetching studies')}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else if (!studies.length) {
|
||||||
|
return <div className="notFound">{translate('No matching results')}</div>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class StudyList extends Component {
|
class StudyList extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@@ -111,24 +179,27 @@ class StudyList extends Component {
|
|||||||
getBlurHandler(key) {
|
getBlurHandler(key) {
|
||||||
return event => {
|
return event => {
|
||||||
this.delayedSearch.cancel();
|
this.delayedSearch.cancel();
|
||||||
this.setSearchData(key, event.target.value, this.search);
|
this.setSearchData(key, event.target.value);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
setSearchData(key, value, callback) {
|
setSearchData(key, value) {
|
||||||
const searchData = this.state.searchData;
|
const searchData = { ...this.state.searchData };
|
||||||
searchData[key] = value;
|
searchData[key] = value;
|
||||||
this.setState({ searchData }, callback);
|
|
||||||
|
if (!isEqual(searchData[key], this.state.searchData[key])) {
|
||||||
|
this.setState({ ...this.state, searchData });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setSearchDataBatch(keyValues, callback) {
|
setSearchDataBatch(keyValues) {
|
||||||
const searchData = this.state.searchData;
|
const searchData = { ...this.state.searchData };
|
||||||
|
|
||||||
Object.keys(keyValues).forEach(key => {
|
Object.keys(keyValues).forEach(key => {
|
||||||
searchData[key] = keyValues[key];
|
searchData[key] = keyValues[key];
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setState({ searchData }, callback);
|
this.setState({ searchData });
|
||||||
}
|
}
|
||||||
|
|
||||||
async onInputKeydown(event) {
|
async onInputKeydown(event) {
|
||||||
@@ -138,7 +209,7 @@ class StudyList extends Component {
|
|||||||
|
|
||||||
this.delayedSearch.cancel();
|
this.delayedSearch.cancel();
|
||||||
// reset the page because user is doing a new search
|
// reset the page because user is doing a new search
|
||||||
this.setSearchData('currentPage', 0, this.search);
|
this.setSearchData('currentPage', 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,45 +225,21 @@ class StudyList extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderNoMachingResults() {
|
|
||||||
if (!this.props.studies.length && !this.state.error) {
|
|
||||||
return <div className="notFound">No matching results</div>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderHasError() {
|
|
||||||
if (this.state.error) {
|
|
||||||
return (
|
|
||||||
<div className="notFound">There was an error fetching studies</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderIsLoading() {
|
|
||||||
if (this.state.loading) {
|
|
||||||
return (
|
|
||||||
<div className="loading">
|
|
||||||
<StudyListLoadingText />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nextPage(currentPage) {
|
nextPage(currentPage) {
|
||||||
currentPage = currentPage + 1;
|
currentPage = currentPage + 1;
|
||||||
this.delayedSearch.cancel();
|
this.delayedSearch.cancel();
|
||||||
this.setSearchData('currentPage', currentPage, this.search);
|
this.setSearchData('currentPage', currentPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
prevPage(currentPage) {
|
prevPage(currentPage) {
|
||||||
currentPage = currentPage - 1;
|
currentPage = currentPage - 1;
|
||||||
this.delayedSearch.cancel();
|
this.delayedSearch.cancel();
|
||||||
this.setSearchData('currentPage', currentPage, this.search);
|
this.setSearchData('currentPage', currentPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
onRowsPerPageChange(rowsPerPage) {
|
onRowsPerPageChange(rowsPerPage) {
|
||||||
this.delayedSearch.cancel();
|
this.delayedSearch.cancel();
|
||||||
this.setSearchDataBatch({ rowsPerPage, currentPage: 0 }, this.search);
|
this.setSearchDataBatch({ rowsPerPage, currentPage: 0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
onSortClick(field) {
|
onSortClick(field) {
|
||||||
@@ -213,7 +260,7 @@ class StudyList extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.delayedSearch.cancel();
|
this.delayedSearch.cancel();
|
||||||
this.setSearchData('sortData', { field, order }, this.search);
|
this.setSearchData('sortData', { field, order });
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,10 +268,39 @@ class StudyList extends Component {
|
|||||||
this.setState({ highlightedItem: studyItemUid });
|
this.setState({ highlightedItem: studyItemUid });
|
||||||
}
|
}
|
||||||
|
|
||||||
renderTableRow(study) {
|
getTableRow(study, index) {
|
||||||
|
const trKey = `trStudy${index}${study.studyInstanceUid}`;
|
||||||
|
|
||||||
|
if (!study) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTableCell = (
|
||||||
|
study,
|
||||||
|
studyKey,
|
||||||
|
emptyValue = '',
|
||||||
|
emptyClass = ''
|
||||||
|
) => {
|
||||||
|
const componentKey = `td${studyKey}`;
|
||||||
|
const isValidValue = study && typeof study[studyKey] === 'string';
|
||||||
|
let className = emptyClass;
|
||||||
|
let value = emptyValue;
|
||||||
|
|
||||||
|
if (isValidValue) {
|
||||||
|
className = studyKey;
|
||||||
|
value = study[studyKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<td key={componentKey} className={className}>
|
||||||
|
{value}
|
||||||
|
</td>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr
|
<tr
|
||||||
key={study.studyInstanceUid}
|
key={trKey}
|
||||||
className={
|
className={
|
||||||
this.state.highlightedItem === study.studyInstanceUid
|
this.state.highlightedItem === study.studyInstanceUid
|
||||||
? 'studylistStudy noselect active'
|
? 'studylistStudy noselect active'
|
||||||
@@ -241,47 +317,35 @@ class StudyList extends Component {
|
|||||||
this.props.onSelectItem(study.studyInstanceUid);
|
this.props.onSelectItem(study.studyInstanceUid);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<td className={study.patientName ? 'patientName' : 'emptyCell'}>
|
{getTableCell(
|
||||||
{study.patientName || `(${this.props.t('Empty')})`}
|
study,
|
||||||
</td>
|
'patientName',
|
||||||
|
`(${this.props.t('Empty')})`,
|
||||||
<td className="patientId">{study.patientId}</td>
|
'emptyCell'
|
||||||
<td className="accessionNumber">{study.accessionNumber}</td>
|
)}
|
||||||
<td className="studyDate">{study.studyDate}</td>
|
{getTableCell(study, 'patientId')}
|
||||||
<td className="modalities">{study.modalities}</td>
|
{getTableCell(study, 'accessionNumber')}
|
||||||
<td className="studyDescription">{study.studyDescription}</td>
|
{getTableCell(study, 'studyDate')}
|
||||||
|
{getTableCell(study, 'modalities')}
|
||||||
|
{getTableCell(study, 'studyDescription')}
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(previousProps, previousState) {
|
||||||
|
if (!isEqual(previousState.searchData, this.state.searchData)) {
|
||||||
|
this.search();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTableBody(noListFragment) {
|
||||||
|
return !noListFragment && this.props.studies
|
||||||
|
? this.props.studies.map(this.getTableRow.bind(this))
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const tableMeta = {
|
const tableMeta = getTableMeta(this.props.t);
|
||||||
patientName: {
|
|
||||||
displayText: this.props.t('PatientName'),
|
|
||||||
sort: 0,
|
|
||||||
},
|
|
||||||
patientId: {
|
|
||||||
displayText: this.props.t('MRN'),
|
|
||||||
sort: 0,
|
|
||||||
},
|
|
||||||
accessionNumber: {
|
|
||||||
displayText: this.props.t('AccessionNumber'),
|
|
||||||
sort: 0,
|
|
||||||
},
|
|
||||||
studyDate: {
|
|
||||||
displayText: this.props.t('StudyDate'),
|
|
||||||
inputType: 'date-range',
|
|
||||||
sort: 0,
|
|
||||||
},
|
|
||||||
modalities: {
|
|
||||||
displayText: this.props.t('Modality'),
|
|
||||||
sort: 0,
|
|
||||||
},
|
|
||||||
studyDescription: {
|
|
||||||
displayText: this.props.t('StudyDescription'),
|
|
||||||
sort: 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Apply sort
|
// Apply sort
|
||||||
const sortedFieldName = this.state.searchData.sortData.field;
|
const sortedFieldName = this.state.searchData.sortData.field;
|
||||||
@@ -294,14 +358,20 @@ class StudyList extends Component {
|
|||||||
|
|
||||||
// Sort Icons
|
// Sort Icons
|
||||||
const sortIcons = ['sort', 'sort-up', 'sort-down'];
|
const sortIcons = ['sort', 'sort-up', 'sort-down'];
|
||||||
|
const noListFragment = getNoListFragment(
|
||||||
|
this.props.t,
|
||||||
|
this.props.studies,
|
||||||
|
this.state.error,
|
||||||
|
this.props.loading || this.state.loading
|
||||||
|
);
|
||||||
|
const tableBody = this.renderTableBody(noListFragment);
|
||||||
|
const studiesNum = (this.props.studies && this.props.studies.length) || 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="StudyList">
|
<div className="StudyList">
|
||||||
<div className="studyListToolbar clearfix">
|
<div className="studyListToolbar clearfix">
|
||||||
<div className="header pull-left">{this.props.t('StudyList')}</div>
|
<div className="header pull-left">{this.props.t('StudyList')}</div>
|
||||||
<div className="studyCount pull-right">
|
<div className="studyCount pull-right">{studiesNum}</div>
|
||||||
{this.props.studies.length}
|
|
||||||
</div>
|
|
||||||
<div className="pull-right">
|
<div className="pull-right">
|
||||||
{this.props.studyListFunctionsEnabled ? (
|
{this.props.studyListFunctionsEnabled ? (
|
||||||
<StudylistToolbar onImport={this.props.onImport} />
|
<StudylistToolbar onImport={this.props.onImport} />
|
||||||
@@ -367,22 +437,16 @@ class StudyList extends Component {
|
|||||||
(this.state.focusedInput === 'endDate' ||
|
(this.state.focusedInput === 'endDate' ||
|
||||||
preset)
|
preset)
|
||||||
) {
|
) {
|
||||||
this.setSearchDataBatch(
|
this.setSearchDataBatch({
|
||||||
{
|
studyDateFrom: startDate.toDate(),
|
||||||
studyDateFrom: startDate.toDate(),
|
studyDateTo: endDate.toDate(),
|
||||||
studyDateTo: endDate.toDate(),
|
});
|
||||||
},
|
|
||||||
this.search
|
|
||||||
);
|
|
||||||
this.setState({ focusedInput: false });
|
this.setState({ focusedInput: false });
|
||||||
} else if (!startDate && !endDate) {
|
} else if (!startDate && !endDate) {
|
||||||
this.setSearchDataBatch(
|
this.setSearchDataBatch({
|
||||||
{
|
studyDateFrom: null,
|
||||||
studyDateFrom: null,
|
studyDateTo: null,
|
||||||
studyDateTo: null,
|
});
|
||||||
},
|
|
||||||
this.search
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
focusedInput={this.state.focusedInput}
|
focusedInput={this.state.focusedInput}
|
||||||
@@ -398,26 +462,18 @@ class StudyList extends Component {
|
|||||||
})}
|
})}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="studyListData">
|
<tbody id="studyListData">{tableBody}</tbody>
|
||||||
{this.props.studies.map(study => {
|
|
||||||
return this.renderTableRow(study);
|
|
||||||
})}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
{this.renderIsLoading()}
|
{noListFragment
|
||||||
{this.renderHasError()}
|
? noListFragment
|
||||||
{this.renderNoMachingResults()}
|
: getPaginationFragment(
|
||||||
|
this.props,
|
||||||
<PaginationArea
|
this.state.searchData,
|
||||||
pageOptions={this.props.pageOptions}
|
this.nextPage,
|
||||||
currentPage={this.state.searchData.currentPage}
|
this.prevPage,
|
||||||
nextPageFunc={this.nextPage}
|
this.onRowsPerPageChange
|
||||||
prevPageFunc={this.prevPage}
|
)}
|
||||||
onRowsPerPageChange={this.onRowsPerPageChange}
|
|
||||||
rowsPerPage={this.state.searchData.rowsPerPage}
|
|
||||||
recordCount={this.props.studies.length}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -82,9 +82,14 @@ placeholder-color(c)
|
|||||||
position: absolute
|
position: absolute
|
||||||
z-index: 2
|
z-index: 2
|
||||||
|
|
||||||
.loading-text
|
.loading
|
||||||
color: var(--table-text-secondary-color)
|
display: flex;
|
||||||
font-size: 30px
|
justify-content: center;
|
||||||
|
|
||||||
|
.loading-text
|
||||||
|
color: var(--table-text-secondary-color)
|
||||||
|
font-size: 30px
|
||||||
|
width: fit-content;
|
||||||
|
|
||||||
.notFound
|
.notFound
|
||||||
color: var(--table-text-secondary-color)
|
color: var(--table-text-secondary-color)
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
import { Icon } from './../../elements/Icon';
|
import { Icon } from './../../elements/Icon';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { withTranslation } from '../../utils/LanguageProvider';
|
||||||
|
|
||||||
function StudyListLoadingText() {
|
function StudyListLoadingText({ t: translate }) {
|
||||||
return (
|
return (
|
||||||
<div className="loading-text">
|
<div className="loading-text">
|
||||||
Loading... <Icon name="circle-notch" animation="pulse" />
|
{translate('Loading')}... <Icon name="circle-notch" animation="pulse" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { StudyListLoadingText };
|
const connectedComponent = withTranslation('StudyListLoadingText')(
|
||||||
|
StudyListLoadingText
|
||||||
|
);
|
||||||
|
|
||||||
|
export { connectedComponent as StudyListLoadingText };
|
||||||
@@ -3,6 +3,46 @@
|
|||||||
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.11.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.11.1...@ohif/viewer@1.11.2) (2019-10-23)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.11.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.11.0...@ohif/viewer@1.11.1) (2019-10-23)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [1.11.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.10.3...@ohif/viewer@1.11.0) (2019-10-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* MPR initialization ([#1062](https://github.com/OHIF/Viewers/issues/1062)) ([b037394](https://github.com/OHIF/Viewers/commit/b03739428f72bb50bdabdd6f83b7af885057da69))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 🎸 Load spinner when selecting gcloud store. Add key on td ([#1034](https://github.com/OHIF/Viewers/issues/1034)) ([e62f403](https://github.com/OHIF/Viewers/commit/e62f403fe9e3df56713128e3d59045824b086d8d)), closes [#1057](https://github.com/OHIF/Viewers/issues/1057)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [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)
|
## [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
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|||||||
@@ -2,5 +2,9 @@
|
|||||||
"baseUrl": "http://localhost:3000",
|
"baseUrl": "http://localhost:3000",
|
||||||
"video": false,
|
"video": false,
|
||||||
"chromeWebSecurity": false,
|
"chromeWebSecurity": false,
|
||||||
|
"waitForAnimations": true,
|
||||||
|
"defaultCommandTimeout": 10000,
|
||||||
|
"requestTimeout": 10000,
|
||||||
|
"responseTimeout": 10000,
|
||||||
"projectId": "4oe38f"
|
"projectId": "4oe38f"
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@ describe('OHIF Cornerstone Toolbar', () => {
|
|||||||
before(() => {
|
before(() => {
|
||||||
cy.openStudy('MISTER^MR');
|
cy.openStudy('MISTER^MR');
|
||||||
cy.waitDicomImage();
|
cy.waitDicomImage();
|
||||||
cy.expectMinimumThumbnails(1);
|
cy.expectMinimumThumbnails(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -57,12 +57,11 @@ describe('OHIF Cornerstone Toolbar', () => {
|
|||||||
|
|
||||||
//drags the mouse inside the viewport to be able to interact with series
|
//drags the mouse inside the viewport to be able to interact with series
|
||||||
cy.get('@viewport')
|
cy.get('@viewport')
|
||||||
.trigger('mousedown', 'top', { which: 1 })
|
.trigger('mousedown', 'center', { which: 1 })
|
||||||
.trigger('mousemove', 'center', { which: 1 })
|
.trigger('mousemove', 'top', { which: 1 })
|
||||||
.trigger('mouseup');
|
.trigger('mouseup');
|
||||||
|
|
||||||
const expectedText =
|
const expectedText =
|
||||||
'Ser: 1Img: 14 14/26256 x 256Loc: 0.00 mm Thick: 5.00 mm';
|
'Ser: 1Img: 1 1/26256 x 256Loc: -30.00 mm Thick: 5.00 mm';
|
||||||
cy.get('@viewportInfoBottomLeft').should('have.text', expectedText);
|
cy.get('@viewportInfoBottomLeft').should('have.text', expectedText);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -76,11 +75,11 @@ describe('OHIF Cornerstone Toolbar', () => {
|
|||||||
|
|
||||||
//drags the mouse inside the viewport to be able to interact with series
|
//drags the mouse inside the viewport to be able to interact with series
|
||||||
cy.get('@viewport')
|
cy.get('@viewport')
|
||||||
.trigger('mousedown', 'top', { which: 1 })
|
.trigger('mousedown', 'center', { which: 1 })
|
||||||
.trigger('mousemove', 'center', { which: 1 })
|
.trigger('mousemove', 'top', { which: 1 })
|
||||||
.trigger('mouseup');
|
.trigger('mouseup');
|
||||||
|
|
||||||
const expectedText = 'Zoom: 884%W: 820 L: 410Lossless / Uncompressed';
|
const expectedText = 'Zoom: 50%W: 958 L: 479Lossless / Uncompressed';
|
||||||
cy.get('@viewportInfoBottomRight').should('have.text', expectedText);
|
cy.get('@viewportInfoBottomRight').should('have.text', expectedText);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -94,14 +93,14 @@ describe('OHIF Cornerstone Toolbar', () => {
|
|||||||
|
|
||||||
//drags the mouse inside the viewport to be able to interact with series
|
//drags the mouse inside the viewport to be able to interact with series
|
||||||
cy.get('@viewport')
|
cy.get('@viewport')
|
||||||
.trigger('mousedown', 'top', { which: 1 })
|
.trigger('mousedown', 'center', { which: 1 })
|
||||||
.trigger('mousemove', 'center', { which: 1 })
|
.trigger('mousemove', 'top', { which: 1 })
|
||||||
.trigger('mouseup')
|
.trigger('mouseup')
|
||||||
.trigger('mousedown', 'center', { which: 1 })
|
.trigger('mousedown', 'center', { which: 1 })
|
||||||
.trigger('mousemove', 'left', { which: 1 })
|
.trigger('mousemove', 'left', { which: 1 })
|
||||||
.trigger('mouseup');
|
.trigger('mouseup');
|
||||||
|
|
||||||
const expectedText = 'Zoom: 211%W: 544 L: 626Lossless / Uncompressed';
|
const expectedText = 'Zoom: 211%W: 635 L: 226Lossless / Uncompressed';
|
||||||
cy.get('@viewportInfoBottomRight').should('have.text', expectedText);
|
cy.get('@viewportInfoBottomRight').should('have.text', expectedText);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -197,11 +196,14 @@ describe('OHIF Cornerstone Toolbar', () => {
|
|||||||
//Click on reset button
|
//Click on reset button
|
||||||
cy.get('@resetBtn').click();
|
cy.get('@resetBtn').click();
|
||||||
|
|
||||||
const expectedText = 'Zoom: 211%W: 820 L: 410Lossless / Uncompressed';
|
const expectedText = 'Zoom: 211%W: 958 L: 479Lossless / Uncompressed';
|
||||||
cy.get('@viewportInfoBottomRight').should('have.text', expectedText);
|
cy.get('@viewportInfoBottomRight').should('have.text', expectedText);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('checks if CINE tool will prompt a modal with working controls', () => {
|
it('checks if CINE tool will prompt a modal with working controls', () => {
|
||||||
|
cy.server();
|
||||||
|
cy.route('GET', '/**/studies/**/').as('studies');
|
||||||
|
|
||||||
//Click on button
|
//Click on button
|
||||||
cy.get('@cineBtn').click();
|
cy.get('@cineBtn').click();
|
||||||
//Vefiry if cine control overlay is being displayed
|
//Vefiry if cine control overlay is being displayed
|
||||||
@@ -216,26 +218,49 @@ describe('OHIF Cornerstone Toolbar', () => {
|
|||||||
.click();
|
.click();
|
||||||
|
|
||||||
let expectedText = 'Img: 1 1/26';
|
let expectedText = 'Img: 1 1/26';
|
||||||
cy.get('@viewportInfoBottomLeft').should('not.have.text', expectedText);
|
cy.get('@viewportInfoBottomLeft', { timeout: 15000 }).should(
|
||||||
|
'not.have.text',
|
||||||
|
expectedText
|
||||||
|
);
|
||||||
|
|
||||||
//Test SKIP TO FIRST IMAGE button
|
//Test SKIP TO FIRST IMAGE button
|
||||||
cy.get('[title="Skip to first Image"]').click();
|
cy.get('[title="Skip to first Image"]')
|
||||||
cy.get('@viewportInfoBottomLeft').should('contain.text', expectedText);
|
.click()
|
||||||
|
.wait(1000);
|
||||||
|
cy.get('@viewportInfoBottomLeft', { timeout: 15000 }).should(
|
||||||
|
'contain.text',
|
||||||
|
expectedText
|
||||||
|
);
|
||||||
|
|
||||||
//Test NEXT IMAGE button
|
//Test NEXT IMAGE button
|
||||||
cy.get('[title="Next Image"]').click();
|
cy.get('[title="Next Image"]')
|
||||||
|
.click()
|
||||||
|
.wait(1000);
|
||||||
expectedText = 'Img: 2 2/26';
|
expectedText = 'Img: 2 2/26';
|
||||||
cy.get('@viewportInfoBottomLeft').should('contain.text', expectedText);
|
cy.get('@viewportInfoBottomLeft', { timeout: 15000 }).should(
|
||||||
|
'contain.text',
|
||||||
|
expectedText
|
||||||
|
);
|
||||||
|
|
||||||
//Test SKIP TO LAST IMAGE button
|
//Test SKIP TO LAST IMAGE button
|
||||||
cy.get('[title="Skip to last Image"]').click();
|
cy.get('[title="Skip to last Image"]')
|
||||||
|
.click()
|
||||||
|
.wait(2000);
|
||||||
expectedText = 'Img: 27 26/26';
|
expectedText = 'Img: 27 26/26';
|
||||||
cy.get('@viewportInfoBottomLeft').should('contain.text', expectedText);
|
cy.get('@viewportInfoBottomLeft', { timeout: 15000 }).should(
|
||||||
|
'contain.text',
|
||||||
|
expectedText
|
||||||
|
);
|
||||||
|
|
||||||
//Test PREVIOUS IMAGE button
|
//Test PREVIOUS IMAGE button
|
||||||
cy.get('[title="Previous Image"]').click();
|
cy.get('[title="Previous Image"]')
|
||||||
|
.click()
|
||||||
|
.wait(1000);
|
||||||
expectedText = 'Img: 26 25/26';
|
expectedText = 'Img: 26 25/26';
|
||||||
cy.get('@viewportInfoBottomLeft').should('contain.text', expectedText);
|
cy.get('@viewportInfoBottomLeft', { timeout: 15000 }).should(
|
||||||
|
'contain.text',
|
||||||
|
expectedText
|
||||||
|
);
|
||||||
|
|
||||||
//Click on Cine button
|
//Click on Cine button
|
||||||
cy.get('@cineBtn').click();
|
cy.get('@cineBtn').click();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
describe('OHIFStandaloneViewer', () => {
|
describe('OHIFStandaloneViewer', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.visit('/');
|
cy.openStudyList();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('loads route with at least 2 rows', () => {
|
it('loads route with at least 2 rows', () => {
|
||||||
@@ -13,7 +13,11 @@ describe('OHIFStandaloneViewer', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('first 2 rows has values', () => {
|
it('first 2 rows has values', () => {
|
||||||
cy.get('#studyListData > :nth-child(1) > .patientId').should('be.visible');
|
cy.get('#studyListData > :nth-child(1) > .patientId', {
|
||||||
cy.get('#studyListData > :nth-child(2) > .patientId').should('be.visible');
|
timeout: 15000,
|
||||||
|
}).should('be.visible');
|
||||||
|
cy.get('#studyListData > :nth-child(2) > .patientId', {
|
||||||
|
timeout: 15000,
|
||||||
|
}).should('be.visible');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -2,7 +2,7 @@ describe('OHIF Study Viewer Page', () => {
|
|||||||
before(() => {
|
before(() => {
|
||||||
cy.openStudy('MISTER^MR');
|
cy.openStudy('MISTER^MR');
|
||||||
cy.waitDicomImage();
|
cy.waitDicomImage();
|
||||||
cy.expectMinimumThumbnails(1);
|
cy.expectMinimumThumbnails(6);
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -14,13 +14,13 @@ describe('OHIF Study Viewer Page', () => {
|
|||||||
cy.screenshot();
|
cy.screenshot();
|
||||||
cy.percySnapshot();
|
cy.percySnapshot();
|
||||||
|
|
||||||
cy.get('.ThumbnailEntryContainer')
|
cy.get('[data-cy="thumbnail-list"]')
|
||||||
.its('length')
|
.its('length')
|
||||||
.should('be.gt', 1);
|
.should('be.gt', 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('drags and drop a series thumbnail into viewport', () => {
|
it('drags and drop a series thumbnail into viewport', () => {
|
||||||
cy.get('.ThumbnailEntryContainer:nth-child(2)') //element to be dragged
|
cy.get('[data-cy="thumbnail-list"]:nth-child(2)') //element to be dragged
|
||||||
.drag('.cornerstone-canvas'); //dropzone element
|
.drag('.cornerstone-canvas'); //dropzone element
|
||||||
|
|
||||||
const expectedText =
|
const expectedText =
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
describe('OHIF HTML Extension', () => {
|
describe('OHIF HTML Extension', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.openStudy('Dummy');
|
cy.openStudy('Dummy');
|
||||||
cy.expectMinimumThumbnails(1);
|
cy.expectMinimumThumbnails(5);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('checks if series thumbnails are being displayed', () => {
|
it('checks if series thumbnails are being displayed', () => {
|
||||||
@@ -14,6 +14,7 @@ describe('OHIF HTML Extension', () => {
|
|||||||
it('drags and drop a SR thumbnail into viewport', () => {
|
it('drags and drop a SR thumbnail into viewport', () => {
|
||||||
cy.get('[data-cy="thumbnail-list"]')
|
cy.get('[data-cy="thumbnail-list"]')
|
||||||
.contains('SR')
|
.contains('SR')
|
||||||
|
.first()
|
||||||
.drag('.viewport-drop-target');
|
.drag('.viewport-drop-target');
|
||||||
|
|
||||||
cy.get(':nth-child(2) > h1').should(
|
cy.get(':nth-child(2) > h1').should(
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
describe('OHIF Microscopy Extension', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.openStudyModality('SM');
|
||||||
|
cy.expectMinimumThumbnails(6);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks if series thumbnails are being displayed', () => {
|
||||||
|
cy.get('[data-cy="thumbnail-list"]')
|
||||||
|
.contains('SM')
|
||||||
|
.its('length')
|
||||||
|
.should('to.be.at.least', 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('drags and drop a SM thumbnail into viewport', () => {
|
||||||
|
cy.get('[data-cy="thumbnail-list"]')
|
||||||
|
.contains('SM')
|
||||||
|
.drag('.viewport-drop-target');
|
||||||
|
|
||||||
|
cy.get('.DicomMicroscopyViewer')
|
||||||
|
.its('length')
|
||||||
|
.should('be.eq', 1);
|
||||||
|
|
||||||
|
cy.wait(3000); //Waiting for image to render before taking the snapshot
|
||||||
|
cy.screenshot();
|
||||||
|
cy.percySnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
describe('OHIF PDF Extension', () => {
|
describe('OHIF PDF Extension', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.openStudy('Dummy');
|
cy.openStudy('Dummy');
|
||||||
cy.expectMinimumThumbnails(1);
|
cy.expectMinimumThumbnails(6);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('checks if series thumbnails are being displayed', () => {
|
it('checks if series thumbnails are being displayed', () => {
|
||||||
@@ -19,5 +19,8 @@ describe('OHIF PDF Extension', () => {
|
|||||||
cy.get('.DicomPDFViewport')
|
cy.get('.DicomPDFViewport')
|
||||||
.its('length')
|
.its('length')
|
||||||
.should('be.eq', 1);
|
.should('be.eq', 1);
|
||||||
|
|
||||||
|
cy.screenshot();
|
||||||
|
cy.percySnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -2,7 +2,7 @@ describe('OHIF VTK Extension', () => {
|
|||||||
before(() => {
|
before(() => {
|
||||||
cy.openStudy('Juno');
|
cy.openStudy('Juno');
|
||||||
cy.waitDicomImage();
|
cy.waitDicomImage();
|
||||||
cy.expectMinimumThumbnails(1);
|
cy.expectMinimumThumbnails(7);
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -11,7 +11,7 @@ describe('OHIF VTK Extension', () => {
|
|||||||
// has data from a drag-n-drop
|
// has data from a drag-n-drop
|
||||||
// Drag and drop first thumbnail into first viewport
|
// Drag and drop first thumbnail into first viewport
|
||||||
cy.get('[data-cy="thumbnail-list"]:nth-child(3)').drag(
|
cy.get('[data-cy="thumbnail-list"]:nth-child(3)').drag(
|
||||||
'.cornerstone-canvas'
|
'.viewport-drop-target'
|
||||||
);
|
);
|
||||||
|
|
||||||
cy.get('.PluginSwitch > .toolbar-button')
|
cy.get('.PluginSwitch > .toolbar-button')
|
||||||
@@ -22,12 +22,15 @@ describe('OHIF VTK Extension', () => {
|
|||||||
btn.click();
|
btn.click();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
//wait VTK toolbar and images to be loaded
|
||||||
|
cy.wait(3000);
|
||||||
cy.initVTKToolsAliases();
|
cy.initVTKToolsAliases();
|
||||||
cy.wait(1000);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('checks if VTK buttons are displayed on the toolbar', () => {
|
it('checks if VTK buttons are displayed on the toolbar', () => {
|
||||||
|
cy.screenshot();
|
||||||
|
cy.percySnapshot();
|
||||||
|
|
||||||
cy.get('@crosshairsBtn')
|
cy.get('@crosshairsBtn')
|
||||||
.should('be.visible')
|
.should('be.visible')
|
||||||
.contains('Crosshairs');
|
.contains('Crosshairs');
|
||||||
|
|||||||
@@ -23,6 +23,12 @@ module.exports = (on, config) => {
|
|||||||
// whatever you return here becomes the new args
|
// whatever you return here becomes the new args
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (browser.name === 'chromium') {
|
||||||
|
const newArgs = args.filter(arg => arg !== '--disable-gpu');
|
||||||
|
newArgs.push('--ignore-gpu-blacklist');
|
||||||
|
return newArgs;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
on('task', percyHealthCheck);
|
on('task', percyHealthCheck);
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ export function initCommonElementsAliases() {
|
|||||||
//Creating aliases for Routes
|
//Creating aliases for Routes
|
||||||
export function initRouteAliases() {
|
export function initRouteAliases() {
|
||||||
cy.server();
|
cy.server();
|
||||||
cy.route('GET', '/dcm4chee-arc/**/series').as('getStudySeries');
|
cy.route('GET', '**/series**').as('getStudySeries');
|
||||||
|
cy.route('GET', '**/studies**').as('getStudies');
|
||||||
}
|
}
|
||||||
|
|
||||||
//Creating aliases for VTK tools buttons
|
//Creating aliases for VTK tools buttons
|
||||||
|
|||||||
@@ -39,19 +39,46 @@ 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.openStudyList();
|
||||||
|
cy.get('#patientName').type(patientName);
|
||||||
|
cy.wait('@getStudies');
|
||||||
|
cy.get('#studyListData .studylistStudy', { timeout: 5000 })
|
||||||
|
.contains(patientName)
|
||||||
|
.first()
|
||||||
|
.click({ force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command to search for a modality and open the study.
|
||||||
|
*
|
||||||
|
* @param {string} modality - Modality type that we would like to search for
|
||||||
|
*/
|
||||||
|
Cypress.Commands.add('openStudyModality', modality => {
|
||||||
cy.initRouteAliases();
|
cy.initRouteAliases();
|
||||||
cy.visit('/');
|
cy.visit('/');
|
||||||
cy.get('#patientName').type(patientName);
|
cy.get('#modalities')
|
||||||
|
.type(modality)
|
||||||
|
.wait(2000);
|
||||||
|
|
||||||
cy.get('.studylistStudy > .patientName', { timeout: 5000 })
|
cy.get('#studyListData')
|
||||||
.contains(patientName)
|
.contains(modality)
|
||||||
|
.first()
|
||||||
.click();
|
.click();
|
||||||
|
});
|
||||||
|
|
||||||
// cy.get('.studylistStudy > .patientName')
|
/**
|
||||||
// .as('patientResult')
|
* Command to wait and check if a new page was loaded
|
||||||
// .should({ timeout: 3000 }, () => {
|
*
|
||||||
// cy.contains(patientName).click();
|
* @param {string} url - part of the expected url. Default value is /viewer/
|
||||||
// });
|
*/
|
||||||
|
Cypress.Commands.add('isPageLoaded', (url = '/viewer/') => {
|
||||||
|
return cy.location('pathname', { timeout: 60000 }).should('include', url);
|
||||||
|
});
|
||||||
|
|
||||||
|
Cypress.Commands.add('openStudyList', patientName => {
|
||||||
|
cy.initRouteAliases();
|
||||||
|
cy.visit('/');
|
||||||
|
cy.wait('@getStudies');
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -119,31 +146,35 @@ Cypress.Commands.add('expectMinimumThumbnails', (seriesToWait = 1) => {
|
|||||||
|
|
||||||
//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()
|
const loaded = cy.isPageLoaded();
|
||||||
.its('cornerstone')
|
|
||||||
.then({ timeout }, $cornerstone => {
|
|
||||||
return new Cypress.Promise(resolve => {
|
|
||||||
const onEvent = renderedEvt => {
|
|
||||||
const element = renderedEvt.detail.element;
|
|
||||||
|
|
||||||
element.removeEventListener('cornerstoneimagerendered', onEvent);
|
if (loaded) {
|
||||||
$cornerstone.events.removeEventListener(
|
cy.window()
|
||||||
'cornerstoneimagerendered',
|
.its('cornerstone')
|
||||||
onEvent
|
.then({ timeout }, $cornerstone => {
|
||||||
|
return new Cypress.Promise(resolve => {
|
||||||
|
const onEvent = renderedEvt => {
|
||||||
|
const element = renderedEvt.detail.element;
|
||||||
|
|
||||||
|
element.removeEventListener('cornerstoneimagerendered', onEvent);
|
||||||
|
$cornerstone.events.removeEventListener(
|
||||||
|
'cornerstoneimagerendered',
|
||||||
|
onEvent
|
||||||
|
);
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
const onEnabled = enabledEvt => {
|
||||||
|
const element = enabledEvt.detail.element;
|
||||||
|
|
||||||
|
element.addEventListener('cornerstoneimagerendered', onEvent);
|
||||||
|
};
|
||||||
|
$cornerstone.events.addEventListener(
|
||||||
|
'cornerstoneelementenabled',
|
||||||
|
onEnabled
|
||||||
);
|
);
|
||||||
resolve();
|
});
|
||||||
};
|
|
||||||
const onEnabled = enabledEvt => {
|
|
||||||
const element = enabledEvt.detail.element;
|
|
||||||
|
|
||||||
element.addEventListener('cornerstoneimagerendered', onEvent);
|
|
||||||
};
|
|
||||||
$cornerstone.events.addEventListener(
|
|
||||||
'cornerstoneelementenabled',
|
|
||||||
onEnabled
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
//Command to reset and clear all the changes made to the viewport
|
//Command to reset and clear all the changes made to the viewport
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/viewer",
|
"name": "@ohif/viewer",
|
||||||
"version": "1.10.2",
|
"version": "1.11.2",
|
||||||
"description": "OHIF Viewer",
|
"description": "OHIF Viewer",
|
||||||
"author": "OHIF Contributors",
|
"author": "OHIF Contributors",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
"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": "percy exec -- cypress run",
|
"test:e2e:ci": "percy exec -- cypress run --browser chrome",
|
||||||
"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 -n -l 3000 -s dist",
|
"test:e2e:serve": "serve -n -l 3000 -s dist",
|
||||||
"test:unit": "jest --watchAll",
|
"test:unit": "jest --watchAll",
|
||||||
@@ -50,9 +50,9 @@
|
|||||||
"@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.12",
|
"@ohif/extension-vtk": "^0.52.15",
|
||||||
"@ohif/i18n": "^0.51.0",
|
"@ohif/i18n": "^0.51.0",
|
||||||
"@ohif/ui": "^0.55.0",
|
"@ohif/ui": "^0.56.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",
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ class DicomStorePickerModal extends Component {
|
|||||||
handleEvent = data => {
|
handleEvent = data => {
|
||||||
const servers = GoogleCloudUtilServers.getServers(data, data.dicomstore);
|
const servers = GoogleCloudUtilServers.getServers(data, data.dicomstore);
|
||||||
this.props.setServers(servers);
|
this.props.setServers(servers);
|
||||||
|
// Force auto close
|
||||||
|
this.props.onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { StudyList } from '@ohif/ui';
|
|||||||
import ConnectedHeader from '../connectedComponents/ConnectedHeader.js';
|
import ConnectedHeader from '../connectedComponents/ConnectedHeader.js';
|
||||||
import * as RoutesUtil from '../routes/routesUtil';
|
import * as RoutesUtil from '../routes/routesUtil';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import isEqual from 'lodash.isequal';
|
||||||
import ConnectedDicomFilesUploader from '../googleCloud/ConnectedDicomFilesUploader';
|
import ConnectedDicomFilesUploader from '../googleCloud/ConnectedDicomFilesUploader';
|
||||||
import ConnectedDicomStorePicker from '../googleCloud/ConnectedDicomStorePicker';
|
import ConnectedDicomStorePicker from '../googleCloud/ConnectedDicomStorePicker';
|
||||||
import filesToStudies from '../lib/filesToStudies.js';
|
import filesToStudies from '../lib/filesToStudies.js';
|
||||||
@@ -20,8 +21,9 @@ import AppContext from '../context/AppContext';
|
|||||||
class StudyListWithData extends Component {
|
class StudyListWithData extends Component {
|
||||||
static contextType = AppContext;
|
static contextType = AppContext;
|
||||||
state = {
|
state = {
|
||||||
searchData: {},
|
searchOutdated: true,
|
||||||
studies: [],
|
studies: [],
|
||||||
|
searchingStudies: false,
|
||||||
error: null,
|
error: null,
|
||||||
modalComponentId: null,
|
modalComponentId: null,
|
||||||
};
|
};
|
||||||
@@ -62,6 +64,7 @@ class StudyListWithData extends Component {
|
|||||||
if (!this.props.server && appConfig.enableGoogleCloudAdapter) {
|
if (!this.props.server && appConfig.enableGoogleCloudAdapter) {
|
||||||
this.setState({
|
this.setState({
|
||||||
modalComponentId: 'DicomStorePicker',
|
modalComponentId: 'DicomStorePicker',
|
||||||
|
searchOutdated: false,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.searchForStudies({
|
this.searchForStudies({
|
||||||
@@ -71,21 +74,33 @@ class StudyListWithData extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps, prevState) {
|
||||||
if (!this.state.searchData && !this.state.studies) {
|
const hasNewServer = !isEqual(this.props.server, prevProps.server);
|
||||||
this.searchForStudies();
|
|
||||||
}
|
const { searchOutdated, searchingStudies } = this.state;
|
||||||
if (this.props.server !== prevProps.server) {
|
|
||||||
this.setState({
|
if (!searchingStudies) {
|
||||||
modalComponentId: null,
|
if (hasNewServer) {
|
||||||
searchData: null,
|
const { appConfig = {} } = this.context;
|
||||||
studies: null,
|
|
||||||
});
|
const newState = {
|
||||||
|
searchOutdated: true,
|
||||||
|
studies: null,
|
||||||
|
};
|
||||||
|
if (appConfig.enableGoogleCloudAdapter) {
|
||||||
|
newState.modalComponentId = null;
|
||||||
|
}
|
||||||
|
this.setState(newState);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searchOutdated) {
|
||||||
|
this.searchForStudies();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
searchForStudies = (searchData = StudyListWithData.defaultSearchData) => {
|
searchForStudies = (searchData = StudyListWithData.defaultSearchData) => {
|
||||||
const { server } = this.props;
|
const { server = {} } = this.props;
|
||||||
const filter = {
|
const filter = {
|
||||||
patientId: searchData.patientId,
|
patientId: searchData.patientId,
|
||||||
patientName: searchData.patientName,
|
patientName: searchData.patientName,
|
||||||
@@ -105,7 +120,9 @@ class StudyListWithData extends Component {
|
|||||||
// TODO: add sorting
|
// TODO: add sorting
|
||||||
const promise = OHIF.studies.searchStudies(server, filter);
|
const promise = OHIF.studies.searchStudies(server, filter);
|
||||||
|
|
||||||
// Render the viewer when the data is ready
|
this.setState({
|
||||||
|
searchingStudies: true,
|
||||||
|
});
|
||||||
promise
|
promise
|
||||||
.then(studies => {
|
.then(studies => {
|
||||||
if (!studies) {
|
if (!studies) {
|
||||||
@@ -150,11 +167,15 @@ class StudyListWithData extends Component {
|
|||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
studies: sortedStudies,
|
studies: sortedStudies,
|
||||||
|
searchingStudies: false,
|
||||||
|
searchOutdated: false,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
this.setState({
|
this.setState({
|
||||||
error: true,
|
error: true,
|
||||||
|
searchingStudies: false,
|
||||||
|
searchOutdated: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
@@ -208,8 +229,6 @@ class StudyListWithData extends Component {
|
|||||||
|
|
||||||
if (this.state.error) {
|
if (this.state.error) {
|
||||||
return <div>Error: {JSON.stringify(this.state.error)}</div>;
|
return <div>Error: {JSON.stringify(this.state.error)}</div>;
|
||||||
} else if (this.state.studies === null && !this.state.modalComponentId) {
|
|
||||||
return <div>Loading...</div>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let healthCareApiButtons = null;
|
let healthCareApiButtons = null;
|
||||||
@@ -240,8 +259,9 @@ class StudyListWithData extends Component {
|
|||||||
|
|
||||||
const studyList = (
|
const studyList = (
|
||||||
<div className="paginationArea">
|
<div className="paginationArea">
|
||||||
{this.state.studies ? (
|
{this.state.studies || this.state.searchingStudies ? (
|
||||||
<StudyList
|
<StudyList
|
||||||
|
loading={this.state.searchingStudies}
|
||||||
studies={this.state.studies}
|
studies={this.state.studies}
|
||||||
studyListFunctionsEnabled={this.props.studyListFunctionsEnabled}
|
studyListFunctionsEnabled={this.props.studyListFunctionsEnabled}
|
||||||
onImport={this.onImport}
|
onImport={this.onImport}
|
||||||
|
|||||||
Reference in new issue
Block a user