Compare commits
36
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84144bc3ef | ||
|
|
5cbdd0c616 | ||
|
|
fc015326c7 | ||
|
|
01860b6094 | ||
|
|
cfdf827ff0 | ||
|
|
ceb6837153 | ||
|
|
b01b0108b0 | ||
|
|
d6862e7418 | ||
|
|
beb7083e55 | ||
|
|
412fe4e23f | ||
|
|
659649e10b | ||
|
|
f431b277dd | ||
|
|
2bc361cdca | ||
|
|
c6f306c0de | ||
|
|
c0ea27f6b3 | ||
|
|
df8d4029f1 | ||
|
|
a36cb63926 | ||
|
|
e51529e6c9 | ||
|
|
750a150644 | ||
|
|
a9d28d85da | ||
|
|
6ee5094cbe | ||
|
|
6bfb9bb140 | ||
|
|
db5b760432 | ||
|
|
e7de8be2d8 | ||
|
|
cb2088daf7 | ||
|
|
2a743554b6 | ||
|
|
a28984ef1f | ||
|
|
95e5e11d09 | ||
|
|
ff9f199850 | ||
|
|
1cc94f36a7 | ||
|
|
5c5223fcf1 | ||
|
|
8513ad9ef3 | ||
|
|
66d8bc6d6f | ||
|
|
0064a4b1cd | ||
|
|
74ce89b22b | ||
|
|
c92975f555 |
No files matched your search
+93
-38
@@ -17,46 +17,107 @@ orbs:
|
||||
|
||||
defaults: &defaults
|
||||
docker:
|
||||
- image: circleci/node:10.16.0
|
||||
- image: circleci/node:12.9.1
|
||||
environment:
|
||||
TERM: xterm # Enable colors in term
|
||||
working_directory: ~/repo
|
||||
|
||||
jobs:
|
||||
CHECKOUT:
|
||||
UNIT_TESTS:
|
||||
<<: *defaults
|
||||
steps:
|
||||
# Update yarn
|
||||
- run: yarn -v
|
||||
#- run: rm -rf ~/.yarn && npm i -g yarn && yarn -v
|
||||
|
||||
# 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-v1-{{ .Branch }}-{{ checksum "yarn.lock" }}
|
||||
- yarn-packages-v1-{{ .Branch }}-
|
||||
- yarn-packages-v1-
|
||||
- yarn-packages-{{ checksum "yarn.lock" }}
|
||||
- yarn-packages-
|
||||
|
||||
- run:
|
||||
name: Install Dependencies
|
||||
command: yarn install --frozen-lockfile
|
||||
|
||||
- save_cache:
|
||||
name: Save Yarn Package Cache
|
||||
paths:
|
||||
- ~/.cache ## Cache yarn and Cypress
|
||||
key: yarn-packages-v1-{{ .Branch }}-{{ checksum "yarn.lock" }}
|
||||
# Persist :+1:
|
||||
- persist_to_workspace:
|
||||
root: ~/repo
|
||||
paths: .
|
||||
key: yarn-packages-{{ checksum "yarn.lock" }}
|
||||
|
||||
UNIT_TESTS:
|
||||
# RUN TESTS
|
||||
- run:
|
||||
name: 'JavaScript Test Suite'
|
||||
command: yarn run test:unit:ci
|
||||
|
||||
# PLATFORM/VIEWER
|
||||
- run:
|
||||
name: 'VIEWER: Combine report output'
|
||||
command: |
|
||||
viewerCov="/home/circleci/repo/platform/viewer/coverage"
|
||||
touch "${viewerCov}/reports"
|
||||
cat "${viewerCov}/clover.xml" >> "${viewerCov}/reports"
|
||||
echo "\<<\<<\<< EOF" >> "${viewerCov}/reports"
|
||||
cat "${viewerCov}/lcov.info" >>"${viewerCov}/reports"
|
||||
echo "\<<\<<\<< EOF" >> "${viewerCov}/reports"
|
||||
- codecov/upload:
|
||||
file: '/home/circleci/repo/platform/viewer/coverage/reports'
|
||||
flags: 'viewer'
|
||||
|
||||
# PLATFORM/CORE
|
||||
- run:
|
||||
name: 'CORE: Combine report output'
|
||||
command: |
|
||||
coreCov="/home/circleci/repo/platform/core/coverage"
|
||||
touch "${coreCov}/reports"
|
||||
cat "${coreCov}/clover.xml" >> "${coreCov}/reports"
|
||||
echo "\<<\<<\<< EOF" >> "${coreCov}/reports"
|
||||
cat "${coreCov}/lcov.info" >> "${coreCov}/reports"
|
||||
echo "\<<\<<\<< EOF" >> "${coreCov}/reports"
|
||||
- codecov/upload:
|
||||
file: '/home/circleci/repo/platform/core/coverage/reports'
|
||||
flags: 'core'
|
||||
|
||||
UNIT_TESTS_PERSIST_WORKSPACE:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: ~/repo
|
||||
# Update yarn
|
||||
#- run: rm -rf ~/.yarn && npm i -g yarn && yarn -v
|
||||
|
||||
# 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
|
||||
|
||||
- save_cache:
|
||||
name: Save Yarn Package Cache
|
||||
paths:
|
||||
- ~/.cache ## Cache yarn and Cypress
|
||||
key: yarn-packages-{{ checksum "yarn.lock" }}
|
||||
|
||||
# RUN TESTS
|
||||
- run:
|
||||
@@ -92,7 +153,7 @@ jobs:
|
||||
flags: 'core'
|
||||
|
||||
# Persist :+1:
|
||||
- persist_to_workspace:
|
||||
- persist_to_workspace: &persist_to_workspace
|
||||
root: ~/repo
|
||||
paths: .
|
||||
|
||||
@@ -143,7 +204,7 @@ jobs:
|
||||
- attach_workspace:
|
||||
at: ~/repo
|
||||
- setup_remote_docker:
|
||||
docker_layer_caching: true
|
||||
docker_layer_caching: false
|
||||
- run:
|
||||
name: Build and push Docker image
|
||||
command: |
|
||||
@@ -199,7 +260,7 @@ jobs:
|
||||
- attach_workspace:
|
||||
at: ~/repo
|
||||
- setup_remote_docker:
|
||||
docker_layer_caching: true
|
||||
docker_layer_caching: false
|
||||
- run:
|
||||
name: Deploy latest version to viewer.ohif.org
|
||||
command: |
|
||||
@@ -217,16 +278,13 @@ workflows:
|
||||
# PULL REQUESTS
|
||||
PR_CHECKS:
|
||||
jobs:
|
||||
- CHECKOUT:
|
||||
- UNIT_TESTS:
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- master
|
||||
- feature/*
|
||||
- hotfix/*
|
||||
- UNIT_TESTS:
|
||||
requires:
|
||||
- CHECKOUT
|
||||
# E2E: PWA
|
||||
- cypress/run:
|
||||
name: 'E2E: PWA'
|
||||
@@ -236,10 +294,10 @@ workflows:
|
||||
yarn: true
|
||||
store_artifacts: false
|
||||
working_directory: platform/viewer
|
||||
build: yarn run build:e2e
|
||||
start: yarn run test:e2e:dist
|
||||
build: npx cross-env QUICK_BUILD=true yarn run build
|
||||
start: yarn run test:e2e:serve
|
||||
wait-on: 'http://localhost:3000'
|
||||
cache-key: 'yarn-packages-v1-{{ .Branch }}-{{ checksum "yarn.lock" }}'
|
||||
cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
|
||||
no-workspace: true # Don't persist workspace
|
||||
post-steps:
|
||||
- store_artifacts:
|
||||
@@ -255,10 +313,10 @@ workflows:
|
||||
yarn: true
|
||||
store_artifacts: false
|
||||
working_directory: platform/viewer
|
||||
build: yarn run build:package
|
||||
start: yarn run test:e2e:dist
|
||||
build: npx cross-env QUICK_BUILD=true yarn run build:package
|
||||
start: yarn run test:e2e:serve
|
||||
wait-on: 'http://localhost:3000'
|
||||
cache-key: 'yarn-packages-v1-{{ .Branch }}-{{ checksum "yarn.lock" }}'
|
||||
cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
|
||||
no-workspace: true # Don't persist workspace
|
||||
post-steps:
|
||||
- store_artifacts:
|
||||
@@ -269,13 +327,10 @@ workflows:
|
||||
# MERGE TO MASTER
|
||||
RELEASE:
|
||||
jobs:
|
||||
- CHECKOUT:
|
||||
- UNIT_TESTS_PERSIST_WORKSPACE:
|
||||
filters:
|
||||
branches:
|
||||
only: master
|
||||
- UNIT_TESTS:
|
||||
requires:
|
||||
- CHECKOUT
|
||||
# E2E: PWA + Persist
|
||||
- cypress/run:
|
||||
name: 'E2E: PWA'
|
||||
@@ -286,10 +341,10 @@ workflows:
|
||||
record: true
|
||||
store_artifacts: true
|
||||
working_directory: platform/viewer
|
||||
build: yarn run build:e2e
|
||||
start: yarn run test:e2e:dist
|
||||
build: npx cross-env QUICK_BUILD=true yarn run build
|
||||
start: yarn run test:e2e:serve
|
||||
wait-on: 'http://localhost:3000'
|
||||
cache-key: 'yarn-packages-v1-{{ .Branch }}-{{ checksum "yarn.lock" }}'
|
||||
cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
|
||||
no-workspace: true # Don't persist workspace
|
||||
post-steps:
|
||||
- store_artifacts:
|
||||
@@ -297,7 +352,7 @@ workflows:
|
||||
- store_test_results:
|
||||
path: cypress/results
|
||||
requires:
|
||||
- UNIT_TESTS
|
||||
- UNIT_TESTS_PERSIST_WORKSPACE
|
||||
# E2E: script-tag
|
||||
- cypress/run:
|
||||
name: 'E2E: Script Tag'
|
||||
@@ -307,10 +362,10 @@ workflows:
|
||||
yarn: true
|
||||
store_artifacts: false
|
||||
working_directory: platform/viewer
|
||||
build: yarn run build:package
|
||||
start: yarn run test:e2e:dist
|
||||
build: npx cross-env QUICK_BUILD=true yarn run build:package
|
||||
start: yarn run test:e2e:serve
|
||||
wait-on: 'http://localhost:3000'
|
||||
cache-key: 'yarn-packages-v1-{{ .Branch }}-{{ checksum "yarn.lock" }}'
|
||||
cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
|
||||
no-workspace: true # Don't persist workspace
|
||||
post-steps:
|
||||
- store_artifacts:
|
||||
@@ -318,7 +373,7 @@ workflows:
|
||||
- store_test_results:
|
||||
path: cypress/results
|
||||
requires:
|
||||
- UNIT_TESTS
|
||||
- UNIT_TESTS_PERSIST_WORKSPACE
|
||||
# Update NPM
|
||||
- NPM_PUBLISH:
|
||||
requires:
|
||||
|
||||
@@ -1,23 +1,46 @@
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
// ~~ RULES
|
||||
const loadShadersRule = require('./rules/loadShaders.js');
|
||||
const loadWebWorkersRule = require('./rules/loadWebWorkers.js');
|
||||
const transpileJavaScriptRule = require('./rules/transpileJavaScript.js');
|
||||
// ~~ PLUGINS
|
||||
const TerserJSPlugin = require('terser-webpack-plugin');
|
||||
// ~~ ENV VARS
|
||||
const NODE_ENV = process.env.NODE_ENV;
|
||||
const QUICK_BUILD = process.env.QUICK_BUILD;
|
||||
|
||||
module.exports = (env, argv, { SRC_DIR, DIST_DIR }) => {
|
||||
if (!process.env.NODE_ENV) {
|
||||
throw new Error('process.env.NODE_ENV not set');
|
||||
}
|
||||
|
||||
const mode =
|
||||
process.env.NODE_ENV === 'production' ? 'production' : 'development';
|
||||
const mode = NODE_ENV === 'production' ? 'production' : 'development';
|
||||
const isProdBuild = NODE_ENV === 'production';
|
||||
const isQuickBuild = QUICK_BUILD === 'true';
|
||||
|
||||
return {
|
||||
mode,
|
||||
const config = {
|
||||
mode: isProdBuild ? 'production' : 'development',
|
||||
devtool: isProdBuild ? 'source-map' : 'cheap-module-eval-source-map',
|
||||
entry: {
|
||||
app: `${SRC_DIR}/index.js`,
|
||||
},
|
||||
optimization: {
|
||||
minimize: isProdBuild,
|
||||
sideEffects: true,
|
||||
},
|
||||
context: SRC_DIR,
|
||||
stats: {
|
||||
colors: true,
|
||||
hash: true,
|
||||
timings: true,
|
||||
assets: true,
|
||||
chunks: false,
|
||||
chunkModules: false,
|
||||
modules: false,
|
||||
children: false,
|
||||
warnings: true,
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
transpileJavaScriptRule(mode),
|
||||
@@ -53,4 +76,23 @@ module.exports = (env, argv, { SRC_DIR, DIST_DIR }) => {
|
||||
fs: 'empty',
|
||||
},
|
||||
};
|
||||
|
||||
if (isProdBuild) {
|
||||
config.optimization.minimizer = [
|
||||
new TerserJSPlugin({
|
||||
// Supports:
|
||||
// source-map and inline-source-map
|
||||
sourceMap: isProdBuild && !isQuickBuild,
|
||||
parallel: true,
|
||||
terserOptions: {},
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
if (isQuickBuild) {
|
||||
config.optimization.minimize = false;
|
||||
config.devtool = false;
|
||||
}
|
||||
|
||||
return config;
|
||||
};
|
||||
@@ -140,8 +140,8 @@ children: PropTypes.node,
|
||||
customProps: PropTypes.object
|
||||
```
|
||||
|
||||
Viewport components are managed by the `LayoutManager`. Which Viewport component
|
||||
is used depends on:
|
||||
Viewport components are managed by the `ViewportGrid` Component. Which Viewport
|
||||
component is used depends on:
|
||||
|
||||
- The Layout Configuration
|
||||
- Registered SopClassHandlers
|
||||
@@ -159,8 +159,8 @@ For a complete example implementation,
|
||||
An extension can register a Toolbar Module by providing a `getToolbarModule()`
|
||||
method that returns a React Component. The component does not receive any props.
|
||||
If you want to modify or react to state, you will need to connect to the redux
|
||||
store.
|
||||
The given toolbar must determine its set of elements and the context of them. The set of elements will be listed on toolbar `definitions`.
|
||||
store. The given toolbar must determine its set of elements and the context of
|
||||
them. The set of elements will be listed on toolbar `definitions`.
|
||||
|
||||

|
||||
|
||||
@@ -173,7 +173,11 @@ For a complete example implementation,
|
||||
|
||||
##### Toolbar Custom Component
|
||||
|
||||
Toolbar elements can define its own custom react component to be consumed when rendering it. So far, it accepts `Functional` and `Class` Components. For that, you just need to expose your `CustomToolbarComponent` as the value of key `CustomComponent`. In case the property `CustomComponent` is not present, a default toolbar component will be used to render it. See bellow
|
||||
Toolbar elements can define its own custom react component to be consumed when
|
||||
rendering it. So far, it accepts `Functional` and `Class` Components. For that,
|
||||
you just need to expose your `CustomToolbarComponent` as the value of key
|
||||
`CustomComponent`. In case the property `CustomComponent` is not present, a
|
||||
default toolbar component will be used to render it. See bellow
|
||||
|
||||
```js
|
||||
definitions: [
|
||||
@@ -188,8 +192,11 @@ definitions: [
|
||||
]
|
||||
|
||||
```
|
||||
|
||||
`CustomComponent` components will receive the following props:
|
||||
- parentContext: parent context. (In most of the cases it will be a ToolbarRow instance)
|
||||
|
||||
- parentContext: parent context. (In most of the cases it will be a ToolbarRow
|
||||
instance)
|
||||
- toolbarClickCallback: callback method when clicking on toolbar
|
||||
- button: its own definition object
|
||||
- key: react key prop
|
||||
|
||||
@@ -3,6 +3,28 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.3.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.3.0...@ohif/extension-cornerstone@1.3.1) (2019-10-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 set tools bidirectional, eraser and annotate command ([#1020](https://github.com/OHIF/Viewers/issues/1020)) ([a28984e](https://github.com/OHIF/Viewers/commit/a28984e))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.3.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.2.5...@ohif/extension-cornerstone@1.3.0) (2019-10-09)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Multiple fixes and implementation changes to react-cornerstone-viewport ([1cc94f3](https://github.com/OHIF/Viewers/commit/1cc94f3))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.2.4...@ohif/extension-cornerstone@1.2.5) (2019-09-27)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-cornerstone",
|
||||
"version": "1.2.5",
|
||||
"version": "1.3.1",
|
||||
"description": "OHIF extension for Cornerstone",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -48,6 +48,6 @@
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"classnames": "^2.2.6",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"react-cornerstone-viewport": "0.1.30"
|
||||
"react-cornerstone-viewport": "2.x.x"
|
||||
}
|
||||
}
|
||||
@@ -23,36 +23,40 @@ const MEASUREMENT_ACTION_MAP = {
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
let dataFromStore;
|
||||
|
||||
// TODO: This may not be updated anymore :thinking:
|
||||
if (state.extensions && state.extensions.cornerstone) {
|
||||
dataFromStore = state.extensions.cornerstone;
|
||||
}
|
||||
|
||||
// TODO: This should be extension configuration
|
||||
// ...dataFromStore -->
|
||||
// availableTools,
|
||||
// onNewImage,
|
||||
// onRightClick,
|
||||
// onTouchPress,
|
||||
// onTouchStart,
|
||||
// onMouseClick,
|
||||
|
||||
// If this is the active viewport, enable prefetching.
|
||||
const { viewportIndex } = ownProps; //.viewportData;
|
||||
const isActive = viewportIndex === state.viewports.activeViewportIndex;
|
||||
const viewportSpecificData =
|
||||
state.viewports.viewportSpecificData[viewportIndex] || {};
|
||||
|
||||
// CINE
|
||||
let isPlaying = false;
|
||||
let frameRate = 24;
|
||||
|
||||
if (viewportSpecificData && viewportSpecificData.cine) {
|
||||
const cine = viewportSpecificData.cine;
|
||||
|
||||
isPlaying = cine.isPlaying === true;
|
||||
frameRate = cine.cineFrameRate || frameRate;
|
||||
}
|
||||
|
||||
return {
|
||||
layout: state.viewports.layout,
|
||||
// layout: state.viewports.layout,
|
||||
isActive,
|
||||
// TODO: Need a cleaner and more versatile way.
|
||||
// Currently justing using escape hatch + commands
|
||||
// activeTool: activeButton && activeButton.command,
|
||||
...dataFromStore,
|
||||
enableStackPrefetch: isActive,
|
||||
isStackPrefetchEnabled: isActive,
|
||||
isPlaying,
|
||||
frameRate,
|
||||
//stack: viewportSpecificData.stack,
|
||||
cineToolData: viewportSpecificData.cine,
|
||||
viewport: viewportSpecificData.viewport,
|
||||
// viewport: viewportSpecificData.viewport,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React, { Component } from "react";
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import ConnectedCornerstoneViewport from "./ConnectedCornerstoneViewport";
|
||||
import OHIF from "@ohif/core";
|
||||
import PropTypes from "prop-types";
|
||||
import cornerstone from "cornerstone-core";
|
||||
import handleSegmentationStorage from "./handleSegmentationStorage.js";
|
||||
import ConnectedCornerstoneViewport from './ConnectedCornerstoneViewport';
|
||||
import OHIF from '@ohif/core';
|
||||
import PropTypes from 'prop-types';
|
||||
import cornerstone from 'cornerstone-core';
|
||||
import handleSegmentationStorage from './handleSegmentationStorage.js';
|
||||
|
||||
const { StackManager } = OHIF.utils;
|
||||
|
||||
@@ -18,7 +18,7 @@ cornerstone.metaData.addProvider(
|
||||
StackManager.setMetadataProvider(metadataProvider);
|
||||
|
||||
const SOP_CLASSES = {
|
||||
SEGMENTATION_STORAGE: "1.2.840.10008.5.1.4.1.1.66.4"
|
||||
SEGMENTATION_STORAGE: '1.2.840.10008.5.1.4.1.1.66.4',
|
||||
};
|
||||
|
||||
const specialCaseHandlers = {};
|
||||
@@ -28,11 +28,11 @@ specialCaseHandlers[
|
||||
|
||||
class OHIFCornerstoneViewport extends Component {
|
||||
state = {
|
||||
viewportData: null
|
||||
viewportData: null,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
customProps: {}
|
||||
customProps: {},
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
@@ -40,17 +40,17 @@ class OHIFCornerstoneViewport extends Component {
|
||||
displaySet: PropTypes.object,
|
||||
viewportIndex: PropTypes.number,
|
||||
children: PropTypes.node,
|
||||
customProps: PropTypes.object
|
||||
customProps: PropTypes.object,
|
||||
};
|
||||
|
||||
static id = "OHIFCornerstoneViewport";
|
||||
static id = 'OHIFCornerstoneViewport';
|
||||
|
||||
static init() {
|
||||
console.log("OHIFCornerstoneViewport init()");
|
||||
console.log('OHIFCornerstoneViewport init()');
|
||||
}
|
||||
|
||||
static destroy() {
|
||||
console.log("OHIFCornerstoneViewport destroy()");
|
||||
console.log('OHIFCornerstoneViewport destroy()');
|
||||
StackManager.clearStacks();
|
||||
}
|
||||
|
||||
@@ -72,15 +72,15 @@ class OHIFCornerstoneViewport extends Component {
|
||||
frameIndex = 0
|
||||
) {
|
||||
if (!studies || !studies.length) {
|
||||
throw new Error("Studies not provided.");
|
||||
throw new Error('Studies not provided.');
|
||||
}
|
||||
|
||||
if (!studyInstanceUid) {
|
||||
throw new Error("StudyInstanceUID not provided.");
|
||||
throw new Error('StudyInstanceUID not provided.');
|
||||
}
|
||||
|
||||
if (!displaySetInstanceUid) {
|
||||
throw new Error("StudyInstanceUID not provided.");
|
||||
throw new Error('StudyInstanceUID not provided.');
|
||||
}
|
||||
|
||||
// Create shortcut to displaySet
|
||||
@@ -89,7 +89,7 @@ class OHIFCornerstoneViewport extends Component {
|
||||
);
|
||||
|
||||
if (!study) {
|
||||
throw new Error("Study not found.");
|
||||
throw new Error('Study not found.');
|
||||
}
|
||||
|
||||
const displaySet = study.displaySets.find(set => {
|
||||
@@ -97,7 +97,7 @@ class OHIFCornerstoneViewport extends Component {
|
||||
});
|
||||
|
||||
if (!displaySet) {
|
||||
throw new Error("Display Set not found.");
|
||||
throw new Error('Display Set not found.');
|
||||
}
|
||||
|
||||
// Get stack from Stack Manager
|
||||
@@ -110,7 +110,7 @@ class OHIFCornerstoneViewport extends Component {
|
||||
if (sopInstanceUid) {
|
||||
const index = stack.imageIds.findIndex(imageId => {
|
||||
const sopCommonModule = cornerstone.metaData.get(
|
||||
"sopCommonModule",
|
||||
'sopCommonModule',
|
||||
imageId
|
||||
);
|
||||
if (!sopCommonModule) {
|
||||
@@ -124,7 +124,7 @@ class OHIFCornerstoneViewport extends Component {
|
||||
stack.currentImageIdIndex = index;
|
||||
} else {
|
||||
console.warn(
|
||||
"SOPInstanceUID provided was not found in specified DisplaySet"
|
||||
'SOPInstanceUID provided was not found in specified DisplaySet'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -132,22 +132,6 @@ class OHIFCornerstoneViewport extends Component {
|
||||
return stack;
|
||||
}
|
||||
|
||||
static getViewportData = (
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
sopInstanceUid,
|
||||
frameIndex
|
||||
) => {
|
||||
return OHIFCornerstoneViewport.getCornerstoneStack(
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
sopInstanceUid,
|
||||
frameIndex
|
||||
);
|
||||
};
|
||||
|
||||
getViewportData = async (
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
@@ -172,7 +156,7 @@ class OHIFCornerstoneViewport extends Component {
|
||||
);
|
||||
break;
|
||||
default:
|
||||
const stack = OHIFCornerstoneViewport.getViewportData(
|
||||
const stack = OHIFCornerstoneViewport.getCornerstoneStack(
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
@@ -183,7 +167,7 @@ class OHIFCornerstoneViewport extends Component {
|
||||
viewportData = {
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
stack
|
||||
stack,
|
||||
};
|
||||
|
||||
break;
|
||||
@@ -199,7 +183,7 @@ class OHIFCornerstoneViewport extends Component {
|
||||
displaySetInstanceUid,
|
||||
sopClassUids,
|
||||
sopInstanceUid,
|
||||
frameIndex
|
||||
frameIndex,
|
||||
} = displaySet;
|
||||
|
||||
if (!studyInstanceUid || !displaySetInstanceUid) {
|
||||
@@ -208,7 +192,7 @@ class OHIFCornerstoneViewport extends Component {
|
||||
|
||||
if (sopClassUids && sopClassUids.length > 1) {
|
||||
console.warn(
|
||||
"More than one SOPClassUid in the same series is not yet supported."
|
||||
'More than one SOPClassUid in the same series is not yet supported.'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -223,7 +207,7 @@ class OHIFCornerstoneViewport extends Component {
|
||||
frameIndex
|
||||
).then(viewportData => {
|
||||
this.setState({
|
||||
viewportData
|
||||
viewportData,
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -249,25 +233,42 @@ class OHIFCornerstoneViewport extends Component {
|
||||
render() {
|
||||
let childrenWithProps = null;
|
||||
|
||||
if (!this.state.viewportData) {
|
||||
return null;
|
||||
}
|
||||
const { viewportIndex } = this.props;
|
||||
const {
|
||||
imageIds,
|
||||
currentImageIdIndex,
|
||||
// If this comes from the instance, would be a better default
|
||||
// `FrameTime` in the instance
|
||||
// frameRate = 0,
|
||||
} = this.state.viewportData.stack;
|
||||
|
||||
// TODO: Does it make more sense to use Context?
|
||||
if (this.props.children && this.props.children.length) {
|
||||
childrenWithProps = this.props.children.map((child, index) => {
|
||||
return React.cloneElement(child, {
|
||||
viewportIndex: this.props.viewportIndex,
|
||||
key: index
|
||||
key: index,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{this.state.viewportData && (
|
||||
<ConnectedCornerstoneViewport
|
||||
viewportData={this.state.viewportData}
|
||||
viewportIndex={this.props.viewportIndex}
|
||||
{...this.props.customProps}
|
||||
/>
|
||||
)}
|
||||
<ConnectedCornerstoneViewport
|
||||
viewportIndex={viewportIndex}
|
||||
imageIds={imageIds}
|
||||
imageIdIndex={currentImageIdIndex}
|
||||
// ~~ Connected (From REDUX)
|
||||
// frameRate={frameRate}
|
||||
// isPlaying={false}
|
||||
// isStackPrefetchEnabled={true}
|
||||
// onElementEnabled={() => {}}
|
||||
// setViewportActive{() => {}}
|
||||
{...this.props.customProps}
|
||||
/>
|
||||
{childrenWithProps}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import init from './init.js';
|
||||
import asyncComponent from './asyncComponent.js';
|
||||
import commandsModule from './commandsModule.js';
|
||||
import toolbarModule from './toolbarModule.js';
|
||||
|
||||
const OHIFCornerstoneViewport = asyncComponent(() => import( /* webpackChunkName: "OHIFCornerstoneViewport" */ './OHIFCornerstoneViewport.js'));
|
||||
const OHIFCornerstoneViewport = asyncComponent(() =>
|
||||
import(
|
||||
/* webpackChunkName: "OHIFCornerstoneViewport" */ './OHIFCornerstoneViewport.js'
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -13,6 +18,15 @@ export default {
|
||||
*/
|
||||
id: 'cornerstone',
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param {object} [configuration={}]
|
||||
* @param {object|array} [configuration.csToolsConfig] - Passed directly to `initCornerstoneTools`
|
||||
*/
|
||||
preRegistration(configuration = {}) {
|
||||
init(configuration);
|
||||
},
|
||||
getViewportModule() {
|
||||
return OHIFCornerstoneViewport;
|
||||
},
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import OHIF from '@ohif/core';
|
||||
import cornerstone from 'cornerstone-core';
|
||||
import csTools from 'cornerstone-tools';
|
||||
import initCornerstoneTools from './initCornerstoneTools.js';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} configuration
|
||||
* @param {Object|Array} configuration.csToolsConfig
|
||||
*/
|
||||
export default function init(configuration = {}) {
|
||||
const { csToolsConfig } = configuration;
|
||||
const { StackManager } = OHIF.utils;
|
||||
const metadataProvider = new OHIF.cornerstone.MetadataProvider();
|
||||
|
||||
// ~~ Set our MetadataProvider
|
||||
cornerstone.metaData.addProvider(
|
||||
metadataProvider.provider.bind(metadataProvider)
|
||||
);
|
||||
|
||||
StackManager.setMetadataProvider(metadataProvider);
|
||||
|
||||
// ~~
|
||||
const defaultCsToolsConfig = csToolsConfig || {
|
||||
globalToolSyncEnabled: true,
|
||||
showSVGCursors: true,
|
||||
autoResizeViewports: false,
|
||||
};
|
||||
|
||||
initCornerstoneTools(defaultCsToolsConfig);
|
||||
|
||||
// ~~ Toooools 🙌
|
||||
const {
|
||||
PanTool,
|
||||
ZoomTool,
|
||||
WwwcTool,
|
||||
MagnifyTool,
|
||||
StackScrollTool,
|
||||
StackScrollMouseWheelTool,
|
||||
// Touch
|
||||
PanMultiTouchTool,
|
||||
ZoomTouchPinchTool,
|
||||
// Annotations
|
||||
EraserTool,
|
||||
ArrowAnnotateTool,
|
||||
BidirectionalTool,
|
||||
LengthTool,
|
||||
AngleTool,
|
||||
FreehandRoiTool,
|
||||
EllipticalRoiTool,
|
||||
DragProbeTool,
|
||||
RectangleRoiTool,
|
||||
// Segmentation
|
||||
BrushTool,
|
||||
} = csTools;
|
||||
const tools = [
|
||||
PanTool,
|
||||
ZoomTool,
|
||||
WwwcTool,
|
||||
MagnifyTool,
|
||||
StackScrollTool,
|
||||
StackScrollMouseWheelTool,
|
||||
// Touch
|
||||
PanMultiTouchTool,
|
||||
ZoomTouchPinchTool,
|
||||
// Annotations
|
||||
EraserTool,
|
||||
ArrowAnnotateTool,
|
||||
BidirectionalTool,
|
||||
LengthTool,
|
||||
AngleTool,
|
||||
FreehandRoiTool,
|
||||
EllipticalRoiTool,
|
||||
DragProbeTool,
|
||||
RectangleRoiTool,
|
||||
// Segmentation
|
||||
BrushTool,
|
||||
];
|
||||
|
||||
tools.forEach(tool => csTools.addTool(tool));
|
||||
csTools.setToolActive('Pan', { mouseButtonMask: 4 });
|
||||
csTools.setToolActive('Zoom', { mouseButtonMask: 2 });
|
||||
csTools.setToolActive('Wwwc', { mouseButtonMask: 1 });
|
||||
csTools.setToolActive('StackScrollMouseWheel', {}); // TODO: Empty options should not be required
|
||||
csTools.setToolActive('PanMultiTouch', { pointers: 2 }); // TODO: Better error if no options
|
||||
csTools.setToolActive('ZoomTouchPinch', {});
|
||||
}
|
||||
File renamed without changes.
@@ -74,7 +74,7 @@ const definitions = [
|
||||
label: 'Annotate',
|
||||
icon: 'measure-non-target',
|
||||
//
|
||||
type: TOOLBAR_BUTTON_TYPES.COMMAND,
|
||||
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
|
||||
commandName: 'setToolActive',
|
||||
commandOptions: { toolName: 'ArrowAnnotate' },
|
||||
},
|
||||
@@ -200,7 +200,7 @@ const definitions = [
|
||||
label: 'Bidirectional',
|
||||
icon: 'measure-target',
|
||||
//
|
||||
type: TOOLBAR_BUTTON_TYPES.COMMAND,
|
||||
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
|
||||
commandName: 'setToolActive',
|
||||
commandOptions: { toolName: 'Bidirectional' },
|
||||
},
|
||||
@@ -209,7 +209,7 @@ const definitions = [
|
||||
label: 'Eraser',
|
||||
icon: 'eraser',
|
||||
//
|
||||
type: TOOLBAR_BUTTON_TYPES.COMMAND,
|
||||
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
|
||||
commandName: 'setToolActive',
|
||||
commandOptions: { toolName: 'Eraser' },
|
||||
},
|
||||
|
||||
@@ -3,6 +3,68 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.52.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.5...@ohif/extension-vtk@0.52.6) (2019-10-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.52.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.4...@ohif/extension-vtk@0.52.5) (2019-10-10)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* ⚡️ Update react-vtkjs-viewport for performance improvement ([6ee5094](https://github.com/OHIF/Viewers/commit/6ee5094))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.52.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.3...@ohif/extension-vtk@0.52.4) (2019-10-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.52.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.2...@ohif/extension-vtk@0.52.3) (2019-10-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.52.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.1...@ohif/extension-vtk@0.52.2) (2019-10-09)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 🎸 Update vtk viewport for faster image reconstruction ([#1016](https://github.com/OHIF/Viewers/issues/1016)) ([8513ad9](https://github.com/OHIF/Viewers/commit/8513ad9))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.52.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.0...@ohif/extension-vtk@0.52.1) (2019-10-04)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-vtk",
|
||||
"version": "0.52.1",
|
||||
"version": "0.52.8",
|
||||
"description": "OHIF extension for VTK.js",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -48,11 +48,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"react-vtkjs-viewport": "0.1.2"
|
||||
"react-vtkjs-viewport": "^0.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ohif/core": "^1.1.0",
|
||||
"@ohif/ui": "^0.53.1",
|
||||
"@ohif/core": "^1.3.2",
|
||||
"@ohif/ui": "^0.53.4",
|
||||
"cornerstone-tools": "^4.0.9",
|
||||
"cornerstone-wado-image-loader": "^3.0.0",
|
||||
"dcmjs": "^0.6.1",
|
||||
|
||||
@@ -153,7 +153,7 @@ class OHIFVTKViewport extends Component {
|
||||
switch (sopClassUid) {
|
||||
case SOP_CLASSES.SEGMENTATION_STORAGE:
|
||||
throw new Error('Not yet implemented');
|
||||
|
||||
/*
|
||||
const data = handleSegmentationStorage(
|
||||
stack.imageIds,
|
||||
displaySetInstanceUid
|
||||
@@ -168,10 +168,13 @@ class OHIFVTKViewport extends Component {
|
||||
labelmap: labelmapDataObject,
|
||||
};
|
||||
});
|
||||
*/
|
||||
default:
|
||||
imageDataObject = getImageData(stack.imageIds, displaySetInstanceUid);
|
||||
|
||||
return loadImageData(imageDataObject).then(() => {
|
||||
const loadImageDataPromise = loadImageData(imageDataObject);
|
||||
|
||||
return loadImageDataPromise.then(() => {
|
||||
return {
|
||||
data: imageDataObject.vtkImageData,
|
||||
};
|
||||
@@ -265,7 +268,6 @@ class OHIFVTKViewport extends Component {
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
console.log(this.props.viewportIndex);
|
||||
vtkViewportSubscriptionManager.unsubscribe(this.props.viewportIndex);
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,13 @@ function getCrosshairCallbackForIndex(index) {
|
||||
}
|
||||
|
||||
async function _getActiveViewportVTKApi(viewports) {
|
||||
const { layout, viewportSpecificData, activeViewportIndex } = viewports;
|
||||
const {
|
||||
numRows,
|
||||
numColumns,
|
||||
layout,
|
||||
viewportSpecificData,
|
||||
activeViewportIndex,
|
||||
} = viewports;
|
||||
|
||||
const currentData = layout.viewports[activeViewportIndex];
|
||||
if (currentData && currentData.plugin === 'vtk') {
|
||||
@@ -84,6 +90,8 @@ async function _getActiveViewportVTKApi(viewports) {
|
||||
api = await setViewportToVTK(
|
||||
displaySet,
|
||||
activeViewportIndex,
|
||||
numRows,
|
||||
numColumns,
|
||||
layout,
|
||||
viewportSpecificData
|
||||
);
|
||||
|
||||
@@ -1,60 +1,43 @@
|
||||
import setLayoutAndViewportData from './setLayoutAndViewportData.js';
|
||||
import setSingleLayoutData from './setSingleLayoutData.js';
|
||||
|
||||
export default function setMPRLayout(displaySet) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let viewports = [];
|
||||
const rows = 1;
|
||||
const columns = 3;
|
||||
const numViewports = rows * columns;
|
||||
const viewports = [];
|
||||
const numRows = 1;
|
||||
const numColumns = 3;
|
||||
const numViewports = numRows * numColumns;
|
||||
const viewportSpecificData = {};
|
||||
for (let i = 0; i < numViewports; i++) {
|
||||
viewports.push({
|
||||
height: `${100 / rows}%`,
|
||||
width: `${100 / columns}%`,
|
||||
});
|
||||
|
||||
for (let i = 0; i < numViewports; i++) {
|
||||
viewports.push({});
|
||||
viewportSpecificData[i] = displaySet;
|
||||
viewportSpecificData[i].plugin = 'vtk';
|
||||
}
|
||||
const layout = {
|
||||
viewports,
|
||||
};
|
||||
|
||||
const viewportIndices = [0, 1, 2];
|
||||
let updatedViewports = layout.viewports;
|
||||
|
||||
const apis = [];
|
||||
viewportIndices.forEach(viewportIndex => {
|
||||
apis[viewportIndex] = null;
|
||||
/*const currentData = layout.viewports[viewportIndex];
|
||||
if (currentData && currentData.plugin === 'vtk') {
|
||||
reject(new Error('Should not have reached this point??'));
|
||||
}*/
|
||||
|
||||
const data = {
|
||||
viewports.forEach((viewport, index) => {
|
||||
apis[index] = null;
|
||||
viewports[index] = Object.assign({}, viewports[index], {
|
||||
// plugin: 'vtk',
|
||||
vtk: {
|
||||
mode: 'mpr', // TODO: not used
|
||||
afterCreation: api => {
|
||||
apis[viewportIndex] = api;
|
||||
apis[index] = api;
|
||||
|
||||
if (apis.every(a => !!a)) {
|
||||
resolve(apis);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
updatedViewports = setSingleLayoutData(
|
||||
updatedViewports,
|
||||
viewportIndex,
|
||||
data
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
setLayoutAndViewportData(
|
||||
{ viewports: updatedViewports },
|
||||
{
|
||||
numRows,
|
||||
numColumns,
|
||||
viewports,
|
||||
},
|
||||
viewportSpecificData
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
export default function setSingleLayoutData(
|
||||
originalArray,
|
||||
viewportIndex,
|
||||
data
|
||||
) {
|
||||
const viewports = originalArray.slice();
|
||||
const layoutData = Object.assign({}, viewports[viewportIndex], data);
|
||||
|
||||
viewports[viewportIndex] = layoutData;
|
||||
|
||||
return viewports;
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
import setLayoutAndViewportData from './setLayoutAndViewportData.js';
|
||||
import setSingleLayoutData from './setSingleLayoutData.js';
|
||||
|
||||
export default function setViewportToVTK(
|
||||
displaySet,
|
||||
viewportIndex,
|
||||
numRows,
|
||||
numColumns,
|
||||
layout,
|
||||
viewportSpecificData
|
||||
) {
|
||||
@@ -13,7 +14,9 @@ export default function setViewportToVTK(
|
||||
reject(new Error('Should not have reached this point??'));
|
||||
}*/
|
||||
|
||||
const data = {
|
||||
const viewports = layout.viewports.slice();
|
||||
|
||||
viewports[viewportIndex] = Object.assign({}, viewports[viewportIndex], {
|
||||
// plugin: 'vtk',
|
||||
vtk: {
|
||||
mode: 'mpr', // TODO: not used
|
||||
@@ -21,18 +24,16 @@ export default function setViewportToVTK(
|
||||
resolve(api);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const updatedViewports = setSingleLayoutData(
|
||||
layout.viewports,
|
||||
viewportIndex,
|
||||
data
|
||||
);
|
||||
});
|
||||
|
||||
const updatedViewportData = viewportSpecificData;
|
||||
|
||||
setLayoutAndViewportData(
|
||||
{ viewports: updatedViewports },
|
||||
{
|
||||
numRows,
|
||||
numColumns,
|
||||
viewports,
|
||||
},
|
||||
updatedViewportData
|
||||
);
|
||||
});
|
||||
|
||||
+1
-1
@@ -13,7 +13,6 @@
|
||||
"scripts": {
|
||||
"cm": "npx git-cz",
|
||||
"build": "lerna run build:viewer --stream",
|
||||
"build:e2e": "lerna run build:viewer:e2e --stream",
|
||||
"build:ci": "lerna run build:viewer:ci --stream",
|
||||
"build:demo": "lerna run build:viewer:demo --stream",
|
||||
"build:package": "lerna run build:viewer:package --stream",
|
||||
@@ -28,6 +27,7 @@
|
||||
"test:unit:ci": "lerna run test:unit:ci --parallel --stream",
|
||||
"test:e2e": "lerna run test:e2e --stream",
|
||||
"test:e2e:dist": "lerna run test:e2e:dist --stream",
|
||||
"test:e2e:serve": "lerna run test:e2e:serve --stream",
|
||||
"see-changed": "lerna changed",
|
||||
"docs:publish": "chmod +x ./build-and-publish-docs.sh && ./build-and-publish-docs.sh",
|
||||
"release": "yarn run lerna:version && yarn run lerna:publish",
|
||||
|
||||
@@ -3,6 +3,52 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.3.0](https://github.com/OHIF/Viewers/compare/@ohif/core@1.2.0...@ohif/core@1.3.0) (2019-10-11)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 🎸 Improve usability of Google Cloud adapter, including direct routes to studies ([#989](https://github.com/OHIF/Viewers/issues/989)) ([2bc361c](https://github.com/OHIF/Viewers/commit/2bc361c))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.2.0](https://github.com/OHIF/Viewers/compare/@ohif/core@1.1.0...@ohif/core@1.2.0) (2019-10-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* OHIF-1002 Study lazy load should be true by default ([#1004](https://github.com/OHIF/Viewers/issues/1004)) ([66d8bc6](https://github.com/OHIF/Viewers/commit/66d8bc6))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Allow a server requestOptions.auth to be a function that returns the Authorization header. ([#928](https://github.com/OHIF/Viewers/issues/928)) ([0064a4b](https://github.com/OHIF/Viewers/commit/0064a4b))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.1.0](https://github.com/OHIF/Viewers/compare/@ohif/core@1.0.2...@ohif/core@1.1.0) (2019-10-03)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/core",
|
||||
"version": "1.1.0",
|
||||
"version": "1.3.2",
|
||||
"description": "Generic business logic for web-based medical imaging applications",
|
||||
"author": "OHIF Core Team",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -9,7 +9,7 @@ import user from '../user';
|
||||
* @export
|
||||
* @param {Object} [server={}]
|
||||
* @param {Object} [server.requestOptions]
|
||||
* @param {string} [server.requestOptions.auth]
|
||||
* @param {string|function} [server.requestOptions.auth]
|
||||
* @returns {Object} { Authorization }
|
||||
*/
|
||||
export default function getAuthorizationHeader({ requestOptions } = {}) {
|
||||
@@ -19,8 +19,13 @@ export default function getAuthorizationHeader({ requestOptions } = {}) {
|
||||
const accessToken = user && user.getAccessToken && user.getAccessToken();
|
||||
|
||||
if (requestOptions && requestOptions.auth) {
|
||||
// HTTP Basic Auth (user:password)
|
||||
headers.Authorization = `Basic ${btoa(requestOptions.auth)}`;
|
||||
if (typeof requestOptions.auth === 'function') {
|
||||
// Custom Auth Header
|
||||
headers.Authorization = requestOptions.auth(requestOptions);
|
||||
} else {
|
||||
// HTTP Basic Auth (user:password)
|
||||
headers.Authorization = `Basic ${btoa(requestOptions.auth)}`;
|
||||
}
|
||||
} else if (accessToken) {
|
||||
headers.Authorization = `Bearer ${accessToken}`;
|
||||
}
|
||||
|
||||
@@ -7,10 +7,7 @@ describe('getAuthorizationHeader', () => {
|
||||
it('should return a HTTP Basic Auth when server contains requestOptions.auth', () => {
|
||||
const validServer = {
|
||||
requestOptions: {
|
||||
auth: {
|
||||
user: 'dummy_user',
|
||||
password: 'dummy_password',
|
||||
},
|
||||
auth: 'dummy_user:dummy_password',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -26,9 +23,7 @@ describe('getAuthorizationHeader', () => {
|
||||
it('should return a HTTP Basic Auth when server contains requestOptions.auth even though there is no password', () => {
|
||||
const validServerWithoutPassword = {
|
||||
requestOptions: {
|
||||
auth: {
|
||||
user: 'dummy_user',
|
||||
},
|
||||
auth: 'dummy_user',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -43,22 +38,19 @@ describe('getAuthorizationHeader', () => {
|
||||
expect(authentication).toEqual(expectedAuthorizationHeader);
|
||||
});
|
||||
|
||||
it('should return a HTTP Basic Auth when server contains requestOptions.auth even though there is no username', () => {
|
||||
const validServerWithoutPassword = {
|
||||
it('should return a HTTP Basic Auth when server contains requestOptions.auth custom function', () => {
|
||||
const validServerCustomAuth = {
|
||||
requestOptions: {
|
||||
auth: {
|
||||
user: 'dummy_user',
|
||||
},
|
||||
auth: options => `Basic ${options.token}`,
|
||||
token: 'ZHVtbXlfdXNlcjpkdW1teV9wYXNzd29yZA==',
|
||||
},
|
||||
};
|
||||
|
||||
const expectedAuthorizationHeader = {
|
||||
Authorization: `Basic ${btoa(
|
||||
validServerWithoutPassword.requestOptions.auth
|
||||
)}`,
|
||||
Authorization: `Basic ${validServerCustomAuth.requestOptions.token}`,
|
||||
};
|
||||
|
||||
const authentication = getAuthorizationHeader(validServerWithoutPassword);
|
||||
const authentication = getAuthorizationHeader(validServerCustomAuth);
|
||||
|
||||
expect(authentication).toEqual(expectedAuthorizationHeader);
|
||||
});
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
# Table of contents
|
||||
In this document, some important objects are described. In the files there are comments that can help better undestand their methods and properties.
|
||||
- [ResizeViewportManager object](#the-resize-viewport-manager-object)
|
||||
- [ImageSet object](#the-image-set-object)
|
||||
- [Layout Manager](#the-layout-manager-object)
|
||||
- [Type Safe Collections](#the-type-safe-collections)
|
||||
|
||||
# The Resize Viewport Manager object
|
||||
This object has multiple functions to manage window resize event. It relocates Dialogs, resizes viewport elements and scrollbars and some other UI components such as Study and Series Quick Switch, when available.
|
||||
|
||||
## Usage
|
||||
It's only necessary to bind **handleResize** function to the window resize event as follows. The **ohif:viewerbase** package needs to be imported by the referring code as well.
|
||||
```javascript
|
||||
import { Viewerbase } from 'meteor/ohif:viewerbase';
|
||||
|
||||
const ResizeViewportManager = new Viewerbase.ResizeViewportManager();
|
||||
window.addEventListener('resize', ResizeViewportManager.getResizeHandler());
|
||||
```
|
||||
An example os its usage can be found in **ohif-viewerbase/client/components/viewer/viewerMain/viewerMain.js**.
|
||||
|
||||
# The Image Set object
|
||||
An object that represents a list of images that are associated by any arbitrary criteria being thus content agnostic. Besides the main attributes (**images** and **uid**) it allows additional attributes to be appended to it (currently indiscriminately, but this should be changed).
|
||||
|
||||
## Usage
|
||||
ImageSet constructor requires an array of SOP instances like in the example below. It's necessary to import **ohif:viewerbase**.
|
||||
|
||||
```javascript
|
||||
import { Viewerbase } from 'meteor/ohif:viewerbase';
|
||||
|
||||
const imageSet = new Viewerbase.ImageSet(sopInstances);
|
||||
|
||||
imageSet.setAttributes({
|
||||
displaySetInstanceUid: imageSet.uid,
|
||||
seriesInstanceUid: seriesData.seriesInstanceUid,
|
||||
seriesNumber: seriesData.seriesNumber,
|
||||
seriesDescription: seriesData.seriesDescription,
|
||||
numImageFrames: instances.length,
|
||||
frameRate: instance.getRawValue('x00181063'),
|
||||
modality: seriesData.modality,
|
||||
isMultiFrame: isMultiFrame(instance)
|
||||
});
|
||||
|
||||
// Sort instances by InstanceNumber (0020,0013)
|
||||
imageSet.sortBy((a, b) => {
|
||||
return (parseInt(a.getRawValue('x00200013', 0)) || 0) - (parseInt(b.getRawValue('x00200013', 0)) || 0);
|
||||
});
|
||||
```
|
||||
Each SOP instance in this example is an instance of **OHIFInstanceMetadata** object, which is a specialization of **InstanceMetadata**. To read more about the **Metadata API** click [here](metadata/).
|
||||
|
||||
# The Layout Manager object
|
||||
Objects of this class are responsible for creating, organizing and maintaining (manage) viewport rendering. It creates a grid, positioning viewports accordingly to it's configuration keeping all viewports data (in **viewportData** property) for easy access from other components. It support many layout configurations and some of them were fully tested: 1x1, 1x2, 1x3, 2x1, 2x2, 2x3, 3x1, 3x2, 3x3. Other configurations may work as well.
|
||||
Finally it provides some useful functions to move through viewports and zoom it.
|
||||
|
||||
## Usage
|
||||
In order to use _LayoutManager_ the **ohif:viewerbase** package needs to be imported by the referring code and instantiated as follows. An example os its usage is in **ohif-viewerbase/client/components/viewer/viewerMain/viewerMain.js**.
|
||||
|
||||
```javascript
|
||||
import { Viewerbase } from 'meteor/ohif:viewerbase';
|
||||
|
||||
// Get an array of studies object. This function needs to be implemented, it does not exist.
|
||||
const studies = getArrayOfStudiesObjects();
|
||||
const parentElement = document.getElementById('layoutManagerTarget');
|
||||
const LayoutManager = new Viewerbase.LayoutManager(parentElement, studies);
|
||||
```
|
||||
|
||||
The default configuration is 1x1, and to change it just set **layoutProps** and call **updateViewports** to update the layout as follows.
|
||||
|
||||
```javascript
|
||||
import { Viewerbase } from 'meteor/ohif:viewerbase';
|
||||
|
||||
// Get an array of studies object. This function needs to be implemented, it does not exist.
|
||||
const studies = getArrayOfStudiesObjects();
|
||||
const parentElement = document.getElementById('layoutManagerTarget');
|
||||
const LayoutManager = new LayoutManager(parentElement, studies);
|
||||
|
||||
// Set the layout proprerties to 2x2 layout
|
||||
LayoutManager.layoutProps = {
|
||||
rows: 2,
|
||||
columns: 2
|
||||
};
|
||||
|
||||
// It will render four viewports: two in each row.
|
||||
LayoutManager.updateViewports();
|
||||
```
|
||||
|
||||
The layoutManagerTarget element will have a new class **layout-2-2** (to allow further styling) and it's inner content will a new div#imageViewerViewports that has four inner elements like the following (some elements and attributes were removed for example purpose):
|
||||
```html
|
||||
<div class="viewportContainer active" style="height:50%; width:50%;">
|
||||
<div class="removable">
|
||||
<div class="imageViewerViewport">
|
||||
<canvas></canvas>
|
||||
</div>
|
||||
<div class="imageViewerViewportOverlay"></div>
|
||||
<div class="imageViewerLoadingIndicator"></div>
|
||||
<div class="imageViewerErrorLoadingIndicator"></div>
|
||||
<div class="viewportOrientationMarkers"></div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
Each of this _div.viewportContainer_ will have some classes to help CSS specific styling accordingly to the element's position in the grid: **top**, **middle** and **bottom**. This classes are added by **viewer/components/gridLayout/** component in ohif-viewerbase package.
|
||||
|
||||
# The Type Safe Collections
|
||||
|
||||
With the introduction of the new _Study Metadata API_ in which study metadata is represented by class hierarchies (using prototype-based inheritance), the usage of standard _Minimongo_ collections as a central client-side storage for this data became no longer an option. Standard _Mongo_ and _Minimongo_ collections internally _flatten_ data (in other words, data gets serialized) before storage hence no functions or prototype chains are preserved. In that scenario, when an object is restored (fetched), what is returned is actually a flattened copy of the original object with no functions or prototype (it's no longer an instance of it's original class). As an attempt to overcome this limitation a new type of collection was intruduced: the *TypeSafeCollection*.
|
||||
|
||||
The `TypeSafeCollection` is a simple list-like collection which tries to implement an API _similar_ but not compatible with _Mongo_'s API. It supports basic features like search by attribute map and ID, retrieval by index, sorting of result sets, insertion, removal and reactive operations but, unlike _Mongo_'s API, it (still) lacks support to advanced functionality like complex search criterea or flexible sorting options.
|
||||
|
||||
## Implementation
|
||||
|
||||
The `TypeSafeCollection` is implemented on top of the _JavaScript_ `Array` object. Each element inserted in the collection is appended to the end of its internal array as a _key-value pair (KVP)_ object where the _key_ is a unique randomly generated ID string and the _value_ is the element itself. Once the object has been successfully stored, the generated ID (its ID) is returned to the client code and can later be used to access that specific element. At this point, an important difference to the _Minimongo_ API can be highlighted: a _TypeSafeCollection_ instance will never make any changes to the stored element (e.g., no "\_id" property will ever be assigned to the original object). Another relevant feature that is supported by this design decision is that _not only objects_ can be stored in this collections, but literally _anything_.
|
||||
|
||||
Inside the codebase, the _value_ attribute of each _KVP_ entry in the collection is refered to as _the **payload** of the entry_ since it's what really matters to the user. Hence, this term will also be used here to refer to the _value that has been stored in the collection_. That being said, we can approach another important feature of these collections: A single _payload_ cannot be stored more than once in a given collection. When an attempt of inserting a _payload_ which is already present in the collection is detected, the insert operation will fail and `null` will be returned. In that regard, the collection behaves like `Set` object not permitting a payload to be stored more than once. Strict equality is used when comparing payloads, thus cloned objects are not considered the same. This feature adds an additional garantee that a given study/series/instance will not be listed more than once (it was designed as a replacement for central study collections which were always checked for duplicates).
|
||||
|
||||
Please refer to the codebase for the full `TypeSafeCollection` API.
|
||||
|
||||
## Usage
|
||||
|
||||
In order to use the `TypeSafeCollection` class, the **ohif:viewerbase** package needs to be imported by the referring code and instantiated as follows:
|
||||
|
||||
```javascript
|
||||
import { Viewerbase } from 'meteor/ohif:viewerbase'; // i.e., Viewerbase.TypeSafeCollection
|
||||
OR
|
||||
|
||||
import 'meteor/ohif:viewerbase'; // i.e., OHIF.viewerbase.TypeSafeCollection
|
||||
// The later is preferred when the client code already makes use of the "OHIF" namespace making the second
|
||||
// "import" a garantee that the ".viewerbase" namespace has been properly loaded.
|
||||
```
|
||||
|
||||
A few usage examples:
|
||||
|
||||
```javascript
|
||||
|
||||
const Users = new OHIF.viewerbase.TypeSafeCollection();
|
||||
|
||||
[[ ... ]]
|
||||
|
||||
// Insert a User object...
|
||||
let userId = Users.insert({
|
||||
data: {
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
age: 45
|
||||
},
|
||||
getFullName() {
|
||||
return `${this.data.firstName} ${this.data.lastName}`;
|
||||
},
|
||||
getAge() {
|
||||
return this.data.age;
|
||||
}
|
||||
});
|
||||
|
||||
[[ ... ]]
|
||||
|
||||
let theUserWeJustStored = Users.findById(userId); // ;-)
|
||||
|
||||
[[ ... ]]
|
||||
|
||||
// Retrieve a single user with "Doe" as `lastName`...
|
||||
let myUser = Users.findBy({ 'data.lastName': 'Doe' });
|
||||
// Or all users with "Doe" as `lastName`, sorted by `firstName` in ascending
|
||||
// order and using the `age` attribute to break ties in descending order...
|
||||
let myUsers = Users.findAllBy({ 'data.lastName': 'Doe' }, {
|
||||
sort: [ [ 'data.firstName', 'asc' ], [ 'data.age', 'desc' ] ]
|
||||
});
|
||||
|
||||
```
|
||||
@@ -121,7 +121,7 @@ export class StudyMetadata extends Metadata {
|
||||
if (displaySet) {
|
||||
displaySet.sopClassModule = true;
|
||||
displaySets.push(displaySet);
|
||||
return;
|
||||
return displaySets;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -504,12 +504,12 @@ export default class ProtocolEngine {
|
||||
/**
|
||||
* Sets the current layout
|
||||
*
|
||||
* @param rows
|
||||
* @param columns
|
||||
* @param {number} numRows
|
||||
* @param {number} numColumns
|
||||
*/
|
||||
setLayout(rows, columns) {
|
||||
if (rows < 1 && columns < 1) {
|
||||
log.error(`Invalid layout ${rows} x ${columns}`);
|
||||
setLayout(numRows, numColumns) {
|
||||
if (numRows < 1 && numColumns < 1) {
|
||||
log.error(`Invalid layout ${numRows} x ${numColumns}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -519,16 +519,13 @@ export default class ProtocolEngine {
|
||||
}
|
||||
|
||||
let viewports = [];
|
||||
const numViewports = rows * columns;
|
||||
const numViewports = numRows * numColumns;
|
||||
|
||||
for (let i = 0; i < numViewports; i++) {
|
||||
viewports.push({
|
||||
height: `${100 / rows}%`,
|
||||
width: `${100 / columns}%`,
|
||||
});
|
||||
viewports.push({});
|
||||
}
|
||||
|
||||
this.options.setLayout({ viewports });
|
||||
this.options.setLayout({ numRows, numColumns, viewports });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -634,14 +631,10 @@ export default class ProtocolEngine {
|
||||
//console.log('renderedCallback for ' + element.id);
|
||||
customSettings.forEach(customSetting => {
|
||||
log.trace(
|
||||
`ProtocolEngine::currentViewportData.renderedCallback Applying custom setting: ${
|
||||
customSetting.id
|
||||
}`
|
||||
`ProtocolEngine::currentViewportData.renderedCallback Applying custom setting: ${customSetting.id}`
|
||||
);
|
||||
log.trace(
|
||||
`ProtocolEngine::currentViewportData.renderedCallback with value: ${
|
||||
customSetting.value
|
||||
}`
|
||||
`ProtocolEngine::currentViewportData.renderedCallback with value: ${customSetting.value}`
|
||||
);
|
||||
|
||||
const setting = CustomViewportSettings[customSetting.id];
|
||||
|
||||
@@ -26,9 +26,34 @@ export const setViewportActive = viewportIndex => ({
|
||||
viewportIndex,
|
||||
});
|
||||
|
||||
export const setLayout = layout => ({
|
||||
/**
|
||||
* @param {object} layout
|
||||
* @param {number} layout.numRows
|
||||
* @param {number} layout.numColumns
|
||||
* @param {array} layout.viewports
|
||||
*/
|
||||
export const setLayout = ({ numRows, numColumns, viewports }) => ({
|
||||
type: SET_VIEWPORT_LAYOUT,
|
||||
layout,
|
||||
numRows,
|
||||
numColumns,
|
||||
viewports,
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {object} layout
|
||||
* @param {number} layout.numRows
|
||||
* @param {number} layout.numColumns
|
||||
* @param {array} layout.viewports
|
||||
*/
|
||||
export const setViewportLayoutAndData = (
|
||||
{ numRows, numColumns, viewports },
|
||||
viewportSpecificData
|
||||
) => ({
|
||||
type: SET_VIEWPORT_LAYOUT_AND_DATA,
|
||||
numRows,
|
||||
numColumns,
|
||||
viewports,
|
||||
viewportSpecificData,
|
||||
});
|
||||
|
||||
export const clearViewportSpecificData = viewportIndex => ({
|
||||
@@ -87,12 +112,6 @@ export const setServers = servers => ({
|
||||
servers,
|
||||
});
|
||||
|
||||
export const setViewportLayoutAndData = (layout, viewportSpecificData) => ({
|
||||
type: SET_VIEWPORT_LAYOUT_AND_DATA,
|
||||
layout,
|
||||
viewportSpecificData,
|
||||
});
|
||||
|
||||
const actions = {
|
||||
// VIEWPORT
|
||||
setViewportActive,
|
||||
|
||||
@@ -94,19 +94,20 @@ describe('actions', () => {
|
||||
});
|
||||
|
||||
it('should create an action to set the viewport layout', () => {
|
||||
const layout = {
|
||||
viewports: [
|
||||
{
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
},
|
||||
],
|
||||
};
|
||||
const numRows = 1;
|
||||
const numColumns = 2;
|
||||
const viewports = [{ plugin: 'vtk' }, { plugin: 'vtk' }];
|
||||
|
||||
const expectedAction = {
|
||||
type: types.SET_VIEWPORT_LAYOUT,
|
||||
layout,
|
||||
numRows,
|
||||
numColumns,
|
||||
viewports,
|
||||
};
|
||||
expect(actions.setLayout(layout)).toEqual(expectedAction);
|
||||
|
||||
expect(actions.setLayout({ numRows, numColumns, viewports })).toEqual(
|
||||
expectedAction
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,11 +1,13 @@
|
||||
import actions from './actions.js';
|
||||
import reducers from './reducers';
|
||||
import localStorage from './localStorage.js';
|
||||
import sessionStorage from './sessionStorage.js';
|
||||
|
||||
const redux = {
|
||||
reducers,
|
||||
actions,
|
||||
localStorage,
|
||||
sessionStorage,
|
||||
};
|
||||
|
||||
export default redux;
|
||||
@@ -2,7 +2,7 @@ import redux from './index.js';
|
||||
|
||||
describe('redux exports', () => {
|
||||
test('have not changed', () => {
|
||||
const expectedExports = ['actions', 'reducers', 'localStorage'].sort();
|
||||
const expectedExports = ['actions', 'reducers', 'localStorage', 'sessionStorage'].sort();
|
||||
|
||||
const exports = Object.keys(redux).sort();
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
const LocalStorageApi = window.localStorage;
|
||||
const localStorageKey = 'state';
|
||||
export const loadState = () => {
|
||||
try {
|
||||
const serializedState = window.localStorage.getItem('state');
|
||||
const serializedState = LocalStorageApi.getItem(localStorageKey);
|
||||
if (!serializedState) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -14,7 +16,7 @@ export const loadState = () => {
|
||||
export const saveState = state => {
|
||||
try {
|
||||
const serializedState = JSON.stringify(state);
|
||||
localStorage.setItem('state', serializedState);
|
||||
LocalStorageApi.setItem(localStorageKey, serializedState);
|
||||
} catch (e) {}
|
||||
};
|
||||
|
||||
|
||||
@@ -12,13 +12,13 @@ import cloneDeep from 'lodash.clonedeep';
|
||||
import merge from 'lodash.merge';
|
||||
|
||||
const defaultState = {
|
||||
numRows: 1,
|
||||
numColumns: 1,
|
||||
activeViewportIndex: 0,
|
||||
layout: {
|
||||
viewports: [
|
||||
{
|
||||
// plugin: 'cornerstone',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -38,17 +38,31 @@ const viewports = (state = defaultState, action) => {
|
||||
let viewportSpecificData;
|
||||
let useActiveViewport = false;
|
||||
switch (action.type) {
|
||||
case SET_VIEWPORT_LAYOUT_AND_DATA:
|
||||
return Object.assign({}, state, {
|
||||
viewportSpecificData: action.viewportSpecificData,
|
||||
layout: action.layout,
|
||||
});
|
||||
case SET_VIEWPORT_ACTIVE:
|
||||
return Object.assign({}, state, {
|
||||
activeViewportIndex: action.viewportIndex,
|
||||
});
|
||||
case SET_VIEWPORT_LAYOUT:
|
||||
return Object.assign({}, state, { layout: action.layout });
|
||||
case SET_VIEWPORT_LAYOUT: {
|
||||
const { numRows, numColumns, viewports } = action;
|
||||
const layout = {
|
||||
viewports: [...viewports],
|
||||
};
|
||||
|
||||
return Object.assign({}, state, { numRows, numColumns, layout });
|
||||
}
|
||||
case SET_VIEWPORT_LAYOUT_AND_DATA: {
|
||||
const { numRows, numColumns, viewports, viewportSpecificData } = action;
|
||||
const layout = {
|
||||
viewports: [...viewports],
|
||||
};
|
||||
|
||||
return Object.assign({}, state, {
|
||||
numRows,
|
||||
numColumns,
|
||||
layout,
|
||||
viewportSpecificData: cloneDeep(viewportSpecificData),
|
||||
});
|
||||
}
|
||||
case SET_VIEWPORT: {
|
||||
const layout = cloneDeep(state.layout);
|
||||
const hasPlugin = action.data && action.data.plugin;
|
||||
|
||||
@@ -6,13 +6,10 @@ describe('viewports reducer', () => {
|
||||
it('should return the initial state', () => {
|
||||
expect(reducer(undefined, {})).toEqual({
|
||||
activeViewportIndex: 0,
|
||||
numRows: 1,
|
||||
numColumns: 1,
|
||||
layout: {
|
||||
viewports: [
|
||||
{
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
},
|
||||
],
|
||||
viewports: [{}],
|
||||
},
|
||||
viewportSpecificData: {},
|
||||
});
|
||||
@@ -34,23 +31,25 @@ describe('viewports reducer', () => {
|
||||
it('should handle SET_VIEWPORT_LAYOUT', () => {
|
||||
const setViewportLayoutAction = {
|
||||
type: types.SET_VIEWPORT_LAYOUT,
|
||||
layout: {
|
||||
viewports: [
|
||||
{
|
||||
height: '100%',
|
||||
width: '50%',
|
||||
},
|
||||
{
|
||||
height: '100%',
|
||||
width: '50%',
|
||||
},
|
||||
],
|
||||
},
|
||||
numRows: 1,
|
||||
numColumns: 2,
|
||||
viewports: [
|
||||
{
|
||||
plugin: 'cornerstone',
|
||||
},
|
||||
{
|
||||
plugin: 'vtk',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const updatedState = reducer({}, setViewportLayoutAction);
|
||||
|
||||
expect(updatedState.layout).toEqual(setViewportLayoutAction.layout);
|
||||
expect(updatedState.numRows).toEqual(setViewportLayoutAction.numRows);
|
||||
expect(updatedState.numColumns).toEqual(setViewportLayoutAction.numColumns);
|
||||
expect(updatedState.layout.viewports).toEqual(
|
||||
setViewportLayoutAction.viewports
|
||||
);
|
||||
});
|
||||
|
||||
// If there were previous keys, this would have
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
const SessionStorageApi = window.sessionStorage;
|
||||
const sessionStorageKey = 'state';
|
||||
export const loadState = () => {
|
||||
try {
|
||||
const serializedState = SessionStorageApi.getItem(sessionStorageKey);
|
||||
if (!serializedState) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return JSON.parse(serializedState);
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
export const saveState = state => {
|
||||
try {
|
||||
const serializedState = JSON.stringify(state);
|
||||
SessionStorageApi.setItem(sessionStorageKey, serializedState);
|
||||
} catch (e) {}
|
||||
};
|
||||
|
||||
const sessionStorage = {
|
||||
saveState,
|
||||
loadState,
|
||||
};
|
||||
|
||||
export default sessionStorage;
|
||||
@@ -469,7 +469,7 @@ async function createStudyFromSOPInstanceList(server, sopInstanceList) {
|
||||
* @returns {Object} A study descriptor object
|
||||
*/
|
||||
async function RetrieveMetadata(server, studyInstanceUid) {
|
||||
return (server.enableStudyLazyLoad
|
||||
return (server.enableStudyLazyLoad !== false
|
||||
? lazyLoadStudyMetadata
|
||||
: loadStudyMetadata)(server, studyInstanceUid);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import studyMetadataManager from './studyMetadataManager';
|
||||
import updateMetaDataManager from './updateMetaDataManager.js';
|
||||
import writeScript from './writeScript.js';
|
||||
import DicomLoaderService from './dicomLoaderService.js';
|
||||
import * as urlUtil from './urlUtil';
|
||||
|
||||
const utils = {
|
||||
guid,
|
||||
@@ -23,6 +24,7 @@ const utils = {
|
||||
updateMetaDataManager,
|
||||
DICOMTagDescriptions,
|
||||
DicomLoaderService,
|
||||
urlUtil,
|
||||
};
|
||||
|
||||
export {
|
||||
@@ -38,6 +40,7 @@ export {
|
||||
updateMetaDataManager,
|
||||
DICOMTagDescriptions,
|
||||
DicomLoaderService,
|
||||
urlUtil,
|
||||
};
|
||||
|
||||
export default utils;
|
||||
@@ -15,6 +15,7 @@ describe('Top level exports', () => {
|
||||
'updateMetaDataManager',
|
||||
'DICOMTagDescriptions',
|
||||
'DicomLoaderService',
|
||||
'urlUtil'
|
||||
].sort();
|
||||
|
||||
const exports = Object.keys(utils.default).sort();
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import lib from 'query-string';
|
||||
|
||||
const PARAM_SEPARATOR = ';';
|
||||
const PARAM_PATTERN_IDENTIFIER = ':';
|
||||
|
||||
function toLowerCaseFirstLetter(word) {
|
||||
return word[0].toLowerCase() + word.slice(1);
|
||||
}
|
||||
const getFilters = (location = {}) => {
|
||||
const { search } = location;
|
||||
|
||||
if (!search) {
|
||||
return;
|
||||
}
|
||||
|
||||
const searchParameters = parse(search);
|
||||
const filters = {};
|
||||
|
||||
Object.entries(searchParameters).forEach(([key, value]) => {
|
||||
filters[toLowerCaseFirstLetter(key)] = value;
|
||||
});
|
||||
|
||||
return filters;
|
||||
};
|
||||
|
||||
const decode = (strToDecode = '') => {
|
||||
try {
|
||||
const decoded = window.atob(strToDecode);
|
||||
return decoded;
|
||||
} catch (e) {
|
||||
return strToDecode;
|
||||
}
|
||||
};
|
||||
|
||||
const parse = toParse => {
|
||||
if (toParse) {
|
||||
return lib.parse(toParse);
|
||||
}
|
||||
|
||||
return {};
|
||||
};
|
||||
const parseParam = paramStr => {
|
||||
const _paramDecoded = decode(paramStr);
|
||||
if (_paramDecoded && typeof _paramDecoded === 'string') {
|
||||
return _paramDecoded.split(PARAM_SEPARATOR);
|
||||
}
|
||||
};
|
||||
|
||||
const replaceParam = (path = '', paramKey, paramValue) => {
|
||||
const paramPattern = `${PARAM_PATTERN_IDENTIFIER}${paramKey}`;
|
||||
if (paramValue) {
|
||||
return path.replace(paramPattern, paramValue);
|
||||
}
|
||||
|
||||
return path;
|
||||
};
|
||||
|
||||
const queryString = {
|
||||
getQueryFilters: getFilters,
|
||||
};
|
||||
|
||||
const paramString = {
|
||||
parseParam: parseParam,
|
||||
replaceParam: replaceParam,
|
||||
};
|
||||
|
||||
export { parse, queryString, paramString };
|
||||
@@ -3,6 +3,33 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.53.3](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.53.2...@ohif/ui@0.53.3) (2019-10-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🎸 switch ohif logo from text + font to SVG ([#1021](https://github.com/OHIF/Viewers/issues/1021)) ([e7de8be](https://github.com/OHIF/Viewers/commit/e7de8be))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.53.2](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.53.1...@ohif/ui@0.53.2) (2019-10-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/ui
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.53.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.53.0...@ohif/ui@0.53.1) (2019-10-04)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/ui",
|
||||
"version": "0.53.1",
|
||||
"version": "0.53.4",
|
||||
"description": "A set of React components for Medical Imaging Viewers",
|
||||
"author": "OHIF Contributors",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,162 +0,0 @@
|
||||
import './LayoutManager.css';
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import LayoutPanelDropTarget from './LayoutPanelDropTarget.js';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
function defaultViewportPlugin(props) {
|
||||
return <div>{JSON.stringify(props)}</div>;
|
||||
}
|
||||
|
||||
function EmptyViewport() {
|
||||
return (
|
||||
<div className="EmptyViewport">
|
||||
<p>Please drag a stack here to view images.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export class LayoutManager extends Component {
|
||||
static className = 'LayoutManager';
|
||||
static defaultProps = {
|
||||
viewportData: [],
|
||||
layout: {
|
||||
viewports: [
|
||||
{
|
||||
top: 0,
|
||||
left: 0,
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
},
|
||||
],
|
||||
},
|
||||
activeViewportIndex: 0,
|
||||
supportsDragAndDrop: true,
|
||||
availablePlugins: {
|
||||
defaultViewportPlugin,
|
||||
},
|
||||
defaultPlugin: 'defaultViewportPlugin',
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
viewportData: PropTypes.array.isRequired,
|
||||
supportsDragAndDrop: PropTypes.bool.isRequired,
|
||||
activeViewportIndex: PropTypes.number.isRequired,
|
||||
layout: PropTypes.object.isRequired,
|
||||
availablePlugins: PropTypes.object.isRequired,
|
||||
setViewportData: PropTypes.func,
|
||||
studies: PropTypes.array,
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
onDrop = ({ viewportIndex, item }) => {
|
||||
if (this.props.setViewportData) {
|
||||
this.props.setViewportData({ viewportIndex, item });
|
||||
}
|
||||
};
|
||||
|
||||
getPluginComponent = plugin => {
|
||||
const pluginComponent = this.props.availablePlugins[
|
||||
plugin || this.props.defaultPlugin
|
||||
];
|
||||
|
||||
if (!pluginComponent) {
|
||||
throw new Error(
|
||||
`No Viewport Plugin available for plugin ${plugin}. Available plugins: ${JSON.stringify(
|
||||
this.props.availablePlugins
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
return pluginComponent;
|
||||
};
|
||||
|
||||
getChildComponent(plugin, data, viewportIndex, children) {
|
||||
if (data.displaySet) {
|
||||
const PluginComponent = this.getPluginComponent(plugin);
|
||||
|
||||
return (
|
||||
<PluginComponent
|
||||
viewportData={data}
|
||||
viewportIndex={viewportIndex}
|
||||
children={[children]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <EmptyViewport />;
|
||||
}
|
||||
|
||||
getContent(childComponent, supportsDragAndDrop, viewportIndex) {
|
||||
if (supportsDragAndDrop) {
|
||||
return (
|
||||
<LayoutPanelDropTarget
|
||||
onDrop={this.onDrop}
|
||||
viewportIndex={viewportIndex}
|
||||
>
|
||||
{childComponent}
|
||||
</LayoutPanelDropTarget>
|
||||
);
|
||||
}
|
||||
|
||||
return <div className="LayoutPanel">{childComponent}</div>;
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.props.viewportData.length) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const { supportsDragAndDrop, studies, viewportData } = this.props;
|
||||
const viewports = this.props.layout.viewports;
|
||||
const viewportElements = viewports.map((layout, viewportIndex) => {
|
||||
const displaySet = viewportData[viewportIndex];
|
||||
const data = {
|
||||
displaySet,
|
||||
studies,
|
||||
};
|
||||
|
||||
// Use whichever plugin is currently in use in the panel
|
||||
// unless nothing is specified. If nothing is specified
|
||||
// and the display set has a plugin specified, use that.
|
||||
//
|
||||
// TODO: Change this logic to:
|
||||
// - Plugins define how capable they are of displaying a SopClass
|
||||
// - When updating a panel, ensure that the currently enabled plugin
|
||||
// in the viewport is capable of rendering this display set. If not
|
||||
// then use the most capable available plugin
|
||||
let plugin = layout.plugin;
|
||||
if (!layout.plugin && displaySet && displaySet.plugin) {
|
||||
plugin = displaySet.plugin;
|
||||
}
|
||||
|
||||
const childComponent = this.getChildComponent(
|
||||
plugin,
|
||||
data,
|
||||
viewportIndex,
|
||||
this.props.children
|
||||
);
|
||||
const content = this.getContent(
|
||||
childComponent,
|
||||
supportsDragAndDrop,
|
||||
viewportIndex
|
||||
);
|
||||
|
||||
let className = 'viewport-container';
|
||||
if (this.props.activeViewportIndex === viewportIndex) {
|
||||
className += ' active';
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={viewportIndex} className={className} style={{ ...layout }}>
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
return <div className={LayoutManager.className}>{viewportElements}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export default LayoutManager;
|
||||
@@ -1,12 +0,0 @@
|
||||
.LayoutPanelDropTarget {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
opacity: 1;
|
||||
transition: 0.3s all ease;
|
||||
}
|
||||
|
||||
.LayoutPanelDropTarget.hovered {
|
||||
opacity: 0.5;
|
||||
cursor: copy;
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { DropTarget } from 'react-dnd';
|
||||
import './LayoutPanelDropTarget.css';
|
||||
|
||||
// Drag sources and drop targets only interact
|
||||
// if they have the same string type.
|
||||
const Types = {
|
||||
THUMBNAIL: 'thumbnail',
|
||||
};
|
||||
|
||||
const divTarget = {
|
||||
drop(props, monitor, component) {
|
||||
const item = monitor.getItem();
|
||||
|
||||
if (props.onDrop) {
|
||||
props.onDrop({
|
||||
viewportIndex: props.viewportIndex,
|
||||
item,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
id: `LayoutPanelDropTarget-${props.viewportIndex}`,
|
||||
viewportIndex: props.viewportIndex,
|
||||
item,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
// TODO: Find out why we can't move this into the Example app instead.
|
||||
// It looks like the context isn't properly shared.
|
||||
class LayoutPanelDropTarget extends Component {
|
||||
static className = 'LayoutPanelDropTarget';
|
||||
|
||||
static defaultProps = {
|
||||
isOver: false,
|
||||
canDrop: false,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
connectDropTarget: PropTypes.func.isRequired,
|
||||
canDrop: PropTypes.bool.isRequired,
|
||||
isOver: PropTypes.bool.isRequired,
|
||||
viewportComponent: PropTypes.object,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { canDrop, isOver, connectDropTarget } = this.props;
|
||||
const isActive = canDrop && isOver;
|
||||
|
||||
let className = LayoutPanelDropTarget.className;
|
||||
|
||||
if (isActive) {
|
||||
className += ' hovered';
|
||||
} else if (canDrop) {
|
||||
className += ' can-drop';
|
||||
}
|
||||
|
||||
return connectDropTarget(
|
||||
<div className={className}>{this.props.children}</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const collect = (connect, monitor) => ({
|
||||
connectDropTarget: connect.dropTarget(),
|
||||
canDrop: monitor.canDrop(),
|
||||
isOver: monitor.isOver(),
|
||||
});
|
||||
|
||||
export default DropTarget(Types.THUMBNAIL, divTarget, collect)(
|
||||
LayoutPanelDropTarget
|
||||
);
|
||||
@@ -33,8 +33,8 @@ as easy as possible. You will, however, need to include either the default font
|
||||
or the font you set for your theme:
|
||||
|
||||
```js
|
||||
// Google Fonts, Sanchez & Roboto
|
||||
'https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700|Sanchez&display=swap';
|
||||
// Google Fonts, Roboto
|
||||
'https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700&display=swap';
|
||||
```
|
||||
|
||||
`@ohif/ui` also looks for theme CSS variabled defined on `:root`. You can find a
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ lang }}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="description" content="{{ description }}">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
|
||||
<title>{{ title }}</title>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="description" content="{{ description }}">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
|
||||
<!-- Latest compiled and minified CSS -->
|
||||
<link rel="stylesheet" href="/public/docz.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700|Sanchez&display=swap">
|
||||
<title>{{ title }}</title>
|
||||
|
||||
<style>
|
||||
<!-- Latest compiled and minified CSS -->
|
||||
<link rel="stylesheet" href="/public/docz.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700&display=swap">
|
||||
|
||||
<style>
|
||||
/* "Tide" theme */
|
||||
/* TODO:
|
||||
- Convert the other themes to CSS Variables
|
||||
@@ -40,7 +41,7 @@
|
||||
--table-text-primary-color: #ffffff;
|
||||
--table-text-secondary-color: #91b9cd;
|
||||
|
||||
--large-numbers-color:#6FBDE2;
|
||||
--large-numbers-color: #6FBDE2;
|
||||
|
||||
--state-error: #FFCCCC;
|
||||
--state-error-border: #FFCCCC;
|
||||
@@ -70,11 +71,13 @@
|
||||
body {
|
||||
font-family: 'Roboto', sans-serif !important;
|
||||
}
|
||||
</style>
|
||||
{{ head }}
|
||||
</head>
|
||||
<body>
|
||||
<div id="root" />
|
||||
{{ footer }}
|
||||
</body>
|
||||
</style>
|
||||
{{ head }}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root" />
|
||||
{{ footer }}
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,8 +1,4 @@
|
||||
import {
|
||||
ExampleDropTarget,
|
||||
StudyBrowser,
|
||||
ThumbnailEntry,
|
||||
} from './studyBrowser';
|
||||
import { StudyBrowser, ThumbnailEntry } from './studyBrowser';
|
||||
import { LayoutButton, LayoutChooser } from './layoutButton';
|
||||
import { MeasurementTable, MeasurementTableItem } from './measurementTable';
|
||||
import { Overlay, OverlayTrigger } from './overlayTrigger';
|
||||
@@ -26,7 +22,6 @@ import { Tooltip } from './tooltip';
|
||||
export {
|
||||
Checkbox,
|
||||
CineDialog,
|
||||
ExampleDropTarget,
|
||||
LayoutButton,
|
||||
LayoutChooser,
|
||||
MeasurementTable,
|
||||
|
||||
@@ -35,7 +35,7 @@ class StudyBrowser extends Component {
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="ThumbnailEntryContainer">
|
||||
<div className="ThumbnailEntryContainer" data-cy="thumbnail-list">
|
||||
<ThumbnailEntry
|
||||
key={thumb.displaySetInstanceUid}
|
||||
{...study}
|
||||
|
||||
@@ -45,7 +45,7 @@ class ThumbnailEntryDragSource extends Component {
|
||||
const dropEffect = 'copy';
|
||||
|
||||
return connectDragSource(
|
||||
<div className="ThumbnailEntryContainer">
|
||||
<div className="ThumbnailEntryContainer" data-cy="thumbnail-list">
|
||||
<DragPreview {...this.props} />
|
||||
<ThumbnailEntry {...this.props} />
|
||||
</div>,
|
||||
|
||||
File renamed without changes.
File renamed without changes.
@@ -8,7 +8,8 @@ import {
|
||||
onThumbnailClick,
|
||||
onThumbnailDoubleClick,
|
||||
} from './exampleStudies.js';
|
||||
import { ExampleDropTarget, StudyBrowser } from './../index.js';
|
||||
import ExampleDropTarget from './ExampleDropTarget.js';
|
||||
import { StudyBrowser } from './../index.js';
|
||||
|
||||
class StudyBrowserContainer extends Component {
|
||||
render() {
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export { ExampleDropTarget } from './ExampleDropTarget.js';
|
||||
export { StudyBrowser } from './StudyBrowser.js';
|
||||
export { ThumbnailEntry } from './ThumbnailEntry.js';
|
||||
@@ -53,6 +53,7 @@ import measureTargetUn from './icons/measure-target-un.svg';
|
||||
import measureTemp from './icons/measure-temp.svg';
|
||||
import objectGroup from './icons/object-group.svg';
|
||||
import ohifLogo from './icons/ohif-logo.svg';
|
||||
import ohifTextLogo from './icons/ohif-text-logo.svg';
|
||||
import oval from './icons/oval.svg';
|
||||
import palette from './icons/palette.svg';
|
||||
import play from './icons/play.svg';
|
||||
@@ -120,6 +121,7 @@ const ICONS = {
|
||||
'inline-edit': inlineEdit,
|
||||
list,
|
||||
'ohif-logo': ohifLogo,
|
||||
'ohif-text-logo': ohifTextLogo,
|
||||
lock,
|
||||
play,
|
||||
database,
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 224 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.416 5.932C10.416 7.46267 9.954 8.70867 9.03 9.67C8.11533 10.622 6.958 11.098 5.558 11.098C4.16733 11.098 3.00533 10.6173 2.072 9.656C1.13867 8.68533 0.672 7.43933 0.672 5.918C0.672 4.38733 1.15267 3.16467 2.114 2.25C3.07533 1.326 4.22333 0.863999 5.558 0.863999C6.89267 0.863999 8.036 1.326 8.988 2.25C9.94 3.16467 10.416 4.392 10.416 5.932ZM3.052 8.9C3.77067 9.60933 4.606 9.964 5.558 9.964C6.51 9.964 7.34067 9.60933 8.05 8.9C8.75933 8.18133 9.114 7.20133 9.114 5.96C9.114 4.70933 8.75 3.734 8.022 3.034C7.30333 2.32467 6.482 1.97 5.558 1.97C4.634 1.97 3.808 2.32467 3.08 3.034C2.352 3.734 1.988 4.70933 1.988 5.96C1.988 7.20133 2.34267 8.18133 3.052 8.9ZM14.6296 14.15H11.8156C11.6942 14.15 11.6336 14.094 11.6336 13.982V13.38C11.6336 13.268 11.6942 13.212 11.8156 13.212H12.5996V5.05H11.8436C11.7222 5.05 11.6616 4.994 11.6616 4.882V4.28C11.6616 4.168 11.7222 4.112 11.8436 4.112H13.4816C13.6029 4.112 13.6636 4.168 13.6636 4.28L13.6496 5.386H13.6636C13.8409 5.00333 14.1256 4.68133 14.5176 4.42C14.9189 4.14933 15.4089 4.014 15.9876 4.014C16.9302 4.014 17.6909 4.336 18.2696 4.98C18.8482 5.61467 19.1376 6.464 19.1376 7.528C19.1376 8.58267 18.8529 9.44133 18.2836 10.104C17.7142 10.7667 16.9536 11.098 16.0016 11.098C15.4229 11.098 14.9376 10.9673 14.5456 10.706C14.1629 10.4353 13.8876 10.1087 13.7196 9.726H13.6776C13.7056 10.0247 13.7196 10.2393 13.7196 10.37V13.212H14.6296C14.7509 13.212 14.8116 13.268 14.8116 13.38V13.982C14.8116 14.094 14.7509 14.15 14.6296 14.15ZM13.6916 7.584C13.6916 8.396 13.9016 9.02133 14.3216 9.46C14.7509 9.89867 15.2642 10.118 15.8616 10.118C16.4682 10.118 16.9722 9.88467 17.3736 9.418C17.7842 8.942 17.9896 8.32133 17.9896 7.556C17.9896 6.78133 17.7842 6.16067 17.3736 5.694C16.9722 5.22733 16.4682 4.994 15.8616 4.994C15.2549 4.994 14.7416 5.22267 14.3216 5.68C13.9016 6.13733 13.6916 6.772 13.6916 7.584ZM26.3811 7.696H21.3411C21.3785 8.47067 21.6211 9.068 22.0691 9.488C22.5265 9.908 23.0398 10.118 23.6091 10.118C24.1785 10.118 24.6638 9.99667 25.0651 9.754C25.4758 9.51133 25.7511 9.27333 25.8911 9.04C25.9658 8.94667 26.0498 8.93267 26.1431 8.998L26.5211 9.376C26.5771 9.432 26.6051 9.48333 26.6051 9.53C26.6051 9.56733 26.5818 9.61867 26.5351 9.684C26.4978 9.74933 26.4138 9.852 26.2831 9.992C26.1525 10.132 25.9658 10.2907 25.7231 10.468C25.4898 10.6453 25.1818 10.7947 24.7991 10.916C24.4258 11.0373 24.0245 11.098 23.5951 11.098C22.6151 11.098 21.7938 10.776 21.1311 10.132C20.4778 9.488 20.1511 8.62467 20.1511 7.542C20.1511 6.45 20.4778 5.59133 21.1311 4.966C21.7845 4.33133 22.5825 4.014 23.5251 4.014C24.4771 4.014 25.2238 4.322 25.7651 4.938C26.3065 5.54467 26.5771 6.366 26.5771 7.402C26.5771 7.43933 26.5771 7.47667 26.5771 7.514C26.5771 7.63533 26.5118 7.696 26.3811 7.696ZM25.4151 6.842C25.3871 6.26333 25.2051 5.80133 24.8691 5.456C24.5331 5.11067 24.0851 4.938 23.5251 4.938C22.9651 4.938 22.4845 5.09667 22.0831 5.414C21.6818 5.73133 21.4438 6.20733 21.3691 6.842H25.4151ZM35.1323 11H33.8023C33.401 11 33.2003 10.804 33.2003 10.412V6.73C33.2003 6.18867 33.0697 5.76867 32.8083 5.47C32.547 5.17133 32.1643 5.022 31.6603 5.022C30.9417 5.022 30.3817 5.316 29.9803 5.904C29.9337 5.96 29.873 6.06267 29.7983 6.212V10.062H30.5403C30.6617 10.062 30.7223 10.118 30.7223 10.23V10.832C30.7223 10.944 30.6617 11 30.5403 11H27.8663C27.745 11 27.6843 10.944 27.6843 10.832V10.23C27.6843 10.118 27.745 10.062 27.8663 10.062H28.6363V5.05H27.8663C27.745 5.05 27.6843 4.994 27.6843 4.882V4.28C27.6843 4.168 27.745 4.112 27.8663 4.112H29.5043C29.6257 4.112 29.6863 4.168 29.6863 4.28L29.6583 5.232H29.7003C30.1857 4.42 30.937 4.014 31.9543 4.014C32.701 4.014 33.2843 4.23333 33.7043 4.672C34.1243 5.11067 34.339 5.70333 34.3483 6.45V10.062H35.1323C35.2537 10.062 35.3143 10.118 35.3143 10.23V10.832C35.3143 10.944 35.2537 11 35.1323 11ZM50.8763 11H47.5023C47.3903 11 47.3343 10.944 47.3343 10.832V10.118C47.3343 10.006 47.3903 9.95 47.5023 9.95H48.5803V6.52H43.3723V9.95H44.4503C44.5717 9.95 44.6323 10.006 44.6323 10.118V10.832C44.6323 10.944 44.5717 11 44.4503 11H41.0903C40.969 11 40.9083 10.944 40.9083 10.832V10.118C40.9083 10.006 40.969 9.95 41.0903 9.95H42.1543V1.998H41.0903C40.969 1.998 40.9083 1.942 40.9083 1.83V1.116C40.9083 1.004 40.969 0.947999 41.0903 0.947999H44.4503C44.5717 0.947999 44.6323 1.004 44.6323 1.116V1.83C44.6323 1.942 44.5717 1.998 44.4503 1.998H43.3723V5.498H48.5803V1.998H47.5023C47.3903 1.998 47.3343 1.942 47.3343 1.83V1.116C47.3343 1.004 47.3903 0.947999 47.5023 0.947999H50.8763C50.9883 0.947999 51.0443 1.004 51.0443 1.116V1.83C51.0443 1.942 50.9883 1.998 50.8763 1.998H49.7983V9.95H50.8763C50.9883 9.95 51.0443 10.006 51.0443 10.118V10.832C51.0443 10.944 50.9883 11 50.8763 11ZM58.0452 7.696H53.0052C53.0425 8.47067 53.2852 9.068 53.7332 9.488C54.1905 9.908 54.7039 10.118 55.2732 10.118C55.8425 10.118 56.3279 9.99667 56.7292 9.754C57.1399 9.51133 57.4152 9.27333 57.5552 9.04C57.6299 8.94667 57.7139 8.93267 57.8072 8.998L58.1852 9.376C58.2412 9.432 58.2692 9.48333 58.2692 9.53C58.2692 9.56733 58.245Line truncated
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 25 KiB |
@@ -1,3 +1,3 @@
|
||||
<svg width="3.73em" height=".25em" viewBox="0 0 224 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg viewBox="0 0 224 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.416 5.932C10.416 7.46267 9.954 8.70867 9.03 9.67C8.11533 10.622 6.958 11.098 5.558 11.098C4.16733 11.098 3.00533 10.6173 2.072 9.656C1.13867 8.68533 0.672 7.43933 0.672 5.918C0.672 4.38733 1.15267 3.16467 2.114 2.25C3.07533 1.326 4.22333 0.863999 5.558 0.863999C6.89267 0.863999 8.036 1.326 8.988 2.25C9.94 3.16467 10.416 4.392 10.416 5.932ZM3.052 8.9C3.77067 9.60933 4.606 9.964 5.558 9.964C6.51 9.964 7.34067 9.60933 8.05 8.9C8.75933 8.18133 9.114 7.20133 9.114 5.96C9.114 4.70933 8.75 3.734 8.022 3.034C7.30333 2.32467 6.482 1.97 5.558 1.97C4.634 1.97 3.808 2.32467 3.08 3.034C2.352 3.734 1.988 4.70933 1.988 5.96C1.988 7.20133 2.34267 8.18133 3.052 8.9ZM14.6296 14.15H11.8156C11.6942 14.15 11.6336 14.094 11.6336 13.982V13.38C11.6336 13.268 11.6942 13.212 11.8156 13.212H12.5996V5.05H11.8436C11.7222 5.05 11.6616 4.994 11.6616 4.882V4.28C11.6616 4.168 11.7222 4.112 11.8436 4.112H13.4816C13.6029 4.112 13.6636 4.168 13.6636 4.28L13.6496 5.386H13.6636C13.8409 5.00333 14.1256 4.68133 14.5176 4.42C14.9189 4.14933 15.4089 4.014 15.9876 4.014C16.9302 4.014 17.6909 4.336 18.2696 4.98C18.8482 5.61467 19.1376 6.464 19.1376 7.528C19.1376 8.58267 18.8529 9.44133 18.2836 10.104C17.7142 10.7667 16.9536 11.098 16.0016 11.098C15.4229 11.098 14.9376 10.9673 14.5456 10.706C14.1629 10.4353 13.8876 10.1087 13.7196 9.726H13.6776C13.7056 10.0247 13.7196 10.2393 13.7196 10.37V13.212H14.6296C14.7509 13.212 14.8116 13.268 14.8116 13.38V13.982C14.8116 14.094 14.7509 14.15 14.6296 14.15ZM13.6916 7.584C13.6916 8.396 13.9016 9.02133 14.3216 9.46C14.7509 9.89867 15.2642 10.118 15.8616 10.118C16.4682 10.118 16.9722 9.88467 17.3736 9.418C17.7842 8.942 17.9896 8.32133 17.9896 7.556C17.9896 6.78133 17.7842 6.16067 17.3736 5.694C16.9722 5.22733 16.4682 4.994 15.8616 4.994C15.2549 4.994 14.7416 5.22267 14.3216 5.68C13.9016 6.13733 13.6916 6.772 13.6916 7.584ZM26.3811 7.696H21.3411C21.3785 8.47067 21.6211 9.068 22.0691 9.488C22.5265 9.908 23.0398 10.118 23.6091 10.118C24.1785 10.118 24.6638 9.99667 25.0651 9.754C25.4758 9.51133 25.7511 9.27333 25.8911 9.04C25.9658 8.94667 26.0498 8.93267 26.1431 8.998L26.5211 9.376C26.5771 9.432 26.6051 9.48333 26.6051 9.53C26.6051 9.56733 26.5818 9.61867 26.5351 9.684C26.4978 9.74933 26.4138 9.852 26.2831 9.992C26.1525 10.132 25.9658 10.2907 25.7231 10.468C25.4898 10.6453 25.1818 10.7947 24.7991 10.916C24.4258 11.0373 24.0245 11.098 23.5951 11.098C22.6151 11.098 21.7938 10.776 21.1311 10.132C20.4778 9.488 20.1511 8.62467 20.1511 7.542C20.1511 6.45 20.4778 5.59133 21.1311 4.966C21.7845 4.33133 22.5825 4.014 23.5251 4.014C24.4771 4.014 25.2238 4.322 25.7651 4.938C26.3065 5.54467 26.5771 6.366 26.5771 7.402C26.5771 7.43933 26.5771 7.47667 26.5771 7.514C26.5771 7.63533 26.5118 7.696 26.3811 7.696ZM25.4151 6.842C25.3871 6.26333 25.2051 5.80133 24.8691 5.456C24.5331 5.11067 24.0851 4.938 23.5251 4.938C22.9651 4.938 22.4845 5.09667 22.0831 5.414C21.6818 5.73133 21.4438 6.20733 21.3691 6.842H25.4151ZM35.1323 11H33.8023C33.401 11 33.2003 10.804 33.2003 10.412V6.73C33.2003 6.18867 33.0697 5.76867 32.8083 5.47C32.547 5.17133 32.1643 5.022 31.6603 5.022C30.9417 5.022 30.3817 5.316 29.9803 5.904C29.9337 5.96 29.873 6.06267 29.7983 6.212V10.062H30.5403C30.6617 10.062 30.7223 10.118 30.7223 10.23V10.832C30.7223 10.944 30.6617 11 30.5403 11H27.8663C27.745 11 27.6843 10.944 27.6843 10.832V10.23C27.6843 10.118 27.745 10.062 27.8663 10.062H28.6363V5.05H27.8663C27.745 5.05 27.6843 4.994 27.6843 4.882V4.28C27.6843 4.168 27.745 4.112 27.8663 4.112H29.5043C29.6257 4.112 29.6863 4.168 29.6863 4.28L29.6583 5.232H29.7003C30.1857 4.42 30.937 4.014 31.9543 4.014C32.701 4.014 33.2843 4.23333 33.7043 4.672C34.1243 5.11067 34.339 5.70333 34.3483 6.45V10.062H35.1323C35.2537 10.062 35.3143 10.118 35.3143 10.23V10.832C35.3143 10.944 35.2537 11 35.1323 11ZM50.8763 11H47.5023C47.3903 11 47.3343 10.944 47.3343 10.832V10.118C47.3343 10.006 47.3903 9.95 47.5023 9.95H48.5803V6.52H43.3723V9.95H44.4503C44.5717 9.95 44.6323 10.006 44.6323 10.118V10.832C44.6323 10.944 44.5717 11 44.4503 11H41.0903C40.969 11 40.9083 10.944 40.9083 10.832V10.118C40.9083 10.006 40.969 9.95 41.0903 9.95H42.1543V1.998H41.0903C40.969 1.998 40.9083 1.942 40.9083 1.83V1.116C40.9083 1.004 40.969 0.947999 41.0903 0.947999H44.4503C44.5717 0.947999 44.6323 1.004 44.6323 1.116V1.83C44.6323 1.942 44.5717 1.998 44.4503 1.998H43.3723V5.498H48.5803V1.998H47.5023C47.3903 1.998 47.3343 1.942 47.3343 1.83V1.116C47.3343 1.004 47.3903 0.947999 47.5023 0.947999H50.8763C50.9883 0.947999 51.0443 1.004 51.0443 1.116V1.83C51.0443 1.942 50.9883 1.998 50.8763 1.998H49.7983V9.95H50.8763C50.9883 9.95 51.0443 10.006 51.0443 10.118V10.832C51.0443 10.944 50.9883 11 50.8763 11ZM58.0452 7.696H53.0052C53.0425 8.47067 53.2852 9.068 53.7332 9.488C54.1905 9.908 54.7039 10.118 55.2732 10.118C55.8425 10.118 56.3279 9.99667 56.7292 9.754C57.1399 9.51133 57.4152 9.27333 57.5552 9.04C57.6299 8.94667 57.7139 8.93267 57.8072 8.998L58.1852 9.376C58.2412 9.432 58.2692 9.48333 58.2692 9.53C58.2692 9.56733 58.245Line truncated
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
@@ -1,7 +1,6 @@
|
||||
import {
|
||||
Checkbox,
|
||||
CineDialog,
|
||||
ExampleDropTarget,
|
||||
LayoutButton,
|
||||
LayoutChooser,
|
||||
MeasurementTable,
|
||||
@@ -28,8 +27,6 @@ import { ICONS, Icon } from './elements';
|
||||
// Alias this for now as not all dependents are using strict versioning
|
||||
import { DropdownMenu as Dropdown, Range, Select } from './elements/form';
|
||||
import ExpandableToolMenu from './viewer/ExpandableToolMenu.js';
|
||||
import LayoutManager from './LayoutChooser/LayoutManager.js';
|
||||
import LayoutPanelDropTarget from './LayoutChooser/LayoutPanelDropTarget.js';
|
||||
import PlayClipButton from './viewer/PlayClipButton.js';
|
||||
import { ScrollableArea } from './ScrollableArea/ScrollableArea.js';
|
||||
import Toolbar from './viewer/Toolbar.js';
|
||||
@@ -43,12 +40,9 @@ export {
|
||||
CineDialog,
|
||||
Dropdown,
|
||||
ExpandableToolMenu,
|
||||
ExampleDropTarget,
|
||||
Icon,
|
||||
LayoutButton,
|
||||
LayoutChooser,
|
||||
LayoutManager,
|
||||
LayoutPanelDropTarget,
|
||||
MeasurementTable,
|
||||
MeasurementTableItem,
|
||||
Overlay,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// ~~ WebPack
|
||||
const webpack = require('webpack');
|
||||
const path = require('path');
|
||||
const merge = require('webpack-merge');
|
||||
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
|
||||
//
|
||||
// ~~ Plugins
|
||||
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
||||
const fontsToJavaScriptRule = require('./rules/fontsToJavaScript.js');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
@@ -12,31 +13,14 @@ const DIST_DIR = path.join(__dirname, '../dist');
|
||||
const PUBLIC_DIR = path.join(__dirname, '../public');
|
||||
// ~~ Env Vars
|
||||
const HTML_TEMPLATE = process.env.HTML_TEMPLATE || 'script-tag.html';
|
||||
const PUBLIC_URL = process.env.PUBLIC_URL || '/';
|
||||
|
||||
module.exports = (env, argv) => {
|
||||
const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR });
|
||||
|
||||
return merge(commonConfig, {
|
||||
const mergedConfig = merge(commonConfig, {
|
||||
entry: {
|
||||
app: `${SRC_DIR}/index-umd.js`,
|
||||
},
|
||||
devtool: 'source-map',
|
||||
stats: {
|
||||
colors: true,
|
||||
hash: true,
|
||||
timings: true,
|
||||
assets: true,
|
||||
chunks: false,
|
||||
chunkModules: false,
|
||||
modules: false,
|
||||
children: false,
|
||||
warnings: true,
|
||||
},
|
||||
optimization: {
|
||||
minimize: true,
|
||||
sideEffects: true,
|
||||
},
|
||||
output: {
|
||||
path: DIST_DIR,
|
||||
library: 'OHIFViewer',
|
||||
@@ -50,14 +34,17 @@ module.exports = (env, argv) => {
|
||||
// Clean output.path
|
||||
new CleanWebpackPlugin(),
|
||||
// Generate "index.html" w/ correct includes/imports
|
||||
// NOTE: We use this for E2E Tests
|
||||
new HtmlWebpackPlugin({
|
||||
inject: false,
|
||||
template: `${PUBLIC_DIR}/html-templates/${HTML_TEMPLATE}`,
|
||||
filename: 'index.html',
|
||||
}),
|
||||
new webpack.optimize.LimitChunkCountPlugin({
|
||||
maxChunks: 1
|
||||
})
|
||||
maxChunks: 1,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
return mergedConfig;
|
||||
};
|
||||
@@ -32,27 +32,11 @@ module.exports = (env, argv) => {
|
||||
const hasProxy = PROXY_TARGET && PROXY_DOMAIN;
|
||||
|
||||
const mergedConfig = merge(baseConfig, {
|
||||
devtool: isProdBuild ? 'source-map' : 'cheap-module-eval-source-map',
|
||||
output: {
|
||||
path: DIST_DIR,
|
||||
filename: isProdBuild ? '[name].bundle.[chunkhash].js' : '[name].js',
|
||||
publicPath: PUBLIC_URL, // Used by HtmlWebPackPlugin for asset prefix
|
||||
},
|
||||
stats: {
|
||||
colors: true,
|
||||
hash: true,
|
||||
timings: true,
|
||||
assets: true,
|
||||
chunks: false,
|
||||
chunkModules: false,
|
||||
modules: false,
|
||||
children: false,
|
||||
warnings: true,
|
||||
},
|
||||
optimization: {
|
||||
minimize: isProdBuild,
|
||||
sideEffects: true,
|
||||
},
|
||||
module: {
|
||||
rules: [...extractStyleChunksRule(isProdBuild)],
|
||||
},
|
||||
@@ -85,8 +69,8 @@ module.exports = (env, argv) => {
|
||||
]),
|
||||
// https://github.com/faceyspacey/extract-css-chunks-webpack-plugin#webpack-4-standalone-installation
|
||||
new ExtractCssChunksPlugin({
|
||||
filename: '[name].css',
|
||||
chunkFilename: '[id].css',
|
||||
filename: isProdBuild ? '[name].[hash].css' : '[name].css',
|
||||
chunkFilename: isProdBuild ? '[id].[hash].css' : '[id].css',
|
||||
ignoreOrder: false, // Enable to remove warnings about conflicting order
|
||||
}),
|
||||
// Generate "index.html" w/ correct includes/imports
|
||||
@@ -129,15 +113,6 @@ module.exports = (env, argv) => {
|
||||
|
||||
if (!isProdBuild) {
|
||||
mergedConfig.plugins.push(new webpack.HotModuleReplacementPlugin());
|
||||
|
||||
//
|
||||
mergedConfig.optimization.minimizer = [
|
||||
new TerserJSPlugin({
|
||||
sourceMap: true,
|
||||
parallel: true,
|
||||
}),
|
||||
new OptimizeCSSAssetsPlugin({}),
|
||||
];
|
||||
}
|
||||
|
||||
return mergedConfig;
|
||||
|
||||
@@ -3,6 +3,109 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.6.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.6.0...@ohif/viewer@1.6.1) (2019-10-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Switch token storage back to localStorage because in-memory was annoying for end users ([#1030](https://github.com/OHIF/Viewers/issues/1030)) ([412fe4e](https://github.com/OHIF/Viewers/commit/412fe4e))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.6.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.5.4...@ohif/viewer@1.6.0) (2019-10-11)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 🎸 Improve usability of Google Cloud adapter, including direct routes to studies ([#989](https://github.com/OHIF/Viewers/issues/989)) ([2bc361c](https://github.com/OHIF/Viewers/commit/2bc361c))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.5.4](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.5.3...@ohif/viewer@1.5.4) (2019-10-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.5.3](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.5.2...@ohif/viewer@1.5.3) (2019-10-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.5.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.5.1...@ohif/viewer@1.5.2) (2019-10-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🎸 switch ohif logo from text + font to SVG ([#1021](https://github.com/OHIF/Viewers/issues/1021)) ([e7de8be](https://github.com/OHIF/Viewers/commit/e7de8be))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.5.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.5.0...@ohif/viewer@1.5.1) (2019-10-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 set current viewport as active when switching layouts ([#1018](https://github.com/OHIF/Viewers/issues/1018)) ([2a74355](https://github.com/OHIF/Viewers/commit/2a74355))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.5.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.4.5...@ohif/viewer@1.5.0) (2019-10-09)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Multiple fixes and implementation changes to react-cornerstone-viewport ([1cc94f3](https://github.com/OHIF/Viewers/commit/1cc94f3))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.4.5](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.4.4...@ohif/viewer@1.4.5) (2019-10-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.4.4](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.4.3...@ohif/viewer@1.4.4) (2019-10-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.4.3](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.4.2...@ohif/viewer@1.4.3) (2019-10-04)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
@@ -0,0 +1,457 @@
|
||||
describe('OHIF Cornerstone Toolbar', () => {
|
||||
before(() => {
|
||||
cy.openStudy('MISTER^MR');
|
||||
cy.waitDicomImage();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.initCornerstoneToolsAliases();
|
||||
cy.initCommonElementsAliases();
|
||||
//Following best practices, reset should be done before each test
|
||||
cy.resetViewport();
|
||||
});
|
||||
|
||||
it('checks if all primary buttons are being displayed', () => {
|
||||
cy.get('@stackScrollBtn')
|
||||
.should('be.visible')
|
||||
.contains('Stack Scroll');
|
||||
cy.get('@zoomBtn')
|
||||
.should('be.visible')
|
||||
.contains('Zoom');
|
||||
cy.get('@levelsBtn')
|
||||
.should('be.visible')
|
||||
.contains('Levels');
|
||||
cy.get('@panBtn')
|
||||
.should('be.visible')
|
||||
.contains('Pan');
|
||||
cy.get('@lengthBtn')
|
||||
.should('be.visible')
|
||||
.contains('Length');
|
||||
cy.get('@annotateBtn')
|
||||
.should('be.visible')
|
||||
.contains('Annotate');
|
||||
cy.get('@angleBtn')
|
||||
.should('be.visible')
|
||||
.contains('Angle');
|
||||
cy.get('@resetBtn')
|
||||
.should('be.visible')
|
||||
.contains('Reset');
|
||||
cy.get('@cineBtn')
|
||||
.should('be.visible')
|
||||
.contains('CINE');
|
||||
cy.get('@moreBtn')
|
||||
.should('be.visible')
|
||||
.contains('More');
|
||||
cy.get('@twodmprBtn')
|
||||
.should('be.visible')
|
||||
.contains('2D MPR');
|
||||
cy.get('@layoutBtn')
|
||||
.should('be.visible')
|
||||
.contains('Layout');
|
||||
});
|
||||
|
||||
it('checks if Stack Scroll tool will navigate across all series in the viewport', () => {
|
||||
//Click on button and vefiry if icon is active on toolbar
|
||||
cy.get('@stackScrollBtn')
|
||||
.click()
|
||||
.then($stackScrollBtn => {
|
||||
cy.wrap($stackScrollBtn).should('have.class', 'active');
|
||||
});
|
||||
|
||||
//drags the mouse inside the viewport to be able to interact with series
|
||||
cy.get('@viewport')
|
||||
.trigger('mousedown', 'top', { which: 1 })
|
||||
.trigger('mousemove', 'center', { which: 1 })
|
||||
.trigger('mouseup');
|
||||
|
||||
const expectedText =
|
||||
'Ser: 1Img: 14 14/26256 x 256Loc: 0.00 mm Thick: 5.00 mm';
|
||||
cy.get('@viewportInfoBottomLeft').should('have.text', expectedText);
|
||||
});
|
||||
|
||||
it('checks if Zoom tool will zoom in/out an image in the viewport', () => {
|
||||
//Click on button and vefiry if icon is active on toolbar
|
||||
cy.get('@zoomBtn')
|
||||
.click()
|
||||
.then($zoomBtn => {
|
||||
cy.wrap($zoomBtn).should('have.class', 'active');
|
||||
});
|
||||
|
||||
//drags the mouse inside the viewport to be able to interact with series
|
||||
cy.get('@viewport')
|
||||
.trigger('mousedown', 'top', { which: 1 })
|
||||
.trigger('mousemove', 'center', { which: 1 })
|
||||
.trigger('mouseup');
|
||||
|
||||
const expectedText = 'Zoom: 884%W: 820 L: 410Lossless / Uncompressed';
|
||||
cy.get('@viewportInfoBottomRight').should('have.text', expectedText);
|
||||
});
|
||||
|
||||
it('checks if Levels tool will change the contrast and brightness of an image in the viewport', () => {
|
||||
//Click on button and vefiry if icon is active on toolbar
|
||||
cy.get('@levelsBtn')
|
||||
.click()
|
||||
.then($levelsBtn => {
|
||||
cy.wrap($levelsBtn).should('have.class', 'active');
|
||||
});
|
||||
|
||||
//drags the mouse inside the viewport to be able to interact with series
|
||||
cy.get('@viewport')
|
||||
.trigger('mousedown', 'top', { which: 1 })
|
||||
.trigger('mousemove', 'center', { which: 1 })
|
||||
.trigger('mouseup')
|
||||
.trigger('mousedown', 'center', { which: 1 })
|
||||
.trigger('mousemove', 'left', { which: 1 })
|
||||
.trigger('mouseup');
|
||||
|
||||
const expectedText = 'Zoom: 211%W: 544 L: 626Lossless / Uncompressed';
|
||||
cy.get('@viewportInfoBottomRight').should('have.text', expectedText);
|
||||
});
|
||||
|
||||
it('checks if Pan tool will move the image inside the viewport', () => {
|
||||
//Click on button and vefiry if icon is active on toolbar
|
||||
cy.get('@panBtn')
|
||||
.click()
|
||||
.then($panBtn => {
|
||||
cy.wrap($panBtn).should('have.class', 'active');
|
||||
});
|
||||
|
||||
//Get image position from cornerstone and check if y axis was modified
|
||||
let cornerstone;
|
||||
let currentPan;
|
||||
|
||||
cy.window()
|
||||
.its('cornerstone')
|
||||
.then(c => {
|
||||
cornerstone = c;
|
||||
currentPan = () =>
|
||||
cornerstone.getEnabledElements()[0].viewport.translation;
|
||||
});
|
||||
|
||||
cy.get('@viewport')
|
||||
.trigger('mousedown', 'center', { which: 1 })
|
||||
.trigger('mousemove', 'bottom', { which: 1 })
|
||||
.trigger('mouseup', 'bottom')
|
||||
.then(() => {
|
||||
expect(currentPan().y > 0).to.eq(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('checks if Length annotation can be added on viewport and on measurements panel', () => {
|
||||
//Click on button and vefiry if icon is active on toolbar
|
||||
cy.get('@lengthBtn')
|
||||
.click()
|
||||
.then($lengthbtn => {
|
||||
cy.wrap($lengthbtn).should('have.class', 'active');
|
||||
});
|
||||
|
||||
//Add annotation on the viewport
|
||||
const firstClick = [150, 100];
|
||||
const secondClick = [130, 170];
|
||||
cy.addLine('.cornerstone-canvas', firstClick, secondClick);
|
||||
|
||||
//Verify if measurement annotation was added into the measurements panel
|
||||
cy.get('@measurementsBtn')
|
||||
.click()
|
||||
.then($measurementsBtn => {
|
||||
cy.get('@measurementsPanel').should('be.visible');
|
||||
|
||||
cy.get('.measurementItem')
|
||||
.its('length')
|
||||
.should('be.eq', 1);
|
||||
|
||||
cy.wrap($measurementsBtn).click();
|
||||
});
|
||||
});
|
||||
|
||||
it('checks if Angle annotation can be added on viewport and on measurements panel', () => {
|
||||
//Click on button and vefiry if icon is active on toolbar
|
||||
cy.get('@angleBtn')
|
||||
.click()
|
||||
.then($angleBtn => {
|
||||
cy.wrap($angleBtn).should('have.class', 'active');
|
||||
});
|
||||
|
||||
//Add annotation on the viewport
|
||||
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
|
||||
cy.get('@measurementsBtn')
|
||||
.click()
|
||||
.then($measurementsBtn => {
|
||||
cy.get('@measurementsPanel').should('be.visible');
|
||||
|
||||
cy.get('.measurementItem')
|
||||
.its('length')
|
||||
.should('be.eq', 1);
|
||||
|
||||
cy.wrap($measurementsBtn).click();
|
||||
});
|
||||
});
|
||||
|
||||
it('checks if Reset tool will reset all changes made on the image', () => {
|
||||
//Make some changes by zooming in and rotating the image
|
||||
cy.imageZoomIn();
|
||||
cy.imageContrast();
|
||||
|
||||
//Click on reset button
|
||||
cy.get('@resetBtn').click();
|
||||
|
||||
const expectedText = 'Zoom: 211%W: 820 L: 410Lossless / Uncompressed';
|
||||
cy.get('@viewportInfoBottomRight').should('have.text', expectedText);
|
||||
});
|
||||
|
||||
it('checks if CINE tool will prompt a modal with working controls', () => {
|
||||
//Click on button
|
||||
cy.get('@cineBtn').click();
|
||||
//Vefiry if cine control overlay is being displayed
|
||||
cy.get('.cine-controls')
|
||||
.as('cineControls')
|
||||
.should('be.visible');
|
||||
|
||||
//Test PLAY button
|
||||
cy.get('[title="Play / Stop"]')
|
||||
.click()
|
||||
.wait(100)
|
||||
.click();
|
||||
|
||||
let expectedText = 'Img: 1 1/26';
|
||||
cy.get('@viewportInfoBottomLeft').should('not.have.text', expectedText);
|
||||
|
||||
//Test SKIP TO FIRST IMAGE button
|
||||
cy.get('[title="Skip to first Image"]').click();
|
||||
cy.get('@viewportInfoBottomLeft').should('contain.text', expectedText);
|
||||
|
||||
//Test NEXT IMAGE button
|
||||
cy.get('[title="Next Image"]').click();
|
||||
expectedText = 'Img: 2 2/26';
|
||||
cy.get('@viewportInfoBottomLeft').should('contain.text', expectedText);
|
||||
|
||||
//Test SKIP TO LAST IMAGE button
|
||||
cy.get('[title="Skip to last Image"]').click();
|
||||
expectedText = 'Img: 27 26/26';
|
||||
cy.get('@viewportInfoBottomLeft').should('contain.text', expectedText);
|
||||
|
||||
//Test PREVIOUS IMAGE button
|
||||
cy.get('[title="Previous Image"]').click();
|
||||
expectedText = 'Img: 26 25/26';
|
||||
cy.get('@viewportInfoBottomLeft').should('contain.text', expectedText);
|
||||
|
||||
//Click on Cine button
|
||||
cy.get('@cineBtn').click();
|
||||
//Vefiry if cine control overlay is hidden
|
||||
cy.get('@cineControls').should('not.be.visible');
|
||||
});
|
||||
|
||||
it('checks if More button will prompt a modal with secondary tools', () => {
|
||||
cy.get('@moreBtn').click();
|
||||
//Verify if overlay is displayed
|
||||
cy.get('.tooltip-toolbar-overlay')
|
||||
.as('toolbarOverlay')
|
||||
.should('be.visible');
|
||||
|
||||
let iconName;
|
||||
//Click on one of the secondary tools from the overlay
|
||||
cy.get('.tooltip-inner > :nth-child(1)')
|
||||
.click()
|
||||
.then($magnifyBtn => {
|
||||
cy.wrap($magnifyBtn)
|
||||
.should('have.class', 'active')
|
||||
.find('svg')
|
||||
.then($icon => {
|
||||
iconName = $icon.text();
|
||||
});
|
||||
});
|
||||
|
||||
//Check if More button is active and if it has same icon as the secondary tool selected
|
||||
cy.get('@moreBtn')
|
||||
.click()
|
||||
.then($moreBtn => {
|
||||
cy.wrap($moreBtn)
|
||||
.should('have.class', 'active')
|
||||
.contains(iconName);
|
||||
});
|
||||
|
||||
//Verify if overlay is hidden
|
||||
cy.get('@toolbarOverlay').should('not.be.visible');
|
||||
});
|
||||
|
||||
it('checks if Layout tool will multiply the number of viewports displayed', () => {
|
||||
//Click on Layout button and verify if overlay is displayed
|
||||
cy.get('@layoutBtn')
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.get('.layoutChooser')
|
||||
.as('layoutChooser')
|
||||
.should('be.visible')
|
||||
.find('td')
|
||||
.its('length')
|
||||
.should('be.eq', 9);
|
||||
});
|
||||
|
||||
//verify if layout has changed to 2 viewports
|
||||
cy.get('tbody > :nth-child(1) > :nth-child(2)').click();
|
||||
cy.get('.viewport-container').then($viewport => {
|
||||
cy.wrap($viewport)
|
||||
.its('length')
|
||||
.should('be.eq', 2);
|
||||
});
|
||||
|
||||
cy.get('@layoutBtn').click();
|
||||
cy.get('tbody > :nth-child(2) > :nth-child(1)').click();
|
||||
cy.get('.viewport-container').then($viewport => {
|
||||
cy.wrap($viewport)
|
||||
.its('length')
|
||||
.should('be.eq', 2);
|
||||
});
|
||||
|
||||
//verify if layout has changed to 3 viewports
|
||||
cy.get('@layoutBtn').click();
|
||||
cy.get('tbody > :nth-child(1) > :nth-child(3)').click();
|
||||
cy.get('.viewport-container').then($viewport => {
|
||||
cy.wait(1000);
|
||||
cy.wrap($viewport)
|
||||
.its('length')
|
||||
.should('be.eq', 3);
|
||||
});
|
||||
|
||||
cy.get('@layoutBtn').click();
|
||||
cy.get('tbody > :nth-child(3) > :nth-child(1)').click();
|
||||
cy.get('.viewport-container').then($viewport => {
|
||||
cy.wrap($viewport)
|
||||
.its('length')
|
||||
.should('be.eq', 3);
|
||||
});
|
||||
|
||||
//verify if layout has changed to 4 viewports
|
||||
cy.get('@layoutBtn').click();
|
||||
cy.get('tbody > :nth-child(2) > :nth-child(2)').click();
|
||||
cy.get('.viewport-container').then($viewport => {
|
||||
cy.wrap($viewport)
|
||||
.its('length')
|
||||
.should('be.eq', 4);
|
||||
});
|
||||
|
||||
//verify if layout has changed to 6 viewports
|
||||
cy.get('@layoutBtn').click();
|
||||
cy.get('tbody > :nth-child(2) > :nth-child(3)').click();
|
||||
cy.get('.viewport-container').then($viewport => {
|
||||
cy.wrap($viewport)
|
||||
.its('length')
|
||||
.should('be.eq', 6);
|
||||
});
|
||||
|
||||
cy.get('@layoutBtn').click();
|
||||
cy.get('tbody > :nth-child(3) > :nth-child(2)').click();
|
||||
cy.get('.viewport-container').then($viewport => {
|
||||
cy.wrap($viewport)
|
||||
.its('length')
|
||||
.should('be.eq', 6);
|
||||
});
|
||||
|
||||
//verify if layout has changed to 9 viewports
|
||||
cy.get('@layoutBtn').click();
|
||||
cy.get('tbody > :nth-child(3) > :nth-child(3)').click();
|
||||
cy.get('.viewport-container').then($viewport => {
|
||||
cy.wrap($viewport)
|
||||
.its('length')
|
||||
.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
|
||||
// cy.get('@layoutBtn').click();
|
||||
// cy.get('tbody > :nth-child(1) > :nth-child(1)').click();
|
||||
// cy.get('.viewport-container').then(($viewport) =>{
|
||||
// cy.wrap($viewport)
|
||||
// .its('length')
|
||||
// .should('be.eq', 1);
|
||||
// })
|
||||
cy.reload();
|
||||
cy.waitDicomImage();
|
||||
});
|
||||
|
||||
it('checks if Clear tool will delete all measurements added in the viewport', () => {
|
||||
//Add measurements in the viewport
|
||||
cy.get('@lengthBtn').click();
|
||||
const firstClick = [150, 100];
|
||||
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
|
||||
cy.get('@measurementsBtn').click();
|
||||
cy.get('.measurementItem')
|
||||
.its('length')
|
||||
.should('be.eq', 2);
|
||||
|
||||
//Click on More button
|
||||
cy.get('@moreBtn').click();
|
||||
//Verify if overlay is displayed
|
||||
cy.get('.tooltip-toolbar-overlay')
|
||||
.as('toolbarOverlay')
|
||||
.should('be.visible');
|
||||
//Click on Clear button
|
||||
cy.get('.tooltip-inner > :nth-child(10)').click();
|
||||
|
||||
//Verify if measurements were removed from the measurements panel
|
||||
cy.get('.measurementItem').should('not.exist');
|
||||
|
||||
//Close More button overlay
|
||||
cy.get('@moreBtn').click();
|
||||
|
||||
//Close the measurements panel
|
||||
cy.get('@measurementsBtn').then($btn => {
|
||||
$btn.click();
|
||||
cy.get('@measurementsPanel').should('not.be.enabled');
|
||||
});
|
||||
});
|
||||
|
||||
it('checks if Eraser tool will remove the measurements added in the viewport', () => {
|
||||
//Add measurements in the viewport
|
||||
cy.get('@lengthBtn').click();
|
||||
const firstClick = [150, 100];
|
||||
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
|
||||
cy.get('@measurementsBtn').click();
|
||||
cy.get('.measurementItem')
|
||||
.its('length')
|
||||
.should('be.eq', 2);
|
||||
cy.get('@measurementsBtn').click();
|
||||
|
||||
//Click More button
|
||||
cy.get('@moreBtn').click();
|
||||
//Click Eraser button
|
||||
cy.get('.tooltip-inner > :nth-child(12)').click();
|
||||
|
||||
//Erase measurement #1 and Verify if it was removed from the measurements panel
|
||||
const [x1, y1] = firstClick;
|
||||
cy.get('@viewport').click(x1, y1, { force: true });
|
||||
cy.get('.measurementItem')
|
||||
.its('length')
|
||||
.should('be.eq', 1);
|
||||
|
||||
//Erase measurement #2 and Verify if it was removed from the measurements panel
|
||||
const [x2, y2] = initPos;
|
||||
cy.get('@viewport').click(x2, y2, { force: true });
|
||||
cy.get('.measurementItem').should('not.exist');
|
||||
});
|
||||
});
|
||||
@@ -1,50 +1,93 @@
|
||||
/*
|
||||
TODO: Temporarily commented out because it is failing on CI due to timing issues
|
||||
describe('OHIF Study Viewer Page', () => {
|
||||
|
||||
before(() => {
|
||||
cy.openStudy("MISTER^MR");
|
||||
});
|
||||
|
||||
it('checks if series thumbnails are being displayed', ()=> {
|
||||
cy.get('.ThumbnailEntryContainer')
|
||||
cy.openStudy('MISTER^MR');
|
||||
cy.waitDicomImage();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.initCommonElementsAliases();
|
||||
//Following best practices, reset should be done before each test
|
||||
cy.resetViewport();
|
||||
});
|
||||
|
||||
it('checks if series thumbnails are being displayed', () => {
|
||||
cy.get('.ThumbnailEntryContainer')
|
||||
.its('length')
|
||||
.should('be.gt', 1);
|
||||
});
|
||||
|
||||
it('drags and drop a series thumbnail into viewport', () => {
|
||||
cy.get('.ThumbnailEntryContainer:nth-child(2)') //element to be dragged
|
||||
.drag('.cornerstone-canvas'); //dropzone element
|
||||
|
||||
const overlaySeriesInformation = 'div.ViewportOverlay > div.bottom-left.overlay-element > div';
|
||||
const expectedText = 'Ser: 2Img: 1 1/13512 x 512Loc: -17.60 mm Thick: 3.00 mm';
|
||||
|
||||
cy.get(overlaySeriesInformation)
|
||||
.should('have.text', expectedText);
|
||||
});
|
||||
|
||||
it('checks if Series left panel can be hidden/displayed', ()=> {
|
||||
const seriesButton = '.pull-left > .RoundedButtonGroup > .roundedButtonWrapper > .roundedButton';
|
||||
const leftPanel = 'section.sidepanel.from-left';
|
||||
|
||||
cy.get(seriesButton).click();
|
||||
cy.get(leftPanel).should('not.be.enabled')
|
||||
|
||||
cy.get(seriesButton).click();
|
||||
cy.get(leftPanel).should('be.visible');
|
||||
});
|
||||
|
||||
it('checks if Measurements right panel can be hidden/displayed', ()=> {
|
||||
const measurementsButton = '.pull-right > .RoundedButtonGroup > .roundedButtonWrapper > .roundedButton';
|
||||
const rightPanel = 'section.sidepanel.from-right';
|
||||
|
||||
cy.get(measurementsButton).click();
|
||||
cy.get(rightPanel).should('be.visible');
|
||||
|
||||
|
||||
cy.get(measurementsButton).click();
|
||||
cy.get(rightPanel).should('not.be.enabled');
|
||||
});
|
||||
|
||||
});
|
||||
*/
|
||||
|
||||
it('drags and drop a series thumbnail into viewport', () => {
|
||||
cy.get('.ThumbnailEntryContainer:nth-child(2)') //element to be dragged
|
||||
.drag('.cornerstone-canvas'); //dropzone element
|
||||
|
||||
const expectedText =
|
||||
'Ser: 2Img: 1 1/13512 x 512Loc: -17.60 mm Thick: 3.00 mm';
|
||||
cy.get('@viewportInfoBottomLeft').should('contain.text', expectedText);
|
||||
});
|
||||
|
||||
it('checks if Series left panel can be hidden/displayed', () => {
|
||||
cy.get('@seriesBtn').click();
|
||||
cy.get('@seriesPanel').should('not.be.enabled');
|
||||
|
||||
cy.get('@seriesBtn').click();
|
||||
cy.get('@seriesPanel').should('be.visible');
|
||||
});
|
||||
|
||||
it('checks if Measurements right panel can be hidden/displayed', () => {
|
||||
cy.get('@measurementsBtn').click();
|
||||
cy.get('@measurementsPanel').should('be.visible');
|
||||
|
||||
cy.get('@measurementsBtn').click();
|
||||
cy.get('@measurementsPanel').should('not.be.enabled');
|
||||
});
|
||||
|
||||
it('checks if measurement item can be Relabeled under Measurements panel', () => {
|
||||
cy.addLengthMeasurement(); //Adding measurement in the viewport
|
||||
cy.get('@measurementsBtn').click();
|
||||
cy.get('.measurementItem').click();
|
||||
|
||||
// Click "Relabel"
|
||||
cy.get('.btnAction')
|
||||
.contains('Relabel')
|
||||
.click();
|
||||
|
||||
// Search for "Bone"
|
||||
cy.get('.searchInput').type('Bone');
|
||||
|
||||
// Select "Bone" Result
|
||||
cy.get('.treeInputs > .wrapperLabel')
|
||||
.contains('Bone')
|
||||
.click();
|
||||
|
||||
// Confirm Selection
|
||||
cy.get('.checkIconWrapper').click();
|
||||
|
||||
//Verify if 'Bone' label was added
|
||||
cy.get('.measurementLocation').should('contain.text', 'Bone');
|
||||
});
|
||||
|
||||
//TO-DO: Test case will fail due to issue #1013: https://github.com/OHIF/Viewers/issues/1013
|
||||
|
||||
// it('checks if Description can be added to measurement item under Measurements panel', () => {
|
||||
// cy.addLengthMeasurement(); //Adding measurement in the viewport
|
||||
// cy.get('@measurementsBtn').click();
|
||||
// cy.get('.measurementItem').click();
|
||||
//
|
||||
// // Click "Description"
|
||||
// cy.get('.btnAction')
|
||||
// .contains('Description')
|
||||
// .click();
|
||||
//
|
||||
// // Enter description text
|
||||
// const descriptionText = 'Adding text for description test';
|
||||
// cy.get('#description')
|
||||
// .type(descriptionText);
|
||||
//
|
||||
// // Confirm
|
||||
// cy.get('.btn-confirm').click();
|
||||
//
|
||||
// //Verify if descriptionText was added
|
||||
// cy.get('.measurementLocation')
|
||||
// .should('contain.text', descriptionText);
|
||||
// });
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
//Creating aliases for Cornerstone tools buttons
|
||||
export function initCornerstoneToolsAliases() {
|
||||
cy.get('.ToolbarRow > :nth-child(2)').as('stackScrollBtn');
|
||||
cy.get('.ToolbarRow > :nth-child(3)').as('zoomBtn');
|
||||
cy.get('.ToolbarRow > :nth-child(4)').as('levelsBtn');
|
||||
cy.get('.ToolbarRow > :nth-child(5)').as('panBtn');
|
||||
cy.get('.ToolbarRow > :nth-child(6)').as('lengthBtn');
|
||||
cy.get('.ToolbarRow > :nth-child(7)').as('annotateBtn');
|
||||
cy.get('.ToolbarRow > :nth-child(8)').as('angleBtn');
|
||||
cy.get('.ToolbarRow > :nth-child(9)').as('resetBtn');
|
||||
cy.get('.ToolbarRow > :nth-child(10)').as('cineBtn');
|
||||
cy.get('.expandableToolMenu').as('moreBtn');
|
||||
cy.get('.PluginSwitch > .toolbar-button').as('twodmprBtn');
|
||||
cy.get('.btn-group > .toolbar-button').as('layoutBtn');
|
||||
}
|
||||
|
||||
//Creating aliases for Common page elements
|
||||
export function initCommonElementsAliases() {
|
||||
cy.get(
|
||||
'.pull-right > .RoundedButtonGroup > .roundedButtonWrapper > .roundedButton'
|
||||
).as('measurementsBtn');
|
||||
cy.get('.viewport-element').as('viewport');
|
||||
cy.get('section.sidepanel.from-right').as('measurementsPanel');
|
||||
cy.get(
|
||||
'.pull-left > .RoundedButtonGroup > .roundedButtonWrapper > .roundedButton'
|
||||
).as('seriesBtn');
|
||||
cy.get('section.sidepanel.from-left').as('seriesPanel');
|
||||
cy.get('div.ViewportOverlay > div.bottom-left.overlay-element > div').as(
|
||||
'viewportInfoBottomLeft'
|
||||
);
|
||||
cy.get('div.ViewportOverlay > div.bottom-right.overlay-element > div').as(
|
||||
'viewportInfoBottomRight'
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
import { DragSimulator } from "../helpers/DragSimulator.js";
|
||||
import { DragSimulator } from '../helpers/DragSimulator.js';
|
||||
import {
|
||||
initCornerstoneToolsAliases,
|
||||
initCommonElementsAliases,
|
||||
} from './aliases.js';
|
||||
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
@@ -26,16 +30,173 @@ import { DragSimulator } from "../helpers/DragSimulator.js";
|
||||
// -- This is will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
||||
|
||||
Cypress.Commands.add('openStudy', (patientName) => {
|
||||
cy.visit('/');
|
||||
cy.get('#patientName')
|
||||
.type(patientName);
|
||||
cy.get('.studylistStudy > .patientName')
|
||||
.contains(patientName)
|
||||
.click();
|
||||
/**
|
||||
* Command to search for a patient name and open his/her study.
|
||||
*
|
||||
* @param {string} PatientName - Patient name that we would like to search for
|
||||
*/
|
||||
Cypress.Commands.add('openStudy', patientName => {
|
||||
cy.visit('/');
|
||||
cy.get('#patientName').type(patientName);
|
||||
|
||||
cy.get('.studylistStudy > .patientName')
|
||||
.as('patientResult')
|
||||
.then({ timeout: 5000 }, $patientResult => {
|
||||
cy.contains(patientName).click();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Command to perform a drag and drop action. Before using this command, we must get the element that should be dragged first.
|
||||
* Example of usage: cy.get(element-to-be-dragged).drag(dropzone-element)
|
||||
*
|
||||
* @param {*} element - Selector for element that we want to use as dropzone
|
||||
*/
|
||||
Cypress.Commands.add('drag', { prevSubject: 'element' }, (...args) =>
|
||||
DragSimulator.simulate(...args)
|
||||
);
|
||||
|
||||
/**
|
||||
* Command to perform two clicks into two different positions. Each position must be [x, y].
|
||||
* The positions are considering the element as reference, therefore, top-left of the element will be (0, 0).
|
||||
*
|
||||
* @param {*} viewport - Selector for viewport we would like to interact with
|
||||
* @param {number[]} firstClick - Click position [x, y]
|
||||
* @param {number[]} secondClick - Click position [x, y]
|
||||
*/
|
||||
Cypress.Commands.add('addLine', (viewport, firstClick, secondClick) => {
|
||||
cy.get(viewport).then($viewport => {
|
||||
const [x1, y1] = firstClick;
|
||||
const [x2, y2] = secondClick;
|
||||
|
||||
cy.wrap($viewport)
|
||||
.click(x1, y1, { force: true })
|
||||
.trigger('mousemove', { clientX: x2, clientY: y2 })
|
||||
.click(x2, y2, { force: true });
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Command to perform three clicks into three different positions. Each position must be [x, y].
|
||||
* The positions are considering the element as reference, therefore, top-left of the element will be (0, 0).
|
||||
*
|
||||
* @param {*} viewport - Selector for viewport we would like to interact with
|
||||
* @param {number[]} firstClick - Click position [x, y]
|
||||
* @param {number[]} secondClick - Click position [x, y]
|
||||
* @param {number[]} thirdClick - Click position [x, y]
|
||||
*/
|
||||
Cypress.Commands.add(
|
||||
'addAngle',
|
||||
(viewport, firstClick, secondClick, thirdClick) => {
|
||||
cy.get(viewport).then($viewport => {
|
||||
const [x1, y1] = firstClick;
|
||||
const [x2, y2] = secondClick;
|
||||
const [x3, y3] = thirdClick;
|
||||
|
||||
cy.wrap($viewport)
|
||||
.click(x1, y1, { force: true })
|
||||
.trigger('mousemove', { clientX: x2, clientY: y2 })
|
||||
.click(x2, y2, { force: true })
|
||||
.trigger('mousemove', { clientX: x3, clientY: y3 })
|
||||
.click(x3, y3, { force: true });
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
Cypress.Commands.add('drag', {prevSubject: 'element',},
|
||||
(...args) => DragSimulator.simulate(...args)
|
||||
);
|
||||
//Command to wait DICOM image to load into the viewport
|
||||
Cypress.Commands.add('waitDicomImage', (timeout = 20000) => {
|
||||
cy.window()
|
||||
.its('cornerstone')
|
||||
.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
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
//Command to reset and clear all the changes made to the viewport
|
||||
Cypress.Commands.add('resetViewport', () => {
|
||||
cy.initCornerstoneToolsAliases();
|
||||
cy.get('@resetBtn').click();
|
||||
//Click on More button
|
||||
cy.get('@moreBtn').click();
|
||||
//Verify if overlay is displayed
|
||||
cy.get('body').then(body => {
|
||||
if (body.find('.tooltip-toolbar-overlay').length == 0) {
|
||||
cy.get('@moreBtn').click();
|
||||
}
|
||||
});
|
||||
//Click on Clear button
|
||||
cy.get('.tooltip-inner > :nth-child(10)')
|
||||
.as('clearBtn')
|
||||
.click();
|
||||
});
|
||||
|
||||
Cypress.Commands.add('imageZoomIn', () => {
|
||||
cy.initCornerstoneToolsAliases();
|
||||
cy.get('@zoomBtn').click();
|
||||
|
||||
//drags the mouse inside the viewport to be able to interact with series
|
||||
cy.get('@viewport')
|
||||
.trigger('mousedown', 'top', { which: 1 })
|
||||
.trigger('mousemove', 'center', { which: 1 })
|
||||
.trigger('mouseup');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('imageContrast', () => {
|
||||
cy.initCornerstoneToolsAliases();
|
||||
cy.get('@levelsBtn').click();
|
||||
|
||||
//drags the mouse inside the viewport to be able to interact with series
|
||||
cy.get('@viewport')
|
||||
.trigger('mousedown', 'center', { which: 1 })
|
||||
.trigger('mousemove', 'top', { which: 1 })
|
||||
.trigger('mouseup');
|
||||
});
|
||||
|
||||
//Initialize aliases for Cornerstone tools buttons
|
||||
Cypress.Commands.add('initCornerstoneToolsAliases', () => {
|
||||
initCornerstoneToolsAliases();
|
||||
});
|
||||
|
||||
//Initialize aliases for Common page elements
|
||||
Cypress.Commands.add('initCommonElementsAliases', () => {
|
||||
initCommonElementsAliases();
|
||||
});
|
||||
|
||||
//Add measurements in the viewport
|
||||
Cypress.Commands.add('addLengthMeasurement', () => {
|
||||
cy.initCornerstoneToolsAliases();
|
||||
cy.get('@lengthBtn').click();
|
||||
const firstClick = [150, 100];
|
||||
const secondClick = [130, 170];
|
||||
cy.addLine('.cornerstone-canvas', firstClick, secondClick);
|
||||
});
|
||||
|
||||
//Add measurements in the viewport
|
||||
Cypress.Commands.add('addAngleMeasurement', () => {
|
||||
cy.initCornerstoneToolsAliases();
|
||||
cy.get('@angleBtn').click();
|
||||
const initPos = [180, 390];
|
||||
const midPos = [300, 410];
|
||||
const finalPos = [180, 450];
|
||||
cy.addAngle('.cornerstone-canvas', initPos, midPos, finalPos);
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/viewer",
|
||||
"version": "1.4.3",
|
||||
"version": "1.6.3",
|
||||
"description": "OHIF Viewer",
|
||||
"author": "OHIF Contributors",
|
||||
"license": "MIT",
|
||||
@@ -17,11 +17,10 @@
|
||||
},
|
||||
"proxy": "http://localhost:8042",
|
||||
"scripts": {
|
||||
"build:package": "cross-env NODE_ENV=production node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --config .webpack/webpack.commonjs.js",
|
||||
"build:package": "cross-env NODE_ENV=production node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --config .webpack/webpack.commonjs.js --progress",
|
||||
"build:viewer": "cross-env NODE_ENV=production node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --config .webpack/webpack.pwa.js --progress",
|
||||
"build:viewer:e2e": "cross-env NODE_ENV=development node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --config .webpack/webpack.pwa.js --progress",
|
||||
"build:viewer:ci": "cross-env NODE_ENV=production PUBLIC_URL=/pwa/ APP_CONFIG=config/netlify.js node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --config .webpack/webpack.pwa.js",
|
||||
"build:viewer:demo": "cross-env NODE_ENV=production APP_CONFIG=config/demo.js HTML_TEMPLATE=rollbar.html node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --progress --config .webpack/webpack.pwa.js",
|
||||
"build:viewer:ci": "cross-env NODE_ENV=production PUBLIC_URL=/pwa/ APP_CONFIG=config/netlify.js QUICK_BUILD=true node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --config .webpack/webpack.pwa.js",
|
||||
"build:viewer:demo": "cross-env NODE_ENV=production APP_CONFIG=config/demo.js HTML_TEMPLATE=rollbar.html QUICK_BUILD=true node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --progress --config .webpack/webpack.pwa.js",
|
||||
"build:viewer:package": "yarn run build:package",
|
||||
"dev": "cross-env NODE_ENV=development webpack-dev-server --config .webpack/webpack.pwa.js --watch",
|
||||
"dev:orthanc": "cross-env NODE_ENV=development PROXY_TARGET=/dicom-web PROXY_DOMAIN=http://localhost:8042 APP_CONFIG=config/docker_nginx-orthanc.js webpack-dev-server --config .webpack/webpack.pwa.js --watch",
|
||||
@@ -30,7 +29,7 @@
|
||||
"test:e2e": "cypress open",
|
||||
"test:e2e:ci": "cypress run",
|
||||
"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:ci": "jest --ci --runInBand --collectCoverage",
|
||||
"prepublishOnly": "yarn run build:package",
|
||||
@@ -46,14 +45,14 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"@ohif/core": "^1.1.0",
|
||||
"@ohif/core": "^1.3.2",
|
||||
"@ohif/extension-cornerstone": "^2.0.0",
|
||||
"@ohif/extension-dicom-html": "^1.0.0",
|
||||
"@ohif/extension-dicom-microscopy": "^0.50.6",
|
||||
"@ohif/extension-dicom-pdf": "^1.0.0",
|
||||
"@ohif/extension-vtk": "^0.52.1",
|
||||
"@ohif/extension-vtk": "^0.52.8",
|
||||
"@ohif/i18n": "^0.50.5",
|
||||
"@ohif/ui": "^0.53.1",
|
||||
"@ohif/ui": "^0.53.4",
|
||||
"@tanem/react-nprogress": "^1.1.25",
|
||||
"classnames": "^2.2.6",
|
||||
"core-js": "^3.2.1",
|
||||
|
||||
@@ -1,41 +1,35 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
||||
/>
|
||||
<meta name="theme-color" content="#000000" />
|
||||
|
||||
<link rel="manifest" href="<%= PUBLIC_URL %>manifest.json" />
|
||||
<!-- Built with: https://polyfill.io/v3/url-builder/ -->
|
||||
<!-- Targets IE11 -->
|
||||
<script src="https://polyfill.io/v3/polyfill.min.js?flags=gated&features=default%2CObject.values%2CArray.prototype.flat%2CObject.entries%2CSymbol%2CArray.prototype.includes%2CString.prototype.repeat%2CArray.prototype.find"></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="<%= PUBLIC_URL %>app-config.js"
|
||||
></script>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
|
||||
<title>OHIF Viewer</title>
|
||||
<link rel="manifest" href="<%= PUBLIC_URL %>manifest.json" />
|
||||
<!-- Built with: https://polyfill.io/v3/url-builder/ -->
|
||||
<!-- Targets IE11 -->
|
||||
<script
|
||||
src="https://polyfill.io/v3/polyfill.min.js?flags=gated&features=default%2CObject.values%2CArray.prototype.flat%2CObject.entries%2CSymbol%2CArray.prototype.includes%2CString.prototype.repeat%2CArray.prototype.find"></script>
|
||||
<script type="text/javascript" src="<%= PUBLIC_URL %>app-config.js"></script>
|
||||
|
||||
<!-- WEB FONTS -->
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700|Sanchez&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<title>OHIF Viewer</title>
|
||||
|
||||
<!-- EXTENSIONS -->
|
||||
<!-- <script type="text/javascript" src="path/to/some-extension.js"></script>
|
||||
<!-- WEB FONTS -->
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700&display=swap" rel="stylesheet" />
|
||||
|
||||
<!-- EXTENSIONS -->
|
||||
<!-- <script type="text/javascript" src="path/to/some-extension.js"></script>
|
||||
|
||||
<script>
|
||||
window.config.extensions = [SomeExtension];
|
||||
</script> -->
|
||||
</head>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript> You need to enable JavaScript to run this app. </noscript>
|
||||
<body>
|
||||
<noscript> You need to enable JavaScript to run this app. </noscript>
|
||||
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,53 +1,50 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
||||
/>
|
||||
<meta name="theme-color" content="#000000" />
|
||||
|
||||
<link rel="manifest" href="<%= PUBLIC_URL %>manifest.json" />
|
||||
<!-- Built with: https://polyfill.io/v3/url-builder/ -->
|
||||
<!-- Targets IE11 -->
|
||||
<script src="https://polyfill.io/v3/polyfill.min.js?flags=gated&features=default%2CObject.values%2CArray.prototype.flat%2CObject.entries%2CSymbol%2CArray.prototype.includes%2CString.prototype.repeat%2CArray.prototype.find"></script>
|
||||
<script type="text/javascript" src="<%= PUBLIC_URL %>app-config.js"></script>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
|
||||
<title>OHIF Viewer</title>
|
||||
<link rel="manifest" href="<%= PUBLIC_URL %>manifest.json" />
|
||||
<!-- Built with: https://polyfill.io/v3/url-builder/ -->
|
||||
<!-- Targets IE11 -->
|
||||
<script
|
||||
src="https://polyfill.io/v3/polyfill.min.js?flags=gated&features=default%2CObject.values%2CArray.prototype.flat%2CObject.entries%2CSymbol%2CArray.prototype.includes%2CString.prototype.repeat%2CArray.prototype.find"></script>
|
||||
<script type="text/javascript" src="<%= PUBLIC_URL %>app-config.js"></script>
|
||||
|
||||
<!-- WEB FONTS -->
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700|Sanchez&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<title>OHIF Viewer</title>
|
||||
|
||||
<!-- EXTENSIONS -->
|
||||
<!-- <script type="text/javascript" src="path/to/some-extension.js"></script>
|
||||
<!-- WEB FONTS -->
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700&display=swap" rel="stylesheet" />
|
||||
|
||||
<!-- EXTENSIONS -->
|
||||
<!-- <script type="text/javascript" src="path/to/some-extension.js"></script>
|
||||
|
||||
<script>
|
||||
window.config.extensions = [SomeExtension];
|
||||
</script> -->
|
||||
</head>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript> You need to enable JavaScript to run this app. </noscript>
|
||||
<body>
|
||||
<noscript> You need to enable JavaScript to run this app. </noscript>
|
||||
|
||||
<div id="root"></div>
|
||||
<div id="root"></div>
|
||||
|
||||
<script>
|
||||
// Note: This is a client access token so there is no issue with having it publicly available
|
||||
var _rollbarConfig = {
|
||||
accessToken: "739c64cd39e84592ba062bc29aac9769",
|
||||
captureUncaught: true,
|
||||
captureUnhandledRejections: true,
|
||||
payload: {
|
||||
environment: "viewer.ohif.org"
|
||||
}
|
||||
};
|
||||
// Rollbar Snippet
|
||||
!function(r){var e={};function o(n){if(e[n])return e[n].exports;var t=e[n]={i:n,l:!1,exports:{}};return r[n].call(t.exports,t,t.exports,o),t.l=!0,t.exports}o.m=r,o.c=e,o.d=function(r,e,n){o.o(r,e)||Object.defineProperty(r,e,{enumerable:!0,get:n})},o.r=function(r){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(r,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(r,"__esModule",{value:!0})},o.t=function(r,e){if(1&e&&(r=o(r)),8&e)return r;if(4&e&&"object"==typeof r&&r&&r.__esModule)return r;var n=Object.create(null);if(o.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:r}),2&e&&"string"!=typeof r)for(var t in r)o.d(n,t,function(e){return r[e]}.bind(null,t));return n},o.n=function(r){var e=r&&r.__esModule?function(){return r.default}:function(){return r};return o.d(e,"a",e),e},o.o=function(r,e){return Object.prototype.hasOwnProperty.call(r,e)},o.p="",o(o.s=0)}([function(r,e,o){var n=o(1),t=o(4);_rollbarConfig=_rollbarConfig||{},_rollbarConfig.rollbarJsUrl=_rollbarConfig.rollbarJsUrl||"https://cdnjs.cloudflare.com/ajax/libs/rollbar.js/2.7.1/rollbar.min.js",_rollbarConfig.async=void 0===_rollbarConfig.async||_rollbarConfig.async;var a=n.setupShim(window,_rollbarConfig),l=t(_rollbarConfig);window.rollbar=n.Rollbar,a.loadFull(window,document,!_rollbarConfig.async,_rollbarConfig,l)},function(r,e,o){var n=o(2);function t(r){return function(){try{return r.apply(this,arguments)}catch(r){try{console.error("[Rollbar]: Internal error",r)}catch(r){}}}}var a=0;function l(r,e){this.options=r,this._rollbarOldOnError=null;var o=a++;this.shimId=function(){return o},"undefined"!=typeof window&&window._rollbarShims&&(window._rollbarShims[o]={handler:e,messages:[]})}var i=o(3),d=function(r,e){return new l(r,e)},c=function(r){return new i(d,r)};function s(r){return t(function(){var e=Array.prototype.slice.call(arguments,0),o={shim:this,method:r,args:e,ts:new Date};window._rollbarShims[this.shimId()].messages.push(o)})}l.prototype.loadFull=function(r,e,o,n,a){var l=!1,i=e.createElement("script"),d=e.getElementsByTagName("script")[0],c=d.parentNode;i.crossOrigin="",i.src=n.rollbarJsUrl,o||(i.async=!0),i.onload=i.onreadystatechange=t(function(){if(!(l||this.readyState&&"loaded"!==this.readyState&&"complete"!==this.readyState)){i.onload=i.onreadystatechange=null;try{c.removeChild(i)}catch(r){}l=!0,function(){var e;if(void 0===r._rollbarDidLoad){e=new Error("rollbar.js did not load");for(var o,n,t,l,i=0;o=r._rollbarShims[i++];)for(o=o.messages||[];n=o.shift();)for(t=n.args||[],i=0;i<t.length;++i)if("function"==typeof(l=t[i])){l(e);break}}"function"==typeof a&&a(e)}()}}),c.insertBefore(i,d)},l.prototype.wrap=function(r,e,o){try{var n;if(n="function"==typeof e?e:function(){return e||{}},"function"!=typeof r)return r;if(r._isWrap)return r;if(!r._rollbar_wrapped&&(r._rollbar_wrapped=function(){o&&"function"==typeof o&&o.apply(this,arguments);try{return r.apply(this,arguments)}catch(o){var e=o;throw e&&("string"==typeof e&&(e=new String(e)),e._rollbarContext=n()||{},e._rollbarContext._wrappedSource=r.toString(),window._rollbarWrappedError=e),e}},r._rollbar_wrapped._isWrap=!0,r.hasOwnProperty))for(var t in r)r.hasOwnProperty(t)&&(r._rollbar_wrapped[t]=r[t]);return r._rollbar_wrapped}catch(e){return r}};for(var p="log,debug,info,warn,warning,error,critical,global,configure,handleUncaughtException,handleUnhandledRejection,captureEvent,captureDomContentLoaded,captureLoad".split(","),u=0;u<p.length;++u)l.prototype[p[u]]=s(p[u]);r.exports={setupShim:function(r,e){if(r){var o=e.globalAlias||"Rollbar";if("object"==typeof r[o])return r[o];r._rollbarShims={},r._rollbarWrappedError=null;var a=new c(e);return t(function(){e.captureUncaught&&(a._rollbarOldOnError=r.onerror,n.captureUncaughtExceptions(r,a,!0),n.wrapGlobals(r,a,!0)),e.captureUnhandledRejections&&n.captureUnhandledRejections(r,a,!0);var t=e.autoInstrument;return!1!==e.enabled&&(void 0===t||!0===t||"object"==typeof t&&t.network)&&r.addEventListener&&(r.addEventListener("load",a.captureLoad.bind(a)),r.addEventListener("DOMContentLoaded",a.captureDomContentLoaded.bind(a))),r[o]=a,a})()}},Rollbar:c}},function(r,e){function o(r,e,o){if(e.hasOwnProperty&&e.hasOwnProperty("addEventListener")){for(var n=e.addEventListener;n._rollbarOldAdd&&n.belongsToShim;)n=n._rollbarOldAdd;var t=function(e,o,t){n.call(this,e,r.wrap(o),t)};t._rollbarOldAdd=n,t.belongsToShim=o,e.addEventListener=t;for(var a=e.removeEventListener;a._rollbarOldRemove&&a.belongsToShim;)a=a._rollbarOldRemove;var l=function(r,e,o){a.call(this,r,e&&e._rollbar_wrapped||e,o)};l._rollbarOldRemove=a,l.belongsToShim=o,e.removeEventListener=l}}r.exports={captureUncaughtExceptions:function(r,e,o){if(r){var n;if("function"==typeof e._rollbarOldOnError)n=e._rollbarOldOnError;else if(r.onerror){for(n=r.onerror;n._rollbarOldOnError;)n=n._rollbarOldOnError;e._rollbarOldOnError=n}var t=function(){var o=Array.prototype.slice.call(arguments,0);!function(r,e,o,n){r._rollbarWrappedError&&(n[4]||(n[4]=r._roLine truncated
|
||||
<script>
|
||||
// Note: This is a client access token so there is no issue with having it publicly available
|
||||
var _rollbarConfig = {
|
||||
accessToken: "739c64cd39e84592ba062bc29aac9769",
|
||||
captureUncaught: true,
|
||||
captureUnhandledRejections: true,
|
||||
payload: {
|
||||
environment: "viewer.ohif.org"
|
||||
}
|
||||
};
|
||||
// Rollbar Snippet
|
||||
!function (r) { var e = {}; function o(n) { if (e[n]) return e[n].exports; var t = e[n] = { i: n, l: !1, exports: {} }; return r[n].call(t.exports, t, t.exports, o), t.l = !0, t.exports } o.m = r, o.c = e, o.d = function (r, e, n) { o.o(r, e) || Object.defineProperty(r, e, { enumerable: !0, get: n }) }, o.r = function (r) { "undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(r, Symbol.toStringTag, { value: "Module" }), Object.defineProperty(r, "__esModule", { value: !0 }) }, o.t = function (r, e) { if (1 & e && (r = o(r)), 8 & e) return r; if (4 & e && "object" == typeof r && r && r.__esModule) return r; var n = Object.create(null); if (o.r(n), Object.defineProperty(n, "default", { enumerable: !0, value: r }), 2 & e && "string" != typeof r) for (var t in r) o.d(n, t, function (e) { return r[e] }.bind(null, t)); return n }, o.n = function (r) { var e = r && r.__esModule ? function () { return r.default } : function () { return r }; return o.d(e, "a", e), e }, o.o = function (r, e) { return Object.prototype.hasOwnProperty.call(r, e) }, o.p = "", o(o.s = 0) }([function (r, e, o) { var n = o(1), t = o(4); _rollbarConfig = _rollbarConfig || {}, _rollbarConfig.rollbarJsUrl = _rollbarConfig.rollbarJsUrl || "https://cdnjs.cloudflare.com/ajax/libs/rollbar.js/2.7.1/rollbar.min.js", _rollbarConfig.async = void 0 === _rollbarConfig.async || _rollbarConfig.async; var a = n.setupShim(window, _rollbarConfig), l = t(_rollbarConfig); window.rollbar = n.Rollbar, a.loadFull(window, document, !_rollbarConfig.async, _rollbarConfig, l) }, function (r, e, o) { var n = o(2); function t(r) { return function () { try { return r.apply(this, arguments) } catch (r) { try { console.error("[Rollbar]: Internal error", r) } catch (r) { } } } } var a = 0; function l(r, e) { this.options = r, this._rollbarOldOnError = null; var o = a++; this.shimId = function () { return o }, "undefined" != typeof window && window._rollbarShims && (window._rollbarShims[o] = { handler: e, messages: [] }) } var i = o(3), d = function (r, e) { return new l(r, e) }, c = function (r) { return new i(d, r) }; function s(r) { return t(function () { var e = Array.prototype.slice.call(arguments, 0), o = { shim: this, method: r, args: e, ts: new Date }; window._rollbarShims[this.shimId()].messages.push(o) }) } l.prototype.loadFull = function (r, e, o, n, a) { var l = !1, i = e.createElement("script"), d = e.getElementsByTagName("script")[0], c = d.parentNode; i.crossOrigin = "", i.src = n.rollbarJsUrl, o || (i.async = !0), i.onload = i.onreadystatechange = t(function () { if (!(l || this.readyState && "loaded" !== this.readyState && "complete" !== this.readyState)) { i.onload = i.onreadystatechange = null; try { c.removeChild(i) } catch (r) { } l = !0, function () { var e; if (void 0 === r._rollbarDidLoad) { e = new Error("rollbar.js did not load"); for (var o, n, t, l, i = 0; o = r._rollbarShims[i++];)for (o = o.messages || []; n = o.shift();)for (t = n.args || [], i = 0; i < t.length; ++i)if ("function" == typeof (l = t[i])) { l(e); break } } "function" == typeof a && a(e) }() } }), c.insertBefore(i, d) }, l.prototype.wrap = function (r, e, o) { try { var n; if (n = "function" == typeof e ? e : function () { return e || {} }, "function" != typeof r) return r; if (r._isWrap) return r; if (!r._rollbar_wrapped && (r._rollbar_wrapped = function () { o && "function" == typeof o && o.apply(this, arguments); try { return r.apply(this, arguments) } catch (o) { var e = o; throw e && ("string" == typeof e && (e = new String(e)), e._rollbarContext = n() || {}, e._rollbarContext._wrappedSource = r.toString(), window._rollbarWrappedError = e), e } }, r._rollbar_wrapped._isWrap = !0, r.hasOwnProperty)) for (var t in r) r.hasOwnProperty(t) && (r._rollbar_wrapped[t] = r[t]); return r._rollbar_wrapped } catch (e) { return r } }; for (var p = "log,debug,info,warn,warning,error,critical,global,configure,handleUncaughtException,handleUnhandledRejection,captureEvent,captureDomContentLoaded,captureLoad".split(","), u = 0; u < p.length; ++u)l.prototype[p[u]] = s(p[u]); r.exports = { setupShim: function (r, e) { if (r) { var o = e.globalAlias || "Rollbar"; if ("object" == typeof r[o]) return r[o]; r._rollbarShims = {}, r._rollbarWrappedError = null; var a = new c(e); return t(function () { e.captureUncaught && (a._rollbarOldOnError = r.onerror, n.captureUncaughtExceptions(r, a, !0), n.wrapGlobals(r, a, !0)), e.captureUnhandledRejections && n.captureUnhandledRejections(r, a, !0); var t = e.autoInstrument; return !1 !== e.enabled && (void 0 === t || !0 === t || "object" == typeof t && t.network) && r.addEventListener && (r.addEventListener("load", a.captureLoad.bind(a)), r.addEventListener("DOMContentLoaded", a.captureDomContentLoaded.bind(a))), r[o] = a, a })() } }, Rollbar: c } }, function (r, e) { function o(r, e, o) { if (e.hasOwnProperty && e.hasOwnProperty("addEventListener")) { for (var n = e.addEventListener; n._rollbarOldAdd && n.belongsToShim;)n = n._rolLine truncated
|
||||
// End Rollbar Snippet
|
||||
</script>
|
||||
</body>
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,67 +1,59 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<meta
|
||||
name="description"
|
||||
content="Open Health Imaging Foundation DICOM Viewer"
|
||||
/>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width,initial-scale=1.0,minimum-scale=1,maximum-scale=1,user-scalable=no"
|
||||
/>
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta http-equiv="cleartype" content="on" />
|
||||
<meta name="MobileOptimized" content="320" />
|
||||
<meta name="HandheldFriendly" content="True" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<script src="https://polyfill.io/v3/polyfill.min.js?flags=gated&features=default%2CObject.values%2CArray.prototype.flat%2CObject.entries%2CSymbol%2CArray.prototype.includes%2CString.prototype.repeat%2CArray.prototype.find"></script>
|
||||
<meta name="description" content="Open Health Imaging Foundation DICOM Viewer" />
|
||||
<meta name="viewport"
|
||||
content="width=device-width,initial-scale=1.0,minimum-scale=1,maximum-scale=1,user-scalable=no" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta http-equiv="cleartype" content="on" />
|
||||
<meta name="MobileOptimized" content="320" />
|
||||
<meta name="HandheldFriendly" content="True" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
|
||||
<!-- WEB FONTS -->
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css?family=Sanchez"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<script
|
||||
src="https://polyfill.io/v3/polyfill.min.js?flags=gated&features=default%2CObject.values%2CArray.prototype.flat%2CObject.entries%2CSymbol%2CArray.prototype.includes%2CString.prototype.repeat%2CArray.prototype.find"></script>
|
||||
|
||||
<title>OHIF Standalone Viewer</title>
|
||||
</head>
|
||||
<title>OHIF Standalone Viewer</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript> You need to enable JavaScript to run this app. </noscript>
|
||||
<body>
|
||||
<noscript> You need to enable JavaScript to run this app. </noscript>
|
||||
|
||||
<div id="root"></div>
|
||||
<div id="root"></div>
|
||||
|
||||
<script src="/index.umd.js" crossorigin></script>
|
||||
<script>
|
||||
var containerId = 'root';
|
||||
var componentRenderedOrUpdatedCallback = function() {
|
||||
console.log('OHIF Viewer rendered/updated');
|
||||
};
|
||||
window.OHIFViewer.installViewer(
|
||||
{
|
||||
// routerBasename: '/',
|
||||
servers: {
|
||||
dicomWeb: [
|
||||
{
|
||||
name: 'DCM4CHEE',
|
||||
wadoUriRoot:
|
||||
'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado',
|
||||
qidoRoot:
|
||||
'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
|
||||
wadoRoot:
|
||||
'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
|
||||
qidoSupportsIncludeField: true,
|
||||
imageRendering: 'wadors',
|
||||
thumbnailRendering: 'wadors',
|
||||
},
|
||||
],
|
||||
},
|
||||
<script src="/index.umd.js" crossorigin></script>
|
||||
<script>
|
||||
var containerId = 'root';
|
||||
var componentRenderedOrUpdatedCallback = function () {
|
||||
console.log('OHIF Viewer rendered/updated');
|
||||
};
|
||||
window.OHIFViewer.installViewer(
|
||||
{
|
||||
// routerBasename: '/',
|
||||
servers: {
|
||||
dicomWeb: [
|
||||
{
|
||||
name: 'DCM4CHEE',
|
||||
wadoUriRoot:
|
||||
'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado',
|
||||
qidoRoot:
|
||||
'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
|
||||
wadoRoot:
|
||||
'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
|
||||
qidoSupportsIncludeField: true,
|
||||
imageRendering: 'wadors',
|
||||
thumbnailRendering: 'wadors',
|
||||
},
|
||||
],
|
||||
},
|
||||
containerId,
|
||||
componentRenderedOrUpdatedCallback
|
||||
);
|
||||
</script>
|
||||
</body>
|
||||
},
|
||||
containerId,
|
||||
componentRenderedOrUpdatedCallback
|
||||
);
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
} from './utils/index.js';
|
||||
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import initCornerstoneTools from './initCornerstoneTools.js';
|
||||
|
||||
// ~~ EXTENSIONS
|
||||
import { GenericViewerCommands, MeasurementsPanel } from './appExtensions';
|
||||
@@ -27,7 +26,6 @@ import { Provider } from 'react-redux';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import { getActiveContexts } from './store/layout/selectors.js';
|
||||
import i18n from '@ohif/i18n';
|
||||
import setupTools from './setupTools.js';
|
||||
import store from './store';
|
||||
|
||||
// Contexts
|
||||
@@ -36,11 +34,6 @@ import UserManagerContext from './context/UserManagerContext';
|
||||
import AppContext from './context/AppContext';
|
||||
|
||||
// ~~~~ APP SETUP
|
||||
initCornerstoneTools({
|
||||
globalToolSyncEnabled: true,
|
||||
showSVGCursors: true,
|
||||
});
|
||||
|
||||
const commandsManagerConfig = {
|
||||
getAppState: () => store.getState(),
|
||||
getActiveContexts: () => getActiveContexts(store.getState()),
|
||||
@@ -49,9 +42,6 @@ const commandsManagerConfig = {
|
||||
const commandsManager = new CommandsManager(commandsManagerConfig);
|
||||
const hotkeysManager = new HotkeysManager(commandsManager);
|
||||
const extensionManager = new ExtensionManager({ commandsManager });
|
||||
|
||||
// CornerstoneTools and labeling/measurements?
|
||||
setupTools(store);
|
||||
// ~~~~ END APP SETUP
|
||||
|
||||
// TODO[react] Use a provider when the whole tree is React
|
||||
@@ -175,8 +165,9 @@ class App extends Component {
|
||||
function _initExtensions(extensions, hotkeys) {
|
||||
const defaultExtensions = [
|
||||
GenericViewerCommands,
|
||||
MeasurementsPanel,
|
||||
OHIFCornerstoneExtension,
|
||||
// WARNING: MUST BE REGISTERED _AFTER_ OHIFCORNERSTONEEXTENSION
|
||||
MeasurementsPanel,
|
||||
];
|
||||
const mergedExtensions = defaultExtensions.concat(extensions);
|
||||
extensionManager.registerExtensions(mergedExtensions);
|
||||
|
||||
@@ -8,44 +8,18 @@ import { connect } from 'react-redux';
|
||||
import { ViewerbaseDragDropContext } from '@ohif/ui';
|
||||
import { SignoutCallbackComponent } from 'redux-oidc';
|
||||
import asyncComponent from './components/AsyncComponent.js';
|
||||
import * as RoutesUtil from './routes/routesUtil';
|
||||
|
||||
import NotFound from './routes/NotFound.js';
|
||||
import { Bar, Container } from './components/LoadingBar/';
|
||||
import './OHIFStandaloneViewer.css';
|
||||
import './variables.css';
|
||||
import './theme-tide.css';
|
||||
|
||||
// Contexts
|
||||
import AppContext from './context/AppContext';
|
||||
|
||||
// Dynamic Import Routes (CodeSplitting)
|
||||
const IHEInvokeImageDisplay = asyncComponent(() =>
|
||||
import(
|
||||
/* webpackChunkName: "IHEInvokeImageDisplay" */ './routes/IHEInvokeImageDisplay.js'
|
||||
)
|
||||
);
|
||||
const ViewerRouting = asyncComponent(() =>
|
||||
import(/* webpackChunkName: "ViewerRouting" */ './routes/ViewerRouting.js')
|
||||
);
|
||||
const StudyListRouting = asyncComponent(() =>
|
||||
import(
|
||||
/* webpackChunkName: "StudyListRouting" */ './studylist/StudyListRouting.js'
|
||||
)
|
||||
);
|
||||
const StandaloneRouting = asyncComponent(() =>
|
||||
import(
|
||||
/* webpackChunkName: "StandaloneRouting" */ './routes/StandaloneRouting.js'
|
||||
)
|
||||
);
|
||||
const CallbackPage = asyncComponent(() =>
|
||||
import(/* webpackChunkName: "CallbackPage" */ './routes/CallbackPage.js')
|
||||
);
|
||||
const ViewerLocalFileData = asyncComponent(() =>
|
||||
import(
|
||||
/* webpackChunkName: "ViewerLocalFileData" */ './connectedComponents/ViewerLocalFileData.js'
|
||||
)
|
||||
);
|
||||
|
||||
const reload = () => window.location.reload();
|
||||
|
||||
class OHIFStandaloneViewer extends Component {
|
||||
static contextType = AppContext;
|
||||
@@ -86,7 +60,11 @@ class OHIFStandaloneViewer extends Component {
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route exact path="/silent-refresh.html" onEnter={reload} />
|
||||
<Route
|
||||
exact
|
||||
path="/silent-refresh.html"
|
||||
onEnter={RoutesUtil.reload}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/logout-redirect"
|
||||
@@ -128,41 +106,7 @@ class OHIFStandaloneViewer extends Component {
|
||||
*
|
||||
* See http://reactcommunity.org/react-transition-group/with-react-router/
|
||||
*/
|
||||
const routes = [
|
||||
{
|
||||
path: '/local',
|
||||
Component: ViewerLocalFileData,
|
||||
},
|
||||
{
|
||||
path: '/viewer',
|
||||
Component: StandaloneRouting,
|
||||
},
|
||||
{
|
||||
path: '/viewer/:studyInstanceUids',
|
||||
Component: ViewerRouting,
|
||||
},
|
||||
{
|
||||
path: '/study/:studyInstanceUids/series/:seriesInstanceUids',
|
||||
Component: ViewerRouting,
|
||||
},
|
||||
{
|
||||
path: '/IHEInvokeImageDisplay',
|
||||
Component: IHEInvokeImageDisplay,
|
||||
},
|
||||
];
|
||||
|
||||
const showStudyList =
|
||||
appConfig.showStudyList !== undefined ? appConfig.showStudyList : true;
|
||||
if (showStudyList) {
|
||||
routes.push({
|
||||
path: '/studylist',
|
||||
Component: StudyListRouting,
|
||||
});
|
||||
routes.push({
|
||||
path: '/',
|
||||
Component: StudyListRouting,
|
||||
});
|
||||
}
|
||||
const routes = RoutesUtil.getRoutes(appConfig);
|
||||
|
||||
const currentPath = this.props.location.pathname;
|
||||
const noMatchingRoutes = !routes.find(r =>
|
||||
@@ -184,8 +128,8 @@ class OHIFStandaloneViewer extends Component {
|
||||
</Container>
|
||||
)}
|
||||
</NProgress>
|
||||
<Route exact path="/silent-refresh.html" onEnter={reload} />
|
||||
<Route exact path="/logout-redirect.html" onEnter={reload} />
|
||||
<Route exact path="/silent-refresh.html" onEnter={RoutesUtil.reload} />
|
||||
<Route exact path="/logout-redirect.html" onEnter={RoutesUtil.reload} />
|
||||
{!noMatchingRoutes &&
|
||||
routes.map(({ path, Component }) => (
|
||||
<Route key={path} exact path={path}>
|
||||
|
||||
@@ -6,7 +6,7 @@ import cornerstone from 'cornerstone-core';
|
||||
|
||||
//
|
||||
import jumpToRowItem from './jumpToRowItem.js';
|
||||
import getMeasurementLocationCallback from './../../lib/getMeasurementLocationCallback';
|
||||
import getMeasurementLocationCallback from './getMeasurementLocationCallback';
|
||||
|
||||
const { setViewportSpecificData } = OHIF.redux.actions;
|
||||
const { MeasurementApi } = OHIF.measurements;
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
const setLabellingFlowDataAction = labellingFlowData => ({
|
||||
type: 'SET_LABELLING_FLOW_DATA',
|
||||
labellingFlowData,
|
||||
});
|
||||
|
||||
const resetLabellingAndContextMenuAction = state => ({
|
||||
type: 'RESET_LABELLING_AND_CONTEXT_MENU',
|
||||
state,
|
||||
});
|
||||
|
||||
const setToolContextMenuDataAction = (viewportIndex, toolContextMenuData) => ({
|
||||
type: 'SET_TOOL_CONTEXT_MENU_DATA',
|
||||
viewportIndex,
|
||||
toolContextMenuData,
|
||||
});
|
||||
|
||||
export {
|
||||
resetLabellingAndContextMenuAction,
|
||||
setLabellingFlowDataAction,
|
||||
setToolContextMenuDataAction,
|
||||
};
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import cornerstoneTools from 'cornerstone-tools';
|
||||
import updateTableWithNewMeasurementData from './updateTableWithNewMeasurementData';
|
||||
import updateTableWithNewMeasurementData from './updateTableWithNewMeasurementData.js';
|
||||
|
||||
export default function getMeasurementLocationCallback(
|
||||
eventData,
|
||||
@@ -1,7 +1,15 @@
|
||||
import ConnectedMeasurementTable from './ConnectedMeasurementTable.js';
|
||||
import init from './init.js';
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Only required property. Should be a unique value across all extensions.
|
||||
*/
|
||||
id: 'measurements-table',
|
||||
|
||||
preRegistration(configuration = {}) {
|
||||
init(configuration);
|
||||
},
|
||||
getPanelModule() {
|
||||
return {
|
||||
menuOptions: [
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
import OHIF from '@ohif/core';
|
||||
import cornerstone from 'cornerstone-core';
|
||||
import csTools from 'cornerstone-tools';
|
||||
import {
|
||||
getToolLabellingFlowCallback,
|
||||
getOnRightClickCallback,
|
||||
getOnTouchPressCallback,
|
||||
getResetLabellingAndContextMenu,
|
||||
} from './labelingFlowCallbacks.js';
|
||||
import throttle from 'lodash.throttle';
|
||||
|
||||
// TODO: This only works because we have a hard dependency on this extension
|
||||
// We need to decouple and make stuff like this possible w/o bundling this at
|
||||
// build time
|
||||
import store from './../../store';
|
||||
|
||||
const {
|
||||
onAdded,
|
||||
onRemoved,
|
||||
onModified,
|
||||
} = OHIF.measurements.MeasurementHandlers;
|
||||
|
||||
const MEASUREMENT_ACTION_MAP = {
|
||||
added: onAdded,
|
||||
removed: onRemoved,
|
||||
modified: throttle(event => {
|
||||
return onModified(event);
|
||||
}, 300),
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @export
|
||||
* @param {*} configuration
|
||||
*/
|
||||
export default function init(configuration) {
|
||||
// If these tools were already added by a different extension, we want to replace
|
||||
// them with the same tools that have an alternative configuration. By passing in
|
||||
// our custom `getMeasurementLocationCallback`, we can...
|
||||
const toolLabellingFlowCallback = getToolLabellingFlowCallback(store);
|
||||
|
||||
// Removes all tools from all enabled elements w/ provided name
|
||||
// Not commonly used API, so :eyes: for unknown side-effects
|
||||
csTools.removeTool('Bidirectional');
|
||||
csTools.removeTool('Length');
|
||||
csTools.removeTool('Angle');
|
||||
csTools.removeTool('FreehandRoi');
|
||||
csTools.removeTool('EllipticalRoi');
|
||||
csTools.removeTool('CircleRoi');
|
||||
csTools.removeTool('RectangleRoi');
|
||||
csTools.removeTool('ArrowAnnotate');
|
||||
|
||||
// Re-add each tool w/ our custom configuration
|
||||
csTools.addTool(csTools.BidirectionalTool, {
|
||||
configuration: {
|
||||
getMeasurementLocationCallback: toolLabellingFlowCallback,
|
||||
},
|
||||
});
|
||||
csTools.addTool(csTools.LengthTool, {
|
||||
configuration: {
|
||||
getMeasurementLocationCallback: toolLabellingFlowCallback,
|
||||
},
|
||||
});
|
||||
csTools.addTool(csTools.AngleTool, {
|
||||
configuration: {
|
||||
getMeasurementLocationCallback: toolLabellingFlowCallback,
|
||||
},
|
||||
});
|
||||
csTools.addTool(csTools.FreehandRoiTool, {
|
||||
configuration: {
|
||||
getMeasurementLocationCallback: toolLabellingFlowCallback,
|
||||
},
|
||||
});
|
||||
csTools.addTool(csTools.EllipticalRoiTool, {
|
||||
configuration: {
|
||||
getMeasurementLocationCallback: toolLabellingFlowCallback,
|
||||
},
|
||||
});
|
||||
csTools.addTool(csTools.CircleRoiTool, {
|
||||
configuration: {
|
||||
getMeasurementLocationCallback: toolLabellingFlowCallback,
|
||||
},
|
||||
});
|
||||
csTools.addTool(csTools.RectangleRoiTool, {
|
||||
configuration: {
|
||||
getMeasurementLocationCallback: toolLabellingFlowCallback,
|
||||
},
|
||||
});
|
||||
csTools.addTool(csTools.ArrowAnnotateTool, {
|
||||
configuration: {
|
||||
getMeasurementLocationCallback: toolLabellingFlowCallback,
|
||||
},
|
||||
});
|
||||
|
||||
// TODO: MEASUREMENT_COMPLETED (not present in initial implementation)
|
||||
const onMeasurementsChanged = (action, event) => {
|
||||
return MEASUREMENT_ACTION_MAP[action](event);
|
||||
};
|
||||
const onMeasurementAdded = onMeasurementsChanged.bind(this, 'added');
|
||||
const onMeasurementRemoved = onMeasurementsChanged.bind(this, 'removed');
|
||||
const onMeasurementModified = onMeasurementsChanged.bind(this, 'modified');
|
||||
const onLabelmapModified = onMeasurementsChanged.bind(
|
||||
this,
|
||||
'labelmapModified'
|
||||
);
|
||||
//
|
||||
const onRightClick = getOnRightClickCallback(store);
|
||||
const onTouchPress = getOnTouchPressCallback(store);
|
||||
const onNewImage = getResetLabellingAndContextMenu(store);
|
||||
const onMouseClick = getResetLabellingAndContextMenu(store);
|
||||
const onTouchStart = getResetLabellingAndContextMenu(store);
|
||||
|
||||
// Because click gives us the native "mouse up", buttons will always be `0`
|
||||
// Need to fallback to event.which;
|
||||
const handleClick = cornerstoneMouseClickEvent => {
|
||||
const mouseUpEvent = cornerstoneMouseClickEvent.detail.event;
|
||||
const isRightClick = mouseUpEvent.which === 3;
|
||||
|
||||
if (isRightClick) {
|
||||
onRightClick(cornerstoneMouseClickEvent);
|
||||
} else {
|
||||
onMouseClick(cornerstoneMouseClickEvent);
|
||||
}
|
||||
};
|
||||
|
||||
function elementEnabledHandler(evt) {
|
||||
const element = evt.detail.element;
|
||||
|
||||
element.addEventListener(
|
||||
csTools.EVENTS.MEASUREMENT_ADDED,
|
||||
onMeasurementAdded
|
||||
);
|
||||
element.addEventListener(
|
||||
csTools.EVENTS.MEASUREMENT_REMOVED,
|
||||
onMeasurementRemoved
|
||||
);
|
||||
element.addEventListener(
|
||||
csTools.EVENTS.MEASUREMENT_MODIFIED,
|
||||
onMeasurementModified
|
||||
);
|
||||
element.addEventListener(
|
||||
csTools.EVENTS.LABELMAP_MODIFIED,
|
||||
onLabelmapModified
|
||||
);
|
||||
//
|
||||
element.addEventListener(csTools.EVENTS.TOUCH_PRESS, onTouchPress);
|
||||
element.addEventListener(csTools.EVENTS.MOUSE_CLICK, handleClick);
|
||||
element.addEventListener(csTools.EVENTS.TOUCH_START, onTouchStart);
|
||||
// TODO: This makes scrolling painfully slow
|
||||
// element.addEventListener(cornerstone.EVENTS.NEW_IMAGE, onNewImage);
|
||||
}
|
||||
|
||||
function elementDisabledHandler(evt) {
|
||||
const element = evt.detail.element;
|
||||
|
||||
element.removeEventListener(
|
||||
csTools.EVENTS.MEASUREMENT_ADDED,
|
||||
onMeasurementAdded
|
||||
);
|
||||
element.removeEventListener(
|
||||
csTools.EVENTS.MEASUREMENT_REMOVED,
|
||||
onMeasurementRemoved
|
||||
);
|
||||
element.removeEventListener(
|
||||
csTools.EVENTS.MEASUREMENT_MODIFIED,
|
||||
onMeasurementModified
|
||||
);
|
||||
element.removeEventListener(
|
||||
csTools.EVENTS.LABELMAP_MODIFIED,
|
||||
onLabelmapModified
|
||||
);
|
||||
//
|
||||
element.removeEventListener(csTools.EVENTS.TOUCH_PRESS, onTouchPress);
|
||||
element.removeEventListener(csTools.EVENTS.MOUSE_CLICK, handleClick);
|
||||
element.removeEventListener(csTools.EVENTS.TOUCH_START, onTouchStart);
|
||||
// element.removeEventListener(cornerstone.EVENTS.NEW_IMAGE, onNewImage);
|
||||
}
|
||||
|
||||
cornerstone.events.addEventListener(
|
||||
cornerstone.EVENTS.ELEMENT_ENABLED,
|
||||
elementEnabledHandler
|
||||
);
|
||||
cornerstone.events.addEventListener(
|
||||
cornerstone.EVENTS.ELEMENT_DISABLED,
|
||||
elementDisabledHandler
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
import {
|
||||
resetLabellingAndContextMenuAction,
|
||||
setToolContextMenuDataAction,
|
||||
setLabellingFlowDataAction,
|
||||
} from './actions.js';
|
||||
import updateTableWithNewMeasurementData from './updateTableWithNewMeasurementData.js';
|
||||
|
||||
const VIEWPORT_INDEX = 0;
|
||||
|
||||
function getOnRightClickCallback(store) {
|
||||
const setToolContextMenuData = (viewportIndex, toolContextMenuData) => {
|
||||
store.dispatch(resetLabellingAndContextMenuAction());
|
||||
store.dispatch(
|
||||
setToolContextMenuDataAction(viewportIndex, toolContextMenuData)
|
||||
);
|
||||
};
|
||||
|
||||
const getOnCloseCallback = viewportIndex => {
|
||||
return function onClose() {
|
||||
const toolContextMenuData = {
|
||||
visible: false,
|
||||
};
|
||||
|
||||
store.dispatch(
|
||||
setToolContextMenuDataAction(viewportIndex, toolContextMenuData)
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
return function onRightClick(event) {
|
||||
const eventData = event.detail;
|
||||
const viewportIndex = VIEWPORT_INDEX; // parseInt(eventData.element.dataset.viewportIndex, 10);
|
||||
|
||||
const toolContextMenuData = {
|
||||
eventData,
|
||||
isTouchEvent: false,
|
||||
onClose: getOnCloseCallback(viewportIndex),
|
||||
};
|
||||
|
||||
// setToolContextMenuData(viewportIndex, toolContextMenuData);
|
||||
setToolContextMenuData(0, toolContextMenuData);
|
||||
};
|
||||
}
|
||||
|
||||
function getOnTouchPressCallback(store) {
|
||||
const setToolContextMenuData = (viewportIndex, toolContextMenuData) => {
|
||||
store.dispatch(resetLabellingAndContextMenuAction());
|
||||
store.dispatch(
|
||||
setToolContextMenuDataAction(viewportIndex, toolContextMenuData)
|
||||
);
|
||||
};
|
||||
|
||||
const getOnCloseCallback = viewportIndex => {
|
||||
return function onClose() {
|
||||
const toolContextMenuData = {
|
||||
visible: false,
|
||||
};
|
||||
|
||||
store.dispatch(
|
||||
setToolContextMenuDataAction(viewportIndex, toolContextMenuData)
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
return function onTouchPress(event) {
|
||||
const eventData = event.detail;
|
||||
const viewportIndex = parseInt(eventData.element.dataset.viewportIndex, 10);
|
||||
|
||||
const toolContextMenuData = {
|
||||
eventData,
|
||||
isTouchEvent: true,
|
||||
onClose: getOnCloseCallback(viewportIndex),
|
||||
};
|
||||
|
||||
setToolContextMenuData(viewportIndex, toolContextMenuData);
|
||||
};
|
||||
}
|
||||
|
||||
function getResetLabellingAndContextMenu(store) {
|
||||
return function resetLabellingAndContextMenu() {
|
||||
store.dispatch(resetLabellingAndContextMenuAction());
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param {*} store
|
||||
* @returns
|
||||
*/
|
||||
function getToolLabellingFlowCallback(store) {
|
||||
const setLabellingFlowData = labellingFlowData => {
|
||||
store.dispatch(setLabellingFlowDataAction(labellingFlowData));
|
||||
};
|
||||
|
||||
return function toolLabellingFlowCallback(
|
||||
measurementData,
|
||||
eventData,
|
||||
doneCallback,
|
||||
options = {}
|
||||
) {
|
||||
const updateLabelling = ({ location, response, description }) => {
|
||||
// Update the measurement data with the labelling parameters
|
||||
|
||||
if (location) {
|
||||
measurementData.location = location;
|
||||
}
|
||||
if (description) {
|
||||
measurementData.description = description;
|
||||
}
|
||||
if (response) {
|
||||
measurementData.response = response;
|
||||
}
|
||||
|
||||
updateTableWithNewMeasurementData(measurementData);
|
||||
};
|
||||
|
||||
const labellingDoneCallback = () => {
|
||||
setLabellingFlowData({ visible: false });
|
||||
};
|
||||
|
||||
const labellingFlowData = {
|
||||
visible: true,
|
||||
eventData,
|
||||
measurementData,
|
||||
skipAddLabelButton: options.skipAddLabelButton,
|
||||
editLocation: options.editLocation,
|
||||
editDescription: options.editDescription,
|
||||
editResponse: options.editResponse,
|
||||
editDescriptionOnDialog: options.editDescriptionOnDialog,
|
||||
labellingDoneCallback,
|
||||
updateLabelling,
|
||||
};
|
||||
|
||||
setLabellingFlowData(labellingFlowData);
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
getToolLabellingFlowCallback,
|
||||
getOnRightClickCallback,
|
||||
getOnTouchPressCallback,
|
||||
getResetLabellingAndContextMenu,
|
||||
};
|
||||
+3
-3
@@ -1,11 +1,11 @@
|
||||
import OHIF from "@ohif/core";
|
||||
import cornerstone from "cornerstone-core";
|
||||
import OHIF from '@ohif/core';
|
||||
import cornerstone from 'cornerstone-core';
|
||||
|
||||
export default function updateTableWithNewMeasurementData({
|
||||
toolType,
|
||||
measurementNumber,
|
||||
location,
|
||||
description
|
||||
description,
|
||||
}) {
|
||||
// Update all measurements by measurement number
|
||||
const measurementApi = OHIF.measurements.MeasurementApi.Instance;
|
||||
@@ -4,7 +4,7 @@
|
||||
}
|
||||
|
||||
.entry-header.header-big {
|
||||
background: rgba(21,25,30,0.7);
|
||||
background: rgba(21, 25, 30, 0.7);
|
||||
padding: 10px var(--study-list-padding);
|
||||
height: auto;
|
||||
display: inline-block;
|
||||
@@ -32,7 +32,6 @@
|
||||
padding: 25px 0;
|
||||
}
|
||||
|
||||
|
||||
.header-logo-image {
|
||||
fill: transparent;
|
||||
height: 100%;
|
||||
@@ -47,18 +46,9 @@
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.header-logo-text {
|
||||
display: inline-block;
|
||||
font-family: var(--logo-font-family);
|
||||
font-size: 14px;
|
||||
font-weight: var(--logo-font-weight);
|
||||
color: var(--text-primary-color);
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.entry-header.header-big .header-logo-text {
|
||||
font-size: 30px;
|
||||
line-height: 36px;
|
||||
width: 474px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.header-btn {
|
||||
|
||||
@@ -2,23 +2,19 @@
|
||||
height: 30px;
|
||||
text-decoration: none;
|
||||
text-decoration-color: none;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--text-primary-color);
|
||||
font-size: 14px;
|
||||
font-weight: var(--logo-font-weight);
|
||||
font-family: var(--logo-font-family);
|
||||
}
|
||||
|
||||
.header-brand:hover,
|
||||
.header-brand:active,
|
||||
.header-band:visited {
|
||||
.header-brand:hover, .header-brand:active, .header-band:visited {
|
||||
color: var(--text-primary-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.header-logo-text {
|
||||
line-height: 30px;
|
||||
width: 224px;
|
||||
height: auto;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ function OHIFLogo() {
|
||||
href="http://ohif.org"
|
||||
>
|
||||
<Icon name="ohif-logo" className="header-logo-image" />
|
||||
<div className="header-logo-text">Open Health Imaging Foundation</div>
|
||||
<Icon name="ohif-text-logo" className="header-logo-text" />
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
+11
-7
@@ -1,7 +1,7 @@
|
||||
import { LayoutManager } from '@ohif/ui';
|
||||
import ViewportGrid from './ViewportGrid.js';
|
||||
import { MODULE_TYPES } from '@ohif/core';
|
||||
import { connect } from 'react-redux';
|
||||
import { extensionManager } from './../App.js';
|
||||
import { extensionManager } from './../../App.js';
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const availableViewportModules = {};
|
||||
@@ -18,9 +18,13 @@ const mapStateToProps = state => {
|
||||
defaultPlugin = viewportModules[0].extensionId;
|
||||
}
|
||||
|
||||
const { numRows, numColumns, layout, activeViewportIndex } = state.viewports;
|
||||
|
||||
return {
|
||||
layout: state.viewports.layout,
|
||||
activeViewportIndex: state.viewports.activeViewportIndex,
|
||||
numRows,
|
||||
numColumns,
|
||||
layout,
|
||||
activeViewportIndex,
|
||||
// TODO: rename `availableViewportModules`
|
||||
availablePlugins: availableViewportModules,
|
||||
// TODO: rename `defaultViewportModule`
|
||||
@@ -28,9 +32,9 @@ const mapStateToProps = state => {
|
||||
};
|
||||
};
|
||||
|
||||
const ConnectedLayoutManager = connect(
|
||||
const ConnectedViewportGrid = connect(
|
||||
mapStateToProps,
|
||||
null
|
||||
)(LayoutManager);
|
||||
)(ViewportGrid);
|
||||
|
||||
export default ConnectedLayoutManager;
|
||||
export default ConnectedViewportGrid;
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @export
|
||||
* @param {*} props
|
||||
* @returns
|
||||
*/
|
||||
export default function DefaultViewport(props) {
|
||||
return <div>{JSON.stringify(props)}</div>;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
.empty-viewport {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
color: var(--text-secondary-color);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
function EmptyViewport() {
|
||||
return (
|
||||
<div className="empty-viewport">
|
||||
<p>Please drag a stack here to view images.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
-13
@@ -7,16 +7,3 @@
|
||||
.viewport-container.active {
|
||||
border: var(--viewport-border-thickness) solid var(--active-color);
|
||||
}
|
||||
|
||||
.EmptyViewport {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
color: var(--text-secondary-color);
|
||||
}
|
||||
|
||||
.LayoutPanel {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
import './ViewportGrid.css';
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
//
|
||||
import ViewportPane from './ViewportPane.js';
|
||||
import DefaultViewport from './DefaultViewport.js';
|
||||
import EmptyViewport from './EmptyViewport.js';
|
||||
|
||||
const ViewportGrid = function(props) {
|
||||
const {
|
||||
activeViewportIndex,
|
||||
availablePlugins,
|
||||
defaultPlugin: defaultPluginName,
|
||||
layout,
|
||||
numRows,
|
||||
numColumns,
|
||||
setViewportData,
|
||||
studies,
|
||||
viewportData,
|
||||
children,
|
||||
} = props;
|
||||
|
||||
const rowSize = 100 / numRows;
|
||||
const colSize = 100 / numColumns;
|
||||
|
||||
// http://grid.malven.co/
|
||||
if (!viewportData || !viewportData.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const ViewportPanes = layout.viewports.map((layout, viewportIndex) => {
|
||||
const displaySet = viewportData[viewportIndex];
|
||||
const data = {
|
||||
displaySet,
|
||||
studies,
|
||||
};
|
||||
|
||||
// Use whichever plugin is currently in use in the panel
|
||||
// unless nothing is specified. If nothing is specified
|
||||
// and the display set has a plugin specified, use that.
|
||||
//
|
||||
// TODO: Change this logic to:
|
||||
// - Plugins define how capable they are of displaying a SopClass
|
||||
// - When updating a panel, ensure that the currently enabled plugin
|
||||
// in the viewport is capable of rendering this display set. If not
|
||||
// then use the most capable available plugin
|
||||
const pluginName =
|
||||
!layout.plugin && displaySet && displaySet.plugin
|
||||
? displaySet.plugin
|
||||
: layout.plugin;
|
||||
|
||||
const ViewportComponent = _getViewportComponent(
|
||||
data, // Why do we pass this as `ViewportData`, when that's not really what it is?
|
||||
viewportIndex,
|
||||
children,
|
||||
availablePlugins,
|
||||
pluginName,
|
||||
defaultPluginName
|
||||
);
|
||||
|
||||
return (
|
||||
<ViewportPane
|
||||
onDrop={({
|
||||
viewportIndex,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
}) => {
|
||||
setViewportData({
|
||||
viewportIndex,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
});
|
||||
}}
|
||||
viewportIndex={viewportIndex} // Needed by `setViewportData`
|
||||
className={classNames('viewport-container', {
|
||||
active: activeViewportIndex === viewportIndex,
|
||||
})}
|
||||
key={viewportIndex}
|
||||
>
|
||||
{ViewportComponent}
|
||||
</ViewportPane>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateRows: `repeat(${numRows}, ${rowSize}%)`,
|
||||
gridTemplateColumns: `repeat(${numColumns}, ${colSize}%)`,
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
{ViewportPanes}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ViewportGrid.propTypes = {
|
||||
viewportData: PropTypes.array.isRequired,
|
||||
supportsDragAndDrop: PropTypes.bool.isRequired,
|
||||
activeViewportIndex: PropTypes.number.isRequired,
|
||||
layout: PropTypes.object.isRequired,
|
||||
availablePlugins: PropTypes.object.isRequired,
|
||||
setViewportData: PropTypes.func.isRequired,
|
||||
studies: PropTypes.array,
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
ViewportGrid.defaultProps = {
|
||||
viewportData: [],
|
||||
numRows: 1,
|
||||
numColumns: 1,
|
||||
layout: {
|
||||
viewports: [{}],
|
||||
},
|
||||
activeViewportIndex: 0,
|
||||
supportsDragAndDrop: true,
|
||||
availablePlugins: {
|
||||
DefaultViewport,
|
||||
},
|
||||
defaultPlugin: 'defaultViewportPlugin',
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param {*} plugin
|
||||
* @param {*} viewportData
|
||||
* @param {*} viewportIndex
|
||||
* @param {*} children
|
||||
* @returns
|
||||
*/
|
||||
function _getViewportComponent(
|
||||
viewportData,
|
||||
viewportIndex,
|
||||
children,
|
||||
availablePlugins,
|
||||
pluginName,
|
||||
defaultPluginName
|
||||
) {
|
||||
if (viewportData.displaySet) {
|
||||
pluginName = pluginName || defaultPluginName;
|
||||
const ViewportComponent = availablePlugins[pluginName];
|
||||
|
||||
if (!ViewportComponent) {
|
||||
throw new Error(
|
||||
`No Viewport Component available for name ${pluginName}.
|
||||
Available plugins: ${JSON.stringify(availablePlugins)}`
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ViewportComponent
|
||||
viewportData={viewportData}
|
||||
viewportIndex={viewportIndex}
|
||||
children={[children]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <EmptyViewport />;
|
||||
}
|
||||
|
||||
export default ViewportGrid;
|
||||
@@ -0,0 +1,10 @@
|
||||
.viewport-drop-target {
|
||||
opacity: 1;
|
||||
position: relative; /* Locks in Scrollbar */
|
||||
transition: 0.3s all ease;
|
||||
}
|
||||
|
||||
.viewport-drop-target.hovered {
|
||||
opacity: 0.5;
|
||||
cursor: copy;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import React from 'react';
|
||||
import { useDrop } from 'react-dnd';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import './ViewportPane.css';
|
||||
|
||||
const ViewportPane = function(props) {
|
||||
const { children, onDrop, viewportIndex, className: propClassName } = props;
|
||||
const [{ hovered, highlighted }, drop] = useDrop({
|
||||
accept: 'thumbnail',
|
||||
drop: (droppedItem, monitor) => {
|
||||
const canDrop = monitor.canDrop();
|
||||
const isOver = monitor.isOver();
|
||||
|
||||
if (canDrop && isOver && onDrop) {
|
||||
const { studyInstanceUid, displaySetInstanceUid } = droppedItem;
|
||||
|
||||
onDrop({ viewportIndex, studyInstanceUid, displaySetInstanceUid });
|
||||
}
|
||||
},
|
||||
// Monitor, and collect props.
|
||||
// Returned as values by `useDrop`
|
||||
collect: monitor => ({
|
||||
highlighted: monitor.canDrop(),
|
||||
hovered: monitor.isOver(),
|
||||
}),
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'viewport-drop-target',
|
||||
{ hovered: hovered },
|
||||
{ highlighted: highlighted },
|
||||
propClassName
|
||||
)}
|
||||
ref={drop}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ViewportPane.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
viewportIndex: PropTypes.number.isRequired,
|
||||
onDrop: PropTypes.func.isRequired,
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
export default ViewportPane;
|
||||
@@ -0,0 +1,5 @@
|
||||
import ConnectedViewportGrid from './ConnectedViewportGrid.js';
|
||||
import ViewportGrid from './ViewportGrid.js';
|
||||
|
||||
export default ViewportGrid;
|
||||
export { ConnectedViewportGrid, ViewportGrid };
|
||||
@@ -2,22 +2,24 @@ import { LayoutButton } from '@ohif/ui';
|
||||
import OHIF from '@ohif/core';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
const { setLayout } = OHIF.redux.actions;
|
||||
const { setLayout, setViewportActive } = OHIF.redux.actions;
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
currentLayout: state.viewports.layout,
|
||||
activeViewportIndex: state.viewports.activeViewportIndex,
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
// TODO: Change if layout switched becomes more complex
|
||||
onChange: (selectedCell, currentLayout) => {
|
||||
let viewports = [];
|
||||
const rows = selectedCell.row + 1;
|
||||
const columns = selectedCell.col + 1;
|
||||
const numViewports = rows * columns;
|
||||
onChange: (selectedCell, currentLayout, activeViewportIndex) => {
|
||||
const viewports = [];
|
||||
const numRows = selectedCell.row + 1;
|
||||
const numColumns = selectedCell.col + 1;
|
||||
const numViewports = numRows * numColumns;
|
||||
|
||||
for (let i = 0; i < numViewports; i++) {
|
||||
// Hacky way to allow users to exit MPR "mode"
|
||||
const viewport = currentLayout.viewports[i];
|
||||
@@ -27,15 +29,20 @@ const mapDispatchToProps = dispatch => {
|
||||
}
|
||||
|
||||
viewports.push({
|
||||
height: `${100 / rows}%`,
|
||||
width: `${100 / columns}%`,
|
||||
plugin,
|
||||
});
|
||||
}
|
||||
const layout = {
|
||||
numRows,
|
||||
numColumns,
|
||||
viewports,
|
||||
};
|
||||
|
||||
const maxActiveIndex = numViewports - 1;
|
||||
if (activeViewportIndex > maxActiveIndex) {
|
||||
dispatch(setViewportActive(0));
|
||||
}
|
||||
|
||||
dispatch(setLayout(layout));
|
||||
},
|
||||
};
|
||||
@@ -43,12 +50,13 @@ const mapDispatchToProps = dispatch => {
|
||||
|
||||
const mergeProps = (propsFromState, propsFromDispatch) => {
|
||||
const onChangeFromDispatch = propsFromDispatch.onChange;
|
||||
const { currentLayout } = propsFromState;
|
||||
const { currentLayout, activeViewportIndex } = propsFromState;
|
||||
|
||||
return {
|
||||
onChange: (selectedCell) => onChangeFromDispatch(selectedCell, currentLayout)
|
||||
onChange: selectedCell =>
|
||||
onChangeFromDispatch(selectedCell, currentLayout, activeViewportIndex),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const ConnectedLayoutButton = connect(
|
||||
mapStateToProps,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import OHIF from "@ohif/core";
|
||||
import PluginSwitch from "./PluginSwitch.js";
|
||||
import { commandsManager } from "./../App.js";
|
||||
import { connect } from "react-redux";
|
||||
// import OHIF from '@ohif/core';
|
||||
import PluginSwitch from './PluginSwitch.js';
|
||||
import { commandsManager } from './../App.js';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
const { setLayout } = OHIF.redux.actions;
|
||||
// const { setLayout } = OHIF.redux.actions;
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const { activeViewportIndex, layout, viewportSpecificData } = state.viewports;
|
||||
@@ -11,17 +11,17 @@ const mapStateToProps = state => {
|
||||
return {
|
||||
activeViewportIndex,
|
||||
viewportSpecificData,
|
||||
layout
|
||||
layout,
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
setLayout: data => {
|
||||
dispatch(setLayout(data));
|
||||
}
|
||||
};
|
||||
};
|
||||
// const mapDispatchToProps = dispatch => {
|
||||
// return {
|
||||
// setLayout: data => {
|
||||
// dispatch(setLayout(data));
|
||||
// }
|
||||
// };
|
||||
// };
|
||||
|
||||
/*function setSingleLayoutData(originalArray, viewportIndex, data) {
|
||||
const viewports = originalArray.slice();
|
||||
@@ -39,19 +39,17 @@ const mergeProps = (propsFromState, propsFromDispatch, ownProps) => {
|
||||
// TODO: Do not display certain options if the current display set
|
||||
// cannot be displayed using these view types
|
||||
const mpr = () => {
|
||||
commandsManager.runCommand("mpr2d");
|
||||
}
|
||||
;
|
||||
|
||||
commandsManager.runCommand('mpr2d');
|
||||
};
|
||||
return {
|
||||
mpr
|
||||
mpr,
|
||||
};
|
||||
};
|
||||
|
||||
const ConnectedPluginSwitch = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
null, // mapDispatchToProps
|
||||
mergeProps
|
||||
)(PluginSwitch);
|
||||
|
||||
export default ConnectedPluginSwitch;
|
||||
export default ConnectedPluginSwitch;
|
||||
@@ -10,19 +10,9 @@ const mapStateToProps = (state, ownProps) => {
|
||||
|
||||
const { viewportIndex } = ownProps;
|
||||
const toolContextMenuData = state.ui.contextMenu[viewportIndex];
|
||||
let availableTools;
|
||||
|
||||
if (
|
||||
state.extensions &&
|
||||
state.extensions.cornerstone &&
|
||||
state.extensions.cornerstone.availableTools
|
||||
) {
|
||||
availableTools = state.extensions.cornerstone.availableTools;
|
||||
}
|
||||
|
||||
return {
|
||||
...toolContextMenuData,
|
||||
availableTools,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -13,9 +13,7 @@ class PluginSwitch extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="PluginSwitch">
|
||||
<ToolbarButton label = "2D MPR"
|
||||
icon = "cube"
|
||||
onClick = {this.props.mpr} />
|
||||
<ToolbarButton label="2D MPR" icon="cube" onClick={this.props.mpr} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@ import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cornerstone from 'cornerstone-core';
|
||||
import cornerstoneTools from 'cornerstone-tools';
|
||||
import getMeasurementLocationCallback from '../lib/getMeasurementLocationCallback';
|
||||
// This whole component should live in the Measurements Extension :thinking:
|
||||
import getMeasurementLocationCallback from '../appExtensions/MeasurementsPanel/getMeasurementLocationCallback';
|
||||
|
||||
import './ToolContextMenu.css';
|
||||
|
||||
@@ -97,12 +98,11 @@ function getNearbyToolData(element, coords, toolTypes) {
|
||||
return pointNearTool ? nearbyTool : undefined;
|
||||
}
|
||||
|
||||
function getDropdownItems(eventData, isTouchEvent = false, availableTools) {
|
||||
function getDropdownItems(eventData, isTouchEvent = false) {
|
||||
const nearbyToolData = getNearbyToolData(
|
||||
eventData.element,
|
||||
eventData.currentPoints.canvas,
|
||||
toolTypes,
|
||||
availableTools
|
||||
toolTypes
|
||||
);
|
||||
|
||||
// Annotate tools for touch events already have a press handle to edit it, has a better UX for deleting it
|
||||
@@ -148,7 +148,6 @@ class ToolContextMenu extends Component {
|
||||
isTouchEvent: PropTypes.bool.isRequired,
|
||||
eventData: PropTypes.object,
|
||||
onClose: PropTypes.func,
|
||||
availableTools: PropTypes.array,
|
||||
visible: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
@@ -168,12 +167,8 @@ class ToolContextMenu extends Component {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { isTouchEvent, eventData, availableTools } = this.props;
|
||||
const dropdownItems = getDropdownItems(
|
||||
eventData,
|
||||
isTouchEvent,
|
||||
availableTools
|
||||
);
|
||||
const { isTouchEvent, eventData } = this.props;
|
||||
const dropdownItems = getDropdownItems(eventData, isTouchEvent);
|
||||
|
||||
// Skip if there is no dropdown item
|
||||
if (!dropdownItems.length) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import './ViewerMain.css';
|
||||
|
||||
import { Component } from 'react';
|
||||
import ConnectedLayoutManager from './ConnectedLayoutManager.js';
|
||||
import { ConnectedViewportGrid } from './../components/ViewportGrid/index.js';
|
||||
import ConnectedToolContextMenu from './ConnectedToolContextMenu.js';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
@@ -124,11 +124,15 @@ class ViewerMain extends Component {
|
||||
return viewportData;
|
||||
};
|
||||
|
||||
setViewportData = ({ viewportIndex, item }) => {
|
||||
setViewportData = ({
|
||||
viewportIndex,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
}) => {
|
||||
const displaySet = this.findDisplaySet(
|
||||
this.props.studies,
|
||||
item.studyInstanceUid,
|
||||
item.displaySetInstanceUid
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid
|
||||
);
|
||||
|
||||
this.props.setViewportSpecificData(viewportIndex, displaySet);
|
||||
@@ -138,14 +142,14 @@ class ViewerMain extends Component {
|
||||
return (
|
||||
<div className="ViewerMain">
|
||||
{this.state.displaySets.length && (
|
||||
<ConnectedLayoutManager
|
||||
<ConnectedViewportGrid
|
||||
studies={this.props.studies}
|
||||
viewportData={this.getViewportData()}
|
||||
setViewportData={this.setViewportData}
|
||||
>
|
||||
{/* Children to add to each viewport that support children */}
|
||||
<ConnectedToolContextMenu />
|
||||
</ConnectedLayoutManager>
|
||||
</ConnectedViewportGrid>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import React, { useContext } from 'react';
|
||||
import GoogleCloudApi from '../googleCloud/api/GoogleCloudApi';
|
||||
|
||||
import * as GoogleCloudUtilServers from '../googleCloud/utils/getServers';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
|
||||
// Contexts
|
||||
import AppContext from '../context/AppContext';
|
||||
|
||||
const getActiveServer = servers => {
|
||||
const isActive = a => a.active === true;
|
||||
|
||||
return servers && servers.servers && servers.servers.find(isActive);
|
||||
};
|
||||
|
||||
const getServers = (appConfig, project, location, dataset, dicomStore) => {
|
||||
let servers = [];
|
||||
if (appConfig.enableGoogleCloudAdapter) {
|
||||
const pathUrl = GoogleCloudApi.getUrlBaseDicomWeb(
|
||||
project,
|
||||
location,
|
||||
dataset,
|
||||
dicomStore
|
||||
);
|
||||
const data = {
|
||||
project,
|
||||
location,
|
||||
dataset,
|
||||
dicomStore,
|
||||
wadoUriRoot: pathUrl,
|
||||
qidoRoot: pathUrl,
|
||||
wadoRoot: pathUrl,
|
||||
};
|
||||
servers = GoogleCloudUtilServers.getServers(data, dicomStore);
|
||||
}
|
||||
|
||||
return servers;
|
||||
};
|
||||
|
||||
const updateServer = (
|
||||
appConfig,
|
||||
dispatch,
|
||||
project,
|
||||
location,
|
||||
dataset,
|
||||
dicomStore
|
||||
) => {
|
||||
const servers = getServers(appConfig, project, location, dataset, dicomStore);
|
||||
|
||||
if (servers && servers.length) {
|
||||
const action = {
|
||||
type: 'SET_SERVERS',
|
||||
servers,
|
||||
};
|
||||
dispatch(action);
|
||||
}
|
||||
};
|
||||
|
||||
export default function useServer({
|
||||
project,
|
||||
location,
|
||||
dataset,
|
||||
dicomStore,
|
||||
} = {}) {
|
||||
// Hooks
|
||||
const servers = useSelector(state => state && state.servers);
|
||||
const dispatch = useDispatch();
|
||||
const { appConfig = {} } = useContext(AppContext);
|
||||
|
||||
const server = getActiveServer(servers);
|
||||
|
||||
if (!server) {
|
||||
updateServer(appConfig, dispatch, project, location, dataset, dicomStore);
|
||||
} else {
|
||||
return server;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import Modal from 'react-bootstrap-modal';
|
||||
import DatasetSelector from './DatasetSelector';
|
||||
import './googleCloud.css';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import * as GoogleCloudUtilServers from './utils/getServers';
|
||||
|
||||
class DicomStorePickerModal extends Component {
|
||||
static propTypes = {
|
||||
@@ -19,21 +20,7 @@ class DicomStorePickerModal extends Component {
|
||||
};
|
||||
|
||||
handleEvent = data => {
|
||||
const servers = [
|
||||
{
|
||||
name: data.dicomStore,
|
||||
imageRendering: 'wadors',
|
||||
thumbnailRendering: 'wadors',
|
||||
qidoSupportsIncludeField: false,
|
||||
type: 'dicomWeb',
|
||||
qidoRoot: data.qidoRoot,
|
||||
wadoRoot: data.wadoRoot,
|
||||
wadoUriRoot: data.wadoUriRoot,
|
||||
active: true,
|
||||
supportsFuzzyMatching: false,
|
||||
},
|
||||
];
|
||||
|
||||
const servers = GoogleCloudUtilServers.getServers(data, data.dicomstore);
|
||||
this.props.setServers(servers);
|
||||
};
|
||||
|
||||
|
||||
@@ -22,6 +22,13 @@ class GoogleCloudApi {
|
||||
return this.urlBase + `/projects`;
|
||||
}
|
||||
|
||||
getUrlBaseDicomWeb(project, location, dataset, dicomStore) {
|
||||
return (
|
||||
this.urlBase +
|
||||
`/projects/${project}/locations/${location}/datasets/${dataset}/dicomStores/${dicomStore}/dicomWeb`
|
||||
);
|
||||
}
|
||||
|
||||
async doRequest(urlStr, config = {}, params = {}) {
|
||||
const url = new URL(urlStr);
|
||||
let data = null;
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
const getServers = (data, name) => {
|
||||
const {
|
||||
wadoUriRoot,
|
||||
qidoRoot,
|
||||
wadoRoot,
|
||||
dataset = '',
|
||||
dicomStore = '',
|
||||
location = '',
|
||||
project = '',
|
||||
} = data;
|
||||
|
||||
return [
|
||||
{
|
||||
name: name,
|
||||
dataset,
|
||||
dicomStore,
|
||||
location,
|
||||
project,
|
||||
imageRendering: 'wadors',
|
||||
thumbnailRendering: 'wadors',
|
||||
type: 'dicomWeb',
|
||||
active: true,
|
||||
wadoUriRoot,
|
||||
qidoRoot,
|
||||
wadoRoot,
|
||||
supportsFuzzyMatching: false,
|
||||
qidoSupportsIncludeField: false,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export { getServers };
|
||||
@@ -1,29 +1,16 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import queryString from 'query-string';
|
||||
import ConnectedViewerRetrieveStudyData from '../connectedComponents/ConnectedViewerRetrieveStudyData.js';
|
||||
|
||||
function decodeStudyUids(studyUids) {
|
||||
const decodedData = window.atob(studyUids);
|
||||
|
||||
return decodedData.split(';');
|
||||
}
|
||||
|
||||
function getQueryParameters(location) {
|
||||
if (location) {
|
||||
return queryString.parse(location.search);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
import OHIF from '@ohif/core';
|
||||
const { urlUtil: UrlUtil } = OHIF.utils;
|
||||
|
||||
function IHEInvokeImageDisplay({ location }) {
|
||||
const {
|
||||
// patientID,
|
||||
requestType,
|
||||
studyUID,
|
||||
} = getQueryParameters(location);
|
||||
} = UrlUtil.parse(location.search);
|
||||
|
||||
switch (requestType) {
|
||||
case 'STUDY':
|
||||
@@ -36,7 +23,7 @@ function IHEInvokeImageDisplay({ location }) {
|
||||
case 'STUDYBASE64':
|
||||
return (
|
||||
<ConnectedViewerRetrieveStudyData
|
||||
studyInstanceUids={decodeStudyUids(studyUID)}
|
||||
studyInstanceUids={UrlUtil.paramString.parseParam(studyUID)}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@@ -1,26 +1,47 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ConnectedViewerRetrieveStudyData from '../connectedComponents/ConnectedViewerRetrieveStudyData';
|
||||
import useServer from '../customHooks/useServer';
|
||||
import OHIF from '@ohif/core';
|
||||
const { urlUtil: UrlUtil } = OHIF.utils;
|
||||
|
||||
function ViewerRouting({ match }) {
|
||||
const { studyInstanceUids, seriesInstanceUids } = match.params;
|
||||
/**
|
||||
* Get array of seriesUIDs from param or from queryString
|
||||
* @param {*} seriesInstanceUIDs
|
||||
* @param {*} location
|
||||
*/
|
||||
const getSeriesInstanceUIDs = (seriesInstanceUIDs, routeLocation) => {
|
||||
const queryFilters = UrlUtil.queryString.getQueryFilters(routeLocation);
|
||||
const querySeriesUIDs = queryFilters && queryFilters['SeriesInstanceUID'];
|
||||
const _seriesInstanceUIDs = seriesInstanceUIDs || querySeriesUIDs;
|
||||
|
||||
let studyUIDs;
|
||||
let seriesUIDs;
|
||||
return UrlUtil.paramString.parseParam(_seriesInstanceUIDs);
|
||||
};
|
||||
|
||||
if (studyInstanceUids && !seriesInstanceUids) {
|
||||
studyUIDs = studyInstanceUids.split(';');
|
||||
} else if (studyInstanceUids && seriesInstanceUids) {
|
||||
studyUIDs = [studyInstanceUids];
|
||||
seriesUIDs = match.params.seriesInstanceUids.split(';');
|
||||
function ViewerRouting({ match: routeMatch, location: routeLocation }) {
|
||||
const {
|
||||
project,
|
||||
location,
|
||||
dataset,
|
||||
dicomStore,
|
||||
studyInstanceUids,
|
||||
seriesInstanceUids,
|
||||
} = routeMatch.params;
|
||||
const server = useServer({ project, location, dataset, dicomStore });
|
||||
|
||||
const studyUIDs = UrlUtil.paramString.parseParam(studyInstanceUids);
|
||||
const seriesUIDs = getSeriesInstanceUIDs(seriesInstanceUids, routeLocation);
|
||||
|
||||
if (server && studyUIDs) {
|
||||
return (
|
||||
<ConnectedViewerRetrieveStudyData
|
||||
studyInstanceUids={studyUIDs}
|
||||
seriesInstanceUids={seriesUIDs}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ConnectedViewerRetrieveStudyData
|
||||
studyInstanceUids={studyUIDs}
|
||||
seriesInstanceUids={seriesUIDs}
|
||||
/>
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
ViewerRouting.propTypes = {
|
||||
@@ -28,6 +49,10 @@ ViewerRouting.propTypes = {
|
||||
params: PropTypes.shape({
|
||||
studyInstanceUids: PropTypes.string.isRequired,
|
||||
seriesInstanceUids: PropTypes.string,
|
||||
dataset: PropTypes.string,
|
||||
dicomStore: PropTypes.string,
|
||||
location: PropTypes.string,
|
||||
project: PropTypes.string,
|
||||
}),
|
||||
}),
|
||||
};
|
||||
|
||||
Loaded 100 of 108 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user