Compare commits

...
Author SHA1 Message Date
ohif-bot ded2e61913 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.1.4
 - @ohif/core@2.1.1
 - @ohif/ui@1.1.4
 - @ohif/viewer@3.1.8
2019-12-16 14:28:33 +00:00
Igor Octaviano b58aa4575a refactor: Remove LabelingFlow/ContextMenu services and reimplement on top of the Dialog service #1264 (#1292)
* Remove ContextMenu and Labelling services and provider to use DialogService directly

* Refactor init to display labelling

* Cleanup labelling flow in measurements init

* Cleanup viewer imports

* Add classname to toolcontextmenu

* Add error message
2019-12-16 09:19:40 -05:00
Danny Brown f78abe2afa docs: stub viewer configuration 2019-12-16 09:15:10 -05:00
Danny Brown f48157117f docs: hide empty pages 2019-12-16 09:00:24 -05:00
Danny Brown 6f8bd0dff8 docs: fix scope of diagram path 2019-12-16 08:57:43 -05:00
ohif-bot 943af89df2 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.1.3
 - @ohif/ui@1.1.3
 - @ohif/viewer@3.1.7
2019-12-13 15:14:42 +00:00
Danny Brown cd2da349e5 fix: allow empty values for dimensions (#1295)
* fix: allow empty values for dimensions

* fix: consistently use `defaultSize` for intial values
2019-12-13 10:09:18 -05:00
ohif-bot 14e86dcddd chore(release): publish [skip ci]
- @ohif/viewer@3.1.6
2019-12-13 04:45:56 +00:00
Danny Brown 2c8b93656d ci: fix webgl e2e test failures (only on CI) (#1296)
* ci: update executor and browser flag for circleci orbs

* ci: browser flag when running percy/ci tests

* ci: limit ourselves to the executors defined by the cypress orb for now
2019-12-12 23:43:22 -05:00
ohif-bot 6932468206 chore(release): publish [skip ci]
- @ohif/viewer@3.1.5
2019-12-13 02:19:44 +00:00
Danny Brown 7bc9efb0f5 ci: store cypress videos as artifacts; make sure we upload them to dashboard
* fix: Set VTK viewport as active by interaction

* chore: upgrade cypress to latest

* Revert "fix: Set VTK viewport as active by interaction"

This reverts commit b6e174c2def9d8f688a54d32053679788b9aaf76.

* ci: add record flag for master cypress runs

* ci: simplify cypress orb tasks now that cypress@3.8 uses electron w/ chromium 78

* ci: remove video false flag

* ci: drop browser flag

* ci: store video artifacts
2019-12-12 21:17:35 -05:00
ohif-bot 5d47cc43c1 chore(release): publish [skip ci]
- @ohif/viewer@3.1.4
2019-12-13 01:26:39 +00:00
Danny Brown e46a11a01e ci: upgrade cypress version (#1293)
* fix: Set VTK viewport as active by interaction

* chore: upgrade cypress to latest

* Revert "fix: Set VTK viewport as active by interaction"

This reverts commit b6e174c2def9d8f688a54d32053679788b9aaf76.

* ci: add record flag for master cypress runs
2019-12-12 20:21:27 -05:00
ohif-bot 27a950d956 chore(release): publish [skip ci]
- @ohif/viewer@3.1.3
2019-12-12 18:11:14 +00:00
Danny Brown 03d4c10adc chore: remove unused file 2019-12-12 13:08:30 -05:00
Mirna Silva 7a0c369006 test: PT/CT json, studies, series, frames, metadatas (#1145)
* Tests for routes: PT/CT json, studies, series, frames, metadatas

* Fix based on reviews and JSON file for PT/CT Study added

* chore: revert to remote JSON file

* chore: delete PTCTStudy.json file

We can circle back to this approach when/if we have issues. No need to hold-up this PR. My fault on this one.
2019-12-12 01:05:45 -05:00
ohif-bot 8c946a4742 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.1.2
 - @ohif/i18n@0.52.3
 - @ohif/ui@1.1.2
 - @ohif/viewer@3.1.2
2019-12-12 05:54:34 +00:00
Rodrigo Antinarelli 30b9e44220 fix: translations (#1234) 2019-12-12 00:46:58 -05:00
ohif-bot 380abe2705 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.1.1
 - @ohif/ui@1.1.1
 - @ohif/viewer@3.1.1
2019-12-11 16:48:26 +00:00
Mirna Silva f4570c96c2 tests: Download Image File Modal (#1219)
* E2E Tests: Download Image File Modal

* CI fix

* Fixes for PR comments

* Small change just to check CI behavior

* Fix for CI failure: comparing classes instead of image src

* Fixed Download button test and blocked Download Dialog event

* Deleting file ConnectedViewportDownloadForm.js

This file was not supposed to be created by this PR. It was already deleted by another user on Master branch.

* Removing wrong import line

* Fixes based on PR comments

* Fixes based on PR comments

* test: remove assignment to window
2019-12-11 11:46:10 -05:00
51 changed files with 705 additions and 1903 deletions

No files matched your search

+14 -27
View File
@@ -13,14 +13,7 @@ version: 2.1
## ##
orbs: orbs:
codecov: codecov/codecov@1.0.5 codecov: codecov/codecov@1.0.5
cypress: cypress-io/cypress@1.11.0 cypress: cypress-io/cypress@1.13.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/
defaults: &defaults defaults: &defaults
docker: docker:
@@ -341,6 +334,8 @@ workflows:
post-steps: post-steps:
- store_artifacts: - store_artifacts:
path: platform/viewer/cypress/screenshots path: platform/viewer/cypress/screenshots
- store_artifacts:
path: platform/viewer/cypress/videos
requires: requires:
- UNIT_TESTS - UNIT_TESTS
# E2E: script-tag # E2E: script-tag
@@ -364,6 +359,8 @@ workflows:
post-steps: post-steps:
- store_artifacts: - store_artifacts:
path: platform/viewer/cypress/screenshots path: platform/viewer/cypress/screenshots
- store_artifacts:
path: platform/viewer/cypress/videos
requires: requires:
- UNIT_TESTS - UNIT_TESTS
@@ -375,6 +372,7 @@ workflows:
- cypress/run: - cypress/run:
name: 'Generate Percy Snapshots' name: 'Generate Percy Snapshots'
executor: cypress/browsers-chrome76 executor: cypress/browsers-chrome76
browser: chrome
pre-steps: pre-steps:
- run: 'rm -rf ~/.yarn && npm i -g yarn && yarn -v && yarn global - run: 'rm -rf ~/.yarn && npm i -g yarn && yarn -v && yarn global
add wait-on' # Use yarn latest add wait-on' # Use yarn latest
@@ -389,6 +387,8 @@ workflows:
post-steps: post-steps:
- store_artifacts: - store_artifacts:
path: platform/viewer/cypress/screenshots path: platform/viewer/cypress/screenshots
- store_artifacts:
path: platform/viewer/cypress/videos
requires: requires:
- AWAIT_APPROVAL - AWAIT_APPROVAL
@@ -445,9 +445,11 @@ workflows:
branches: branches:
only: master only: master
# Update base branch snapshots # Update base branch snapshots
# and record a Cypress dashboard test run
- cypress/run: - cypress/run:
name: 'Generate Percy Snapshots' name: 'Generate Percy Snapshots'
executor: cypress/browsers-chrome76 executor: cypress/browsers-chrome76
browser: chrome
pre-steps: pre-steps:
- run: 'rm -rf ~/.yarn && npm i -g yarn && yarn -v && yarn global - run: 'rm -rf ~/.yarn && npm i -g yarn && yarn -v && yarn global
add wait-on' # Use yarn latest add wait-on' # Use yarn latest
@@ -462,28 +464,13 @@ workflows:
post-steps: post-steps:
- store_artifacts: - store_artifacts:
path: platform/viewer/cypress/screenshots path: platform/viewer/cypress/screenshots
- store_artifacts:
path: platform/viewer/cypress/videos
- store_test_results:
path: platform/viewer/cypress/results
filters: filters:
branches: branches:
only: master only: master
- DOCKER_MASTER_PUBLISH: - DOCKER_MASTER_PUBLISH:
requires: requires:
- NPM_PUBLISH - 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
-2
View File
@@ -1,7 +1,6 @@
# OHIF Viewers # OHIF Viewers
- [Our Process](our-process.md) - [Our Process](our-process.md)
- [Quick Start](quick-start.md)
- Development - Development
- [Getting Started](development/getting-started.md) - [Getting Started](development/getting-started.md)
- [Contributing](development/contributing.md) - [Contributing](development/contributing.md)
@@ -31,7 +30,6 @@
- [OHIF Maintained](extensions/index.md#maintained-extensions) - [OHIF Maintained](extensions/index.md#maintained-extensions)
- [Services](services/index.md) - [Services](services/index.md)
- [Default](services/default/index.md) - [Default](services/default/index.md)
- [Measurements](services/default/measurements.md)
- [UI](services/ui/index.md) - [UI](services/ui/index.md)
- [Dialog Service](services/ui/ui-dialog-service.md) - [Dialog Service](services/ui/ui-dialog-service.md)
- [Modal Service](services/ui/ui-modal-service.md) - [Modal Service](services/ui/ui-modal-service.md)
+1 -1
View File
@@ -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 has been built to be highly configurable to work with almost any web accessible
data source. data source.
![scope-of-project diagram](./assets/img/scope-of-project.png) ![scope-of-project diagram](./../assets/img/scope-of-project.png)
To be more specific, the OHIF Viewer is a collection of HTML, JS, and CSS files. 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: These can be delivered to your end users however you would like:
+54 -1
View File
@@ -1,3 +1,56 @@
# Viewer: Configuration # 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 -->
+32
View File
@@ -3,6 +3,38 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.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.1.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.0.2...@ohif/extension-vtk@1.1.0) (2019-12-11)
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-vtk", "name": "@ohif/extension-vtk",
"version": "1.1.0", "version": "1.1.4",
"description": "OHIF extension for VTK.js", "description": "OHIF extension for VTK.js",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -52,8 +52,8 @@
"react-vtkjs-viewport": "^0.3.9" "react-vtkjs-viewport": "^0.3.9"
}, },
"devDependencies": { "devDependencies": {
"@ohif/core": "^2.1.0", "@ohif/core": "^2.1.1",
"@ohif/ui": "^1.1.0", "@ohif/ui": "^1.1.4",
"cornerstone-tools": "^4.8.0", "cornerstone-tools": "^4.8.0",
"cornerstone-wado-image-loader": "^3.0.0", "cornerstone-wado-image-loader": "^3.0.0",
"dicom-parser": "^1.8.3", "dicom-parser": "^1.8.3",
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [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) # [2.1.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.0.2...@ohif/core@2.1.0) (2019-12-11)
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/core", "name": "@ohif/core",
"version": "2.1.0", "version": "2.1.1",
"description": "Generic business logic for web-based medical imaging applications", "description": "Generic business logic for web-based medical imaging applications",
"author": "OHIF Core Team", "author": "OHIF Core Team",
"license": "MIT", "license": "MIT",
-6
View File
@@ -23,8 +23,6 @@ import {
UINotificationService, UINotificationService,
UIModalService, UIModalService,
UIDialogService, UIDialogService,
UIContextMenuService,
UILabellingFlowService,
} from './services'; } from './services';
const OHIF = { const OHIF = {
@@ -55,8 +53,6 @@ const OHIF = {
UINotificationService, UINotificationService,
UIModalService, UIModalService,
UIDialogService, UIDialogService,
UIContextMenuService,
UILabellingFlowService,
}; };
export { export {
@@ -86,8 +82,6 @@ export {
UINotificationService, UINotificationService,
UIModalService, UIModalService,
UIDialogService, UIDialogService,
UIContextMenuService,
UILabellingFlowService,
}; };
export { OHIF }; export { OHIF };
-2
View File
@@ -13,8 +13,6 @@ describe('Top level exports', () => {
'UINotificationService', 'UINotificationService',
'UIModalService', 'UIModalService',
'UIDialogService', 'UIDialogService',
'UIContextMenuService',
'UILabellingFlowService',
// //
'utils', 'utils',
'studies', '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;
},
};
-4
View File
@@ -2,14 +2,10 @@ import ServicesManager from './ServicesManager.js';
import UINotificationService from './UINotificationService'; import UINotificationService from './UINotificationService';
import UIModalService from './UIModalService'; import UIModalService from './UIModalService';
import UIDialogService from './UIDialogService'; import UIDialogService from './UIDialogService';
import UIContextMenuService from './UIContextMenuService';
import UILabellingFlowService from './UILabellingFlowService';
export { export {
UINotificationService, UINotificationService,
UIModalService, UIModalService,
UIDialogService, UIDialogService,
UIContextMenuService,
UILabellingFlowService,
ServicesManager, ServicesManager,
}; };
+11
View File
@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.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) ## [0.52.2](https://github.com/OHIF/Viewers/compare/@ohif/i18n@0.52.1...@ohif/i18n@0.52.2) (2019-11-28)
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/i18n", "name": "@ohif/i18n",
"version": "0.52.2", "version": "0.52.3",
"description": "Internationalization library for The OHIF Viewer", "description": "Internationalization library for The OHIF Viewer",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -10,6 +10,7 @@
"Clear": "Clear", "Clear": "Clear",
"Coronal": "Coronal", "Coronal": "Coronal",
"Crosshairs": "Crosshairs", "Crosshairs": "Crosshairs",
"Download": "Download",
"Ellipse": "Ellipse", "Ellipse": "Ellipse",
"Elliptical": "Elliptical", "Elliptical": "Elliptical",
"Flip H": "Flip H", "Flip H": "Flip H",
+10 -9
View File
@@ -10,18 +10,19 @@
"Clear": "Limpiar", "Clear": "Limpiar",
"Coronal": "Coronal", "Coronal": "Coronal",
"Crosshairs": "Punto de mira", "Crosshairs": "Punto de mira",
"Download": "Descargar",
"Ellipse": "Elipse", "Ellipse": "Elipse",
"Elliptical": "Elíptico", "Elliptical": "Elíptico",
"Flip H": "Espejo H", "Flip H": "Voltear H",
"Flip V": "Espejo V", "Flip V": "Voltear V",
"Freehand": "Mano alzada", "Freehand": "Mano alzada",
"Invert": "Invertir", "Invert": "Negativo",
"Layout": "$t(Common:Layout)", "Layout": "$t(Common:Layout)",
"Length": "Longitud", "Length": "Longitud",
"Levels": "Niveles", "Levels": "W/L",
"Magnify": "Aumentar", "Magnify": "Lupa",
"Manual": "Manual", "Manual": "Manual",
"Measurements": "Mediciones", "Measurements": "Medidas",
"More": "$t(Common:More)", "More": "$t(Common:More)",
"Next": "$t(Common:Next)", "Next": "$t(Common:Next)",
"Pan": "Mover", "Pan": "Mover",
@@ -32,11 +33,11 @@
"Rectangle": "Rectángulo", "Rectangle": "Rectángulo",
"Reset": "$t(Common:Reset)", "Reset": "$t(Common:Reset)",
"Reset to Defaults": "$t(Common:Reset) por defecto", "Reset to Defaults": "$t(Common:Reset) por defecto",
"Rotate Right": "Rotar ->", "Rotate Right": "Girar ->",
"Sagittal": "Sagital", "Sagittal": "Sagital",
"Save": "Guardar", "Save": "Guardar",
"Stack Scroll": "Scroll en pila", "Stack Scroll": "Scroll",
"Stop": "$t(Common:Stop)", "Stop": "$t(Common:Stop)",
"Themes": "Temas", "Themes": "Temas",
"Zoom": "Zoom" "Zoom": "Ampliar"
} }
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"Image": "Imagen", "Image": "Imagen",
"Layout": "Disposición", "Layout": "Formato",
"Measurements": "Medidas", "Measurements": "Medidas",
"More": "Más", "More": "Más",
"Next": "Siguiente", "Next": "Siguiente",
"Play": "Play", "Play": "Play",
"Previous": "Anterior", "Previous": "Anterior",
"Reset": "Resetear", "Reset": "Restaurar",
"RowsPerPage": "filas por página", "RowsPerPage": "filas por página",
"Series": "Secuencia", "Series": "Secuencia",
"Show": "Mostrar", "Show": "Mostrar",
+3 -2
View File
@@ -10,6 +10,7 @@
"Clear": "Limpar", "Clear": "Limpar",
"Coronal": "Coronal", "Coronal": "Coronal",
"Crosshairs": "Localizador", "Crosshairs": "Localizador",
"Download": "Baixar",
"Ellipse": "Elipse", "Ellipse": "Elipse",
"Elliptical": "Elíptico", "Elliptical": "Elíptico",
"Flip H": "Inverter H", "Flip H": "Inverter H",
@@ -30,8 +31,8 @@
"Probe": "Prova", "Probe": "Prova",
"ROI Window": "Janela ROI", "ROI Window": "Janela ROI",
"Rectangle": "Retângulo", "Rectangle": "Retângulo",
"Reset": "Resetar", "Reset": "$t(Common:Reset)",
"Reset to Defaults": "Restaurar para o Padrão", "Reset to Defaults": "$t(Common:Reset) para o Padrão",
"Rotate Right": "Girar à direita", "Rotate Right": "Girar à direita",
"Sagittal": "Sagital", "Sagittal": "Sagital",
"Save": "Salvar", "Save": "Salvar",
+1 -1
View File
@@ -6,6 +6,6 @@
"Next": "Próximo", "Next": "Próximo",
"Play": "Play", "Play": "Play",
"Previous": "Anterior", "Previous": "Anterior",
"Reset": "Reset", "Reset": "Restaurar",
"Stop": "Stop" "Stop": "Stop"
} }
@@ -1,6 +1,6 @@
{ {
"Cancel": "Cancelar", "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.", "ResetDefaultMessage": "Preferências resetadas com sucesso. <br /> Você deve <strong>Salvar</strong> para que essa ação seja realizada.",
"Save": "Salvar", "Save": "Salvar",
"SaveMessage": "Preferências salvas", "SaveMessage": "Preferências salvas",
+38
View File
@@ -3,6 +3,44 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.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.1.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.0.1...@ohif/ui@1.1.0) (2019-12-11)
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/ui", "name": "@ohif/ui",
"version": "1.1.0", "version": "1.1.4",
"description": "A set of React components for Medical Imaging Viewers", "description": "A set of React components for Medical Imaging Viewers",
"author": "OHIF Contributors", "author": "OHIF Contributors",
"license": "MIT", "license": "MIT",
@@ -32,69 +32,52 @@ const ViewportDownloadForm = ({
maximumSize, maximumSize,
canvasClass, canvasClass,
}) => { }) => {
const [t] = useTranslation('ViewportDownloadForm'); const [t] = useTranslation('Buttons');
const [filename, setFilename] = useState(DEFAULT_FILENAME); const [filename, setFilename] = useState(DEFAULT_FILENAME);
const [fileType, setFileType] = useState('jpg'); const [fileType, setFileType] = useState('jpg');
const [height, setHeight] = useState(defaultSize); const [dimensions, setDimensions] = useState({
const [width, setWidth] = useState(defaultSize); width: defaultSize,
height: defaultSize,
});
const [showAnnotations, setShowAnnotations] = useState(true); const [showAnnotations, setShowAnnotations] = useState(true);
const [keepAspect, setKeepAspect] = useState(true);
const [lastImage, setLastImage] = useState();
const [viewportElement, setViewportElement] = useState(); const [viewportElement, setViewportElement] = useState();
const [viewportElementHeight, setViewportElementHeight] = useState( const [viewportElementDimensions, setViewportElementDimensions] = useState({
minimumSize width: defaultSize,
); height: defaultSize,
const [viewportElementWidth, setViewportElementWidth] = useState(minimumSize); });
const [downloadCanvas, setDownloadCanvas] = useState({ const [downloadCanvas, setDownloadCanvas] = useState({
ref: createRef(), ref: createRef(),
width: minimumSize, width: defaultSize,
height: minimumSize, height: defaultSize,
}); });
const [viewportPreview, setViewportPreview] = useState({ const [viewportPreview, setViewportPreview] = useState({
src: null, src: null,
width: minimumSize, width: defaultSize,
height: minimumSize, height: defaultSize,
}); });
// Cornerstone's `enable/disable`
useEffect(() => { useEffect(() => {
enableViewport(viewportElement); enableViewport(viewportElement);
return () => { return () => {
disableViewport(viewportElement); disableViewport(viewportElement);
setHeight(defaultSize);
setWidth(defaultSize);
}; };
}, [defaultSize, disableViewport, enableViewport, viewportElement]); }, [disableViewport, enableViewport, viewportElement]);
useEffect(() => { useEffect(() => {
const { width, height } = viewportElementDimensions;
const validSize = value => (value >= minimumSize ? value : minimumSize); const validSize = value => (value >= minimumSize ? value : minimumSize);
const loadAndUpdateViewports = async () => { const loadAndUpdateViewports = async () => {
const { await loadImage(activeViewport, viewportElement, width, height);
image,
width: scaledWidth,
height: scaledHeight,
} = await loadImage(activeViewport, viewportElement, width, height);
setLastImage(image);
toggleAnnotations(showAnnotations, viewportElement); toggleAnnotations(showAnnotations, viewportElement);
setViewportElementHeight(validSize(scaledHeight));
setViewportElementWidth(validSize(scaledWidth));
setDownloadCanvas(state => ({
...state,
height: validSize(scaledHeight),
width: validSize(scaledWidth),
}));
const { const {
dataUrl, dataUrl,
width: viewportElementWidth, width: viewportElementWidth,
@@ -118,8 +101,6 @@ const ViewportDownloadForm = ({
activeViewport, activeViewport,
viewportElement, viewportElement,
showAnnotations, showAnnotations,
height,
width,
loadImage, loadImage,
toggleAnnotations, toggleAnnotations,
updateViewportPreview, updateViewportPreview,
@@ -127,54 +108,41 @@ const ViewportDownloadForm = ({
downloadCanvas.ref, downloadCanvas.ref,
minimumSize, minimumSize,
maximumSize, maximumSize,
viewportElementDimensions,
]); ]);
const onHeightChange = event => { /**
const newHeight = Math.min(event.target.value, maximumSize); * @param {object} event - Input change event
setHeight(newHeight); * @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); if (updatedDimension === dimensions.width) {
return;
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,
}));
} }
};
const onWidthChange = event => { // In current code, keepAspect is always `true`
const newWidth = Math.min(event.target.value, maximumSize); // And we always start w/ a square width/height
setWidth(newWidth); setDimensions({
width: updatedDimension,
setViewportElementWidth(newWidth); height: updatedDimension,
});
setDownloadCanvas(state => ({
...state,
width: newWidth,
}));
if (keepAspect) {
const multiplier = newWidth / lastImage.width;
const newHeight = Math.round(lastImage.height * multiplier);
setHeight(newHeight);
setViewportElementHeight(newHeight);
// Only update if value is non-empty
if (!isEmpty) {
setViewportElementDimensions({
height: updatedDimension,
width: updatedDimension,
});
setDownloadCanvas(state => ({ setDownloadCanvas(state => ({
...state, ...state,
height: newHeight, height: updatedDimension,
width: updatedDimension,
})); }));
} }
}; };
@@ -196,26 +164,22 @@ const ViewportDownloadForm = ({
)} )}
</div> </div>
<div className="file-info-container"> <div className="file-info-container" data-cy="file-info-container">
<div className="col"> <div className="col">
<div className="width"> <div className="width">
<TextInput <TextInput
type="number" data-cy="image-width"
min={minimumSize} value={dimensions.width}
max={maximumSize}
value={width}
label={t('Image width (px)')} label={t('Image width (px)')}
onChange={onWidthChange} onChange={evt => onDimensionsChange(evt, 'height')}
/> />
</div> </div>
<div className="height"> <div className="height">
<TextInput <TextInput
type="number" data-cy="image-height"
min={minimumSize} value={dimensions.height}
max={maximumSize}
value={height}
label={t('Image height (px)')} label={t('Image height (px)')}
onChange={onHeightChange} onChange={evt => onDimensionsChange(evt, 'width')}
/> />
</div> </div>
</div> </div>
@@ -224,6 +188,7 @@ const ViewportDownloadForm = ({
<div className="file-name"> <div className="file-name">
<TextInput <TextInput
type="text" type="text"
data-cy="file-name"
value={filename} value={filename}
onChange={event => setFilename(event.target.value)} onChange={event => setFilename(event.target.value)}
label={t('File name')} label={t('File name')}
@@ -233,6 +198,7 @@ const ViewportDownloadForm = ({
<div className="file-type"> <div className="file-type">
<Select <Select
value={fileType} value={fileType}
data-cy="file-type"
onChange={event => setFileType(event.target.value)} onChange={event => setFileType(event.target.value)}
options={FILE_TYPE_OPTIONS} options={FILE_TYPE_OPTIONS}
label={t('File type')} label={t('File type')}
@@ -245,6 +211,7 @@ const ViewportDownloadForm = ({
<label htmlFor="show-annotations" className="form-check-label"> <label htmlFor="show-annotations" className="form-check-label">
<input <input
id="show-annotations" id="show-annotations"
data-cy="show-annotations"
type="checkbox" type="checkbox"
className="form-check-input" className="form-check-input"
checked={showAnnotations} checked={showAnnotations}
@@ -258,8 +225,8 @@ const ViewportDownloadForm = ({
<div <div
style={{ style={{
height: viewportElementHeight, height: viewportElementDimensions.height,
width: viewportElementWidth, width: viewportElementDimensions.width,
position: 'absolute', position: 'absolute',
left: '9999px', left: '9999px',
}} }}
@@ -278,23 +245,33 @@ const ViewportDownloadForm = ({
></canvas> ></canvas>
</div> </div>
<div className="preview"> <div className="preview" data-cy="image-preview">
<h4> {t('Image Preview')}</h4> <h4> {t('Image Preview')}</h4>
<img <img
className="viewport-preview" className="viewport-preview"
src={viewportPreview.src} src={viewportPreview.src}
alt="Viewport Preview" alt="Viewport Preview"
data-cy="viewport-preview-img"
/> />
</div> </div>
<div className="actions"> <div className="actions">
<div className="action-cancel"> <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')} {t('Cancel')}
</button> </button>
</div> </div>
<div className="action-save"> <div className="action-save">
<button onClick={downloadImage} className="btn btn-primary"> <button
onClick={downloadImage}
className="btn btn-primary"
data-cy="download-btn"
>
{t('Download')} {t('Download')}
</button> </button>
</div> </div>
@@ -312,6 +289,7 @@ ViewportDownloadForm.propTypes = {
toggleAnnotations: PropTypes.func.isRequired, toggleAnnotations: PropTypes.func.isRequired,
loadImage: PropTypes.func.isRequired, loadImage: PropTypes.func.isRequired,
downloadBlob: PropTypes.func.isRequired, downloadBlob: PropTypes.func.isRequired,
/** A default width & height, between the minimum and maximum size */
defaultSize: PropTypes.number.isRequired, defaultSize: PropTypes.number.isRequired,
minimumSize: PropTypes.number.isRequired, minimumSize: PropTypes.number.isRequired,
maximumSize: PropTypes.number.isRequired, maximumSize: PropTypes.number.isRequired,
@@ -1,20 +1,19 @@
.ToolContextMenu { .ContextMenu {
position: relative; position: relative;
background-color: white; background-color: white;
border: 1px solid white;
border-radius: 5px; border-radius: 5px;
z-index: 1000; z-index: 1000;
display: block; display: block;
width: 170px; width: 170px;
} }
.ToolContextMenu > ul { .ContextMenu>ul {
list-style-type: none; list-style-type: none;
padding-left: 0; padding-left: 0;
margin: 0; margin: 0;
} }
.ToolContextMenu > ul > li > button { .ContextMenu>ul>li>button {
padding: 10px; padding: 10px;
font-size: 14px; font-size: 14px;
border: none; border: none;
@@ -25,10 +24,10 @@
background: none; background: none;
} }
.ToolContextMenu > ul > li > button:hover { .ContextMenu>ul>li>button:hover {
color: #16202b; color: #16202b;
} }
.ToolContextMenu > ul > li > button:active { .ContextMenu>ul>li>button:active {
color: #79f9fe; 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';
+2
View File
@@ -17,6 +17,7 @@ import { RoundedButtonGroup } from './roundedButtonGroup';
import { SelectTree } from './selectTree'; import { SelectTree } from './selectTree';
import { SimpleDialog } from './simpleDialog'; import { SimpleDialog } from './simpleDialog';
import { OHIFModal } from './ohifModal'; import { OHIFModal } from './ohifModal';
import { ContextMenu } from './contextMenu';
import { import {
PageToolbar, PageToolbar,
StudyList, StudyList,
@@ -27,6 +28,7 @@ import { ToolbarSection } from './toolbarSection';
import { Tooltip } from './tooltip'; import { Tooltip } from './tooltip';
export { export {
ContextMenu,
Checkbox, Checkbox,
CineDialog, CineDialog,
ViewportDownloadForm, ViewportDownloadForm,
@@ -29,7 +29,7 @@ const OHIFModal = ({
const renderHeader = () => { const renderHeader = () => {
return ( return (
title && ( title && (
<div className="OHIFModal__header"> <div className="OHIFModal__header" data-cy="modal-header">
<h4>{title}</h4> <h4>{title}</h4>
{closeButton && ( {closeButton && (
<button data-cy="close-button" onClick={onClose}> <button data-cy="close-button" onClick={onClose}>
@@ -44,6 +44,7 @@ const OHIFModal = ({
return ( return (
<Modal <Modal
className={classNames('OHIFModal', className)} className={classNames('OHIFModal', className)}
data-cy="modal"
shouldCloseOnEsc={shouldCloseOnEsc} shouldCloseOnEsc={shouldCloseOnEsc}
isOpen={isOpen} isOpen={isOpen}
title={title} title={title}
@@ -51,7 +52,9 @@ const OHIFModal = ({
> >
<> <>
{renderHeader()} {renderHeader()}
<div className="OHIFModal__content">{children}</div> <div className="OHIFModal__content" data-cy="modal-content">
{children}
</div>
</> </>
</Modal> </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;
-12
View File
@@ -18,15 +18,3 @@ export {
withDialog, withDialog,
useDialog, useDialog,
} from './DialogProvider.js'; } from './DialogProvider.js';
export {
default as ContextMenuProvider,
withContextMenu,
useContextMenu,
ContextMenuConsumer,
} from './ContextMenuProvider.js';
export {
default as LabellingFlowProvider,
withLabellingFlow,
useLabellingFlow,
LabellingFlowConsumer,
} from './LabellingFlowProvider.js';
+2 -16
View File
@@ -1,4 +1,5 @@
import { import {
ContextMenu,
Checkbox, Checkbox,
CineDialog, CineDialog,
ViewportDownloadForm, ViewportDownloadForm,
@@ -60,14 +61,6 @@ import {
ModalConsumer, ModalConsumer,
useModal, useModal,
withModal, withModal,
ContextMenuProvider,
ContextMenuConsumer,
useContextMenu,
withContextMenu,
LabellingFlowProvider,
LabellingFlowConsumer,
useLabellingFlow,
withLabellingFlow,
} from './contextProviders'; } from './contextProviders';
export { export {
@@ -80,6 +73,7 @@ export {
TextArea, TextArea,
TextInput, TextInput,
CineDialog, CineDialog,
ContextMenu,
ViewportDownloadForm, ViewportDownloadForm,
ExpandableToolMenu, ExpandableToolMenu,
Icon, Icon,
@@ -125,14 +119,6 @@ export {
DialogProvider, DialogProvider,
withDialog, withDialog,
useDialog, useDialog,
ContextMenuProvider,
ContextMenuConsumer,
useContextMenu,
withContextMenu,
LabellingFlowProvider,
LabellingFlowConsumer,
useLabellingFlow,
withLabellingFlow,
// Hooks // Hooks
useDebounce, useDebounce,
useMedia, useMedia,
+64
View File
@@ -3,6 +3,70 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [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) # [3.1.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.0.3...@ohif/viewer@3.1.0) (2019-12-11)
-1
View File
@@ -1,6 +1,5 @@
{ {
"baseUrl": "http://localhost:3000", "baseUrl": "http://localhost:3000",
"video": false,
"chromeWebSecurity": false, "chromeWebSecurity": false,
"waitForAnimations": true, "waitForAnimations": true,
"defaultCommandTimeout": 10000, "defaultCommandTimeout": 10000,
@@ -81,10 +81,8 @@ describe('OHIF Cornerstone Hotkeys', () => {
}); });
it('uses hotkeys "RightArrow" and "LeftArrow" to navigate between multiple viewports', () => { it('uses hotkeys "RightArrow" and "LeftArrow" to navigate between multiple viewports', () => {
//Click on Layout button //Select viewport layout (3,1)
cy.get('@layoutBtn').click(); cy.setLayout(3, 1);
//Select 3 viewports
cy.get('tbody > :nth-child(1) > :nth-child(3)').click();
cy.waitViewportImageLoading(); cy.waitViewportImageLoading();
// Press multiples hotkeys on viewport #1 // 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('.ToolbarRow > :nth-child(10)').as('cineBtn');
cy.get('.expandableToolMenu').as('moreBtn'); cy.get('.expandableToolMenu').as('moreBtn');
cy.get('.btn-group > .toolbar-button').as('layoutBtn'); cy.get('.btn-group > .toolbar-button').as('layoutBtn');
cy.get('.viewport-element').as('viewport');
} }
//Creating aliases for Common page elements //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) => { Cypress.Commands.add('setLanguage', (language, save = true) => {
cy.openPreferences(); cy.openPreferences();
+7 -7
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/viewer", "name": "@ohif/viewer",
"version": "3.1.0", "version": "3.1.8",
"description": "OHIF Viewer", "description": "OHIF Viewer",
"author": "OHIF Contributors", "author": "OHIF Contributors",
"license": "MIT", "license": "MIT",
@@ -27,7 +27,7 @@
"dev:viewer": "yarn run dev", "dev:viewer": "yarn run dev",
"start": "yarn run dev", "start": "yarn run dev",
"test:e2e": "cypress open", "test:e2e": "cypress open",
"test:e2e:ci": "percy exec -- cypress run --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:dist": "start-server-and-test test:e2e:serve http://localhost:3000 test:e2e:ci",
"test:e2e:serve": "serve -n -l 3000 -s dist", "test:e2e:serve": "serve -n -l 3000 -s dist",
"test:unit": "jest --watchAll", "test:unit": "jest --watchAll",
@@ -45,14 +45,14 @@
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.5.5", "@babel/runtime": "^7.5.5",
"@ohif/core": "^2.1.0", "@ohif/core": "^2.1.1",
"@ohif/extension-cornerstone": "^2.1.0", "@ohif/extension-cornerstone": "^2.1.0",
"@ohif/extension-dicom-html": "^1.1.0", "@ohif/extension-dicom-html": "^1.1.0",
"@ohif/extension-dicom-microscopy": "^0.50.6", "@ohif/extension-dicom-microscopy": "^0.50.6",
"@ohif/extension-dicom-pdf": "^1.0.0", "@ohif/extension-dicom-pdf": "^1.0.0",
"@ohif/extension-vtk": "^1.1.0", "@ohif/extension-vtk": "^1.1.4",
"@ohif/i18n": "^0.52.2", "@ohif/i18n": "^0.52.3",
"@ohif/ui": "^1.1.0", "@ohif/ui": "^1.1.4",
"@tanem/react-nprogress": "^1.1.25", "@tanem/react-nprogress": "^1.1.25",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"core-js": "^3.2.1", "core-js": "^3.2.1",
@@ -85,7 +85,7 @@
"vtk.js": "^11.7.2" "vtk.js": "^11.7.2"
}, },
"devDependencies": { "devDependencies": {
"cypress": "^3.3.1", "cypress": "^3.8.0",
"gh-pages": "2.0.1", "gh-pages": "2.0.1",
"identity-obj-proxy": "3.0.x", "identity-obj-proxy": "3.0.x",
"lodash": "4.17.11", "lodash": "4.17.11",
File diff suppressed because it is too large. Load diff
+5 -47
View File
@@ -8,9 +8,6 @@ import { hot } from 'react-hot-loader/root';
import OHIFCornerstoneExtension from '@ohif/extension-cornerstone'; import OHIFCornerstoneExtension from '@ohif/extension-cornerstone';
import ToolContextMenu from './connectedComponents/ToolContextMenu';
import LabellingFlow from './components/Labelling/LabellingFlow';
import { import {
SnackbarProvider, SnackbarProvider,
ModalProvider, ModalProvider,
@@ -18,11 +15,6 @@ import {
OHIFModal, OHIFModal,
} from '@ohif/ui'; } from '@ohif/ui';
import {
LabellingFlowProvider,
ContextMenuProvider,
} from './appCustomProviders';
import { import {
CommandsManager, CommandsManager,
ExtensionManager, ExtensionManager,
@@ -31,8 +23,6 @@ import {
UINotificationService, UINotificationService,
UIModalService, UIModalService,
UIDialogService, UIDialogService,
UIContextMenuService,
UILabellingFlowService,
utils, utils,
redux as reduxOHIF, redux as reduxOHIF,
} from '@ohif/core'; } from '@ohif/core';
@@ -135,13 +125,7 @@ class App extends Component {
} = this._appConfig; } = this._appConfig;
this.initUserManager(oidc); this.initUserManager(oidc);
_initServices([ _initServices([UINotificationService, UIModalService, UIDialogService]);
UINotificationService,
UIModalService,
UIDialogService,
UIContextMenuService,
UILabellingFlowService,
]);
_initExtensions( _initExtensions(
[...defaultExtensions, ...extensions], [...defaultExtensions, ...extensions],
cornerstoneExtensionConfig cornerstoneExtensionConfig
@@ -161,9 +145,7 @@ class App extends Component {
const { const {
UINotificationService, UINotificationService,
UIDialogService, UIDialogService,
UILabellingFlowService,
UIModalService, UIModalService,
UIContextMenuService,
} = servicesManager.services; } = servicesManager.services;
if (this._userManager) { if (this._userManager) {
@@ -181,21 +163,9 @@ class App extends Component {
modal={OHIFModal} modal={OHIFModal}
service={UIModalService} service={UIModalService}
> >
<LabellingFlowProvider <OHIFStandaloneViewer
service={UILabellingFlowService} userManager={this._userManager}
labellingComponent={LabellingFlow} />
commandsManager={commandsManager}
>
<ContextMenuProvider
service={UIContextMenuService}
contextMenuComponent={ToolContextMenu}
commandsManager={commandsManager}
>
<OHIFStandaloneViewer
userManager={this._userManager}
/>
</ContextMenuProvider>
</LabellingFlowProvider>
</ModalProvider> </ModalProvider>
</DialogProvider> </DialogProvider>
</SnackbarProvider> </SnackbarProvider>
@@ -218,19 +188,7 @@ class App extends Component {
<SnackbarProvider service={UINotificationService}> <SnackbarProvider service={UINotificationService}>
<DialogProvider service={UIDialogService}> <DialogProvider service={UIDialogService}>
<ModalProvider modal={OHIFModal} service={UIModalService}> <ModalProvider modal={OHIFModal} service={UIModalService}>
<LabellingFlowProvider <OHIFStandaloneViewer />
service={UILabellingFlowService}
labellingComponent={LabellingFlow}
commandsManager={commandsManager}
>
<ContextMenuProvider
service={UIContextMenuService}
contextMenuComponent={ToolContextMenu}
commandsManager={commandsManager}
>
<OHIFStandaloneViewer />
</ContextMenuProvider>
</LabellingFlowProvider>
</ModalProvider> </ModalProvider>
</DialogProvider> </DialogProvider>
</SnackbarProvider> </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 ConnectedMeasurementTable from './ConnectedMeasurementTable.js';
import init from './init.js'; import init from './init.js';
import LabellingFlow from '../../components/Labelling/LabellingFlow';
export default { export default {
/** /**
* Only required property. Should be a unique value across all extensions. * Only required property. Should be a unique value across all extensions.
@@ -13,32 +15,51 @@ export default {
}, },
getPanelModule({ servicesManager, commandsManager }) { 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 = () => ( const ExtendedConnectedMeasurementTable = () => (
<ConnectedMeasurementTable <ConnectedMeasurementTable
onRelabel={tool => { onRelabel={tool =>
if (UILabellingFlowService) { showLabellingDialog(
UILabellingFlowService.show({ { editLocation: true, skipAddLabelButton: true },
centralize: true, tool
props: { )
skipAddLabelButton: true, }
editLocation: true, onEditDescription={tool =>
measurementData: tool, showLabellingDialog({ editDescriptionOnDialog: true }, tool)
}, }
});
}
}}
onEditDescription={tool => {
if (UILabellingFlowService) {
UILabellingFlowService.show({
centralize: true,
props: {
editDescriptionOnDialog: true,
measurementData: tool,
},
});
}
}}
onSaveComplete={message => { onSaveComplete={message => {
if (UINotificationService) { if (UINotificationService) {
UINotificationService.show(message); UINotificationService.show(message);
@@ -3,6 +3,9 @@ import cornerstone from 'cornerstone-core';
import csTools from 'cornerstone-tools'; import csTools from 'cornerstone-tools';
import throttle from 'lodash.throttle'; import throttle from 'lodash.throttle';
import LabellingFlow from '../../components/Labelling/LabellingFlow';
import ToolContextMenu from '../../connectedComponents/ToolContextMenu';
const { const {
onAdded, onAdded,
onRemoved, onRemoved,
@@ -29,10 +32,7 @@ export default function init({
commandsManager, commandsManager,
configuration, configuration,
}) { }) {
const { const { UIDialogService } = servicesManager.services;
UIContextMenuService,
UILabellingFlowService,
} = servicesManager.services;
// TODO: MEASUREMENT_COMPLETED (not present in initial implementation) // TODO: MEASUREMENT_COMPLETED (not present in initial implementation)
const onMeasurementsChanged = (action, event) => { const onMeasurementsChanged = (action, event) => {
@@ -46,21 +46,108 @@ export default function init({
'labelmapModified' 'labelmapModified'
); );
const onRightClick = event => { const _getDefaultPosition = event => ({
if (UIContextMenuService) { x: (event && event.currentPoints.client.x) || 0,
UIContextMenuService.show({ event: event.detail }); 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 => { const onTouchPress = event => {
if (UIContextMenuService) { if (!UIDialogService) {
UIContextMenuService.show({ console.warn('Unable to show dialog; no UI Dialog Service available.');
event: event.detail, return;
props: {
isTouchEvent: true,
},
});
} }
UIDialogService.create({
eventData: event.detail,
content: ToolContextMenu,
contentProps: {
isTouchEvent: true,
},
});
}; };
const onTouchStart = () => resetLabelligAndContextMenu(); const onTouchStart = () => resetLabelligAndContextMenu();
@@ -68,10 +155,13 @@ export default function init({
const onMouseClick = () => resetLabelligAndContextMenu(); const onMouseClick = () => resetLabelligAndContextMenu();
const resetLabelligAndContextMenu = () => { const resetLabelligAndContextMenu = () => {
if (UILabellingFlowService && UIContextMenuService) { if (!UIDialogService) {
UILabellingFlowService.hide(); console.warn('Unable to show dialog; no UI Dialog Service available.');
UIContextMenuService.hide(); return;
} }
UIDialogService.dismiss({ id: 'context-menu' });
UIDialogService.dismiss({ id: 'labelling' });
}; };
// TODO: This makes scrolling painfully slow // TODO: This makes scrolling painfully slow
@@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { commandsManager } from './../App.js'; import { commandsManager } from './../App.js';
import './ToolContextMenu.css'; import { ContextMenu } from '@ohif/ui';
const toolTypes = [ const toolTypes = [
'Angle', 'Angle',
@@ -24,11 +24,13 @@ const ToolContextMenu = ({
}) => { }) => {
const defaultDropdownItems = [ const defaultDropdownItems = [
{ {
label: 'Delete measurement',
actionType: 'Delete', actionType: 'Delete',
action: ({ nearbyToolData, eventData }) => action: ({ nearbyToolData, eventData }) =>
onDelete(nearbyToolData, eventData), onDelete(nearbyToolData, eventData),
}, },
{ {
label: 'Relabel',
actionType: 'setLabel', actionType: 'setLabel',
action: ({ nearbyToolData, eventData }) => { action: ({ nearbyToolData, eventData }) => {
const { tool: measurementData } = nearbyToolData; const { tool: measurementData } = nearbyToolData;
@@ -51,7 +53,10 @@ const ToolContextMenu = ({
availableToolTypes: toolTypes, 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 ( if (
isTouchEvent && isTouchEvent &&
nearbyToolData && nearbyToolData &&
@@ -63,21 +68,10 @@ const ToolContextMenu = ({
let dropdownItems = []; let dropdownItems = [];
if (nearbyToolData) { if (nearbyToolData) {
defaultDropdownItems.forEach(item => { defaultDropdownItems.forEach(item => {
item.params = { item.params = { eventData, nearbyToolData };
eventData,
nearbyToolData,
};
if (item.actionType === 'Delete') {
item.text = 'Delete measurement';
}
if (item.actionType === 'setLabel') {
item.text = 'Relabel';
}
if (item.actionType === 'setDescription') { if (item.actionType === 'setDescription') {
item.text = `${ item.label = `${
nearbyToolData.tool.description ? 'Edit' : 'Add' nearbyToolData.tool.description ? 'Edit' : 'Add'
} Description`; } Description`;
} }
@@ -89,7 +83,7 @@ const ToolContextMenu = ({
return dropdownItems; return dropdownItems;
}; };
const itemOnClickHandler = (action, params, onClose) => { const onClickHandler = ({ action, params }) => {
action(params); action(params);
if (onClose) { if (onClose) {
onClose(); onClose();
@@ -99,23 +93,9 @@ const ToolContextMenu = ({
const dropdownItems = getDropdownItems(eventData, isTouchEvent); const dropdownItems = getDropdownItems(eventData, isTouchEvent);
return ( return (
dropdownItems.length && <div className="ToolContextMenu">
eventData && ( <ContextMenu items={dropdownItems} onClick={onClickHandler} />;
<div className="ToolContextMenu"> </div>
<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>
)
); );
}; };
@@ -123,6 +103,9 @@ ToolContextMenu.propTypes = {
isTouchEvent: PropTypes.bool.isRequired, isTouchEvent: PropTypes.bool.isRequired,
eventData: PropTypes.object, eventData: PropTypes.object,
onClose: PropTypes.func, onClose: PropTypes.func,
onSetDescription: PropTypes.func,
onSetLabel: PropTypes.func,
onDelete: PropTypes.func,
}; };
ToolContextMenu.defaultProps = { ToolContextMenu.defaultProps = {
+4 -4
View File
@@ -6441,10 +6441,10 @@ cyclist@^1.0.1:
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=
cypress@^3.3.1: cypress@^3.8.0:
version "3.5.0" version "3.8.0"
resolved "https://registry.yarnpkg.com/cypress/-/cypress-3.5.0.tgz#e188bc8f48782953f6865d8830a4dc342334b81c" resolved "https://registry.yarnpkg.com/cypress/-/cypress-3.8.0.tgz#7d4cd08f81f9048ee36760cc9ee3b9014f9e84ab"
integrity sha512-I1iSReD2C8CTP6s4BvQky4gEqHBnKLmhBIqFyCUZdj6BQ6ZDxGnmIbQPM5g79E2iP60KTIbTK99ZPSDVtsNUUg== integrity sha512-gtEbqCgKETRc3pQFMsELRgIBNgiQg7vbOWTrCi7WE7bgOwNCaW9PEX8Jb3UN8z/maIp9WwzoFfeySfelYY7nRA==
dependencies: dependencies:
"@cypress/listr-verbose-renderer" "0.4.1" "@cypress/listr-verbose-renderer" "0.4.1"
"@cypress/xvfb" "1.2.4" "@cypress/xvfb" "1.2.4"