Compare commits

..
Author SHA1 Message Date
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
47 changed files with 934 additions and 422 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)
+11
View File
@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [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.4.1",
"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"
}
}
+19
View File
@@ -2,6 +2,25 @@ 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);
/**
*
+35
View File
@@ -3,6 +3,41 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.53.4](https://github.com/OHIF/Viewers/compare/@ohif/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.4",
"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.8.0",
"@ohif/ui": "^0.61.0",
"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';
+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.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.8.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;
}
}
@@ -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: '' },
},
};
@@ -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);
}
});
+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.
# [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.61.0",
"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,14 @@ import PropTypes from 'prop-types';
export class HotKeysPreferences extends Component {
static propTypes = {
hotKeysData: PropTypes.objectOf(
PropTypes.shape({
keys: PropTypes.arrayOf(PropTypes.string).isRequired,
label: PropTypes.string.isRequired,
})
).isRequired,
onChange: PropTypes.func,
hotkeyDefinitions: PropTypes.array.isRequired,
};
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 +36,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 +76,7 @@ export class HotKeysPreferences extends Component {
specialKeyName ||
keyDownEvent.key ||
String.fromCharCode(keyDownEvent.keyCode);
pressedKeys.push(keyName.toUpperCase());
pressedKeys.push(keyName);
}
this.updateHotKeysState(commandName, pressedKeys.join('+'));
@@ -136,17 +123,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];
// 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 +141,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;
}
@@ -177,8 +156,7 @@ export class HotKeysPreferences extends Component {
*/
const modifierCommand = pressedKeys
.slice(0, pressedKeys.length - 1)
.join('+')
.toUpperCase();
.join('+');
const disallowedCombination = disallowedCombinations[modifierCommand];
const hasDisallowedCombinations = disallowedCombination
@@ -189,7 +167,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;
}
@@ -8,14 +8,14 @@ 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.array.isRequired,
windowLevelData: PropTypes.object.isRequired,
generalData: PropTypes.object.isRequired,
};
@@ -32,7 +32,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 +68,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 +95,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,86 @@
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.array,
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,
+54
View File
@@ -3,6 +3,60 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [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)
@@ -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);
// });
// });
});
});
@@ -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');
@@ -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');
}
+19 -1
View File
@@ -5,6 +5,8 @@ import {
initCommonElementsAliases,
initRouteAliases,
initVTKToolsAliases,
initStudyListAliasesOnDesktop,
initStudyListAliasesOnTablet,
} from './aliases.js';
// ***********************************************
@@ -76,12 +78,18 @@ 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');
});
});
/**
* 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 +248,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.7.1",
"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.8.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.4",
"@ohif/i18n": "^0.52.0",
"@ohif/ui": "^0.60.0",
"@ohif/ui": "^0.61.0",
"@tanem/react-nprogress": "^1.1.25",
"classnames": "^2.2.6",
"core-js": "^3.2.1",
@@ -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;
+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"