Compare commits

...
Author SHA1 Message Date
ohif-bot 81ad25b39f chore(release): publish [skip ci]
- @ohif/extension-vtk@0.53.6
 - @ohif/ui@0.62.1
 - @ohif/viewer@2.8.2
2019-11-14 03:53:10 +00:00
Igor Octaviano 54d2cde6da refactor: 💡 Improve hotkeyPreferences proptypes / add lowercase (#1165)
* refactor: 💡 Improve hotkeyPreferences proptypes / add lowercase

Improve hotkeyPreferences proptypes and lowercase event keys to guard
against different browsers casings

* Add isRequired to proptypes and empty hotkeys message
2019-11-13 22:42:49 -05:00
ohif-bot 9edc366ff7 chore(release): publish [skip ci]
- @ohif/viewer@2.8.1
2019-11-14 02:48:34 +00:00
Mirna Silva 79c10bb424 test: Visual testing for Cornerstone and VTK (#1169)
* Visual testing for Cornerstone and VTK

* Fix for CI

* Review fix: Removed cornerstone function and added snapshot description

* Added snapshot description

* Fix for CI
2019-11-13 21:36:19 -05:00
ohif-bot d8055e458f chore(release): publish [skip ci]
- @ohif/extension-cornerstone@1.5.0
 - @ohif/extension-vtk@0.53.5
 - @ohif/core@1.9.0
 - @ohif/ui@0.62.0
 - @ohif/viewer@2.8.0
2019-11-13 20:12:25 +00:00
Igor Octaviano 5c04e34c8f feat: expose UiNotifications service (#1172)
* playing around

* Change extension init and preRegistration signature

* Clear test code

* Update core index test

* Fix test

* Tricking the bug

* Renaming file back to trick some weird bug
2019-11-13 15:02:14 -05:00
Egor Lezhnin c59c5b3f14 feat: filter field for google api windows (#1170) 2019-11-13 09:38:49 -05:00
ohif-bot c4adcfd39e chore(release): publish [skip ci]
- @ohif/viewer@2.7.1
2019-11-12 12:23:55 +00:00
ladeirarodolfo 0fbaf95971 fix: 🐛 Fix for JS breaking on header (#1164) 2019-11-12 07:13:59 -05:00
fossabot a2a7e05e7d docs: Add license scan report and status (#1161)
Signed-off-by: fossabot <badges@fossa.io>
2019-11-11 21:43:24 -05:00
ohif-bot 9da8647c84 chore(release): publish [skip ci]
- @ohif/extension-vtk@0.53.4
 - @ohif/core@1.8.0
 - @ohif/ui@0.61.0
 - @ohif/viewer@2.7.0
2019-11-12 02:17:40 +00:00
Igor Octaviano e62f5f8dd2 feat: 🎸 Update hotkeys and user preferences modal (#1135)
* feat: 🎸 Update hotkeys and user preferences modal

This feature fix incompatibility with existent hotkeys component and
adds user preferences modal back

Closes: #923

* Update preferences structure in store

* Hide window level section of user preferences

* Update modal to reflect current hotkey value

* Clone object with hotkeys before passing to manager

* CR Update: Extract hotkeys manager format code to manager

* Fix broken cypress test

* Use new modal provider

* Rename hotkeyDefinitions in hotkeyspreferences and use array as representation

* Update study test and remove unused styles
2019-11-11 21:03:36 -05:00
ohif-bot dc6e9d6bb5 chore(release): publish [skip ci]
- @ohif/extension-vtk@0.53.3
 - @ohif/viewer@2.6.4
2019-11-11 09:48:18 +00:00
Danny Brown 84a097212b fix: mpr2d vtkjs viewport does not render if range is set to NaN values (#1157)
* chore: fix proptypes declaration

* fix: seperate helper method to calculate our initial range

* chore: remove unused import

* chore: update studies propType from object to array
2019-11-11 09:38:36 +00:00
ohif-bot 2381810ba9 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@1.4.1
 - @ohif/extension-vtk@0.53.2
 - @ohif/core@1.7.1
 - @ohif/viewer@2.6.3
2019-11-08 13:07:35 +00:00
Erik Ziegler 31b1adfa59 fix: Add a fallback metadata provider which pulls metadata from WADO-… (#1158)
* fix: Add a fallback metadata provider which pulls metadata from WADO-RS cache when WADO-URI is used.

* cleanup
2019-11-08 13:55:56 +01:00
ohif-bot cd28877ed8 chore(release): publish [skip ci]
- @ohif/extension-vtk@0.53.1
 - @ohif/ui@0.60.1
 - @ohif/viewer@2.6.2
2019-11-08 12:14:44 +00:00
Mirna Silva e81a39f5be Merge pull request #1159 from mirnasilva/fix_studylist
Fix for modalities search result
2019-11-08 09:02:34 -03:00
Mirna Silva 9a2cf70504 Fix for modalities search result 2019-11-08 08:47:32 -03:00
ladeirarodolfo d70eae3eb0 fix: Fix display issues with incorrect thumbnails. Change ImageThumb to functional component. (#1148) 2019-11-08 11:26:31 +01:00
ohif-bot 8481e94d4d chore(release): publish [skip ci]
- @ohif/viewer@2.6.1
2019-11-06 04:56:33 +00:00
Mirna Silva 19b22c39ae test: Study list test cases: Patient Name, MRN, Modality, Description (#1121)
* Study list tests: Patient Name, MRN, Modality, Description

* Fixed field selector

* Desktop tests

* Fix for study list

* Added tablet tests and small refactor on aliases

* Fix based on reviews
2019-11-05 23:46:38 -05:00
74 changed files with 1503 additions and 551 deletions

No files matched your search

+4
View File
@@ -22,6 +22,7 @@
[![NPM downloads][npm-downloads-image]][npm-url]
[![Pulls][docker-pulls-img]][docker-image-url]
[![MIT License][license-image]][license-url]
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FOHIF%2FViewers.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2FOHIF%2FViewers?ref=badge_shield)
[![Netlify Status][netlify-image]][netlify-url]
[![CircleCI][circleci-image]][circleci-url]
@@ -324,3 +325,6 @@ MIT © [OHIF](https://github.com/OHIF)
[extension-vtk]: extensions/vtk/README.md
[vtk-npm]: https://www.npmjs.com/package/@ohif/extension-vtk
<!-- prettier-ignore-end -->
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FOHIF%2FViewers.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FOHIF%2FViewers?ref=badge_large)
+1 -1
View File
@@ -11,7 +11,7 @@ export default {
* LIFECYCLE HOOKS
*/
preRegistration(extensionConfiguration) {},
preRegistration({ serviceManager, configuration: extensionConfiguration }) {},
/**
* MODULE GETTERS
+22
View File
@@ -3,6 +3,28 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.5.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.4.1...@ohif/extension-cornerstone@1.5.0) (2019-11-13)
### Features
* expose UiNotifications service ([#1172](https://github.com/OHIF/Viewers/issues/1172)) ([5c04e34](https://github.com/OHIF/Viewers/commit/5c04e34c8fb2394ab7acd9eb4f2ab12afeb2f255))
## [1.4.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.4.0...@ohif/extension-cornerstone@1.4.1) (2019-11-08)
### Bug Fixes
* Add a fallback metadata provider which pulls metadata from WADO-… ([#1158](https://github.com/OHIF/Viewers/issues/1158)) ([31b1adf](https://github.com/OHIF/Viewers/commit/31b1adfa5993d6c8e3e9c8b03fa9856f2621b037))
# [1.4.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.3.1...@ohif/extension-cornerstone@1.4.0) (2019-10-26)
+2 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone",
"version": "1.4.0",
"version": "1.5.0",
"description": "OHIF extension for Cornerstone",
"author": "OHIF",
"license": "MIT",
@@ -48,6 +48,7 @@
"@babel/runtime": "^7.5.5",
"classnames": "^2.2.6",
"lodash.throttle": "^4.1.1",
"query-string": "^6.8.3",
"react-cornerstone-viewport": "2.x.x"
}
}
+2 -2
View File
@@ -24,8 +24,8 @@ export default {
* @param {object} [configuration={}]
* @param {object|array} [configuration.csToolsConfig] - Passed directly to `initCornerstoneTools`
*/
preRegistration(configuration = {}) {
init(configuration);
preRegistration({ serviceManager, configuration = {} }) {
init({ serviceManager, configuration });
},
getViewportModule() {
return OHIFCornerstoneViewport;
+22 -1
View File
@@ -2,13 +2,33 @@ import OHIF from '@ohif/core';
import cornerstone from 'cornerstone-core';
import csTools from 'cornerstone-tools';
import initCornerstoneTools from './initCornerstoneTools.js';
import queryString from 'query-string';
function fallbackMetaDataProvider(type, imageId) {
if (!imageId.includes('wado?requestType=WADO')) {
return;
}
// If you call for an WADO-URI imageId and get no
// metadata, try reformatting to WADO-RS imageId
const qs = queryString.parse(imageId);
const wadoRoot = window.store.getState().servers.servers[0].wadoRoot;
const wadoRsImageId = `wadors:${wadoRoot}/studies/${qs.studyUID}/series/${
qs.seriesUID
}/instances/${qs.objectUID}/frames/${qs.frame || 1}`;
return cornerstone.metaData.get(type, wadoRsImageId);
}
// Add this fallback provider with a low priority so it is handled last
cornerstone.metaData.addProvider(fallbackMetaDataProvider, -1);
/**
*
* @param {object} configuration
* @param {Object|Array} configuration.csToolsConfig
*/
export default function init(configuration = {}) {
export default function init({ serviceManager, configuration = {} }) {
const { csToolsConfig } = configuration;
const { StackManager } = OHIF.utils;
const metadataProvider = new OHIF.cornerstone.MetadataProvider();
@@ -78,6 +98,7 @@ export default function init(configuration = {}) {
];
tools.forEach(tool => csTools.addTool(tool));
csTools.setToolActive('Pan', { mouseButtonMask: 4 });
csTools.setToolActive('Zoom', { mouseButtonMask: 2 });
csTools.setToolActive('Wwwc', { mouseButtonMask: 1 });
+51
View File
@@ -3,6 +3,57 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.53.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.53.5...@ohif/extension-vtk@0.53.6) (2019-11-14)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.53.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.53.4...@ohif/extension-vtk@0.53.5) (2019-11-13)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.53.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.53.3...@ohif/extension-vtk@0.53.4) (2019-11-12)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.53.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.53.2...@ohif/extension-vtk@0.53.3) (2019-11-11)
### Bug Fixes
* mpr2d vtkjs viewport does not render if range is set to `NaN` values ([#1157](https://github.com/OHIF/Viewers/issues/1157)) ([84a0972](https://github.com/OHIF/Viewers/commit/84a097212babc0f98198b9ced1def9743973a5a8))
## [0.53.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.53.1...@ohif/extension-vtk@0.53.2) (2019-11-08)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.53.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.53.0...@ohif/extension-vtk@0.53.1) (2019-11-08)
**Note:** Version bump only for package @ohif/extension-vtk
# [0.53.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.32...@ohif/extension-vtk@0.53.0) (2019-11-06)
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-vtk",
"version": "0.53.0",
"version": "0.53.6",
"description": "OHIF extension for VTK.js",
"author": "OHIF",
"license": "MIT",
@@ -52,8 +52,8 @@
"react-vtkjs-viewport": "^0.3.9"
},
"devDependencies": {
"@ohif/core": "^1.7.0",
"@ohif/ui": "^0.60.0",
"@ohif/core": "^1.9.0",
"@ohif/ui": "^0.62.1",
"cornerstone-tools": "^4.0.9",
"cornerstone-wado-image-loader": "^3.0.0",
"dcmjs": "^0.6.1",
+56 -14
View File
@@ -65,8 +65,16 @@ class OHIFVTKViewport extends Component {
};
static propTypes = {
studies: PropTypes.object,
displaySet: PropTypes.object,
viewportData: PropTypes.shape({
studies: PropTypes.array,
displaySet: PropTypes.shape({
studyInstanceUid: PropTypes.string,
displaySetInstanceUid: PropTypes.string,
sopClassUids: PropTypes.arrayOf(PropTypes.string),
sopInstanceUid: PropTypes.string,
frameIndex: PropTypes.number,
}),
}),
viewportIndex: PropTypes.number,
children: PropTypes.node,
};
@@ -175,6 +183,19 @@ class OHIFVTKViewport extends Component {
}
};
/**
*
*
* @param {object} imageDataObject
* @param {object} imageDataObject.vtkImageData
* @param {object} imageDataObject.imageMetaData0
* @param {number} [imageDataObject.imageMetaData0.windowWidth] - The volume's initial windowWidth
* @param {number} [imageDataObject.imageMetaData0.windowCenter] - The volume's initial windowCenter
* @param {string} imageDataObject.imageMetaData0.modality - CT, MR, PT, etc
* @param {string} displaySetInstanceUid
* @returns vtkVolumeActor
* @memberof OHIFVTKViewport
*/
getOrCreateVolume(imageDataObject, displaySetInstanceUid) {
if (volumeCache[displaySetInstanceUid]) {
return volumeCache[displaySetInstanceUid];
@@ -183,18 +204,11 @@ class OHIFVTKViewport extends Component {
const { vtkImageData, imageMetaData0 } = imageDataObject;
const { windowWidth, windowCenter, modality } = imageMetaData0;
let lower;
let upper;
if (modality === 'PT') {
// For PET just set the range to 0-5 SUV
lower = 0;
upper = 5;
} else {
lower = windowCenter - windowWidth / 2.0;
upper = windowCenter + windowWidth / 2.0;
}
const { lower, upper } = _getRangeFromWindowLevels(
windowWidth,
windowCenter,
modality
);
const volumeActor = vtkVolume.newInstance();
const volumeMapper = vtkVolumeMapper.newInstance();
@@ -379,4 +393,32 @@ class OHIFVTKViewport extends Component {
}
}
/**
* Takes window levels and converts them to a range (lower/upper)
* for use with VTK RGBTransferFunction
*
* @private
* @param {number} [width] - the width of our window
* @param {number} [center] - the center of our window
* @param {string} [modality] - 'PT', 'CT', etc.
* @returns { lower, upper } - range
*/
function _getRangeFromWindowLevels(width, center, modality = undefined) {
const levelsAreNotNumbers = isNaN(center) || isNaN(width);
if (levelsAreNotNumbers) {
return { lower: 0, upper: 512 };
}
// For PET just set the range to 0-5 SUV
if (modality === 'PT') {
return { lower: 0, upper: 5 };
}
return {
lower: center - width / 2.0,
upper: center + width / 2.0,
};
}
export default OHIFVTKViewport;
-1
View File
@@ -1,7 +1,6 @@
import {
vtkInteractorStyleMPRCrosshairs,
vtkInteractorStyleMPRWindowLevel,
vtkInteractorStyleMPRSlice,
vtkInteractorStyleMPRRotate,
vtkSVGCrosshairsWidget,
} from 'react-vtkjs-viewport';
+33
View File
@@ -3,6 +3,39 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.9.0](https://github.com/OHIF/Viewers/compare/@ohif/core@1.8.0...@ohif/core@1.9.0) (2019-11-13)
### Features
* expose UiNotifications service ([#1172](https://github.com/OHIF/Viewers/issues/1172)) ([5c04e34](https://github.com/OHIF/Viewers/commit/5c04e34c8fb2394ab7acd9eb4f2ab12afeb2f255))
# [1.8.0](https://github.com/OHIF/Viewers/compare/@ohif/core@1.7.1...@ohif/core@1.8.0) (2019-11-12)
### Features
* 🎸 Update hotkeys and user preferences modal ([#1135](https://github.com/OHIF/Viewers/issues/1135)) ([e62f5f8](https://github.com/OHIF/Viewers/commit/e62f5f8dd28ab363f23671cd21cee115abb870ff)), closes [#923](https://github.com/OHIF/Viewers/issues/923)
## [1.7.1](https://github.com/OHIF/Viewers/compare/@ohif/core@1.7.0...@ohif/core@1.7.1) (2019-11-08)
### Bug Fixes
* Add a fallback metadata provider which pulls metadata from WADO-… ([#1158](https://github.com/OHIF/Viewers/issues/1158)) ([31b1adf](https://github.com/OHIF/Viewers/commit/31b1adfa5993d6c8e3e9c8b03fa9856f2621b037))
# [1.7.0](https://github.com/OHIF/Viewers/compare/@ohif/core@1.6.2...@ohif/core@1.7.0) (2019-11-05)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/core",
"version": "1.7.0",
"version": "1.9.0",
"description": "Generic business logic for web-based medical imaging applications",
"author": "OHIF Core Team",
"license": "MIT",
+4 -2
View File
@@ -1,3 +1,4 @@
import cloneDeep from 'lodash.clonedeep';
import hotkeys from './hotkeys';
import log from './../log.js';
@@ -51,10 +52,11 @@ export class HotkeysManager {
* @param {Boolean} [isDefaultDefinitions]
*/
setHotkeys(hotkeyDefinitions, isDefaultDefinitions = false) {
hotkeyDefinitions.forEach(definition => this.registerHotkeys(definition));
const definitions = cloneDeep(hotkeyDefinitions);
definitions.forEach(definition => this.registerHotkeys(definition));
if (isDefaultDefinitions) {
this.hotkeyDefaults = hotkeyDefinitions;
this.hotkeyDefaults = definitions;
}
}
@@ -2,12 +2,13 @@ import MODULE_TYPES from './MODULE_TYPES.js';
import log from './../log.js';
export default class ExtensionManager {
constructor({ commandsManager }) {
constructor({ commandsManager, servicesManager }) {
this.modules = {};
this.registeredExtensionIds = [];
this.moduleTypeNames = Object.values(MODULE_TYPES);
//
this._commandsManager = commandsManager;
this._servicesManager = servicesManager;
this.moduleTypeNames.forEach(moduleType => {
this.modules[moduleType] = [];
@@ -66,7 +67,10 @@ export default class ExtensionManager {
// preRegistrationHook
if (extension.preRegistration) {
extension.preRegistration(configuration);
extension.preRegistration({
serviceManager: this._servicesManager,
configuration,
});
}
// Register Modules
+9
View File
@@ -1,6 +1,7 @@
import './lib';
import { ExtensionManager, MODULE_TYPES } from './extensions';
import { ServicesManager } from './services';
import classes, { CommandsManager, HotkeysManager } from './classes/';
import DICOMWeb from './DICOMWeb';
@@ -18,12 +19,15 @@ import ui from './ui';
import user from './user.js';
import utils from './utils/';
import { createUiNotificationService } from './services';
const OHIF = {
MODULE_TYPES,
//
CommandsManager,
ExtensionManager,
HotkeysManager,
ServicesManager,
//
utils,
studies,
@@ -41,6 +45,8 @@ const OHIF = {
viewer: {},
measurements,
hangingProtocols,
//
createUiNotificationService,
};
export {
@@ -49,6 +55,7 @@ export {
CommandsManager,
ExtensionManager,
HotkeysManager,
ServicesManager,
//
utils,
studies,
@@ -65,6 +72,8 @@ export {
DICOMWeb,
measurements,
hangingProtocols,
//
createUiNotificationService,
};
export { OHIF };
+3
View File
@@ -8,6 +8,9 @@ describe('Top level exports', () => {
'CommandsManager',
'ExtensionManager',
'HotkeysManager',
'ServicesManager',
//
'createUiNotificationService',
//
'utils',
'studies',
@@ -1,18 +1,15 @@
import cloneDeep from 'lodash.clonedeep';
const defaultState = {
// Top level key
viewer: {
// First tab
hotKeysData: {
// hotkeyName, label, keys, column
// zoom: { label: 'Zoom', command: 'Z', column: 0 },
},
// Second tab
windowLevelData: {
// order, description, window (int), level (int)
// 0: { description: 'Soft tissue', window: '', level: '' },
},
// First tab
hotkeyDefinitions: [
// commandName, label, keys
// [{ zoom: { label: 'Zoom', keys: ['z'] }}]
],
// Second tab
windowLevelData: {
// order, description, window (int), level (int)
// 0: { description: 'Soft tissue', window: '', level: '' },
},
};
@@ -0,0 +1,9 @@
export default class ServicesManager {
constructor() {
this.services = {};
}
register(service) {
this.services[service.name] = service;
}
}
@@ -0,0 +1,84 @@
/**
* A UI Notification
*
* @typedef {Object} Notification
* @property {string} title -
* @property {string} message -
* @property {number} [duration=5000] - in ms
* @property {string} [position="bottomRight"] -"topLeft" | "topCenter | "topRight" | "bottomLeft" | "bottomCenter" | "bottomRight"
* @property {string} [type="info"] - "info" | "error" | "warning" | "success"
* @property {boolean} [autoClose=true]
*/
const uiNotificationServicePublicApi = {
name: 'UINotificationService',
hide,
show,
setServiceImplementation,
};
const uiNotificationServiceImplementation = {
_hide: () => console.warn('hide() NOT IMPLEMENTED'),
_show: () => console.warn('show() NOT IMPLEMENTED'),
};
function createUiNotificationService() {
return uiNotificationServicePublicApi;
}
/**
* Create and show a new UI notification; returns the
* ID of the created notification.
*
* @param {Notification} notification { title, message, duration, position, type, autoClose}
* @returns {number} id
*/
function show({
title,
message,
duration = 5000,
position = 'bottomRight',
type = 'info',
autoClose = true,
}) {
return uiNotificationServiceImplementation._show({
title,
message,
duration,
position,
type,
autoClose,
});
}
/**
* Hides/dismisses the notification, if currently shown
*
* @param {number} id - id of the notification to hide/dismiss
* @returns undefined
*/
function hide(id) {
return uiNotificationServiceImplementation._hide({ id });
}
/**
*
*
* @param {*} {
* hide: hideImplementation,
* show: showImplementation,
* }
*/
function setServiceImplementation({
hide: hideImplementation,
show: showImplementation,
}) {
if (hideImplementation) {
uiNotificationServiceImplementation._hide = hideImplementation;
}
if (showImplementation) {
uiNotificationServiceImplementation._show = showImplementation;
}
}
export default createUiNotificationService;
+4
View File
@@ -0,0 +1,4 @@
import ServicesManager from './ServicesManager.js';
import createUiNotificationService from './UINotificationService';
export { createUiNotificationService, ServicesManager };
@@ -77,14 +77,6 @@ const getWadoRsInstanceMetaData = (study, series, instance) => {
*/
function updateMetaDataManagerForSeries(study, series) {
series.instances.forEach(instance => {
// Cache just images that are going to be loaded via WADO-RS
if (
instance.imageRendering !== 'wadors' &&
instance.thumbnailRendering !== 'wadors'
) {
return;
}
const metaData = getWadoRsInstanceMetaData(study, series, instance);
const numberOfFrames = instance.numberOfFrames || 1;
@@ -94,7 +86,7 @@ function updateMetaDataManagerForSeries(study, series) {
for (let frameNumber = 0; frameNumber < numberOfFrames; frameNumber++) {
const imageId = getWADORSImageId(instance, frameNumber);
// TODO Drop dependency on this
// TODO Make a metadata manager which uses study/series/instance UIDS instead of imageIds
cornerstoneWADOImageLoader.wadors.metaDataManager.add(imageId, metaData);
}
});
+41
View File
@@ -3,6 +3,47 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.62.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.62.0...@ohif/ui@0.62.1) (2019-11-14)
**Note:** Version bump only for package @ohif/ui
# [0.62.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.61.0...@ohif/ui@0.62.0) (2019-11-13)
### Features
* expose UiNotifications service ([#1172](https://github.com/OHIF/Viewers/issues/1172)) ([5c04e34](https://github.com/OHIF/Viewers/commit/5c04e34c8fb2394ab7acd9eb4f2ab12afeb2f255))
# [0.61.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.60.1...@ohif/ui@0.61.0) (2019-11-12)
### Features
* 🎸 Update hotkeys and user preferences modal ([#1135](https://github.com/OHIF/Viewers/issues/1135)) ([e62f5f8](https://github.com/OHIF/Viewers/commit/e62f5f8dd28ab363f23671cd21cee115abb870ff)), closes [#923](https://github.com/OHIF/Viewers/issues/923)
## [0.60.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.60.0...@ohif/ui@0.60.1) (2019-11-08)
### Bug Fixes
* Fix display issues with incorrect thumbnails. Change ImageThumb to functional component. ([#1148](https://github.com/OHIF/Viewers/issues/1148)) ([d70eae3](https://github.com/OHIF/Viewers/commit/d70eae3eb04fe854464f3e62316df8869bba6f11))
# [0.60.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.59.1...@ohif/ui@0.60.0) (2019-11-06)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/ui",
"version": "0.60.0",
"version": "0.62.1",
"description": "A set of React components for Medical Imaging Viewers",
"author": "OHIF Contributors",
"license": "MIT",
+3 -3
View File
@@ -6,8 +6,8 @@ import { TableList, TableListItem } from './tableList';
import {
AboutContent,
UserPreferences,
UserPreferencesModal,
} from './userPreferencesModal';
UserPreferencesForm,
} from './userPreferencesForm';
import { Checkbox } from './checkbox';
import { CineDialog } from './cineDialog';
@@ -52,6 +52,6 @@ export {
Tooltip,
AboutContent,
UserPreferences,
UserPreferencesModal,
UserPreferencesForm,
OHIFModal,
};
@@ -2,7 +2,7 @@
import './ImageThumbnail.styl';
import { utils } from '@ohif/core';
import React, { PureComponent } from 'react';
import React, { useState, useEffect, createRef } from 'react';
import PropTypes from 'prop-types';
import ViewportErrorIndicator from '../../viewer/ViewportErrorIndicator';
@@ -13,114 +13,132 @@ import ViewportLoadingIndicator from '../../viewer/ViewportLoadingIndicator';
// - Set as external dependency?
// - Pass in the entire load and render function as a prop?
//import cornerstone from 'cornerstone-core';
function ImageThumbnail(props) {
const {
width,
height,
imageSrc,
imageId,
stackPercentComplete,
error: propsError,
} = props;
export default class ImageThumbnail extends PureComponent {
static propTypes = {
imageSrc: PropTypes.string,
imageId: PropTypes.string,
error: PropTypes.bool.isRequired,
width: PropTypes.number.isRequired,
height: PropTypes.number.isRequired,
stackPercentComplete: PropTypes.number.isRequired,
const [isLoading, setLoading] = useState(false);
const [error, setError] = useState(false);
const [image, setImage] = useState({});
const canvasRef = createRef();
let loadingOrError;
let cancelablePromise;
if (propsError || error) {
loadingOrError = <ViewportErrorIndicator />;
} else if (isLoading) {
loadingOrError = <ViewportLoadingIndicator />;
}
const showStackLoadingProgressBar = stackPercentComplete !== undefined;
const shouldRenderToCanvas = () => {
return imageId && !imageSrc;
};
static defaultProps = {
error: false,
stackPercentComplete: 0,
width: 217,
height: 123,
const fetchImagePromise = () => {
if (!cancelablePromise) {
return;
}
setLoading(true);
cancelablePromise
.then(response => {
setImage(response);
})
.catch(error => {
if (error.isCanceled) return;
setLoading(false);
setError(true);
throw new Error(error);
});
};
constructor(props) {
super(props);
this.cancelablePromises = [];
this.canvas = React.createRef();
this.state = {
loading: this.shouldRenderToCanvas(),
const setImagePromise = () => {
if (shouldRenderToCanvas()) {
cancelablePromise = utils.makeCancelable(
cornerstone.loadAndCacheImage(imageId)
);
}
};
const purgeCancelablePromise = () => {
if (cancelablePromise) {
cancelablePromise.cancel();
}
};
useEffect(() => {
return () => {
purgeCancelablePromise();
};
}
}, []);
shouldRenderToCanvas() {
return this.props.imageId && !this.props.imageSrc;
}
fetchImage() {
const cancelablePromise = utils.makeCancelable(
cornerstone.loadAndCacheImage(this.props.imageId)
);
this.cancelablePromises.push(cancelablePromise);
return cancelablePromise;
}
componentDidMount() {
if (this.shouldRenderToCanvas()) {
this.fetchImage()
.then(image => {
cornerstone.renderToCanvas(this.canvas.current, image);
this.setState({
loading: false,
});
})
.catch(error => {
if (error.isCanceled) return;
this.setState({
loading: false,
error: true,
});
throw new Error(error);
});
useEffect(() => {
if (image.imageId) {
cornerstone.renderToCanvas(canvasRef.current, image);
setLoading(false);
}
}
}, [image.imageId]);
componentWillUnmount() {
while (this.cancelablePromises.length > 0) {
this.cancelablePromises.pop().cancel();
useEffect(() => {
if (!image.imageId || image.imageId !== imageId) {
purgeCancelablePromise();
setImagePromise();
fetchImagePromise();
}
}
}, [imageId]);
render() {
let loadingOrError;
if (this.props.error) {
loadingOrError = <ViewportErrorIndicator />;
} else if (this.state.loading) {
loadingOrError = <ViewportLoadingIndicator />;
}
const showStackLoadingProgressBar =
this.props.stackPercentComplete !== undefined;
return (
<div className="ImageThumbnail">
<div className="image-thumbnail-canvas">
{this.shouldRenderToCanvas() ? (
<canvas
ref={this.canvas}
width={this.props.width}
height={this.props.height}
/>
) : (
<img
className="static-image"
src={this.props.imageSrc}
//width={this.props.width}
height={this.props.height}
alt={''}
/>
)}
</div>
{loadingOrError}
{showStackLoadingProgressBar && (
<div className="image-thumbnail-progress-bar">
<div
className="image-thumbnail-progress-bar-inner"
style={{ width: `${this.props.stackPercentComplete}%` }}
/>
</div>
)}
{this.state.loading && (
<div className="image-thumbnail-loading-indicator"></div>
return (
<div className="ImageThumbnail">
<div className="image-thumbnail-canvas">
{shouldRenderToCanvas() ? (
<canvas ref={canvasRef} width={width} height={height} />
) : (
<img
className="static-image"
src={imageSrc}
//width={this.props.width}
height={height}
alt={''}
/>
)}
</div>
);
}
{loadingOrError}
{showStackLoadingProgressBar && (
<div className="image-thumbnail-progress-bar">
<div
className="image-thumbnail-progress-bar-inner"
style={{ width: `${stackPercentComplete}%` }}
/>
</div>
)}
{isLoading && <div className="image-thumbnail-loading-indicator"></div>}
</div>
);
}
ImageThumbnail.propTypes = {
imageSrc: PropTypes.string,
imageId: PropTypes.string,
error: PropTypes.bool,
width: PropTypes.number,
height: PropTypes.number,
stackPercentComplete: PropTypes.number.isRequired,
};
ImageThumbnail.defaultProps = {
error: false,
stackPercentComplete: 0,
width: 217,
height: 123,
};
export default ImageThumbnail;
@@ -32,7 +32,7 @@ function StudyBrowser(props) {
return (
<div
key={`container_${studyIndex}_${thumbIndex}`}
key={thumb.displaySetInstanceUid}
className="thumbnail-container"
data-cy="thumbnail-list"
>
@@ -10,31 +10,20 @@ import PropTypes from 'prop-types';
export class HotKeysPreferences extends Component {
static propTypes = {
hotKeysData: PropTypes.objectOf(
hotkeyDefinitions: PropTypes.arrayOf(
PropTypes.shape({
keys: PropTypes.arrayOf(PropTypes.string).isRequired,
label: PropTypes.string.isRequired,
commandName: PropTypes.string,
keys: PropTypes.arrayOf(PropTypes.string),
label: PropTypes.string,
})
).isRequired,
onChange: PropTypes.func,
};
constructor(props) {
super(props);
const hotkeyCommands = Object.keys(this.props.hotKeysData);
const localHotKeys = hotkeyCommands.map(commandName => {
const definition = this.props.hotKeysData[commandName];
return {
commandName,
keys: definition.keys,
label: definition.label,
};
});
this.state = {
hotKeys: localHotKeys,
hotKeys: this.props.hotkeyDefinitions,
errorMessages: {},
};
@@ -53,24 +42,28 @@ export class HotKeysPreferences extends Component {
const { ctrlKey, altKey, shiftKey } = keyDownEvent;
if (ctrlKey && !altKey) {
keysPressedArray.push('CTRL');
keysPressedArray.push('ctrl');
}
if (shiftKey && !altKey) {
keysPressedArray.push('SHIFT');
keysPressedArray.push('shift');
}
if (altKey && !ctrlKey) {
keysPressedArray.push('ALT');
keysPressedArray.push('alt');
}
return keysPressedArray;
}
getConflictingCommand(currentToolKey, hotKeyCommand) {
return Object.keys(this.state.hotKeys).find(tool => {
const value = this.state.hotKeys[tool].command;
return value && value === hotKeyCommand && tool !== currentToolKey;
getConflictingCommand(currentCommandName, currentHotKeys) {
return this.state.hotKeys.find((tool, index) => {
const toolHotKeys = tool.keys[0];
return (
toolHotKeys &&
toolHotKeys === currentHotKeys &&
tool.commandName !== currentCommandName
);
});
}
@@ -89,7 +82,7 @@ export class HotKeysPreferences extends Component {
specialKeyName ||
keyDownEvent.key ||
String.fromCharCode(keyDownEvent.keyCode);
pressedKeys.push(keyName.toUpperCase());
pressedKeys.push(keyName.toLowerCase());
}
this.updateHotKeysState(commandName, pressedKeys.join('+'));
@@ -136,17 +129,17 @@ export class HotKeysPreferences extends Component {
const hotKey = this.state.hotKeys[hotKeyIndex];
const keys = hotKey.keys[0];
const pressedKeys = keys.split('+');
const lastPressedKey = pressedKeys[pressedKeys.length - 1].toUpperCase();
const lastPressedKey = pressedKeys[pressedKeys.length - 1].toLowerCase();
// clear the prior errors
this.setState({ errorMessages: {} }, () => {
// Check if it has a valid modifier
const isModifier = ['CTRL', 'ALT', 'SHIFT'].includes(lastPressedKey);
const isModifier = ['ctrl', 'alt', 'shift'].includes(lastPressedKey);
if (isModifier) {
this.updateHotKeysState(commandName, '');
this.updateErrorsState(
commandName,
"It's not possible to define only modifier keys (CTRL, ALT and SHIFT) as a shortcut"
"It's not possible to define only modifier keys (ctrl, alt and shift) as a shortcut"
);
return;
}
@@ -154,21 +147,13 @@ export class HotKeysPreferences extends Component {
/*
* Check if it has some conflict
*/
const conflictedCommandKey = this.getConflictingCommand(
commandName,
keys
);
if (conflictedCommandKey) {
const conflictedCommand = this.state.hotKeys[conflictedCommandKey];
const conflictedCommand = this.getConflictingCommand(commandName, keys);
if (conflictedCommand) {
this.updateHotKeysState(commandName, '');
this.updateErrorsState(
commandName,
`"${conflictedCommand.label}" is already using the "${
conflictedCommand.command
}" shortcut.`
`"${conflictedCommand.label}" is already using the "${keys}" shortcut.`
);
this.updateErrorsState(conflictedCommandKey, '');
this.updateHotKeysState(commandName, '');
return;
}
@@ -178,7 +163,7 @@ export class HotKeysPreferences extends Component {
const modifierCommand = pressedKeys
.slice(0, pressedKeys.length - 1)
.join('+')
.toUpperCase();
.toLowerCase();
const disallowedCombination = disallowedCombinations[modifierCommand];
const hasDisallowedCombinations = disallowedCombination
@@ -189,7 +174,7 @@ export class HotKeysPreferences extends Component {
this.updateHotKeysState(commandName, '');
this.updateErrorsState(
commandName,
"It's not possible to define only modifier keys (CTRL, ALT and SHIFT) as a shortcut"
`"${pressedKeys.join('+')}" shortcut combination is not allowed`
);
return;
}
@@ -237,7 +222,7 @@ export class HotKeysPreferences extends Component {
this.state.hotKeys.length
);
return (
return this.state.hotKeys.length > 0 ? (
<div className="HotKeysPreferences">
{/* <!-- Column 1 --> */}
<div className="column">
@@ -272,6 +257,8 @@ export class HotKeysPreferences extends Component {
</table>
</div>
</div>
) : (
<p>{`No hotkeys are configured for this application. Hotkeys can be configured in the application's app-config.js file.`}</p>
);
}
}
@@ -8,14 +8,20 @@ import './UserPreferences.styl';
export class UserPreferences extends Component {
static defaultProps = {
hotKeysData: {},
hotkeyDefinitions: [],
windowLevelData: {},
generalData: {},
};
// TODO: Make this more generic. Tabs should not be restricted to these entries
static propTypes = {
hotKeysData: PropTypes.object.isRequired,
hotkeyDefinitions: PropTypes.arrayOf(
PropTypes.shape({
commandName: PropTypes.string,
keys: PropTypes.arrayOf(PropTypes.string),
label: PropTypes.string,
})
).isRequired,
windowLevelData: PropTypes.object.isRequired,
generalData: PropTypes.object.isRequired,
};
@@ -32,7 +38,9 @@ export class UserPreferences extends Component {
return (
<form className="form-themed themed">
<div className="form-content">
<HotKeysPreferences hotKeysData={this.props.hotKeysData} />
<HotKeysPreferences
hotkeyDefinitions={this.props.hotkeyDefinitions}
/>
</div>
</form>
);
@@ -66,8 +74,8 @@ export class UserPreferences extends Component {
switch (tabIndex) {
case 0:
return this.renderHotkeysTab();
case 1:
return this.renderWindowLevelTab();
/* case 1:
return this.renderWindowLevelTab(); */
case 2:
return this.renderGeneralTab();
@@ -93,14 +101,16 @@ export class UserPreferences extends Component {
>
<button>Hotkeys</button>
</li>
<li
onClick={() => {
this.tabClick(1);
}}
className={this.getTabClass(1)}
>
<button>Window Level</button>
</li>
{false && (
<li
onClick={() => {
this.tabClick(1);
}}
className={this.getTabClass(1)}
>
<button>Window Level</button>
</li>
)}
<li
onClick={() => {
this.tabClick(2);
@@ -0,0 +1,92 @@
import './UserPreferencesForm.styl';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withTranslation } from '../../utils/LanguageProvider';
import cloneDeep from 'lodash.clonedeep';
import isEqual from 'lodash.isequal';
import { UserPreferences } from './UserPreferences';
class UserPreferencesForm extends Component {
// TODO: Make this component more generic to allow things other than W/L and hotkeys...
static propTypes = {
onClose: PropTypes.func,
onSave: PropTypes.func,
onResetToDefaults: PropTypes.func,
windowLevelData: PropTypes.object,
hotkeyDefinitions: PropTypes.arrayOf(
PropTypes.shape({
commandName: PropTypes.string,
keys: PropTypes.arrayOf(PropTypes.string),
label: PropTypes.string,
})
).isRequired,
t: PropTypes.func,
};
constructor(props) {
super(props);
this.state = {
windowLevelData: cloneDeep(props.windowLevelData),
hotkeyDefinitions: cloneDeep(props.hotkeyDefinitions),
};
}
save = () => {
this.props.onSave({
windowLevelData: this.state.windowLevelData,
hotkeyDefinitions: this.state.hotkeyDefinitions,
});
};
componentDidUpdate(prev, next) {
const newStateData = {};
if (!isEqual(prev.windowLevelData, next.windowLevelData)) {
newStateData.windowLevelData = prev.windowLevelData;
}
if (!isEqual(prev.hotkeyDefinitions, next.hotkeyDefinitions)) {
newStateData.hotkeyDefinitions = prev.hotkeyDefinitions;
}
if (newStateData.hotkeyDefinitions || newStateData.windowLevelData) {
this.setState(newStateData);
}
}
render() {
return (
<div className="UserPreferencesForm">
<UserPreferences
windowLevelData={this.state.windowLevelData}
hotkeyDefinitions={this.state.hotkeyDefinitions}
/>
<div className="footer">
<button
className="btn btn-danger pull-left"
onClick={this.props.onResetToDefaults}
>
{this.props.t('Reset to Defaults')}
</button>
<div>
<div onClick={this.props.onClose} className="btn btn-default">
{this.props.t('Cancel')}
</div>
<button className="btn btn-primary" onClick={this.save}>
{this.props.t('Save')}
</button>
</div>
</div>
</div>
);
}
}
const connectedComponent = withTranslation('UserPreferencesForm')(
UserPreferencesForm
);
export { connectedComponent as UserPreferencesForm };
export default connectedComponent;
@@ -12,6 +12,13 @@
text-shadow: 0 1px 0 #fff;
opacity: .2;
.ModalHeader
ol, ul
margin-top: 0;
.UserPreferencesForm
.footer
display: flex
flex-direction: row
padding-bottom: 20px
justify-content: space-between
div
button:last-child
margin-left: 10px
@@ -1,18 +1,18 @@
---
name: User Preferences Modal
name: User Preferences Form
menu: Components
route: /components/user-preferences-modal
route: /components/user-preferences-form
---
import { Playground, Props } from 'docz'
import { State } from 'react-powerplug'
import { UserPreferencesModal } from './../index.js'
import { UserPreferencesForm } from './../index.js'
import NameSpace from '../../../__docs__/NameSpace'
//
import windowLevelDefaults from './windowLevelDefaults.js'
import hotkeyDefaults from './hotkeyDefaults.js'
# User Preferences Modal
# User Preferences Form
## Basic usage
@@ -20,7 +20,7 @@ import hotkeyDefaults from './hotkeyDefaults.js'
<State initial={{
isOpen: false,
windowLevelData: windowLevelDefaults,
hotKeysData: hotkeyDefaults,
hotkeyDefinitions: hotkeyDefaults,
}}>
{({ state, setState }) => (
@@ -32,7 +32,7 @@ import hotkeyDefaults from './hotkeyDefaults.js'
>
Open user preferences
</button>
<UserPreferencesModal
<UserPreferencesForm
{...state}
onCancel={() => setState({ isOpen: false })}
onSave={() => alert('on save')}
@@ -47,8 +47,8 @@ import hotkeyDefaults from './hotkeyDefaults.js'
## API
<Props of={UserPreferencesModal} />
<Props of={UserPreferencesForm} />
## Translation Namespace
<NameSpace name="UserPreferencesModal" />
<NameSpace name="UserPreferencesForm" />
@@ -4,38 +4,38 @@ const range = (start, end) => {
export const disallowedCombinations = {
'': [],
ALT: ['SPACE'],
SHIFT: [],
CTRL: [
'F4',
'F5',
'F11',
'W',
'R',
'T',
'O',
'P',
'A',
'D',
'F',
'G',
'H',
'J',
'L',
'Z',
'X',
'C',
'V',
'B',
'N',
'PAGEDOWN',
'PAGEUP',
alt: ['space'],
shift: [],
ctrl: [
'f4',
'f5',
'f11',
'w',
'r',
't',
'o',
'p',
'a',
'd',
'f',
'g',
'h',
'j',
'l',
'z',
'x',
'c',
'v',
'b',
'n',
'pagedown',
'pageup',
],
'CTRL+SHIFT': ['Q', 'W', 'R', 'T', 'P', 'A', 'H', 'V', 'B', 'N'],
'ctrl+shift': ['q', 'w', 'r', 't', 'p', 'a', 'h', 'v', 'b', 'n'],
};
export const allowedKeys = [
...[8, 13, 27, 32, 46], // BACKSPACE, ENTER, ESCAPE, SPACE, DELETE
...[8, 13, 27, 32, 46], // backspace, enter, escape, space, delete
...[12, 106, 107, 109, 110, 111], // Numpad keys
...range(218, 220), // [\]
...range(185, 190), // ;=,-./
@@ -1,4 +1,4 @@
export { UserPreferences } from './UserPreferences.js';
export { AboutContent } from '../content/aboutContent/AboutContent.js';
export { UserPreferencesModal } from './UserPreferencesModal.js';
export { UserPreferencesForm } from './UserPreferencesForm.js';
export { GeneralPreferences } from './GeneralPreferences.js';
@@ -1,107 +0,0 @@
import './UserPreferencesModal.styl';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Modal from 'react-bootstrap-modal';
import { withTranslation } from '../../utils/LanguageProvider';
import 'react-bootstrap-modal/lib/css/rbm-patch.css';
import cloneDeep from 'lodash.clonedeep';
import isEqual from 'lodash.isequal';
import { UserPreferences } from './UserPreferences';
// TODO: Is this the only component importing these?
import './../../design/styles/common/modal.styl';
class UserPreferencesModal extends Component {
// TODO: Make this component more generic to allow things other than W/L and hotkeys...
static propTypes = {
isOpen: PropTypes.bool.isRequired,
onCancel: PropTypes.func,
onSave: PropTypes.func,
onResetToDefaults: PropTypes.func,
windowLevelData: PropTypes.object,
hotKeysData: PropTypes.object,
t: PropTypes.func,
};
constructor(props) {
super(props);
this.state = {
windowLevelData: cloneDeep(props.windowLevelData),
hotKeysData: cloneDeep(props.hotKeysData),
};
}
static defaultProps = {
isOpen: false,
};
save = () => {
this.props.onSave({
windowLevelData: this.state.windowLevelData,
hotKeysData: this.state.hotKeysData,
});
};
componentDidUpdate(prev, next) {
const newStateData = {};
if (!isEqual(prev.windowLevelData, next.windowLevelData)) {
newStateData.windowLevelData = prev.windowLevelData;
}
if (!isEqual(prev.hotKeysData, next.hotKeysData)) {
newStateData.hotKeysData = prev.hotKeysData;
}
if (newStateData.hotKeysData || newStateData.windowLevelData) {
this.setState(newStateData);
}
}
render() {
return (
<Modal
show={this.props.isOpen}
onHide={this.props.onCancel}
aria-labelledby="ModalHeader"
className="ModalHeader modal fade themed in"
backdrop={false}
large={true}
keyboard={false}
>
<Modal.Header closeButton>
<Modal.Title>{this.props.t('User Preferences')}</Modal.Title>
</Modal.Header>
<Modal.Body>
<UserPreferences
windowLevelData={this.state.windowLevelData}
hotKeysData={this.state.hotKeysData}
/>
</Modal.Body>
<Modal.Footer>
<button
className="btn btn-danger pull-left"
onClick={this.props.onResetToDefaults}
>
{this.props.t('Reset to Defaults')}
</button>
<Modal.Dismiss className="btn btn-default">
{this.props.t('Cancel')}
</Modal.Dismiss>
<button className="btn btn-primary" onClick={this.save}>
{this.props.t('Save')}
</button>
</Modal.Footer>
</Modal>
);
}
}
const connectedComponent = withTranslation('UserPreferencesModal')(
UserPreferencesModal
);
export { connectedComponent as UserPreferencesModal };
export default connectedComponent;
+2 -2
View File
@@ -24,7 +24,7 @@ import {
Tooltip,
AboutContent,
UserPreferences,
UserPreferencesModal,
UserPreferencesForm,
OHIFModal,
} from './components';
import { useDebounce, useMedia } from './hooks';
@@ -101,7 +101,7 @@ export {
Tooltip,
AboutContent,
UserPreferences,
UserPreferencesModal,
UserPreferencesForm,
ViewerbaseDragDropContext,
SnackbarProvider,
useSnackbarContext,
+16 -6
View File
@@ -1,4 +1,10 @@
import React, { useState, createContext, useContext } from 'react';
import React, {
useState,
createContext,
useContext,
useCallback,
useEffect,
} from 'react';
import SnackbarContainer from '../components/snackbar/SnackbarContainer';
import SnackbarTypes from '../components/snackbar/SnackbarTypes';
@@ -6,7 +12,7 @@ const SnackbarContext = createContext(null);
export const useSnackbarContext = () => useContext(SnackbarContext);
const SnackbarProvider = ({ children }) => {
const SnackbarProvider = ({ children, service }) => {
const DEFAULT_OPTIONS = {
title: '',
message: '',
@@ -19,7 +25,11 @@ const SnackbarProvider = ({ children }) => {
const [count, setCount] = useState(1);
const [snackbarItems, setSnackbarItems] = useState([]);
const show = options => {
useEffect(() => {
service.setServiceImplementation({ hide, show });
}, [service, hide, show]);
const show = useCallback(options => {
if (!options || (!options.title && !options.message)) {
console.warn(
'Snackbar cannot be rendered without required parameters: title | message'
@@ -37,9 +47,9 @@ const SnackbarProvider = ({ children }) => {
setSnackbarItems(state => [...state, newItem]);
setCount(count + 1);
};
});
const hide = id => {
const hide = useCallback(id => {
const hideItem = items => {
const newItems = items.map(item => {
if (item.id === id) {
@@ -57,7 +67,7 @@ const SnackbarProvider = ({ children }) => {
setTimeout(() => {
setSnackbarItems(state => [...state.filter(item => item.id !== id)]);
}, 1000);
};
});
const hideAll = () => {
// reset count
+82
View File
@@ -3,6 +3,88 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.8.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@2.8.1...@ohif/viewer@2.8.2) (2019-11-14)
**Note:** Version bump only for package @ohif/viewer
## [2.8.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@2.8.0...@ohif/viewer@2.8.1) (2019-11-14)
**Note:** Version bump only for package @ohif/viewer
# [2.8.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@2.7.1...@ohif/viewer@2.8.0) (2019-11-13)
### Features
* expose UiNotifications service ([#1172](https://github.com/OHIF/Viewers/issues/1172)) ([5c04e34](https://github.com/OHIF/Viewers/commit/5c04e34c8fb2394ab7acd9eb4f2ab12afeb2f255))
* filter field for google api windows ([#1170](https://github.com/OHIF/Viewers/issues/1170)) ([c59c5b3](https://github.com/OHIF/Viewers/commit/c59c5b3f14d44f1c06aa396125a1f4caaa431c25))
## [2.7.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@2.7.0...@ohif/viewer@2.7.1) (2019-11-12)
### Bug Fixes
* 🐛 Fix for JS breaking on header ([#1164](https://github.com/OHIF/Viewers/issues/1164)) ([0fbaf95](https://github.com/OHIF/Viewers/commit/0fbaf95971dc0b3a671e1f586a876d9019e860ed))
# [2.7.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@2.6.4...@ohif/viewer@2.7.0) (2019-11-12)
### Features
* 🎸 Update hotkeys and user preferences modal ([#1135](https://github.com/OHIF/Viewers/issues/1135)) ([e62f5f8](https://github.com/OHIF/Viewers/commit/e62f5f8dd28ab363f23671cd21cee115abb870ff)), closes [#923](https://github.com/OHIF/Viewers/issues/923)
## [2.6.4](https://github.com/OHIF/Viewers/compare/@ohif/viewer@2.6.3...@ohif/viewer@2.6.4) (2019-11-11)
**Note:** Version bump only for package @ohif/viewer
## [2.6.3](https://github.com/OHIF/Viewers/compare/@ohif/viewer@2.6.2...@ohif/viewer@2.6.3) (2019-11-08)
**Note:** Version bump only for package @ohif/viewer
## [2.6.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@2.6.1...@ohif/viewer@2.6.2) (2019-11-08)
**Note:** Version bump only for package @ohif/viewer
## [2.6.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@2.6.0...@ohif/viewer@2.6.1) (2019-11-06)
**Note:** Version bump only for package @ohif/viewer
# [2.6.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@2.5.0...@ohif/viewer@2.6.0) (2019-11-06)
@@ -46,8 +46,8 @@ describe('OHIF Cornerstone Hotkeys', () => {
// Hotkey I
cy.get('body').type('I');
// Visual comparison
cy.screenshot();
cy.percyCanvasSnapshot('Hotkey I - invert image');
cy.screenshot('Hotkey I - Should Invert Image');
cy.percyCanvasSnapshot('Hotkey I - Should Invert Image');
});
it('checks if hotkeys "+", "-" and "=" can zoom in, out and fit to viewport', () => {
@@ -76,8 +76,8 @@ describe('OHIF Cornerstone Hotkeys', () => {
cy.get('@viewportInfoBottomRight').should('contains.text', 'Zoom: 211%');
// Visual comparison to make sure the 'inverted' image was reset
cy.screenshot();
cy.percyCanvasSnapshot('Hotkey SPACEBAR - Reset Image');
cy.screenshot('Hotkey SPACEBAR - Should Reset Image');
cy.percyCanvasSnapshot('Hotkey SPACEBAR - Should Reset Image');
});
it('uses hotkeys "RightArrow" and "LeftArrow" to navigate between multiple viewports', () => {
@@ -2,7 +2,7 @@ describe('OHIF Cornerstone Toolbar', () => {
before(() => {
cy.openStudy('MISTER^MR');
cy.waitDicomImage();
cy.expectMinimumThumbnails(3);
cy.expectMinimumThumbnails(5);
});
beforeEach(() => {
@@ -112,25 +112,14 @@ describe('OHIF Cornerstone Toolbar', () => {
cy.wrap($panBtn).should('have.class', 'active');
});
//Get image position from cornerstone and check if y axis was modified
let cornerstone;
let currentPan;
cy.window()
.its('cornerstone')
.then(c => {
cornerstone = c;
currentPan = () =>
cornerstone.getEnabledElements()[0].viewport.translation;
});
cy.get('@viewport')
.trigger('mousedown', 'center', { which: 1 })
.trigger('mousemove', 'bottom', { which: 1 })
.trigger('mouseup', 'bottom')
.then(() => {
expect(currentPan().y > 0).to.eq(true);
});
.trigger('mouseup', 'bottom');
// Visual comparison
cy.screenshot('Pan tool moved the image inside the viewport');
cy.percyCanvasSnapshot('Pan tool moved the image inside the viewport');
});
it('checks if Length annotation can be added on viewport and on measurements panel', () => {
@@ -312,18 +301,18 @@ describe('OHIF Cornerstone Toolbar', () => {
.find('td')
.its('length')
.should('be.eq', 9);
cy.get('@layoutBtn').click();
});
//verify if layout has changed to 2 viewports
cy.get('tbody > :nth-child(1) > :nth-child(2)').click();
cy.setLayout(1, 2);
cy.get('.viewport-container').then($viewport => {
cy.wrap($viewport)
.its('length')
.should('be.eq', 2);
});
cy.get('@layoutBtn').click();
cy.get('tbody > :nth-child(2) > :nth-child(1)').click();
cy.setLayout(2, 1);
cy.get('.viewport-container').then($viewport => {
cy.wrap($viewport)
.its('length')
@@ -331,8 +320,7 @@ describe('OHIF Cornerstone Toolbar', () => {
});
//verify if layout has changed to 3 viewports
cy.get('@layoutBtn').click();
cy.get('tbody > :nth-child(1) > :nth-child(3)').click();
cy.setLayout(1, 3);
cy.get('.viewport-container').then($viewport => {
cy.wait(1000);
cy.wrap($viewport)
@@ -340,8 +328,7 @@ describe('OHIF Cornerstone Toolbar', () => {
.should('be.eq', 3);
});
cy.get('@layoutBtn').click();
cy.get('tbody > :nth-child(3) > :nth-child(1)').click();
cy.setLayout(3, 1);
cy.get('.viewport-container').then($viewport => {
cy.wrap($viewport)
.its('length')
@@ -349,8 +336,7 @@ describe('OHIF Cornerstone Toolbar', () => {
});
//verify if layout has changed to 4 viewports
cy.get('@layoutBtn').click();
cy.get('tbody > :nth-child(2) > :nth-child(2)').click();
cy.setLayout(2, 2);
cy.get('.viewport-container').then($viewport => {
cy.wrap($viewport)
.its('length')
@@ -358,16 +344,14 @@ describe('OHIF Cornerstone Toolbar', () => {
});
//verify if layout has changed to 6 viewports
cy.get('@layoutBtn').click();
cy.get('tbody > :nth-child(2) > :nth-child(3)').click();
cy.setLayout(2, 3);
cy.get('.viewport-container').then($viewport => {
cy.wrap($viewport)
.its('length')
.should('be.eq', 6);
});
cy.get('@layoutBtn').click();
cy.get('tbody > :nth-child(3) > :nth-child(2)').click();
cy.setLayout(3, 2);
cy.get('.viewport-container').then($viewport => {
cy.wrap($viewport)
.its('length')
@@ -375,8 +359,7 @@ describe('OHIF Cornerstone Toolbar', () => {
});
//verify if layout has changed to 9 viewports
cy.get('@layoutBtn').click();
cy.get('tbody > :nth-child(3) > :nth-child(3)').click();
cy.setLayout(3, 3);
cy.get('.viewport-container').then($viewport => {
cy.wrap($viewport)
.its('length')
@@ -384,8 +367,7 @@ describe('OHIF Cornerstone Toolbar', () => {
});
//verify if layout has changed to 1 viewport
cy.get('@layoutBtn').click();
cy.get('tbody > :nth-child(1) > :nth-child(1)').click();
cy.setLayout(1, 1);
cy.get('.viewport-container').then($viewport => {
cy.wrap($viewport)
.its('length')
@@ -425,7 +407,9 @@ describe('OHIF Cornerstone Toolbar', () => {
.as('toolbarOverlay')
.should('be.visible');
//Click on Clear button
cy.get('.tooltip-inner > :nth-child(10)').click();
cy.get('.tooltip-inner')
.contains('Clear')
.click();
//Verify if measurements were removed from the measurements panel
cy.get('.measurementItem').should('not.exist');
@@ -457,8 +441,15 @@ describe('OHIF Cornerstone Toolbar', () => {
//Click More button
cy.get('@moreBtn').click();
//Click Eraser button
cy.get('.tooltip-inner > :nth-child(12)').click();
//Verify if overlay is displayed
cy.get('.tooltip-toolbar-overlay')
.should('be.visible')
.then(() => {
//Click Eraser button
cy.get('.tooltip-inner')
.contains('Eraser')
.click({ force: true });
});
//Erase measurement #1 and Verify if it was removed from the measurements panel
const [x1, y1] = [150, 100];
@@ -472,4 +463,70 @@ describe('OHIF Cornerstone Toolbar', () => {
cy.get('@viewport').click(x2, y2, { force: true });
cy.get('.measurementItem').should('not.exist');
});
it('check if Invert tool will change the colors of the image in the viewport', () => {
// Click on More button
cy.get('@moreBtn').click();
// Verify if overlay is displayed
cy.get('.tooltip-toolbar-overlay').should('be.visible');
// Click on Invert button
cy.get('.tooltip-inner')
.contains('Invert')
.click();
// Visual comparison
cy.screenshot('Invert tool - Should Invert Canvas');
cy.percyCanvasSnapshot('Invert tool - Should Invert Canvas');
});
it('check if Rotate tool will change the image orientation in the viewport', () => {
//Click on More button
cy.get('@moreBtn').click();
//Verify if overlay is displayed
cy.get('.tooltip-toolbar-overlay')
.should('be.visible')
.then(() => {
//Click on Rotate button
cy.get('.tooltip-inner')
.contains('Rotate')
.click({ force: true });
});
// Visual comparison
cy.screenshot('Rotate tool - Should Rotate Image to Right');
cy.percyCanvasSnapshot('Rotate tool - Should Rotate Image to Right');
});
it('check if Flip H tool will flip the image horizontally in the viewport', () => {
//Click on More button
cy.get('@moreBtn').click();
//Verify if overlay is displayed
cy.get('.tooltip-toolbar-overlay').should('be.visible');
//Click on Flip H button
cy.get('.tooltip-inner')
.contains('Flip H')
.click();
// Visual comparison
cy.screenshot('Flip H tool - Should Flip Image on Y axis');
cy.percyCanvasSnapshot('Flip H tool - Should Flip Image on Y axis');
});
it('check if Flip V tool will flip the image vertically in the viewport', () => {
//Click on More button
cy.get('@moreBtn').click();
//Verify if overlay is displayed
cy.get('.tooltip-toolbar-overlay').should('be.visible');
//Click on Flip V button
cy.get('.tooltip-inner')
.contains('Flip V')
.click();
// Visual comparison
cy.screenshot('Flip V tool - Should Flip Image on X axis');
cy.percyCanvasSnapshot('Flip V tool - Should Flip Image on X axis');
});
});
@@ -4,7 +4,7 @@ describe('OHIFStandaloneViewer', () => {
});
it('loads route with at least 2 rows', () => {
cy.screenshot();
cy.screenshot('Study List');
cy.percyCanvasSnapshot('Study List');
cy.get('[data-cy="study-list-results"] tr')
@@ -0,0 +1,201 @@
//We are keeping the hardcoded results values for the study list tests
//this is intended to be running in a controled docker environment with test data.
describe('OHIF Study List', function() {
context('Desktop resolution', function() {
beforeEach(function() {
cy.viewport(1750, 720);
cy.openStudyList();
cy.initStudyListAliasesOnDesktop();
});
it('searches Patient Name with exact string', function() {
cy.get('@patientName').type('Juno');
//Wait result list to be displayed
cy.waitStudyList();
cy.get('@searchResult').should($list => {
expect($list.length).to.be.eq(2);
expect($list).to.contain('Juno');
});
});
it('searches MRN with exact string', function() {
cy.get('@MRN').type('ProstateX-0000');
//Wait result list to be displayed
cy.waitStudyList();
cy.get('@searchResult').should($list => {
expect($list.length).to.be.eq(2);
expect($list).to.contain('ProstateX-0000');
});
});
it('searches Accession with exact string', function() {
cy.get('@accessionNumber').type('fpcben98890');
//Wait result list to be displayed
cy.waitStudyList();
cy.get('@searchResult').should($list => {
expect($list.length).to.be.eq(1);
expect($list).to.contain('fpcben98890');
});
});
it('searches Modality with camel case', function() {
cy.get('@modalities').type('Mr');
//Wait result list to be displayed
cy.waitStudyList();
cy.get('@searchResult').should($list => {
expect($list.length).to.be.eq(18);
expect($list).to.contain('MR');
});
});
it('searches Description with exact string', function() {
cy.get('@studyDescription').type('CHEST');
//Wait result list to be displayed
cy.waitStudyList();
cy.get('@searchResult').should($list => {
expect($list.length).to.be.eq(2);
expect($list).to.contain('CHEST');
});
});
it('changes rows per page and checks the study count', function() {
//Show rows per page options
const pageRows = [25, 50, 100];
//Check all options of rows
pageRows.forEach(numRows => {
cy.get('select').select(numRows.toString()); //Select rows per page option
//Wait result list to be displayed
cy.waitStudyList().then(() => {
//Compare the search result with the Study Count on the table header
cy.get('@studyCount')
.should($studyCount => {
expect(parseInt($studyCount.text())).to.be.at.most(numRows); //less than or equals to
})
.then($studyCount => {
//Compare to the number of rows in the search result
cy.get('@searchResult').then($searchResult => {
let countResults = $searchResult.length;
expect($studyCount.text()).to.be.eq(countResults.toString());
});
});
});
});
});
//TO-TO: This test should be uncommented once issue #1120 is fixed:
//https://github.com/OHIF/Viewers/issues/1120
// it('filter study list by Study Date', function() {
// //Type Start and End dates
// cy.get('@studyListStartDate').type('01/01/2000');
// cy.get('@studyListEndDate').type('01/01/2019');
// //Display all results into one page
// cy.get('select').select('100');
// //Checks if all expected results are displayed
// //Wait result list to be displayed
// cy.waitStudyList();
// cy.get('@searchResult').should($list => {
// expect($list.length).to.be.eq(42);
// });
// });
});
context('Tablet resolution', function() {
beforeEach(function() {
cy.viewport(1000, 660);
cy.openStudyList();
cy.initStudyListAliasesOnTablet();
});
it('searches Patient Name with exact string', function() {
cy.get('@patientNameOrMRN').type('Juno');
//Wait result list to be displayed
cy.waitStudyList();
cy.get('@searchResult').should($list => {
expect($list.length).to.be.eq(2);
expect($list).to.contain('Juno');
});
});
it('searches MRN with with exact string', function() {
cy.get('@patientNameOrMRN').type('ProstateX-0000');
//Wait result list to be displayed
cy.waitStudyList();
cy.get('@searchResult').should($list => {
expect($list.length).to.be.eq(2);
expect($list).to.contain('ProstateX-0000');
});
});
it('searches Modality with exact string', function() {
cy.get('@accessionModalityDescription').type('MR');
//Wait result list to be displayed
cy.waitStudyList();
cy.get('@searchResult').should($list => {
expect($list.length).to.be.eq(18);
expect($list).to.contain('MR');
});
});
it('searches Accession with exact string', function() {
cy.get('@accessionModalityDescription').type('fpcben98890');
//Wait result list to be displayed
cy.waitStudyList();
cy.get('@searchResult').should($list => {
expect($list.length).to.be.eq(1);
expect($list).to.contain('fpcben98890');
});
});
it('searches Description with exact string', function() {
cy.get('@accessionModalityDescription').type('CHEST');
//Wait result list to be displayed
cy.waitStudyList();
cy.get('@searchResult').should($list => {
expect($list.length).to.be.eq(2);
expect($list).to.contain('CHEST');
});
});
it('changes rows per page and checks the study count', function() {
//Show rows per page options
const pageRows = [25, 50, 100];
//Check all options of rows
pageRows.forEach(numRows => {
cy.get('select').select(numRows.toString()); //Select rows per page option
//Wait result list to be displayed
cy.waitStudyList().then(() => {
//Compare the search result with the Study Count on the table header
cy.get('@studyCount')
.should($studyCount => {
expect(parseInt($studyCount.text())).to.be.at.most(numRows); //less than or equals to
})
.then($studyCount => {
//Compare to the number of rows in the search result
cy.get('@searchResult').then($searchResult => {
let countResults = $searchResult.length;
expect($studyCount.text()).to.be.eq(countResults.toString());
});
});
});
});
});
//TO-TO: This test should be uncommented once issue #1120 is fixed:
//https://github.com/OHIF/Viewers/issues/1120
// it('filter study list by Study Date', function() {
// //Type Start and End dates
// cy.get('@studyListStartDate').type('01/01/2000');
// cy.get('@studyListEndDate').type('01/01/2019');
// //Display all results into one page
// cy.get('select').select('100');
// //Checks if all expected results are displayed
// //Wait result list to be displayed
// cy.waitStudyList();
// cy.get('@searchResult').should($list => {
// expect($list.length).to.be.eq(42);
// });
// });
});
});
@@ -11,8 +11,8 @@ describe('OHIF Study Viewer Page', function() {
});
it('checks if series thumbnails are being displayed', function() {
cy.screenshot();
cy.percyCanvasSnapshot('Series Thumbnails');
cy.screenshot('Series Thumbnails - Should Display Thumbnails');
cy.percyCanvasSnapshot('Series Thumbnails - Should Display Thumbnails');
cy.get('[data-cy="thumbnail-list"]')
.its('length')
@@ -72,30 +72,26 @@ describe('OHIF Study Viewer Page', function() {
cy.get('@measurementsPanel').should('not.be.enabled');
});
//TO-DO: Test case will fail due to issue #1013: https://github.com/OHIF/Viewers/issues/1013
it('checks if Description can be added to measurement item under Measurements panel', () => {
cy.addLengthMeasurement(); //Adding measurement in the viewport
cy.get('@measurementsBtn').click();
cy.get('.measurementItem').click();
// it('checks if Description can be added to measurement item under Measurements panel', () => {
// cy.addLengthMeasurement(); //Adding measurement in the viewport
// cy.get('@measurementsBtn').click();
// cy.get('.measurementItem').click();
//
// // Click "Description"
// cy.get('.btnAction')
// .contains('Description')
// .click();
//
// // Enter description text
// const descriptionText = 'Adding text for description test';
// cy.get('#description')
// .type(descriptionText);
//
// // Confirm
// cy.get('.btn-confirm').click();
//
// //Verify if descriptionText was added
// cy.get('.measurementLocation')
// .should('contain.text', descriptionText);
// });
// Click "Description"
cy.get('.btnAction')
.contains('Description')
.click();
// Enter description text
const descriptionText = 'Adding text for description test';
cy.get('#description').type(descriptionText);
// Confirm
cy.get('.btn-confirm').click();
//Verify if descriptionText was added
cy.get('.measurementLocation').should('contain.text', descriptionText);
});
it('checks if measurement item can be deleted through the context menu on the viewport', function() {
cy.addLengthMeasurement([100, 100], [200, 100]); //Adding measurement in the viewport
@@ -228,14 +224,12 @@ describe('OHIF Study Viewer Page', function() {
cy.get('@viewportInfoBottomLeft').should('contains.text', expectedText);
});
//TO-DO: this test is blocked due to issue #1072: https://github.com/OHIF/Viewers/issues/1072
// Uncomment this once #1072 is fixed.
// it('performs single-click to load thumbnail in active viewport', () => {
// cy.get('[data-cy="thumbnail-list"]:nth-child(3)').click();
it('performs single-click to load thumbnail in active viewport', () => {
cy.get('[data-cy="thumbnail-list"]:nth-child(3)').click();
// const expectedText = 'Ser 3';
// cy.get('@viewportInfoBottomLeft').should('contains.text', expectedText);
// });
const expectedText = 'Ser: 3';
cy.get('@viewportInfoBottomLeft').should('contains.text', expectedText);
});
it('performs right click to zoom', function() {
//Right click on viewport
@@ -244,7 +238,7 @@ describe('OHIF Study Viewer Page', function() {
.trigger('mousemove', 'center', { which: 3 })
.trigger('mouseup');
const expectedText = 'Zoom: 442%';
const expectedText = 'Zoom: 301%';
cy.get('@viewportInfoBottomRight').should('contains.text', expectedText);
});
@@ -273,8 +267,12 @@ describe('OHIF Study Viewer Page', function() {
});
it('opens About modal and verify the displayed information', function() {
cy.get('[data-cy="options-menu"]').click();
cy.get('[data-cy="about-item-menu"]').click();
cy.get('[data-cy="options-menu"]')
.first()
.click();
cy.get('[data-cy="about-item-menu"]')
.first()
.click();
cy.get('.modal-content')
.as('aboutOverlay')
.should('be.visible');
@@ -293,6 +291,10 @@ describe('OHIF Study Viewer Page', function() {
});
});
// Visual comparison
cy.screenshot('About modal - Should display modal');
cy.percyCanvasSnapshot('About modal - Should display modal');
//close modal
cy.get('.close').click();
cy.get('@aboutOverlay').should('not.be.enabled');
@@ -21,6 +21,10 @@ describe('OHIF Microscopy Extension', () => {
.should('be.eq', 1);
cy.wait(3000); //Waiting for image to render before taking the snapshot
cy.percyCanvasSnapshot('Microscopy Extension');
// Visual comparison
cy.screenshot('Microscopy Extension - Should display loaded canvas');
cy.percyCanvasSnapshot(
'Microscopy Extension - Should display loaded canvas'
);
});
});
@@ -22,6 +22,7 @@ describe('OHIF PDF Extension', () => {
// This won't work unless we switch to an extension that renders using `canvas`
// Currently, we rely on the browser's built-in implementation
cy.percyCanvasSnapshot('PDF Extension');
cy.screenshot('PDF Extension - Should load PDF file');
cy.percyCanvasSnapshot('PDF Extension - Should load PDF file');
});
});
@@ -6,37 +6,36 @@ describe('OHIF VTK Extension', () => {
});
beforeEach(() => {
//TODO: Due to percy snapshot, the images inside the viewports are being resized
//and once the browser continues the test execution, the images are not centered in the viewport.
//To fix that, we need to reload the page before each test.
//https://github.com/OHIF/Viewers/issues/1168
cy.reload();
//Waiting for the desired thumbnail content to be displayed
cy.get('[data-cy="thumbnail-list"]').should($list => {
expect($list).to.contain('CT WB 5.0 B35f');
});
// TODO: We shouldn't have to drag the thumbnail
// This is a known bug; 2D MPR button does not show until viewport
// has data from a drag-n-drop
// Drag and drop first thumbnail into first viewport
cy.get('[data-cy="thumbnail-list"]:nth-child(3)').drag(
'.viewport-drop-target'
);
// Drag and drop third thumbnail into first viewport
cy.get('[data-cy="thumbnail-list"]')
.contains('CT WB 5.0 B35f')
.drag('.viewport-drop-target')
.wait(1000); // TODO: Once issue #1167 is fixed, the wait command should be removed. (https://github.com/OHIF/Viewers/issues/1167)
cy.get('.PluginSwitch > .toolbar-button')
.as('twodmprBtn')
.should('be.visible')
.then(btn => {
if (!btn.text().includes('Exit')) {
btn.click();
}
});
//Select 2D MPR button
cy.get('.PluginSwitch > .toolbar-button').click();
//Wait Reformatting Images
cy.waitVTKReformatting();
cy.initVTKToolsAliases();
});
it('checks if VTK buttons are displayed on the toolbar', () => {
// Wait for start reformatting
cy.get('[data-cy="viewprt-grid"]', { timeout: 10000 }).should($grid => {
expect($grid).to.contain.text('Reform');
});
// Wait for finish reformatting
cy.get('[data-cy="viewprt-grid"]', { timeout: 30000 }).should($grid => {
expect($grid).not.to.contain.text('Reform');
});
cy.get('@crosshairsBtn')
.should('be.visible')
.contains('Crosshairs');
@@ -57,7 +56,67 @@ describe('OHIF VTK Extension', () => {
.should('be.visible')
.contains('Layout');
cy.wait(3000);
cy.percyCanvasSnapshot('VTK Extension');
// Visual comparison
cy.screenshot('VTK initial state - Should display toolbar and 3 viewports');
cy.percyCanvasSnapshot(
'VTK initial state - Should display toolbar and 3 viewports'
);
});
it('checks Crosshairs tool', () => {
cy.get('@crosshairsBtn').click();
// Click and Move the mouse inside the viewport
cy.get('[data-cy="viewport-container-0"]')
.trigger('mousedown', 'center', { which: 1 })
.trigger('mousemove', 'top', { which: 1 })
.trigger('mouseup');
// Visual comparison
cy.screenshot(
"VTK Crosshairs tool - Should display crosshairs' green lines"
);
cy.percyCanvasSnapshot(
"VTK Crosshairs tool - Should display crosshairs' green lines"
);
});
it('checks WWWC tool', () => {
cy.get('@wwwcBtn').click();
// Click and Move the mouse inside the viewport
cy.get('[data-cy="viewport-container-0"]')
.trigger('mousedown', 'center', { which: 1 })
.trigger('mousemove', 'top', { which: 1 })
.trigger('mousedown', 'center', { which: 1 })
.trigger('mousemove', 'top', { which: 1 })
.trigger('mouseup', { which: 1 })
.then(() => {
const expectedText = 'W: 350 L: -1044';
cy.get('.ViewportOverlay > div.bottom-right.overlay-element').should(
'contains.text',
expectedText
);
});
// Visual comparison
cy.screenshot('VTK WWWC tool - Canvas should be bright');
cy.percyCanvasSnapshot('VTK WWWC tool - Canvas should be bright');
});
it('checks Rotate tool', () => {
cy.get('@rotateBtn').click();
// Click and Move the mouse inside the viewport
cy.get('[data-cy="viewport-container-0"]')
.trigger('mousedown', 'center', { which: 1 })
.trigger('mousemove', 'top', { which: 1 })
.trigger('mousedown', 'center', { which: 1 })
.trigger('mousemove', 'top', { which: 1 })
.trigger('mouseup', { which: 1 });
// Visual comparison
cy.screenshot('VTK Rotate tool - Should rotate image');
cy.percyCanvasSnapshot('VTK Rotate tool - Should rotate image');
});
});
@@ -52,3 +52,28 @@ export function initVTKToolsAliases() {
cy.get('.ohif-check-label').as('modeCheckbox');
cy.get('.btn-group > .toolbar-button').as('layoutBtn');
}
//Creating aliases for Study List page elements on Desktop experience
export function initStudyListAliasesOnDesktop() {
cy.get('.study-count').as('studyCount');
cy.get('#filter-patientName').as('patientName');
cy.get('#filter-patientId').as('MRN');
cy.get('#filter-accessionNumber').as('accessionNumber');
cy.get('#start-date').as('studyListStartDate');
cy.get('#end-date').as('studyListEndDate');
cy.get('#filter-modalities').as('modalities');
cy.get('#filter-studyDescription').as('studyDescription');
cy.get('[data-cy="study-list-results"] > tr').as('searchResult');
}
//Creating aliases for Study List page elements on Tablet experience
export function initStudyListAliasesOnTablet() {
cy.get('.study-count').as('studyCount');
cy.get('#filter-patientNameOrId').as('patientNameOrMRN');
cy.get('#filter-accessionOrModalityOrDescription').as(
'accessionModalityDescription'
);
cy.get('#start-date').as('studyListStartDate');
cy.get('#end-date').as('studyListEndDate');
cy.get('[data-cy="study-list-results"] > tr').as('searchResult');
}
+31 -1
View File
@@ -5,6 +5,8 @@ import {
initCommonElementsAliases,
initRouteAliases,
initVTKToolsAliases,
initStudyListAliasesOnDesktop,
initStudyListAliasesOnTablet,
} from './aliases.js';
// ***********************************************
@@ -76,12 +78,30 @@ Cypress.Commands.add('isPageLoaded', (url = '/viewer/') => {
return cy.location('pathname', { timeout: 60000 }).should('include', url);
});
Cypress.Commands.add('openStudyList', patientName => {
Cypress.Commands.add('openStudyList', () => {
cy.initRouteAliases();
cy.visit('/');
cy.wait('@getStudies');
});
Cypress.Commands.add('waitStudyList', () => {
cy.get('@searchResult').should($list => {
expect($list).to.not.have.class('no-hover');
});
});
Cypress.Commands.add('waitVTKReformatting', () => {
// Wait for start reformatting
cy.get('[data-cy="viewprt-grid"]', { timeout: 10000 }).should($grid => {
expect($grid).to.contain.text('Reform');
});
// Wait for finish reformatting
cy.get('[data-cy="viewprt-grid"]', { timeout: 30000 }).should($grid => {
expect($grid).not.to.contain.text('Reform');
});
});
/**
* Command to perform a drag and drop action. Before using this command, we must get the element that should be dragged first.
* Example of usage: cy.get(element-to-be-dragged).drag(dropzone-element)
@@ -240,6 +260,16 @@ Cypress.Commands.add('initVTKToolsAliases', () => {
initVTKToolsAliases();
});
//Initialize aliases for Study List page elements
Cypress.Commands.add('initStudyListAliasesOnDesktop', () => {
initStudyListAliasesOnDesktop();
});
//Initialize aliases for Study List page elements
Cypress.Commands.add('initStudyListAliasesOnTablet', () => {
initStudyListAliasesOnTablet();
});
//Add measurements in the viewport
Cypress.Commands.add(
'addLengthMeasurement',
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/viewer",
"version": "2.6.0",
"version": "2.8.2",
"description": "OHIF Viewer",
"author": "OHIF Contributors",
"license": "MIT",
@@ -45,14 +45,14 @@
},
"dependencies": {
"@babel/runtime": "^7.5.5",
"@ohif/core": "^1.7.0",
"@ohif/core": "^1.9.0",
"@ohif/extension-cornerstone": "^2.0.0",
"@ohif/extension-dicom-html": "^1.0.1",
"@ohif/extension-dicom-microscopy": "^0.50.6",
"@ohif/extension-dicom-pdf": "^1.0.0",
"@ohif/extension-vtk": "^0.53.0",
"@ohif/extension-vtk": "^0.53.6",
"@ohif/i18n": "^0.52.0",
"@ohif/ui": "^0.60.0",
"@ohif/ui": "^0.62.1",
"@tanem/react-nprogress": "^1.1.25",
"classnames": "^2.2.6",
"core-js": "^3.2.1",
+19 -4
View File
@@ -6,7 +6,9 @@ import './config';
import {
CommandsManager,
ExtensionManager,
ServicesManager,
HotkeysManager,
createUiNotificationService,
utils,
} from '@ohif/core';
import React, { Component } from 'react';
@@ -41,9 +43,16 @@ const commandsManagerConfig = {
getActiveContexts: () => getActiveContexts(store.getState()),
};
// Services
const UINotificationService = createUiNotificationService();
const commandsManager = new CommandsManager(commandsManagerConfig);
const hotkeysManager = new HotkeysManager(commandsManager);
const extensionManager = new ExtensionManager({ commandsManager });
const servicesManager = new ServicesManager();
const extensionManager = new ExtensionManager({
commandsManager,
servicesManager,
});
// ~~~~ END APP SETUP
// TODO[react] Use a provider when the whole tree is React
@@ -76,9 +85,11 @@ class App extends Component {
super(props);
this._appConfig = props;
const { servers, extensions, hotkeys, oidc } = props;
this.initUserManager(oidc);
_initServices([UINotificationService]);
_initExtensions(extensions, hotkeys);
_initServers(servers);
initWebWorkers();
@@ -100,7 +111,7 @@ class App extends Component {
<UserManagerContext.Provider value={userManager}>
<Router basename={routerBasename}>
<WhiteLabellingContext.Provider value={whiteLabelling}>
<SnackbarProvider>
<SnackbarProvider service={UINotificationService}>
<ModalProvider modal={OHIFModal}>
<OHIFStandaloneViewer userManager={userManager} />
</ModalProvider>
@@ -121,7 +132,7 @@ class App extends Component {
<I18nextProvider i18n={i18n}>
<Router basename={routerBasename}>
<WhiteLabellingContext.Provider value={whiteLabelling}>
<SnackbarProvider>
<SnackbarProvider service={UINotificationService}>
<ModalProvider modal={OHIFModal}>
<OHIFStandaloneViewer />
</ModalProvider>
@@ -168,6 +179,10 @@ class App extends Component {
}
}
function _initServices(services) {
services.forEach(service => servicesManager.register(service));
}
/**
* @param
*/
@@ -214,4 +229,4 @@ function _makeAbsoluteIfNecessary(url, base_url) {
const ExportedApp = process.env.NODE_ENV === 'development' ? hot(App) : App;
export default ExportedApp;
export { commandsManager, extensionManager, hotkeysManager };
export { commandsManager, extensionManager, hotkeysManager, servicesManager };
@@ -7,8 +7,8 @@ export default {
*/
id: 'measurements-table',
preRegistration(configuration = {}) {
init(configuration);
preRegistration({ serviceManager, configuration = {} }) {
init({ serviceManager, configuration });
},
getPanelModule() {
return {
@@ -34,7 +34,7 @@ const MEASUREMENT_ACTION_MAP = {
* @export
* @param {*} configuration
*/
export default function init(configuration) {
export default function init({ serviceManager, configuration = {} }) {
// If these tools were already added by a different extension, we want to replace
// them with the same tools that have an alternative configuration. By passing in
// our custom `getMeasurementLocationCallback`, we can...
@@ -4,6 +4,10 @@
height: var(--top-bar-height);
}
.dd-item {
width: 100%;
}
/* Home Page */
.entry-header.header-big {
background: rgba(21, 25, 30, 0.7);
+14 -29
View File
@@ -3,13 +3,11 @@ import { Link, withRouter } from 'react-router-dom';
import { withTranslation } from 'react-i18next';
import PropTypes from 'prop-types';
import { Dropdown } from '@ohif/ui';
import { AboutContent } from '@ohif/ui';
import { withModal } from '@ohif/ui';
import ConnectedUserPreferencesForm from '../../connectedComponents/ConnectedUserPreferencesForm';
import { Dropdown, AboutContent, withModal } from '@ohif/ui';
import OHIFLogo from '../OHIFLogo/OHIFLogo.js';
import { hotkeysManager } from './../../App.js';
import './Header.css';
// Context
import AppContext from './../../context/AppContext';
@@ -30,23 +28,9 @@ class Header extends Component {
children: OHIFLogo(),
};
// onSave: data => {
// const contextName = store.getState().commandContext.context;
// const preferences = cloneDeep(store.getState().preferences);
// preferences[contextName] = data;
// dispatch(setUserPreferences(preferences));
// dispatch(setUserPreferencesModalOpen(false));
// OHIF.hotkeysUtil.setHotkeys(data.hotKeysData);
// },
// onResetToDefaults: () => {
// dispatch(setUserPreferences());
// dispatch(setUserPreferencesModalOpen(false));
// OHIF.hotkeysUtil.setHotkeys();
// },
constructor(props) {
super(props);
this.state = { isUserPreferencesOpen: false, isOpen: false };
this.state = { isOpen: false };
this.loadOptions();
}
@@ -68,6 +52,16 @@ class Header extends Component {
customClassName: 'AboutContent',
}),
},
{
title: 'Preferences ',
icon: {
name: 'user',
},
onClick: () =>
show(ConnectedUserPreferencesForm, {
title: t('User Preferences'),
}),
},
];
if (user && userManager) {
@@ -77,15 +71,6 @@ class Header extends Component {
onClick: () => userManager.signoutRedirect(),
});
}
this.hotKeysData = hotkeysManager.hotkeyDefinitions;
}
onUserPreferencesSave({ windowLevelData, hotKeysData }) {
// console.log(windowLevelData);
// console.log(hotKeysData);
// TODO: Update hotkeysManager
// TODO: reset `this.hotKeysData`
}
// ANTD -- Hamburger, Drawer, Menu
@@ -1,10 +1,19 @@
import Header from '../components/Header/Header.js';
import { connect } from 'react-redux';
import { hotkeysManager } from '../App.js';
const mapStateToProps = state => {
const { hotkeyDefinitions = [] } = state.preferences || {};
const _hotkeyDefinitions =
hotkeyDefinitions.length > 0
? hotkeyDefinitions
: hotkeysManager.hotkeyDefaults;
hotkeysManager.setHotkeys(_hotkeyDefinitions);
return {
user: state.oidc && state.oidc.user,
isOpen: state.ui.userPreferencesModalOpen,
};
};
@@ -0,0 +1,41 @@
import { connect } from 'react-redux';
import { UserPreferencesForm } from '@ohif/ui';
import OHIF from '@ohif/core';
import { hotkeysManager } from '../App.js';
const { setUserPreferences } = OHIF.redux.actions;
const mapStateToProps = (state, ownProps) => {
const hotkeyDefinitions =
state.preferences.hotkeyDefinitions.length > 0
? state.preferences.hotkeyDefinitions
: hotkeysManager.hotkeyDefaults;
hotkeysManager.setHotkeys(hotkeyDefinitions);
return {
onClose: ownProps.hide,
windowLevelData: state.preferences ? state.preferences.windowLevelData : {},
hotkeyDefinitions,
};
};
const mapDispatchToProps = (dispatch, ownProps) => {
return {
onSave: ({ windowLevelData, hotkeyDefinitions }) => {
hotkeysManager.setHotkeys(hotkeyDefinitions);
ownProps.hide();
dispatch(setUserPreferences({ windowLevelData, hotkeyDefinitions }));
},
onResetToDefaults: () => {
hotkeysManager.restoreDefaultBindings();
ownProps.hide();
dispatch(setUserPreferences());
},
};
};
const ConnectedUserPreferencesForm = connect(
mapStateToProps,
mapDispatchToProps
)(UserPreferencesForm);
export default ConnectedUserPreferencesForm;
@@ -9,6 +9,7 @@ export default class DatasetPicker extends Component {
error: null,
loading: true,
datasets: [],
filterStr: ""
};
static propTypes = {
@@ -41,15 +42,23 @@ export default class DatasetPicker extends Component {
}
render() {
const { datasets, loading, error } = this.state;
const { datasets, loading, error, filterStr } = this.state;
const { onSelect } = this.props;
return (
<div>
<input class="form-control gcp-input"
type="text"
value={ filterStr }
onChange={ e => this.setState({ filterStr: e.target.value }) }
/>
<DatasetsList
datasets={datasets}
loading={loading}
error={error}
filter={filterStr}
onSelect={onSelect}
/>
</div>
);
}
}
@@ -46,21 +46,23 @@ class DatasetsList extends Component {
}
render() {
if (this.props.error) {
return <p>{this.props.error}</p>;
const { loading, datasets, filter, error } = this.props;
if (error) {
return <p>{error}</p>;
}
const loadingIcon = (
<Icon name="circle-notch" className="loading-icon-spin loading-icon" />
);
if (this.props.loading) {
if (loading) {
return loadingIcon;
}
const body = (
<tbody id="DatasetList">
{this.props.datasets.map(this.renderTableRow)}
{datasets.filter(dataset => (dataset.name.split('/')[5].toLowerCase().includes(filter.toLowerCase()) || filter=="")).map(this.renderTableRow)}
</tbody>
);
@@ -71,7 +73,7 @@ class DatasetsList extends Component {
<th>{this.props.t('Dataset')}</th>
</tr>
</thead>
{this.props.datasets && body}
{datasets && body}
</table>
);
}
@@ -46,20 +46,24 @@ class DicomStoreList extends Component {
}
render() {
if (this.props.error) {
return <p>{this.props.error}</p>;
const { loading, stores, filter, error } = this.props;
if (error) {
return <p>{error}</p>;
}
const loadingIcon = (
<Icon name="circle-notch" className="loading-icon-spin loading-icon" />
);
if (this.props.loading) {
if (loading) {
return loadingIcon;
}
const body = (
<tbody id="StoreList">{this.props.stores.map(this.renderTableRow)}</tbody>
<tbody id="StoreList">{
stores.filter(store => (store.name.split('/')[7].toLowerCase().includes(filter.toLowerCase()) || filter=="")).map(this.renderTableRow)}
</tbody>
);
return (
@@ -69,7 +73,7 @@ class DicomStoreList extends Component {
<th>{this.props.t('DICOM Store')}</th>
</tr>
</thead>
{this.props.stores && body}
{stores && body}
</table>
);
}
@@ -10,6 +10,7 @@ export default class DicomStorePicker extends Component {
loading: true,
stores: [],
locations: [],
filterStr: ""
};
static propTypes = {
@@ -38,16 +39,24 @@ export default class DicomStorePicker extends Component {
}
render() {
const { stores, loading, error } = this.state;
const { stores, loading, error, filterStr } = this.state;
const { onSelect } = this.props;
return (
<DicomStoreList
stores={stores}
loading={loading}
error={error}
onSelect={onSelect}
/>
<div>
<input class="form-control gcp-input"
type="text"
value={ filterStr }
onChange={ e => this.setState({ filterStr: e.target.value }) }
/>
<DicomStoreList
stores={stores}
loading={loading}
error={error}
filter={filterStr}
onSelect={onSelect}
/>
</div>
);
}
}
@@ -9,6 +9,7 @@ export default class LocationPicker extends Component {
error: null,
loading: true,
locations: [],
filterStr: "",
};
static propTypes = {
@@ -37,15 +38,23 @@ export default class LocationPicker extends Component {
}
render() {
const { locations, loading, error } = this.state;
const { locations, loading, error, filterStr } = this.state;
const { onSelect } = this.props;
return (
<LocationsList
locations={locations}
loading={loading}
error={error}
onSelect={onSelect}
/>
<div>
<input class="form-control gcp-input"
type="text"
value={ filterStr }
onChange={ e => this.setState({ filterStr: e.target.value }) }
/>
<LocationsList
locations={locations}
loading={loading}
error={error}
filter={filterStr}
onSelect={onSelect}
/>
</div>
);
}
}
@@ -46,21 +46,23 @@ class LocationsList extends Component {
}
render() {
if (this.props.error) {
return <p>{this.props.error}</p>;
const { loading, locations, filter, error } = this.props;
if (error) {
return <p>{error}</p>;
}
const loadingIcon = (
<Icon name="circle-notch" className="loading-icon-spin loading-icon" />
);
if (this.props.loading) {
if (loading) {
return loadingIcon;
}
const body = (
<tbody id="LocationList">
{this.props.locations.map(this.renderTableRow)}
{locations.filter(location => (location.name.split('/')[3].toLowerCase().includes(filter.toLowerCase()) || filter=="")).map(this.renderTableRow)}
</tbody>
);
@@ -71,7 +73,7 @@ class LocationsList extends Component {
<th>{this.props.t('Location')}</th>
</tr>
</thead>
{this.props.locations && body}
{locations && body}
</table>
);
}
@@ -30,20 +30,29 @@ export default class ProjectPicker extends Component {
this.setState({
projects: response.data.projects || [],
filterStr: "",
loading: false,
});
}
render() {
const { projects, loading, error } = this.state;
const { projects, loading, filterStr, error } = this.state;
const { onSelect } = this.props;
return (
<ProjectsList
projects={projects}
loading={loading}
error={error}
onSelect={onSelect}
/>
<div>
<input class="form-control gcp-input"
type="text"
value={ filterStr }
onChange={ e => this.setState({ filterStr: e.target.value }) }
/>
<ProjectsList
projects={projects}
loading={loading}
filter={filterStr}
error={error}
onSelect={onSelect}
/>
</div>
);
}
}
@@ -50,21 +50,22 @@ class ProjectsList extends Component {
}
render() {
if (this.props.error) {
return <p>{this.props.error}</p>;
const { loading, projects, filter, error } = this.props;
if (error) {
return <p>{error}</p>;
}
const loadingIcon = (
<Icon name="circle-notch" className="loading-icon-spin loading-icon" />
);
if (this.props.loading) {
if (loading) {
return loadingIcon;
}
const body = (
<tbody id="ProjectList">
{this.props.projects.map(this.renderTableRow)}
{projects.filter(project => (project.name.toLowerCase().includes(filter.toLowerCase()) || filter=="")).map(this.renderTableRow)}
</tbody>
);
@@ -76,7 +77,7 @@ class ProjectsList extends Component {
<th>{this.props.t('ID')}</th>
</tr>
</thead>
{this.props.projects && body}
{projects && body}
</table>
);
}
@@ -141,3 +141,15 @@
margin: 20px auto;
}
.gcp-input{
height: 40px;
margin: 0 5px 20px 5px;
padding: 0 20px;
cursor: pointer;
border: none;
background-color: var(--input-background-color);
color: var(--input-placeholder-color);
font-size: 10pt;
font-weight: normal;
border-radius: 4px;
}
+46 -1
View File
@@ -1100,13 +1100,34 @@
pirates "^4.0.0"
source-map-support "^0.5.9"
"@babel/runtime@7.1.2", "@babel/runtime@7.5.5", "@babel/runtime@7.6.0", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.6.3":
"@babel/runtime@7.1.2":
version "7.1.2"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.1.2.tgz#81c89935f4647706fc54541145e6b4ecfef4b8e3"
integrity sha512-Y3SCjmhSupzFB6wcv1KmmFucH6gDVnI30WjOcicV10ju0cZjak3Jcs67YLIXBrmZYw1xCrVeJPbycFwrqNyxpg==
dependencies:
regenerator-runtime "^0.12.0"
"@babel/runtime@7.6.0":
version "7.6.0"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.6.0.tgz#4fc1d642a9fd0299754e8b5de62c631cf5568205"
integrity sha512-89eSBLJsxNxOERC0Op4vd+0Bqm6wRMqMbFtV3i0/fbaWw/mJ8Q3eBvgX0G4SyrOOLCtbu98HspF8o09MRT+KzQ==
dependencies:
regenerator-runtime "^0.13.2"
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5":
version "7.5.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132"
integrity sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ==
dependencies:
regenerator-runtime "^0.13.2"
"@babel/runtime@^7.6.0", "@babel/runtime@^7.6.3":
version "7.7.2"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.2.tgz#111a78002a5c25fc8e3361bedc9529c696b85a6a"
integrity sha512-JONRbXbTXc9WQE2mAZd1p0Z3DZ/6vaQIkgYMSTP3KjRCyd7rCZCcfhCyX+YjwcKxcZ82UrxbRD358bpExNgrjw==
dependencies:
regenerator-runtime "^0.13.2"
"@babel/template@^7.0.0", "@babel/template@^7.1.0", "@babel/template@^7.4.0", "@babel/template@^7.4.4", "@babel/template@^7.6.0":
version "7.6.0"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.6.0.tgz#7f0159c7f5012230dad64cca42ec9bdb5c9536e6"
@@ -15693,6 +15714,15 @@ query-string@^4.1.0:
object-assign "^4.1.0"
strict-uri-encode "^1.0.0"
query-string@^6.8.3:
version "6.8.3"
resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.8.3.tgz#fd9fb7ffb068b79062b43383685611ee47777d4b"
integrity sha512-llcxWccnyaWlODe7A9hRjkvdCKamEKTh+wH8ITdTc3OhchaqUZteiSCX/2ablWHVrkVIe04dntnaZJ7BdyW0lQ==
dependencies:
decode-uri-component "^0.2.0"
split-on-first "^1.0.0"
strict-uri-encode "^2.0.0"
querystring-es3@^0.2.0:
version "0.2.1"
resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
@@ -16604,6 +16634,11 @@ regenerator-runtime@^0.11.0, regenerator-runtime@^0.11.1:
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
regenerator-runtime@^0.12.0:
version "0.12.1"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de"
integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==
regenerator-runtime@^0.13.1, regenerator-runtime@^0.13.2:
version "0.13.3"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz#7cf6a77d8f5c6f60eb73c5fc1955b2ceb01e6bf5"
@@ -18081,6 +18116,11 @@ specificity@^0.4.1:
resolved "https://registry.yarnpkg.com/specificity/-/specificity-0.4.1.tgz#aab5e645012db08ba182e151165738d00887b019"
integrity sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg==
split-on-first@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f"
integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==
split-string@^3.0.1, split-string@^3.0.2:
version "3.1.0"
resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2"
@@ -18301,6 +18341,11 @@ strict-uri-encode@^1.0.0:
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=
strict-uri-encode@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546"
integrity sha1-ucczDHBChi9rFC3CdLvMWGbONUY=
string-argv@^0.3.0:
version "0.3.1"
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da"