Compare commits
23
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25599a2e2a | ||
|
|
1cf7015511 | ||
|
|
756f70cc54 | ||
|
|
ded2e61913 | ||
|
|
b58aa4575a | ||
|
|
f78abe2afa | ||
|
|
f48157117f | ||
|
|
6f8bd0dff8 | ||
|
|
943af89df2 | ||
|
|
cd2da349e5 | ||
|
|
14e86dcddd | ||
|
|
2c8b93656d | ||
|
|
6932468206 | ||
|
|
7bc9efb0f5 | ||
|
|
5d47cc43c1 | ||
|
|
e46a11a01e | ||
|
|
27a950d956 | ||
|
|
03d4c10adc | ||
|
|
7a0c369006 | ||
|
|
8c946a4742 | ||
|
|
30b9e44220 | ||
|
|
380abe2705 | ||
|
|
f4570c96c2 |
No files matched your search
+14
-27
@@ -13,14 +13,7 @@ version: 2.1
|
||||
##
|
||||
orbs:
|
||||
codecov: codecov/codecov@1.0.5
|
||||
cypress: cypress-io/cypress@1.11.0
|
||||
executors:
|
||||
# Custom executor to override Cypress config
|
||||
deploy-to-prod-executor:
|
||||
docker:
|
||||
- image: 'cypress/browsers:node10.16.0-chrome77'
|
||||
environment:
|
||||
CYPRESS_BASE_URL: https://ohif-staging.netlify.com/
|
||||
cypress: cypress-io/cypress@1.13.0
|
||||
|
||||
defaults: &defaults
|
||||
docker:
|
||||
@@ -341,6 +334,8 @@ workflows:
|
||||
post-steps:
|
||||
- store_artifacts:
|
||||
path: platform/viewer/cypress/screenshots
|
||||
- store_artifacts:
|
||||
path: platform/viewer/cypress/videos
|
||||
requires:
|
||||
- UNIT_TESTS
|
||||
# E2E: script-tag
|
||||
@@ -364,6 +359,8 @@ workflows:
|
||||
post-steps:
|
||||
- store_artifacts:
|
||||
path: platform/viewer/cypress/screenshots
|
||||
- store_artifacts:
|
||||
path: platform/viewer/cypress/videos
|
||||
requires:
|
||||
- UNIT_TESTS
|
||||
|
||||
@@ -375,6 +372,7 @@ workflows:
|
||||
- cypress/run:
|
||||
name: 'Generate Percy Snapshots'
|
||||
executor: cypress/browsers-chrome76
|
||||
browser: chrome
|
||||
pre-steps:
|
||||
- run: 'rm -rf ~/.yarn && npm i -g yarn && yarn -v && yarn global
|
||||
add wait-on' # Use yarn latest
|
||||
@@ -389,6 +387,8 @@ workflows:
|
||||
post-steps:
|
||||
- store_artifacts:
|
||||
path: platform/viewer/cypress/screenshots
|
||||
- store_artifacts:
|
||||
path: platform/viewer/cypress/videos
|
||||
requires:
|
||||
- AWAIT_APPROVAL
|
||||
|
||||
@@ -445,9 +445,11 @@ workflows:
|
||||
branches:
|
||||
only: master
|
||||
# Update base branch snapshots
|
||||
# and record a Cypress dashboard test run
|
||||
- cypress/run:
|
||||
name: 'Generate Percy Snapshots'
|
||||
executor: cypress/browsers-chrome76
|
||||
browser: chrome
|
||||
pre-steps:
|
||||
- run: 'rm -rf ~/.yarn && npm i -g yarn && yarn -v && yarn global
|
||||
add wait-on' # Use yarn latest
|
||||
@@ -462,28 +464,13 @@ workflows:
|
||||
post-steps:
|
||||
- store_artifacts:
|
||||
path: platform/viewer/cypress/screenshots
|
||||
- store_artifacts:
|
||||
path: platform/viewer/cypress/videos
|
||||
- store_test_results:
|
||||
path: platform/viewer/cypress/results
|
||||
filters:
|
||||
branches:
|
||||
only: master
|
||||
- DOCKER_MASTER_PUBLISH:
|
||||
requires:
|
||||
- NPM_PUBLISH
|
||||
#
|
||||
#
|
||||
# Potentially use to E2E test STAGING environment
|
||||
# before publishing to production
|
||||
# - cypress/run:
|
||||
# name: 'E2E: PWA'
|
||||
# executor: deploy-to-prod-executor
|
||||
# browser: chrome
|
||||
# working_directory: platform/viewer
|
||||
# cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
|
||||
# yarn: true
|
||||
# record: false
|
||||
# no-workspace: true
|
||||
# store_artifacts: true
|
||||
# post-steps:
|
||||
# - store_artifacts:
|
||||
# path: platform/viewer/cypress/screenshots
|
||||
# - store_test_results:
|
||||
# path: cypress/results
|
||||
@@ -72,12 +72,17 @@ module.exports = (env, argv, { SRC_DIR, DIST_DIR }) => {
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
/* Application */
|
||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
|
||||
'process.env.DEBUG': JSON.stringify(process.env.DEBUG),
|
||||
'process.env.APP_CONFIG': JSON.stringify(process.env.APP_CONFIG || ''),
|
||||
'process.env.PUBLIC_URL': JSON.stringify(process.env.PUBLIC_URL || '/'),
|
||||
'process.env.VERSION_NUMBER': JSON.stringify(PACKAGE.version || ''),
|
||||
'process.env.BUILD_NUM': JSON.stringify(BUILD_NUM),
|
||||
/* i18n */
|
||||
'process.env.USE_LOCIZE': JSON.stringify(process.env.USE_LOCIZE || ''),
|
||||
'process.env.LOCIZE_PROJECTID': JSON.stringify(process.env.LOCIZE_PROJECTID || ''),
|
||||
'process.env.LOCIZE_API_KEY': JSON.stringify(process.env.LOCIZE_API_KEY || ''),
|
||||
}),
|
||||
],
|
||||
// Fix: https://github.com/webpack-contrib/css-loader/issues/447#issuecomment-285598881
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# OHIF Viewers
|
||||
|
||||
- [Our Process](our-process.md)
|
||||
- [Quick Start](quick-start.md)
|
||||
- Development
|
||||
- [Getting Started](development/getting-started.md)
|
||||
- [Contributing](development/contributing.md)
|
||||
@@ -31,7 +30,6 @@
|
||||
- [OHIF Maintained](extensions/index.md#maintained-extensions)
|
||||
- [Services](services/index.md)
|
||||
- [Default](services/default/index.md)
|
||||
- [Measurements](services/default/measurements.md)
|
||||
- [UI](services/ui/index.md)
|
||||
- [Dialog Service](services/ui/ui-dialog-service.md)
|
||||
- [Modal Service](services/ui/ui-modal-service.md)
|
||||
|
||||
@@ -13,7 +13,7 @@ like user preferences and previous query paramaters. Because of this, the Viewer
|
||||
has been built to be highly configurable to work with almost any web accessible
|
||||
data source.
|
||||
|
||||

|
||||

|
||||
|
||||
To be more specific, the OHIF Viewer is a collection of HTML, JS, and CSS files.
|
||||
These can be delivered to your end users however you would like:
|
||||
|
||||
@@ -1,3 +1,56 @@
|
||||
# Viewer: Configuration
|
||||
|
||||
...
|
||||
We maintain a number of common viewer application configurations at [`<root>/platform/viewer/public/configs`][config-dir]. How these values are passed to the viewer depend on how it's deployed, but the two most common paths are:
|
||||
|
||||
- `index.html` looks for `https://your-website.com/app-config.js` OR
|
||||
- `index.html` passes the values to `OHIF.installViewer()`
|
||||
|
||||
```js
|
||||
window.config = {
|
||||
routerBasename: '/',
|
||||
whiteLabelling: {},
|
||||
extensions: [],
|
||||
showStudyList: true,
|
||||
filterQueryParam: false,
|
||||
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',
|
||||
enableStudyLazyLoad: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
// Supported Keys: https://craig.is/killing/mice
|
||||
hotkeys: [
|
||||
{ commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] },
|
||||
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
|
||||
{ commandName: 'invertViewport', label: 'Invert', keys: ['i'] },
|
||||
{
|
||||
commandName: 'flipViewportVertical',
|
||||
label: 'Flip Horizontally',
|
||||
keys: ['h'],
|
||||
},
|
||||
{
|
||||
commandName: 'flipViewportHorizontal',
|
||||
label: 'Flip Vertically',
|
||||
keys: ['v'],
|
||||
},
|
||||
],
|
||||
// Config to pass to the bundled cornerstone extension
|
||||
cornerstoneExtensionConfig: {},
|
||||
};
|
||||
```
|
||||
|
||||
<!--
|
||||
LINKS
|
||||
-->
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
[config-dir]: https://github.com/OHIF/Viewers/tree/master/platform/viewer/public/config
|
||||
<!-- prettier-ignore-end -->
|
||||
@@ -0,0 +1,23 @@
|
||||
# Environment Variables
|
||||
|
||||
There are a number of environment variables we use at build time to influence the output application's behavior.
|
||||
|
||||
```bash
|
||||
# Application
|
||||
NODE_ENV=< production | development >
|
||||
DEBUG=< true | false >
|
||||
APP_CONFIG=< relative path to application configuration file >
|
||||
PUBLIC_URL=<>
|
||||
VERSION_NUMBER=<Set by CircleCI>
|
||||
BUILD_NUM=<Set by CircleCI>
|
||||
# i18n
|
||||
USE_LOCIZE=<false>
|
||||
LOCIZE_PROJECTID=<ProjectID to pull translations for>
|
||||
LOCIZE_API_KEY=<To enable Locize live editing of translations>
|
||||
```
|
||||
|
||||
## Setting Environment Variables
|
||||
|
||||
- `npx cross-env`
|
||||
- `.env` files
|
||||
- env variables on build machine, or for terminal session
|
||||
@@ -3,6 +3,38 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.1.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.1.3...@ohif/extension-vtk@1.1.4) (2019-12-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.1.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.1.2...@ohif/extension-vtk@1.1.3) (2019-12-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.1.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.1.1...@ohif/extension-vtk@1.1.2) (2019-12-12)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.1.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.1.0...@ohif/extension-vtk@1.1.1) (2019-12-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.1.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.0.2...@ohif/extension-vtk@1.1.0) (2019-12-11)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-vtk",
|
||||
"version": "1.1.0",
|
||||
"version": "1.1.4",
|
||||
"description": "OHIF extension for VTK.js",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -52,8 +52,8 @@
|
||||
"react-vtkjs-viewport": "^0.3.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ohif/core": "^2.1.0",
|
||||
"@ohif/ui": "^1.1.0",
|
||||
"@ohif/core": "^2.1.1",
|
||||
"@ohif/ui": "^1.1.4",
|
||||
"cornerstone-tools": "^4.8.0",
|
||||
"cornerstone-wado-image-loader": "^3.0.0",
|
||||
"dicom-parser": "^1.8.3",
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.1.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.1.0...@ohif/core@2.1.1) (2019-12-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.1.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.0.2...@ohif/core@2.1.0) (2019-12-11)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/core",
|
||||
"version": "2.1.0",
|
||||
"version": "2.1.1",
|
||||
"description": "Generic business logic for web-based medical imaging applications",
|
||||
"author": "OHIF Core Team",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -23,8 +23,6 @@ import {
|
||||
UINotificationService,
|
||||
UIModalService,
|
||||
UIDialogService,
|
||||
UIContextMenuService,
|
||||
UILabellingFlowService,
|
||||
} from './services';
|
||||
|
||||
const OHIF = {
|
||||
@@ -55,8 +53,6 @@ const OHIF = {
|
||||
UINotificationService,
|
||||
UIModalService,
|
||||
UIDialogService,
|
||||
UIContextMenuService,
|
||||
UILabellingFlowService,
|
||||
};
|
||||
|
||||
export {
|
||||
@@ -86,8 +82,6 @@ export {
|
||||
UINotificationService,
|
||||
UIModalService,
|
||||
UIDialogService,
|
||||
UIContextMenuService,
|
||||
UILabellingFlowService,
|
||||
};
|
||||
|
||||
export { OHIF };
|
||||
|
||||
@@ -13,8 +13,6 @@ describe('Top level exports', () => {
|
||||
'UINotificationService',
|
||||
'UIModalService',
|
||||
'UIDialogService',
|
||||
'UIContextMenuService',
|
||||
'UILabellingFlowService',
|
||||
//
|
||||
'utils',
|
||||
'studies',
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
/**
|
||||
* UI Context Menu
|
||||
*
|
||||
* @typedef {Object} ContextMenuProps
|
||||
* @property {Event} event The event with tool information.
|
||||
*/
|
||||
|
||||
const name = 'UIContextMenuService';
|
||||
|
||||
const publicAPI = {
|
||||
name,
|
||||
hide: _hide,
|
||||
show: _show,
|
||||
setServiceImplementation,
|
||||
};
|
||||
|
||||
const serviceImplementation = {
|
||||
_show: () => console.warn('show() NOT IMPLEMENTED'),
|
||||
_hide: () => console.warn('hide() NOT IMPLEMENTED'),
|
||||
};
|
||||
|
||||
/**
|
||||
* Show a new UI ContextMenu dialog;
|
||||
*
|
||||
* @param {ContextMenuProps} props { event }
|
||||
*/
|
||||
function _show({ event }) {
|
||||
return serviceImplementation._show({
|
||||
event,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide a UI ContextMenu dialog;
|
||||
*
|
||||
*/
|
||||
function _hide() {
|
||||
return serviceImplementation._hide();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param {*} {
|
||||
* show: showImplementation,
|
||||
* hide: hideImplementation,
|
||||
* }
|
||||
*/
|
||||
function setServiceImplementation({
|
||||
show: showImplementation,
|
||||
hide: hideImplementation,
|
||||
}) {
|
||||
if (showImplementation) {
|
||||
serviceImplementation._show = showImplementation;
|
||||
}
|
||||
if (hideImplementation) {
|
||||
serviceImplementation._hide = hideImplementation;
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
name,
|
||||
create: ({ configuration = {} }) => {
|
||||
return publicAPI;
|
||||
},
|
||||
};
|
||||
@@ -1,71 +0,0 @@
|
||||
/**
|
||||
* UI Labelling Flow
|
||||
*
|
||||
* @typedef {Object} LabellingFlowProps
|
||||
* @property {Object} defaultPosition The position of the labelling dialog.
|
||||
* @property {boolean} centralize conditional to center the labelling dialog.
|
||||
* @property {Object} props The labelling props.
|
||||
*
|
||||
*/
|
||||
|
||||
const name = 'UILabellingFlowService';
|
||||
|
||||
const publicAPI = {
|
||||
name,
|
||||
show: _show,
|
||||
hide: _hide,
|
||||
setServiceImplementation,
|
||||
};
|
||||
|
||||
const serviceImplementation = {
|
||||
_show: () => console.warn('show() NOT IMPLEMENTED'),
|
||||
_hide: () => console.warn('hide() NOT IMPLEMENTED'),
|
||||
};
|
||||
|
||||
/**
|
||||
* Hide a UI LabellingFlow dialog;
|
||||
*
|
||||
*/
|
||||
function _hide() {
|
||||
return serviceImplementation._hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a new UI LabellingFlow dialog;
|
||||
*
|
||||
* @param {LabellingFlowProps} props { defaultPosition, centralize, props }
|
||||
*/
|
||||
function _show({ defaultPosition, centralize, props }) {
|
||||
return serviceImplementation._show({
|
||||
defaultPosition,
|
||||
centralize,
|
||||
props,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param {*} {
|
||||
* show: showImplementation,
|
||||
* hide: hideImplementation,
|
||||
* }
|
||||
*/
|
||||
function setServiceImplementation({
|
||||
show: showImplementation,
|
||||
hide: hideImplementation,
|
||||
}) {
|
||||
if (showImplementation) {
|
||||
serviceImplementation._show = showImplementation;
|
||||
}
|
||||
if (hideImplementation) {
|
||||
serviceImplementation._hide = hideImplementation;
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
name,
|
||||
create: ({ configuration = {} }) => {
|
||||
return publicAPI;
|
||||
},
|
||||
};
|
||||
@@ -2,14 +2,10 @@ import ServicesManager from './ServicesManager.js';
|
||||
import UINotificationService from './UINotificationService';
|
||||
import UIModalService from './UIModalService';
|
||||
import UIDialogService from './UIDialogService';
|
||||
import UIContextMenuService from './UIContextMenuService';
|
||||
import UILabellingFlowService from './UILabellingFlowService';
|
||||
|
||||
export {
|
||||
UINotificationService,
|
||||
UIModalService,
|
||||
UIDialogService,
|
||||
UIContextMenuService,
|
||||
UILabellingFlowService,
|
||||
ServicesManager,
|
||||
};
|
||||
@@ -3,6 +3,25 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.52.4](https://github.com/OHIF/Viewers/compare/@ohif/i18n@0.52.3...@ohif/i18n@0.52.4) (2019-12-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/i18n
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.52.3](https://github.com/OHIF/Viewers/compare/@ohif/i18n@0.52.2...@ohif/i18n@0.52.3) (2019-12-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* translations ([#1234](https://github.com/OHIF/Viewers/issues/1234)) ([30b9e44](https://github.com/OHIF/Viewers/commit/30b9e4422073557287ef26a80b38eeb3f3fcff4c))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.52.2](https://github.com/OHIF/Viewers/compare/@ohif/i18n@0.52.1...@ohif/i18n@0.52.2) (2019-11-28)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/i18n",
|
||||
"version": "0.52.2",
|
||||
"version": "0.52.4",
|
||||
"description": "Internationalization library for The OHIF Viewer",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -29,19 +29,20 @@ function addLocales(newLocales) {
|
||||
customDebug(resourceBundle, 'info');
|
||||
}
|
||||
|
||||
// Note: Developers can add the API key to use the in-context editor.
|
||||
// DO NOT commit the API key
|
||||
const config = (window.config && window.config.i18n) || {};
|
||||
|
||||
/*
|
||||
* Note: Developers can add the API key to use the
|
||||
* in-context editor using environment variables.
|
||||
* (DO NOT commit the API key)
|
||||
*/
|
||||
const locizeOptions = {
|
||||
projectId: config.LOCIZE_PROJECTID,
|
||||
apiKey: config.LOCIZE_API_KEY,
|
||||
projectId: process.env.LOCIZE_PROJECTID,
|
||||
apiKey: process.env.LOCIZE_API_KEY,
|
||||
referenceLng: 'en-US',
|
||||
fallbacklng: 'en-US',
|
||||
};
|
||||
|
||||
const envUseLocize = !!config.USE_LOCIZE;
|
||||
const envApiKeyAvailable = !!config.LOCIZE_API_KEY;
|
||||
const envUseLocize = !!process.env.USE_LOCIZE;
|
||||
const envApiKeyAvailable = !!process.env.LOCIZE_API_KEY;
|
||||
const DEFAULT_LANGUAGE = 'en-US';
|
||||
|
||||
function initI18n(
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"Clear": "Clear",
|
||||
"Coronal": "Coronal",
|
||||
"Crosshairs": "Crosshairs",
|
||||
"Download": "Download",
|
||||
"Ellipse": "Ellipse",
|
||||
"Elliptical": "Elliptical",
|
||||
"Flip H": "Flip H",
|
||||
|
||||
@@ -10,18 +10,19 @@
|
||||
"Clear": "Limpiar",
|
||||
"Coronal": "Coronal",
|
||||
"Crosshairs": "Punto de mira",
|
||||
"Download": "Descargar",
|
||||
"Ellipse": "Elipse",
|
||||
"Elliptical": "Elíptico",
|
||||
"Flip H": "Espejo H",
|
||||
"Flip V": "Espejo V",
|
||||
"Flip H": "Voltear H",
|
||||
"Flip V": "Voltear V",
|
||||
"Freehand": "Mano alzada",
|
||||
"Invert": "Invertir",
|
||||
"Invert": "Negativo",
|
||||
"Layout": "$t(Common:Layout)",
|
||||
"Length": "Longitud",
|
||||
"Levels": "Niveles",
|
||||
"Magnify": "Aumentar",
|
||||
"Levels": "W/L",
|
||||
"Magnify": "Lupa",
|
||||
"Manual": "Manual",
|
||||
"Measurements": "Mediciones",
|
||||
"Measurements": "Medidas",
|
||||
"More": "$t(Common:More)",
|
||||
"Next": "$t(Common:Next)",
|
||||
"Pan": "Mover",
|
||||
@@ -32,11 +33,11 @@
|
||||
"Rectangle": "Rectángulo",
|
||||
"Reset": "$t(Common:Reset)",
|
||||
"Reset to Defaults": "$t(Common:Reset) por defecto",
|
||||
"Rotate Right": "Rotar ->",
|
||||
"Rotate Right": "Girar ->",
|
||||
"Sagittal": "Sagital",
|
||||
"Save": "Guardar",
|
||||
"Stack Scroll": "Scroll en pila",
|
||||
"Stack Scroll": "Scroll",
|
||||
"Stop": "$t(Common:Stop)",
|
||||
"Themes": "Temas",
|
||||
"Zoom": "Zoom"
|
||||
"Zoom": "Ampliar"
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"Image": "Imagen",
|
||||
"Layout": "Disposición",
|
||||
"Layout": "Formato",
|
||||
"Measurements": "Medidas",
|
||||
"More": "Más",
|
||||
"Next": "Siguiente",
|
||||
"Play": "Play",
|
||||
"Previous": "Anterior",
|
||||
"Reset": "Resetear",
|
||||
"Reset": "Restaurar",
|
||||
"RowsPerPage": "filas por página",
|
||||
"Series": "Secuencia",
|
||||
"Show": "Mostrar",
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"Clear": "Limpar",
|
||||
"Coronal": "Coronal",
|
||||
"Crosshairs": "Localizador",
|
||||
"Download": "Baixar",
|
||||
"Ellipse": "Elipse",
|
||||
"Elliptical": "Elíptico",
|
||||
"Flip H": "Inverter H",
|
||||
@@ -30,8 +31,8 @@
|
||||
"Probe": "Prova",
|
||||
"ROI Window": "Janela ROI",
|
||||
"Rectangle": "Retângulo",
|
||||
"Reset": "Resetar",
|
||||
"Reset to Defaults": "Restaurar para o Padrão",
|
||||
"Reset": "$t(Common:Reset)",
|
||||
"Reset to Defaults": "$t(Common:Reset) para o Padrão",
|
||||
"Rotate Right": "Girar à direita",
|
||||
"Sagittal": "Sagital",
|
||||
"Save": "Salvar",
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
"Next": "Próximo",
|
||||
"Play": "Play",
|
||||
"Previous": "Anterior",
|
||||
"Reset": "Reset",
|
||||
"Reset": "Restaurar",
|
||||
"Stop": "Stop"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"Cancel": "Cancelar",
|
||||
"Reset to Defaults": "Restaurar Default",
|
||||
"Reset to Defaults": "$t(Common:Reset) para Padrão",
|
||||
"ResetDefaultMessage": "Preferências resetadas com sucesso. <br /> Você deve <strong>Salvar</strong> para que essa ação seja realizada.",
|
||||
"Save": "Salvar",
|
||||
"SaveMessage": "Preferências salvas",
|
||||
|
||||
@@ -3,6 +3,44 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.1.4](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.1.3...@ohif/ui@1.1.4) (2019-12-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/ui
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.1.3](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.1.2...@ohif/ui@1.1.3) (2019-12-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* allow empty values for dimensions ([#1295](https://github.com/OHIF/Viewers/issues/1295)) ([cd2da34](https://github.com/OHIF/Viewers/commit/cd2da349e5212cccdd8e65ffa3f7fdc6bad1057c))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.1.2](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.1.1...@ohif/ui@1.1.2) (2019-12-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* translations ([#1234](https://github.com/OHIF/Viewers/issues/1234)) ([30b9e44](https://github.com/OHIF/Viewers/commit/30b9e4422073557287ef26a80b38eeb3f3fcff4c))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.1.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.1.0...@ohif/ui@1.1.1) (2019-12-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/ui
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.1.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.0.1...@ohif/ui@1.1.0) (2019-12-11)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/ui",
|
||||
"version": "1.1.0",
|
||||
"version": "1.1.4",
|
||||
"description": "A set of React components for Medical Imaging Viewers",
|
||||
"author": "OHIF Contributors",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -32,69 +32,52 @@ const ViewportDownloadForm = ({
|
||||
maximumSize,
|
||||
canvasClass,
|
||||
}) => {
|
||||
const [t] = useTranslation('ViewportDownloadForm');
|
||||
const [t] = useTranslation('Buttons');
|
||||
|
||||
const [filename, setFilename] = useState(DEFAULT_FILENAME);
|
||||
const [fileType, setFileType] = useState('jpg');
|
||||
|
||||
const [height, setHeight] = useState(defaultSize);
|
||||
const [width, setWidth] = useState(defaultSize);
|
||||
const [dimensions, setDimensions] = useState({
|
||||
width: defaultSize,
|
||||
height: defaultSize,
|
||||
});
|
||||
|
||||
const [showAnnotations, setShowAnnotations] = useState(true);
|
||||
|
||||
const [keepAspect, setKeepAspect] = useState(true);
|
||||
const [lastImage, setLastImage] = useState();
|
||||
|
||||
const [viewportElement, setViewportElement] = useState();
|
||||
const [viewportElementHeight, setViewportElementHeight] = useState(
|
||||
minimumSize
|
||||
);
|
||||
const [viewportElementWidth, setViewportElementWidth] = useState(minimumSize);
|
||||
const [viewportElementDimensions, setViewportElementDimensions] = useState({
|
||||
width: defaultSize,
|
||||
height: defaultSize,
|
||||
});
|
||||
|
||||
const [downloadCanvas, setDownloadCanvas] = useState({
|
||||
ref: createRef(),
|
||||
width: minimumSize,
|
||||
height: minimumSize,
|
||||
width: defaultSize,
|
||||
height: defaultSize,
|
||||
});
|
||||
|
||||
const [viewportPreview, setViewportPreview] = useState({
|
||||
src: null,
|
||||
width: minimumSize,
|
||||
height: minimumSize,
|
||||
width: defaultSize,
|
||||
height: defaultSize,
|
||||
});
|
||||
|
||||
// Cornerstone's `enable/disable`
|
||||
useEffect(() => {
|
||||
enableViewport(viewportElement);
|
||||
|
||||
return () => {
|
||||
disableViewport(viewportElement);
|
||||
|
||||
setHeight(defaultSize);
|
||||
setWidth(defaultSize);
|
||||
};
|
||||
}, [defaultSize, disableViewport, enableViewport, viewportElement]);
|
||||
}, [disableViewport, enableViewport, viewportElement]);
|
||||
|
||||
useEffect(() => {
|
||||
const { width, height } = viewportElementDimensions;
|
||||
const validSize = value => (value >= minimumSize ? value : minimumSize);
|
||||
const loadAndUpdateViewports = async () => {
|
||||
const {
|
||||
image,
|
||||
width: scaledWidth,
|
||||
height: scaledHeight,
|
||||
} = await loadImage(activeViewport, viewportElement, width, height);
|
||||
setLastImage(image);
|
||||
|
||||
await loadImage(activeViewport, viewportElement, width, height);
|
||||
toggleAnnotations(showAnnotations, viewportElement);
|
||||
|
||||
setViewportElementHeight(validSize(scaledHeight));
|
||||
setViewportElementWidth(validSize(scaledWidth));
|
||||
|
||||
setDownloadCanvas(state => ({
|
||||
...state,
|
||||
height: validSize(scaledHeight),
|
||||
width: validSize(scaledWidth),
|
||||
}));
|
||||
|
||||
const {
|
||||
dataUrl,
|
||||
width: viewportElementWidth,
|
||||
@@ -118,8 +101,6 @@ const ViewportDownloadForm = ({
|
||||
activeViewport,
|
||||
viewportElement,
|
||||
showAnnotations,
|
||||
height,
|
||||
width,
|
||||
loadImage,
|
||||
toggleAnnotations,
|
||||
updateViewportPreview,
|
||||
@@ -127,54 +108,41 @@ const ViewportDownloadForm = ({
|
||||
downloadCanvas.ref,
|
||||
minimumSize,
|
||||
maximumSize,
|
||||
viewportElementDimensions,
|
||||
]);
|
||||
|
||||
const onHeightChange = event => {
|
||||
const newHeight = Math.min(event.target.value, maximumSize);
|
||||
setHeight(newHeight);
|
||||
/**
|
||||
* @param {object} event - Input change event
|
||||
* @param {string} dimension - "height" | "width"
|
||||
*/
|
||||
const onDimensionsChange = (event, dimension) => {
|
||||
const sanitizedTargetValue = event.target.value.replace(/\D/, '');
|
||||
const isEmpty = sanitizedTargetValue === '';
|
||||
const updatedDimension = isEmpty
|
||||
? ''
|
||||
: Math.min(sanitizedTargetValue, maximumSize);
|
||||
|
||||
setViewportElementHeight(newHeight);
|
||||
|
||||
setDownloadCanvas(state => ({
|
||||
...state,
|
||||
height: newHeight,
|
||||
}));
|
||||
|
||||
if (keepAspect) {
|
||||
const multiplier = newHeight / lastImage.height;
|
||||
const newWidth = Math.round(lastImage.width * multiplier);
|
||||
|
||||
setWidth(newWidth);
|
||||
setViewportElementWidth(newWidth);
|
||||
|
||||
setDownloadCanvas(state => ({
|
||||
...state,
|
||||
width: newWidth,
|
||||
}));
|
||||
if (updatedDimension === dimensions.width) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const onWidthChange = event => {
|
||||
const newWidth = Math.min(event.target.value, maximumSize);
|
||||
setWidth(newWidth);
|
||||
|
||||
setViewportElementWidth(newWidth);
|
||||
|
||||
setDownloadCanvas(state => ({
|
||||
...state,
|
||||
width: newWidth,
|
||||
}));
|
||||
|
||||
if (keepAspect) {
|
||||
const multiplier = newWidth / lastImage.width;
|
||||
const newHeight = Math.round(lastImage.height * multiplier);
|
||||
|
||||
setHeight(newHeight);
|
||||
setViewportElementHeight(newHeight);
|
||||
// In current code, keepAspect is always `true`
|
||||
// And we always start w/ a square width/height
|
||||
setDimensions({
|
||||
width: updatedDimension,
|
||||
height: updatedDimension,
|
||||
});
|
||||
|
||||
// Only update if value is non-empty
|
||||
if (!isEmpty) {
|
||||
setViewportElementDimensions({
|
||||
height: updatedDimension,
|
||||
width: updatedDimension,
|
||||
});
|
||||
setDownloadCanvas(state => ({
|
||||
...state,
|
||||
height: newHeight,
|
||||
height: updatedDimension,
|
||||
width: updatedDimension,
|
||||
}));
|
||||
}
|
||||
};
|
||||
@@ -196,26 +164,22 @@ const ViewportDownloadForm = ({
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="file-info-container">
|
||||
<div className="file-info-container" data-cy="file-info-container">
|
||||
<div className="col">
|
||||
<div className="width">
|
||||
<TextInput
|
||||
type="number"
|
||||
min={minimumSize}
|
||||
max={maximumSize}
|
||||
value={width}
|
||||
data-cy="image-width"
|
||||
value={dimensions.width}
|
||||
label={t('Image width (px)')}
|
||||
onChange={onWidthChange}
|
||||
onChange={evt => onDimensionsChange(evt, 'height')}
|
||||
/>
|
||||
</div>
|
||||
<div className="height">
|
||||
<TextInput
|
||||
type="number"
|
||||
min={minimumSize}
|
||||
max={maximumSize}
|
||||
value={height}
|
||||
data-cy="image-height"
|
||||
value={dimensions.height}
|
||||
label={t('Image height (px)')}
|
||||
onChange={onHeightChange}
|
||||
onChange={evt => onDimensionsChange(evt, 'width')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -224,6 +188,7 @@ const ViewportDownloadForm = ({
|
||||
<div className="file-name">
|
||||
<TextInput
|
||||
type="text"
|
||||
data-cy="file-name"
|
||||
value={filename}
|
||||
onChange={event => setFilename(event.target.value)}
|
||||
label={t('File name')}
|
||||
@@ -233,6 +198,7 @@ const ViewportDownloadForm = ({
|
||||
<div className="file-type">
|
||||
<Select
|
||||
value={fileType}
|
||||
data-cy="file-type"
|
||||
onChange={event => setFileType(event.target.value)}
|
||||
options={FILE_TYPE_OPTIONS}
|
||||
label={t('File type')}
|
||||
@@ -245,6 +211,7 @@ const ViewportDownloadForm = ({
|
||||
<label htmlFor="show-annotations" className="form-check-label">
|
||||
<input
|
||||
id="show-annotations"
|
||||
data-cy="show-annotations"
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
checked={showAnnotations}
|
||||
@@ -258,8 +225,8 @@ const ViewportDownloadForm = ({
|
||||
|
||||
<div
|
||||
style={{
|
||||
height: viewportElementHeight,
|
||||
width: viewportElementWidth,
|
||||
height: viewportElementDimensions.height,
|
||||
width: viewportElementDimensions.width,
|
||||
position: 'absolute',
|
||||
left: '9999px',
|
||||
}}
|
||||
@@ -278,23 +245,33 @@ const ViewportDownloadForm = ({
|
||||
></canvas>
|
||||
</div>
|
||||
|
||||
<div className="preview">
|
||||
<div className="preview" data-cy="image-preview">
|
||||
<h4> {t('Image Preview')}</h4>
|
||||
<img
|
||||
className="viewport-preview"
|
||||
src={viewportPreview.src}
|
||||
alt="Viewport Preview"
|
||||
data-cy="viewport-preview-img"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="actions">
|
||||
<div className="action-cancel">
|
||||
<button type="button" className="btn btn-danger" onClick={onClose}>
|
||||
<button
|
||||
type="button"
|
||||
data-cy="cancel-btn"
|
||||
className="btn btn-danger"
|
||||
onClick={onClose}
|
||||
>
|
||||
{t('Cancel')}
|
||||
</button>
|
||||
</div>
|
||||
<div className="action-save">
|
||||
<button onClick={downloadImage} className="btn btn-primary">
|
||||
<button
|
||||
onClick={downloadImage}
|
||||
className="btn btn-primary"
|
||||
data-cy="download-btn"
|
||||
>
|
||||
{t('Download')}
|
||||
</button>
|
||||
</div>
|
||||
@@ -312,6 +289,7 @@ ViewportDownloadForm.propTypes = {
|
||||
toggleAnnotations: PropTypes.func.isRequired,
|
||||
loadImage: PropTypes.func.isRequired,
|
||||
downloadBlob: PropTypes.func.isRequired,
|
||||
/** A default width & height, between the minimum and maximum size */
|
||||
defaultSize: PropTypes.number.isRequired,
|
||||
minimumSize: PropTypes.number.isRequired,
|
||||
maximumSize: PropTypes.number.isRequired,
|
||||
|
||||
+5
-6
@@ -1,20 +1,19 @@
|
||||
.ToolContextMenu {
|
||||
.ContextMenu {
|
||||
position: relative;
|
||||
background-color: white;
|
||||
border: 1px solid white;
|
||||
border-radius: 5px;
|
||||
z-index: 1000;
|
||||
display: block;
|
||||
width: 170px;
|
||||
}
|
||||
|
||||
.ToolContextMenu > ul {
|
||||
.ContextMenu>ul {
|
||||
list-style-type: none;
|
||||
padding-left: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ToolContextMenu > ul > li > button {
|
||||
.ContextMenu>ul>li>button {
|
||||
padding: 10px;
|
||||
font-size: 14px;
|
||||
border: none;
|
||||
@@ -25,10 +24,10 @@
|
||||
background: none;
|
||||
}
|
||||
|
||||
.ToolContextMenu > ul > li > button:hover {
|
||||
.ContextMenu>ul>li>button:hover {
|
||||
color: #16202b;
|
||||
}
|
||||
|
||||
.ToolContextMenu > ul > li > button:active {
|
||||
.ContextMenu>ul>li>button:active {
|
||||
color: #79f9fe;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import './ContextMenu.css';
|
||||
|
||||
const ContextMenu = ({ items, onClick }) => {
|
||||
return (
|
||||
<div className="ContextMenu">
|
||||
<ul>
|
||||
{items.map((item, index) => (
|
||||
<li key={index}>
|
||||
<button className="form-action" onClick={() => onClick(item)}>
|
||||
<span key={index}>{item.label}</span>
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ContextMenu.propTypes = {
|
||||
items: PropTypes.array.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default ContextMenu;
|
||||
@@ -0,0 +1 @@
|
||||
export { default as ContextMenu } from './ContextMenu.js';
|
||||
@@ -17,6 +17,7 @@ import { RoundedButtonGroup } from './roundedButtonGroup';
|
||||
import { SelectTree } from './selectTree';
|
||||
import { SimpleDialog } from './simpleDialog';
|
||||
import { OHIFModal } from './ohifModal';
|
||||
import { ContextMenu } from './contextMenu';
|
||||
import {
|
||||
PageToolbar,
|
||||
StudyList,
|
||||
@@ -27,6 +28,7 @@ import { ToolbarSection } from './toolbarSection';
|
||||
import { Tooltip } from './tooltip';
|
||||
|
||||
export {
|
||||
ContextMenu,
|
||||
Checkbox,
|
||||
CineDialog,
|
||||
ViewportDownloadForm,
|
||||
|
||||
@@ -29,7 +29,7 @@ const OHIFModal = ({
|
||||
const renderHeader = () => {
|
||||
return (
|
||||
title && (
|
||||
<div className="OHIFModal__header">
|
||||
<div className="OHIFModal__header" data-cy="modal-header">
|
||||
<h4>{title}</h4>
|
||||
{closeButton && (
|
||||
<button data-cy="close-button" onClick={onClose}>
|
||||
@@ -44,6 +44,7 @@ const OHIFModal = ({
|
||||
return (
|
||||
<Modal
|
||||
className={classNames('OHIFModal', className)}
|
||||
data-cy="modal"
|
||||
shouldCloseOnEsc={shouldCloseOnEsc}
|
||||
isOpen={isOpen}
|
||||
title={title}
|
||||
@@ -51,7 +52,9 @@ const OHIFModal = ({
|
||||
>
|
||||
<>
|
||||
{renderHeader()}
|
||||
<div className="OHIFModal__content">{children}</div>
|
||||
<div className="OHIFModal__content" data-cy="modal-content">
|
||||
{children}
|
||||
</div>
|
||||
</>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
import React, {
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useCallback,
|
||||
} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { useDialog } from '@ohif/ui';
|
||||
import { useLabellingFlow } from '@ohif/ui';
|
||||
|
||||
const ContextMenuContext = createContext(null);
|
||||
const { Provider } = ContextMenuContext;
|
||||
|
||||
export const useContextMenu = () => useContext(ContextMenuContext);
|
||||
|
||||
const ContextMenuProvider = ({
|
||||
children,
|
||||
service,
|
||||
contextMenuComponent: ContextMenuComponent,
|
||||
onDelete,
|
||||
}) => {
|
||||
const { create, dismiss } = useDialog();
|
||||
const { show: showLabellingFlow } = useLabellingFlow();
|
||||
|
||||
/**
|
||||
* Sets the implementation of a context menu service that can be used by extensions.
|
||||
*
|
||||
* @returns void
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (service) {
|
||||
service.setServiceImplementation({
|
||||
show,
|
||||
hide,
|
||||
});
|
||||
}
|
||||
}, [hide, service, show]);
|
||||
|
||||
const hide = useCallback(() => dismiss({ id: 'context-menu' }), [dismiss]);
|
||||
|
||||
/**
|
||||
* Show the context menu and override its configuration props.
|
||||
*
|
||||
* @param {ContextMenuProps} props { eventData, isTouchEvent, onClose, visible }
|
||||
* @returns void
|
||||
*/
|
||||
const show = useCallback(
|
||||
({ event }) => {
|
||||
hide();
|
||||
create({
|
||||
id: 'context-menu',
|
||||
isDraggable: false,
|
||||
preservePosition: false,
|
||||
content: ContextMenuComponent,
|
||||
contentProps: {
|
||||
eventData: event,
|
||||
onDelete: (nearbyToolData, eventData) =>
|
||||
onDelete(nearbyToolData, eventData),
|
||||
onClose: () => dismiss({ id: 'context-menu' }),
|
||||
onSetLabel: (eventData, measurementData) =>
|
||||
showLabellingFlow({
|
||||
event: eventData,
|
||||
centralize: true,
|
||||
props: {
|
||||
measurementData,
|
||||
skipAddLabelButton: true,
|
||||
editLocation: true,
|
||||
},
|
||||
}),
|
||||
onSetDescription: (eventData, measurementData) =>
|
||||
showLabellingFlow({
|
||||
event: eventData,
|
||||
centralize: false,
|
||||
defaultPosition: _getDefaultPosition(eventData),
|
||||
props: {
|
||||
measurementData,
|
||||
editDescriptionOnDialog: true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
defaultPosition: _getDefaultPosition(event),
|
||||
});
|
||||
},
|
||||
[ContextMenuComponent, create, dismiss, hide, onDelete, showLabellingFlow]
|
||||
);
|
||||
|
||||
const _getDefaultPosition = event => ({
|
||||
x: (event && event.currentPoints.client.x) || 0,
|
||||
y: (event && event.currentPoints.client.y) || 0,
|
||||
});
|
||||
|
||||
return (
|
||||
<Provider
|
||||
value={{
|
||||
show,
|
||||
hide,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Higher Order Component to use the context menu methods through a Class Component.
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
export const withContextMenu = Component => {
|
||||
return function WrappedComponent(props) {
|
||||
const { show, hide } = useContextMenu();
|
||||
return (
|
||||
<Component
|
||||
{...props}
|
||||
modal={{
|
||||
show,
|
||||
hide,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
ContextMenuProvider.defaultProps = {
|
||||
service: null,
|
||||
};
|
||||
|
||||
ContextMenuProvider.propTypes = {
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node,
|
||||
]).isRequired,
|
||||
service: PropTypes.shape({
|
||||
setServiceImplementation: PropTypes.func,
|
||||
}),
|
||||
contextMenuComponent: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node,
|
||||
PropTypes.func,
|
||||
]).isRequired,
|
||||
onDelete: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default ContextMenuProvider;
|
||||
|
||||
export const ContextMenuConsumer = ContextMenuContext.Consumer;
|
||||
@@ -1,136 +0,0 @@
|
||||
import React, {
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useCallback,
|
||||
} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { useDialog } from './DialogProvider';
|
||||
|
||||
const LabellingFlowContext = createContext(null);
|
||||
const { Provider } = LabellingFlowContext;
|
||||
|
||||
export const useLabellingFlow = () => useContext(LabellingFlowContext);
|
||||
|
||||
const LabellingFlowProvider = ({
|
||||
children,
|
||||
service,
|
||||
labellingComponent: LabellingComponent,
|
||||
onUpdateLabelling,
|
||||
}) => {
|
||||
const { create, dismiss } = useDialog();
|
||||
|
||||
/**
|
||||
* Sets the implementation of a labelling flow service that can be used by extensions.
|
||||
*
|
||||
* @returns void
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (service) {
|
||||
service.setServiceImplementation({
|
||||
show,
|
||||
hide,
|
||||
});
|
||||
}
|
||||
}, [hide, service, show]);
|
||||
|
||||
const hide = useCallback(() => dismiss({ id: 'labelling' }), [dismiss]);
|
||||
|
||||
const show = useCallback(
|
||||
({ centralize, defaultPosition, props }) => {
|
||||
hide();
|
||||
create({
|
||||
id: 'labelling',
|
||||
centralize,
|
||||
isDraggable: false,
|
||||
showOverlay: true,
|
||||
content: LabellingComponent,
|
||||
defaultPosition,
|
||||
contentProps: {
|
||||
visible: true,
|
||||
measurementData: props.measurementData,
|
||||
labellingDoneCallback: () => dismiss({ id: 'labelling' }),
|
||||
updateLabelling: labellingData =>
|
||||
_updateLabellingHandler(labellingData, props.measurementData),
|
||||
...props,
|
||||
},
|
||||
});
|
||||
},
|
||||
[LabellingComponent, _updateLabellingHandler, create, dismiss, hide]
|
||||
);
|
||||
|
||||
const _updateLabellingHandler = useCallback(
|
||||
(labellingData, measurementData) => {
|
||||
const { location, description, response } = labellingData;
|
||||
|
||||
if (location) {
|
||||
measurementData.location = location;
|
||||
}
|
||||
|
||||
measurementData.description = description || '';
|
||||
|
||||
if (response) {
|
||||
measurementData.response = response;
|
||||
}
|
||||
|
||||
onUpdateLabelling(labellingData, measurementData);
|
||||
},
|
||||
[onUpdateLabelling]
|
||||
);
|
||||
|
||||
return (
|
||||
<Provider
|
||||
value={{
|
||||
show,
|
||||
hide,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Higher Order Component to use the labelling flow methods through a Class Component.
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
export const withLabellingFlow = Component => {
|
||||
return function WrappedComponent(props) {
|
||||
const { show, hide } = useLabellingFlow();
|
||||
return (
|
||||
<Component
|
||||
{...props}
|
||||
modal={{
|
||||
show,
|
||||
hide,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
LabellingFlowProvider.defaultProps = {
|
||||
service: null,
|
||||
};
|
||||
|
||||
LabellingFlowProvider.propTypes = {
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node,
|
||||
]).isRequired,
|
||||
service: PropTypes.shape({
|
||||
setServiceImplementation: PropTypes.func,
|
||||
}),
|
||||
labellingComponent: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node,
|
||||
PropTypes.func,
|
||||
]).isRequired,
|
||||
onUpdateLabelling: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default LabellingFlowProvider;
|
||||
|
||||
export const LabellingFlowConsumer = LabellingFlowContext.Consumer;
|
||||
@@ -18,15 +18,3 @@ export {
|
||||
withDialog,
|
||||
useDialog,
|
||||
} from './DialogProvider.js';
|
||||
export {
|
||||
default as ContextMenuProvider,
|
||||
withContextMenu,
|
||||
useContextMenu,
|
||||
ContextMenuConsumer,
|
||||
} from './ContextMenuProvider.js';
|
||||
export {
|
||||
default as LabellingFlowProvider,
|
||||
withLabellingFlow,
|
||||
useLabellingFlow,
|
||||
LabellingFlowConsumer,
|
||||
} from './LabellingFlowProvider.js';
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
ContextMenu,
|
||||
Checkbox,
|
||||
CineDialog,
|
||||
ViewportDownloadForm,
|
||||
@@ -60,14 +61,6 @@ import {
|
||||
ModalConsumer,
|
||||
useModal,
|
||||
withModal,
|
||||
ContextMenuProvider,
|
||||
ContextMenuConsumer,
|
||||
useContextMenu,
|
||||
withContextMenu,
|
||||
LabellingFlowProvider,
|
||||
LabellingFlowConsumer,
|
||||
useLabellingFlow,
|
||||
withLabellingFlow,
|
||||
} from './contextProviders';
|
||||
|
||||
export {
|
||||
@@ -80,6 +73,7 @@ export {
|
||||
TextArea,
|
||||
TextInput,
|
||||
CineDialog,
|
||||
ContextMenu,
|
||||
ViewportDownloadForm,
|
||||
ExpandableToolMenu,
|
||||
Icon,
|
||||
@@ -125,14 +119,6 @@ export {
|
||||
DialogProvider,
|
||||
withDialog,
|
||||
useDialog,
|
||||
ContextMenuProvider,
|
||||
ContextMenuConsumer,
|
||||
useContextMenu,
|
||||
withContextMenu,
|
||||
LabellingFlowProvider,
|
||||
LabellingFlowConsumer,
|
||||
useLabellingFlow,
|
||||
withLabellingFlow,
|
||||
// Hooks
|
||||
useDebounce,
|
||||
useMedia,
|
||||
|
||||
@@ -3,6 +3,78 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.1.9](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.1.8...@ohif/viewer@3.1.9) (2019-12-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.1.8](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.1.7...@ohif/viewer@3.1.8) (2019-12-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.1.7](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.1.6...@ohif/viewer@3.1.7) (2019-12-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.1.6](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.1.5...@ohif/viewer@3.1.6) (2019-12-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.1.5](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.1.4...@ohif/viewer@3.1.5) (2019-12-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.1.4](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.1.3...@ohif/viewer@3.1.4) (2019-12-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.1.3](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.1.2...@ohif/viewer@3.1.3) (2019-12-12)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.1.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.1.1...@ohif/viewer@3.1.2) (2019-12-12)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.1.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.1.0...@ohif/viewer@3.1.1) (2019-12-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.1.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.0.3...@ohif/viewer@3.1.0) (2019-12-11)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"baseUrl": "http://localhost:3000",
|
||||
"video": false,
|
||||
"chromeWebSecurity": false,
|
||||
"waitForAnimations": true,
|
||||
"defaultCommandTimeout": 10000,
|
||||
|
||||
@@ -81,10 +81,8 @@ describe('OHIF Cornerstone Hotkeys', () => {
|
||||
});
|
||||
|
||||
it('uses hotkeys "RightArrow" and "LeftArrow" to navigate between multiple viewports', () => {
|
||||
//Click on Layout button
|
||||
cy.get('@layoutBtn').click();
|
||||
//Select 3 viewports
|
||||
cy.get('tbody > :nth-child(1) > :nth-child(3)').click();
|
||||
//Select viewport layout (3,1)
|
||||
cy.setLayout(3, 1);
|
||||
cy.waitViewportImageLoading();
|
||||
|
||||
// Press multiples hotkeys on viewport #1
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
describe('OHIF Download Snapshot File', () => {
|
||||
before(() => {
|
||||
cy.openStudy('MISTER^MR');
|
||||
cy.waitDicomImage();
|
||||
cy.expectMinimumThumbnails(5);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.openDownloadImageModal();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Close modal
|
||||
cy.get('[data-cy="close-button"]')
|
||||
.scrollIntoView()
|
||||
.click();
|
||||
});
|
||||
|
||||
it('checks displayed information for Tablet experience', function() {
|
||||
// Set Tablet resolution
|
||||
cy.viewport(1000, 660);
|
||||
// Visual comparison
|
||||
cy.screenshot('Download Image Modal - Tablet experience');
|
||||
cy.percyCanvasSnapshot('Download Image Modal - Tablet experience');
|
||||
});
|
||||
|
||||
it('checks displayed information for Desktop experience', function() {
|
||||
// Set Desktop resolution
|
||||
cy.viewport(1750, 720);
|
||||
// Visual comparison
|
||||
cy.screenshot('Download Image Modal - Desktop experience');
|
||||
cy.percyCanvasSnapshot('Download Image Modal - Desktop experience');
|
||||
//Check if all elements are displayed
|
||||
cy.get('[data-cy=modal-header]')
|
||||
.as('downloadImageModal')
|
||||
.should('contain.text', 'Download High Quality Image');
|
||||
// Check input fields
|
||||
cy.get('[data-cy=file-type]')
|
||||
.select('png')
|
||||
.should('have.value', 'png')
|
||||
.select('jpg')
|
||||
.should('have.value', 'jpg');
|
||||
// Check image preview
|
||||
cy.get('[data-cy="image-preview"]').should('contain.text', 'Image Preview');
|
||||
cy.get('[data-cy="viewport-preview-img"]')
|
||||
.should('have.attr', 'src')
|
||||
.and('include', 'data:image');
|
||||
// Check buttons
|
||||
cy.get('[data-cy="cancel-btn"]')
|
||||
.scrollIntoView()
|
||||
.should('be.visible');
|
||||
cy.get('[data-cy="download-btn"]')
|
||||
.scrollIntoView()
|
||||
.should('be.visible');
|
||||
});
|
||||
|
||||
it('cancel changes on download modal', function() {
|
||||
//Change Image Width, Filename and File Type
|
||||
cy.get('[data-cy="image-width"]')
|
||||
.clear()
|
||||
.type('300');
|
||||
cy.get('[data-cy="image-height"]') //Image Height should be the same as width
|
||||
.should('have.value', '300');
|
||||
cy.get('[data-cy="file-name"]')
|
||||
.clear()
|
||||
.type('new-filename');
|
||||
cy.get('[data-cy=file-type]').select('png');
|
||||
//Click on Cancel button
|
||||
cy.get('[data-cy="cancel-btn"]')
|
||||
.scrollIntoView()
|
||||
.click();
|
||||
//Check modal is closed
|
||||
cy.get('[data-cy="modal"]').should('not.exist');
|
||||
//Open Modal
|
||||
cy.openDownloadImageModal();
|
||||
//Verify default values was restored
|
||||
cy.get('[data-cy="image-width"]').should('have.value', '512');
|
||||
cy.get('[data-cy="file-name"]').should('have.value', 'image');
|
||||
cy.get('[data-cy=file-type]').should('have.value', 'jpg');
|
||||
});
|
||||
|
||||
// TO-DO once issue is fixed: https://github.com/OHIF/Viewers/issues/1217
|
||||
// it('checks error messages for empty fields', function() {
|
||||
// //Clear fields Image Width and Filename
|
||||
// cy.get('[data-cy="image-width"]')
|
||||
// .clear();
|
||||
// cy.get('[data-cy="file-name"]').clear();
|
||||
|
||||
// //Click on Download button
|
||||
// cy.get('[data-cy="download-btn"]')
|
||||
// .scrollIntoView()
|
||||
// .click();
|
||||
// //Check error message
|
||||
// });
|
||||
|
||||
it('checks if "Show Annotations" checkbox will display annotations', function() {
|
||||
// Close modal that is initially opened
|
||||
cy.get('[data-cy="close-button"]').click();
|
||||
|
||||
// Add measurements in the viewport
|
||||
cy.addLengthMeasurement();
|
||||
cy.addAngleMeasurement();
|
||||
|
||||
// Open Modal
|
||||
cy.openDownloadImageModal();
|
||||
// Select "Show Annotations" option
|
||||
cy.get('[data-cy="show-annotations"]').check();
|
||||
// Check image preview
|
||||
cy.get('[data-cy="image-preview"]').scrollIntoView();
|
||||
// Visual comparison
|
||||
cy.screenshot('Download Image Modal - Show Annotations checked');
|
||||
cy.percyCanvasSnapshot('Download Image Modal - Show Annotations checked');
|
||||
//Compare classes that exists on Image Preview with Annotations and Without Annotation
|
||||
cy.get('[data-cy="modal-content"]')
|
||||
.find('canvas')
|
||||
.should('have.class', 'magnifyTool'); //Class "MagnifyTool" exists with annotations displayed on Image preview
|
||||
// Uncheck "Show Annotations" option
|
||||
cy.get('[data-cy="show-annotations"]')
|
||||
.uncheck()
|
||||
.wait(300);
|
||||
// Check that class "MagnifyTool" should not exist
|
||||
cy.get('[data-cy="modal-content"]')
|
||||
.find('canvas')
|
||||
.should('not.have.class', 'magnifyTool');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
//We excluded the tests for '**/studies/**' because the bulk/all of our other study/viewer tests use this route
|
||||
|
||||
describe('OHIF Routes', function() {
|
||||
beforeEach(function() {
|
||||
cy.openStudyList();
|
||||
});
|
||||
|
||||
it('checks PT/CT json url study route', function() {
|
||||
cy.visit('/viewer?url=https://s3.eu-central-1.amazonaws.com/ohif-viewer/JSON/PTCTStudy.json');
|
||||
|
||||
cy.server();
|
||||
cy.route('GET', '**/PTCTStudy/**').as('getPTCTStudy');
|
||||
|
||||
cy.wait('@getPTCTStudy.all');
|
||||
cy.get('@getPTCTStudy').should($route => {
|
||||
expect($route.status).to.be.eq(200);
|
||||
});
|
||||
|
||||
cy.screenshot();
|
||||
cy.percyCanvasSnapshot('PT/CT json study route');
|
||||
});
|
||||
});
|
||||
@@ -1,24 +0,0 @@
|
||||
describe('ViewerRouting', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/');
|
||||
cy.contains('Study List');
|
||||
cy.get(
|
||||
'[data-cy="study-list-results"]> :nth-child(1) > .patientId'
|
||||
).click();
|
||||
});
|
||||
|
||||
// it('thumbnails list has more than 2 items', () => {
|
||||
// cy.get('.scrollable-study-thumbnails div.thumbnail-container')
|
||||
// .its('length')
|
||||
// .should('be.gte', 2);
|
||||
// });
|
||||
|
||||
// it('loads route with at least 2 thumbnails', () => {
|
||||
// cy.get(
|
||||
// ':nth-child(1) > .ThumbnailEntry > .p-x-1 > .ImageThumbnail > .image-thumbnail-canvas > canvas'
|
||||
// ).should('be.visible');
|
||||
// cy.get(
|
||||
// ':nth-child(2) > .ThumbnailEntry > .p-x-1 > .ImageThumbnail > .image-thumbnail-canvas > canvas'
|
||||
// ).should('be.visible');
|
||||
// });
|
||||
});
|
||||
@@ -11,6 +11,7 @@ export function initCornerstoneToolsAliases() {
|
||||
cy.get('.ToolbarRow > :nth-child(10)').as('cineBtn');
|
||||
cy.get('.expandableToolMenu').as('moreBtn');
|
||||
cy.get('.btn-group > .toolbar-button').as('layoutBtn');
|
||||
cy.get('.viewport-element').as('viewport');
|
||||
}
|
||||
|
||||
//Creating aliases for Common page elements
|
||||
|
||||
@@ -469,6 +469,18 @@ Cypress.Commands.add(
|
||||
}
|
||||
);
|
||||
|
||||
Cypress.Commands.add('openDownloadImageModal', () => {
|
||||
// Click on More button
|
||||
cy.get('.expandableToolMenu')
|
||||
.as('moreBtn')
|
||||
.click();
|
||||
|
||||
// Click on Download button
|
||||
cy.get('.tooltip-inner > :nth-child(13)')
|
||||
.as('downloadBtn')
|
||||
.click();
|
||||
});
|
||||
|
||||
Cypress.Commands.add('setLanguage', (language, save = true) => {
|
||||
cy.openPreferences();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/viewer",
|
||||
"version": "3.1.0",
|
||||
"version": "3.1.9",
|
||||
"description": "OHIF Viewer",
|
||||
"author": "OHIF Contributors",
|
||||
"license": "MIT",
|
||||
@@ -27,7 +27,7 @@
|
||||
"dev:viewer": "yarn run dev",
|
||||
"start": "yarn run dev",
|
||||
"test:e2e": "cypress open",
|
||||
"test:e2e:ci": "percy exec -- cypress run --browser chrome",
|
||||
"test:e2e:ci": "percy exec -- cypress run --record --browser chrome",
|
||||
"test:e2e:dist": "start-server-and-test test:e2e:serve http://localhost:3000 test:e2e:ci",
|
||||
"test:e2e:serve": "serve -n -l 3000 -s dist",
|
||||
"test:unit": "jest --watchAll",
|
||||
@@ -45,14 +45,14 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"@ohif/core": "^2.1.0",
|
||||
"@ohif/core": "^2.1.1",
|
||||
"@ohif/extension-cornerstone": "^2.1.0",
|
||||
"@ohif/extension-dicom-html": "^1.1.0",
|
||||
"@ohif/extension-dicom-microscopy": "^0.50.6",
|
||||
"@ohif/extension-dicom-pdf": "^1.0.0",
|
||||
"@ohif/extension-vtk": "^1.1.0",
|
||||
"@ohif/i18n": "^0.52.2",
|
||||
"@ohif/ui": "^1.1.0",
|
||||
"@ohif/extension-vtk": "^1.1.4",
|
||||
"@ohif/i18n": "^0.52.4",
|
||||
"@ohif/ui": "^1.1.4",
|
||||
"@tanem/react-nprogress": "^1.1.25",
|
||||
"classnames": "^2.2.6",
|
||||
"core-js": "^3.2.1",
|
||||
@@ -85,7 +85,7 @@
|
||||
"vtk.js": "^11.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cypress": "^3.3.1",
|
||||
"cypress": "^3.8.0",
|
||||
"gh-pages": "2.0.1",
|
||||
"identity-obj-proxy": "3.0.x",
|
||||
"lodash": "4.17.11",
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -8,9 +8,6 @@ import { hot } from 'react-hot-loader/root';
|
||||
|
||||
import OHIFCornerstoneExtension from '@ohif/extension-cornerstone';
|
||||
|
||||
import ToolContextMenu from './connectedComponents/ToolContextMenu';
|
||||
import LabellingFlow from './components/Labelling/LabellingFlow';
|
||||
|
||||
import {
|
||||
SnackbarProvider,
|
||||
ModalProvider,
|
||||
@@ -18,11 +15,6 @@ import {
|
||||
OHIFModal,
|
||||
} from '@ohif/ui';
|
||||
|
||||
import {
|
||||
LabellingFlowProvider,
|
||||
ContextMenuProvider,
|
||||
} from './appCustomProviders';
|
||||
|
||||
import {
|
||||
CommandsManager,
|
||||
ExtensionManager,
|
||||
@@ -31,8 +23,6 @@ import {
|
||||
UINotificationService,
|
||||
UIModalService,
|
||||
UIDialogService,
|
||||
UIContextMenuService,
|
||||
UILabellingFlowService,
|
||||
utils,
|
||||
redux as reduxOHIF,
|
||||
} from '@ohif/core';
|
||||
@@ -135,13 +125,7 @@ class App extends Component {
|
||||
} = this._appConfig;
|
||||
|
||||
this.initUserManager(oidc);
|
||||
_initServices([
|
||||
UINotificationService,
|
||||
UIModalService,
|
||||
UIDialogService,
|
||||
UIContextMenuService,
|
||||
UILabellingFlowService,
|
||||
]);
|
||||
_initServices([UINotificationService, UIModalService, UIDialogService]);
|
||||
_initExtensions(
|
||||
[...defaultExtensions, ...extensions],
|
||||
cornerstoneExtensionConfig
|
||||
@@ -161,9 +145,7 @@ class App extends Component {
|
||||
const {
|
||||
UINotificationService,
|
||||
UIDialogService,
|
||||
UILabellingFlowService,
|
||||
UIModalService,
|
||||
UIContextMenuService,
|
||||
} = servicesManager.services;
|
||||
|
||||
if (this._userManager) {
|
||||
@@ -181,21 +163,9 @@ class App extends Component {
|
||||
modal={OHIFModal}
|
||||
service={UIModalService}
|
||||
>
|
||||
<LabellingFlowProvider
|
||||
service={UILabellingFlowService}
|
||||
labellingComponent={LabellingFlow}
|
||||
commandsManager={commandsManager}
|
||||
>
|
||||
<ContextMenuProvider
|
||||
service={UIContextMenuService}
|
||||
contextMenuComponent={ToolContextMenu}
|
||||
commandsManager={commandsManager}
|
||||
>
|
||||
<OHIFStandaloneViewer
|
||||
userManager={this._userManager}
|
||||
/>
|
||||
</ContextMenuProvider>
|
||||
</LabellingFlowProvider>
|
||||
<OHIFStandaloneViewer
|
||||
userManager={this._userManager}
|
||||
/>
|
||||
</ModalProvider>
|
||||
</DialogProvider>
|
||||
</SnackbarProvider>
|
||||
@@ -218,19 +188,7 @@ class App extends Component {
|
||||
<SnackbarProvider service={UINotificationService}>
|
||||
<DialogProvider service={UIDialogService}>
|
||||
<ModalProvider modal={OHIFModal} service={UIModalService}>
|
||||
<LabellingFlowProvider
|
||||
service={UILabellingFlowService}
|
||||
labellingComponent={LabellingFlow}
|
||||
commandsManager={commandsManager}
|
||||
>
|
||||
<ContextMenuProvider
|
||||
service={UIContextMenuService}
|
||||
contextMenuComponent={ToolContextMenu}
|
||||
commandsManager={commandsManager}
|
||||
>
|
||||
<OHIFStandaloneViewer />
|
||||
</ContextMenuProvider>
|
||||
</LabellingFlowProvider>
|
||||
<OHIFStandaloneViewer />
|
||||
</ModalProvider>
|
||||
</DialogProvider>
|
||||
</SnackbarProvider>
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ContextMenuProvider } from '@ohif/ui';
|
||||
|
||||
const CustomContextMenuProvider = ({
|
||||
children,
|
||||
service,
|
||||
contextMenuComponent,
|
||||
commandsManager,
|
||||
}) => {
|
||||
const onDeleteHandler = (nearbyToolData, eventData) => {
|
||||
const element = eventData.element;
|
||||
commandsManager.runCommand('removeToolState', {
|
||||
element,
|
||||
toolType: nearbyToolData.toolType,
|
||||
tool: nearbyToolData.tool,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ContextMenuProvider
|
||||
service={service}
|
||||
contextMenuComponent={contextMenuComponent}
|
||||
onDelete={onDeleteHandler}
|
||||
>
|
||||
{children}
|
||||
</ContextMenuProvider>
|
||||
);
|
||||
};
|
||||
|
||||
CustomContextMenuProvider.defaultProps = {
|
||||
service: null,
|
||||
};
|
||||
|
||||
CustomContextMenuProvider.propTypes = {
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node,
|
||||
]).isRequired,
|
||||
service: PropTypes.shape({
|
||||
setServiceImplementation: PropTypes.func,
|
||||
}),
|
||||
contextMenuComponent: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node,
|
||||
PropTypes.func,
|
||||
]).isRequired,
|
||||
commandsManager: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default CustomContextMenuProvider;
|
||||
@@ -1,49 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { LabellingFlowProvider } from '@ohif/ui';
|
||||
|
||||
const CustomLabellingFlowProvider = ({
|
||||
children,
|
||||
service,
|
||||
labellingComponent,
|
||||
commandsManager,
|
||||
}) => {
|
||||
const onUpdateLabellingHandler = (labellingData, measurementData) => {
|
||||
commandsManager.runCommand(
|
||||
'updateTableWithNewMeasurementData',
|
||||
measurementData
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<LabellingFlowProvider
|
||||
service={service}
|
||||
labellingComponent={labellingComponent}
|
||||
onUpdateLabelling={onUpdateLabellingHandler}
|
||||
>
|
||||
{children}
|
||||
</LabellingFlowProvider>
|
||||
);
|
||||
};
|
||||
|
||||
CustomLabellingFlowProvider.defaultProps = {
|
||||
service: null,
|
||||
};
|
||||
|
||||
CustomLabellingFlowProvider.propTypes = {
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node,
|
||||
]).isRequired,
|
||||
service: PropTypes.shape({
|
||||
setServiceImplementation: PropTypes.func,
|
||||
}),
|
||||
labellingComponent: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node,
|
||||
PropTypes.func,
|
||||
]).isRequired,
|
||||
commandsManager: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default CustomLabellingFlowProvider;
|
||||
@@ -1,6 +0,0 @@
|
||||
export {
|
||||
default as LabellingFlowProvider,
|
||||
} from './LabellingFlowProvider/LabellingFlowProvider.js';
|
||||
export {
|
||||
default as ContextMenuProvider,
|
||||
} from './ContextMenuProvider/ContextMenuProvider.js';
|
||||
@@ -2,6 +2,8 @@ import React from 'react';
|
||||
import ConnectedMeasurementTable from './ConnectedMeasurementTable.js';
|
||||
import init from './init.js';
|
||||
|
||||
import LabellingFlow from '../../components/Labelling/LabellingFlow';
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Only required property. Should be a unique value across all extensions.
|
||||
@@ -13,32 +15,51 @@ export default {
|
||||
},
|
||||
|
||||
getPanelModule({ servicesManager, commandsManager }) {
|
||||
const { UILabellingFlowService, UINotificationService } = servicesManager.services;
|
||||
const { UINotificationService, UIDialogService } = servicesManager.services;
|
||||
|
||||
const showLabellingDialog = (props, measurementData) => {
|
||||
if (!UIDialogService) {
|
||||
console.warn('Unable to show dialog; no UI Dialog Service available.');
|
||||
return;
|
||||
}
|
||||
|
||||
UIDialogService.dismiss({ id: 'labelling' });
|
||||
UIDialogService.create({
|
||||
id: 'labelling',
|
||||
centralize: true,
|
||||
isDraggable: false,
|
||||
showOverlay: true,
|
||||
content: LabellingFlow,
|
||||
contentProps: {
|
||||
measurementData,
|
||||
labellingDoneCallback: () =>
|
||||
UIDialogService.dismiss({ id: 'labelling' }),
|
||||
updateLabelling: ({ location, description, response }) => {
|
||||
measurementData.location = location || measurementData.location;
|
||||
measurementData.description = description || '';
|
||||
measurementData.response = response || measurementData.response;
|
||||
|
||||
commandsManager.runCommand(
|
||||
'updateTableWithNewMeasurementData',
|
||||
measurementData
|
||||
);
|
||||
},
|
||||
...props,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const ExtendedConnectedMeasurementTable = () => (
|
||||
<ConnectedMeasurementTable
|
||||
onRelabel={tool => {
|
||||
if (UILabellingFlowService) {
|
||||
UILabellingFlowService.show({
|
||||
centralize: true,
|
||||
props: {
|
||||
skipAddLabelButton: true,
|
||||
editLocation: true,
|
||||
measurementData: tool,
|
||||
},
|
||||
});
|
||||
}
|
||||
}}
|
||||
onEditDescription={tool => {
|
||||
if (UILabellingFlowService) {
|
||||
UILabellingFlowService.show({
|
||||
centralize: true,
|
||||
props: {
|
||||
editDescriptionOnDialog: true,
|
||||
measurementData: tool,
|
||||
},
|
||||
});
|
||||
}
|
||||
}}
|
||||
onRelabel={tool =>
|
||||
showLabellingDialog(
|
||||
{ editLocation: true, skipAddLabelButton: true },
|
||||
tool
|
||||
)
|
||||
}
|
||||
onEditDescription={tool =>
|
||||
showLabellingDialog({ editDescriptionOnDialog: true }, tool)
|
||||
}
|
||||
onSaveComplete={message => {
|
||||
if (UINotificationService) {
|
||||
UINotificationService.show(message);
|
||||
|
||||
@@ -3,6 +3,9 @@ import cornerstone from 'cornerstone-core';
|
||||
import csTools from 'cornerstone-tools';
|
||||
import throttle from 'lodash.throttle';
|
||||
|
||||
import LabellingFlow from '../../components/Labelling/LabellingFlow';
|
||||
import ToolContextMenu from '../../connectedComponents/ToolContextMenu';
|
||||
|
||||
const {
|
||||
onAdded,
|
||||
onRemoved,
|
||||
@@ -29,10 +32,7 @@ export default function init({
|
||||
commandsManager,
|
||||
configuration,
|
||||
}) {
|
||||
const {
|
||||
UIContextMenuService,
|
||||
UILabellingFlowService,
|
||||
} = servicesManager.services;
|
||||
const { UIDialogService } = servicesManager.services;
|
||||
|
||||
// TODO: MEASUREMENT_COMPLETED (not present in initial implementation)
|
||||
const onMeasurementsChanged = (action, event) => {
|
||||
@@ -46,21 +46,108 @@ export default function init({
|
||||
'labelmapModified'
|
||||
);
|
||||
|
||||
const onRightClick = event => {
|
||||
if (UIContextMenuService) {
|
||||
UIContextMenuService.show({ event: event.detail });
|
||||
const _getDefaultPosition = event => ({
|
||||
x: (event && event.currentPoints.client.x) || 0,
|
||||
y: (event && event.currentPoints.client.y) || 0,
|
||||
});
|
||||
|
||||
const _updateLabellingHandler = (labellingData, measurementData) => {
|
||||
const { location, description, response } = labellingData;
|
||||
|
||||
if (location) {
|
||||
measurementData.location = location;
|
||||
}
|
||||
|
||||
measurementData.description = description || '';
|
||||
|
||||
if (response) {
|
||||
measurementData.response = response;
|
||||
}
|
||||
|
||||
commandsManager.runCommand(
|
||||
'updateTableWithNewMeasurementData',
|
||||
measurementData
|
||||
);
|
||||
};
|
||||
|
||||
const showLabellingDialog = (props, contentProps, measurementData) => {
|
||||
if (!UIDialogService) {
|
||||
console.warn('Unable to show dialog; no UI Dialog Service available.');
|
||||
return;
|
||||
}
|
||||
|
||||
UIDialogService.create({
|
||||
id: 'labelling',
|
||||
isDraggable: false,
|
||||
showOverlay: true,
|
||||
content: LabellingFlow,
|
||||
contentProps: {
|
||||
measurementData,
|
||||
labellingDoneCallback: () =>
|
||||
UIDialogService.dismiss({ id: 'labelling' }),
|
||||
updateLabelling: labellingData =>
|
||||
_updateLabellingHandler(labellingData, measurementData),
|
||||
...contentProps,
|
||||
},
|
||||
...props,
|
||||
});
|
||||
};
|
||||
|
||||
const onRightClick = event => {
|
||||
if (!UIDialogService) {
|
||||
console.warn('Unable to show dialog; no UI Dialog Service available.');
|
||||
return;
|
||||
}
|
||||
|
||||
UIDialogService.dismiss({ id: 'context-menu' });
|
||||
UIDialogService.create({
|
||||
id: 'context-menu',
|
||||
isDraggable: false,
|
||||
preservePosition: false,
|
||||
defaultPosition: _getDefaultPosition(event.detail),
|
||||
content: ToolContextMenu,
|
||||
contentProps: {
|
||||
eventData: event.detail,
|
||||
onDelete: (nearbyToolData, eventData) => {
|
||||
const element = eventData.element;
|
||||
commandsManager.runCommand('removeToolState', {
|
||||
element,
|
||||
toolType: nearbyToolData.toolType,
|
||||
tool: nearbyToolData.tool,
|
||||
});
|
||||
},
|
||||
onClose: () => UIDialogService.dismiss({ id: 'context-menu' }),
|
||||
onSetLabel: (eventData, measurementData) => {
|
||||
showLabellingDialog(
|
||||
{ centralize: true, isDraggable: false },
|
||||
{ skipAddLabelButton: true, editLocation: true },
|
||||
measurementData
|
||||
);
|
||||
},
|
||||
onSetDescription: (eventData, measurementData) => {
|
||||
showLabellingDialog(
|
||||
{ defaultPosition: _getDefaultPosition(eventData) },
|
||||
{ editDescriptionOnDialog: true },
|
||||
measurementData
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onTouchPress = event => {
|
||||
if (UIContextMenuService) {
|
||||
UIContextMenuService.show({
|
||||
event: event.detail,
|
||||
props: {
|
||||
isTouchEvent: true,
|
||||
},
|
||||
});
|
||||
if (!UIDialogService) {
|
||||
console.warn('Unable to show dialog; no UI Dialog Service available.');
|
||||
return;
|
||||
}
|
||||
|
||||
UIDialogService.create({
|
||||
eventData: event.detail,
|
||||
content: ToolContextMenu,
|
||||
contentProps: {
|
||||
isTouchEvent: true,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onTouchStart = () => resetLabelligAndContextMenu();
|
||||
@@ -68,10 +155,13 @@ export default function init({
|
||||
const onMouseClick = () => resetLabelligAndContextMenu();
|
||||
|
||||
const resetLabelligAndContextMenu = () => {
|
||||
if (UILabellingFlowService && UIContextMenuService) {
|
||||
UILabellingFlowService.hide();
|
||||
UIContextMenuService.hide();
|
||||
if (!UIDialogService) {
|
||||
console.warn('Unable to show dialog; no UI Dialog Service available.');
|
||||
return;
|
||||
}
|
||||
|
||||
UIDialogService.dismiss({ id: 'context-menu' });
|
||||
UIDialogService.dismiss({ id: 'labelling' });
|
||||
};
|
||||
|
||||
// TODO: This makes scrolling painfully slow
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { commandsManager } from './../App.js';
|
||||
|
||||
import './ToolContextMenu.css';
|
||||
import { ContextMenu } from '@ohif/ui';
|
||||
|
||||
const toolTypes = [
|
||||
'Angle',
|
||||
@@ -24,11 +24,13 @@ const ToolContextMenu = ({
|
||||
}) => {
|
||||
const defaultDropdownItems = [
|
||||
{
|
||||
label: 'Delete measurement',
|
||||
actionType: 'Delete',
|
||||
action: ({ nearbyToolData, eventData }) =>
|
||||
onDelete(nearbyToolData, eventData),
|
||||
},
|
||||
{
|
||||
label: 'Relabel',
|
||||
actionType: 'setLabel',
|
||||
action: ({ nearbyToolData, eventData }) => {
|
||||
const { tool: measurementData } = nearbyToolData;
|
||||
@@ -51,7 +53,10 @@ const ToolContextMenu = ({
|
||||
availableToolTypes: toolTypes,
|
||||
});
|
||||
|
||||
// Annotate tools for touch events already have a press handle to edit it, has a better UX for deleting it
|
||||
/*
|
||||
* Annotate tools for touch events already have a press handle to edit it,
|
||||
* has a better UX for deleting it.
|
||||
*/
|
||||
if (
|
||||
isTouchEvent &&
|
||||
nearbyToolData &&
|
||||
@@ -63,21 +68,10 @@ const ToolContextMenu = ({
|
||||
let dropdownItems = [];
|
||||
if (nearbyToolData) {
|
||||
defaultDropdownItems.forEach(item => {
|
||||
item.params = {
|
||||
eventData,
|
||||
nearbyToolData,
|
||||
};
|
||||
|
||||
if (item.actionType === 'Delete') {
|
||||
item.text = 'Delete measurement';
|
||||
}
|
||||
|
||||
if (item.actionType === 'setLabel') {
|
||||
item.text = 'Relabel';
|
||||
}
|
||||
item.params = { eventData, nearbyToolData };
|
||||
|
||||
if (item.actionType === 'setDescription') {
|
||||
item.text = `${
|
||||
item.label = `${
|
||||
nearbyToolData.tool.description ? 'Edit' : 'Add'
|
||||
} Description`;
|
||||
}
|
||||
@@ -89,7 +83,7 @@ const ToolContextMenu = ({
|
||||
return dropdownItems;
|
||||
};
|
||||
|
||||
const itemOnClickHandler = (action, params, onClose) => {
|
||||
const onClickHandler = ({ action, params }) => {
|
||||
action(params);
|
||||
if (onClose) {
|
||||
onClose();
|
||||
@@ -99,23 +93,9 @@ const ToolContextMenu = ({
|
||||
const dropdownItems = getDropdownItems(eventData, isTouchEvent);
|
||||
|
||||
return (
|
||||
dropdownItems.length &&
|
||||
eventData && (
|
||||
<div className="ToolContextMenu">
|
||||
<ul className="bounded">
|
||||
{dropdownItems.map(({ params, action, text, actionType }) => (
|
||||
<li key={actionType}>
|
||||
<button
|
||||
className="form-action"
|
||||
onClick={() => itemOnClickHandler(action, params, onClose)}
|
||||
>
|
||||
<span key={actionType}>{text}</span>
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
<div className="ToolContextMenu">
|
||||
<ContextMenu items={dropdownItems} onClick={onClickHandler} />;
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -123,6 +103,9 @@ ToolContextMenu.propTypes = {
|
||||
isTouchEvent: PropTypes.bool.isRequired,
|
||||
eventData: PropTypes.object,
|
||||
onClose: PropTypes.func,
|
||||
onSetDescription: PropTypes.func,
|
||||
onSetLabel: PropTypes.func,
|
||||
onDelete: PropTypes.func,
|
||||
};
|
||||
|
||||
ToolContextMenu.defaultProps = {
|
||||
|
||||
@@ -6441,10 +6441,10 @@ cyclist@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
|
||||
integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=
|
||||
|
||||
cypress@^3.3.1:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/cypress/-/cypress-3.5.0.tgz#e188bc8f48782953f6865d8830a4dc342334b81c"
|
||||
integrity sha512-I1iSReD2C8CTP6s4BvQky4gEqHBnKLmhBIqFyCUZdj6BQ6ZDxGnmIbQPM5g79E2iP60KTIbTK99ZPSDVtsNUUg==
|
||||
cypress@^3.8.0:
|
||||
version "3.8.0"
|
||||
resolved "https://registry.yarnpkg.com/cypress/-/cypress-3.8.0.tgz#7d4cd08f81f9048ee36760cc9ee3b9014f9e84ab"
|
||||
integrity sha512-gtEbqCgKETRc3pQFMsELRgIBNgiQg7vbOWTrCi7WE7bgOwNCaW9PEX8Jb3UN8z/maIp9WwzoFfeySfelYY7nRA==
|
||||
dependencies:
|
||||
"@cypress/listr-verbose-renderer" "0.4.1"
|
||||
"@cypress/xvfb" "1.2.4"
|
||||
|
||||
Reference in new issue
Block a user