Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
659649e10b | ||
|
|
f431b277dd | ||
|
|
2bc361cdca | ||
|
|
c6f306c0de | ||
|
|
c0ea27f6b3 | ||
|
|
df8d4029f1 | ||
|
|
a36cb63926 | ||
|
|
e51529e6c9 | ||
|
|
750a150644 | ||
|
|
a9d28d85da | ||
|
|
6ee5094cbe |
No files matched your search
@@ -142,8 +142,8 @@ jobs:
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: ~/repo
|
||||
# - setup_remote_docker:
|
||||
# docker_layer_caching: true
|
||||
- setup_remote_docker:
|
||||
docker_layer_caching: false
|
||||
- run:
|
||||
name: Build and push Docker image
|
||||
command: |
|
||||
@@ -198,8 +198,8 @@ jobs:
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: ~/repo
|
||||
# - setup_remote_docker:
|
||||
# docker_layer_caching: true
|
||||
- setup_remote_docker:
|
||||
docker_layer_caching: false
|
||||
- run:
|
||||
name: Deploy latest version to viewer.ohif.org
|
||||
command: |
|
||||
|
||||
@@ -3,6 +3,25 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.52.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.5...@ohif/extension-vtk@0.52.6) (2019-10-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.52.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.4...@ohif/extension-vtk@0.52.5) (2019-10-10)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* ⚡️ Update react-vtkjs-viewport for performance improvement ([6ee5094](https://github.com/OHIF/Viewers/commit/6ee5094))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.52.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.3...@ohif/extension-vtk@0.52.4) (2019-10-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-vtk",
|
||||
"version": "0.52.4",
|
||||
"version": "0.52.6",
|
||||
"description": "OHIF extension for VTK.js",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -48,10 +48,10 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"react-vtkjs-viewport": "0.1.4"
|
||||
"react-vtkjs-viewport": "^0.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ohif/core": "^1.2.0",
|
||||
"@ohif/core": "^1.3.0",
|
||||
"@ohif/ui": "^0.53.3",
|
||||
"cornerstone-tools": "^4.0.9",
|
||||
"cornerstone-wado-image-loader": "^3.0.0",
|
||||
|
||||
@@ -153,7 +153,7 @@ class OHIFVTKViewport extends Component {
|
||||
switch (sopClassUid) {
|
||||
case SOP_CLASSES.SEGMENTATION_STORAGE:
|
||||
throw new Error('Not yet implemented');
|
||||
|
||||
/*
|
||||
const data = handleSegmentationStorage(
|
||||
stack.imageIds,
|
||||
displaySetInstanceUid
|
||||
@@ -168,10 +168,13 @@ class OHIFVTKViewport extends Component {
|
||||
labelmap: labelmapDataObject,
|
||||
};
|
||||
});
|
||||
*/
|
||||
default:
|
||||
imageDataObject = getImageData(stack.imageIds, displaySetInstanceUid);
|
||||
|
||||
return loadImageData(imageDataObject).then(() => {
|
||||
const loadImageDataPromise = loadImageData(imageDataObject);
|
||||
|
||||
return loadImageDataPromise.then(() => {
|
||||
return {
|
||||
data: imageDataObject.vtkImageData,
|
||||
};
|
||||
@@ -265,7 +268,6 @@ class OHIFVTKViewport extends Component {
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
console.log(this.props.viewportIndex);
|
||||
vtkViewportSubscriptionManager.unsubscribe(this.props.viewportIndex);
|
||||
}
|
||||
|
||||
|
||||
@@ -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.3.0](https://github.com/OHIF/Viewers/compare/@ohif/core@1.2.0...@ohif/core@1.3.0) (2019-10-11)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 🎸 Improve usability of Google Cloud adapter, including direct routes to studies ([#989](https://github.com/OHIF/Viewers/issues/989)) ([2bc361c](https://github.com/OHIF/Viewers/commit/2bc361c))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.2.0](https://github.com/OHIF/Viewers/compare/@ohif/core@1.1.0...@ohif/core@1.2.0) (2019-10-09)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/core",
|
||||
"version": "1.2.0",
|
||||
"version": "1.3.0",
|
||||
"description": "Generic business logic for web-based medical imaging applications",
|
||||
"author": "OHIF Core Team",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import actions from './actions.js';
|
||||
import reducers from './reducers';
|
||||
import localStorage from './localStorage.js';
|
||||
import sessionStorage from './sessionStorage.js';
|
||||
|
||||
const redux = {
|
||||
reducers,
|
||||
actions,
|
||||
localStorage,
|
||||
sessionStorage,
|
||||
};
|
||||
|
||||
export default redux;
|
||||
@@ -2,7 +2,7 @@ import redux from './index.js';
|
||||
|
||||
describe('redux exports', () => {
|
||||
test('have not changed', () => {
|
||||
const expectedExports = ['actions', 'reducers', 'localStorage'].sort();
|
||||
const expectedExports = ['actions', 'reducers', 'localStorage', 'sessionStorage'].sort();
|
||||
|
||||
const exports = Object.keys(redux).sort();
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
const LocalStorageApi = window.localStorage;
|
||||
const localStorageKey = 'state';
|
||||
export const loadState = () => {
|
||||
try {
|
||||
const serializedState = window.localStorage.getItem('state');
|
||||
const serializedState = LocalStorageApi.getItem(localStorageKey);
|
||||
if (!serializedState) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -14,7 +16,7 @@ export const loadState = () => {
|
||||
export const saveState = state => {
|
||||
try {
|
||||
const serializedState = JSON.stringify(state);
|
||||
localStorage.setItem('state', serializedState);
|
||||
LocalStorageApi.setItem(localStorageKey, serializedState);
|
||||
} catch (e) {}
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
const SessionStorageApi = window.sessionStorage;
|
||||
const sessionStorageKey = 'state';
|
||||
export const loadState = () => {
|
||||
try {
|
||||
const serializedState = SessionStorageApi.getItem(sessionStorageKey);
|
||||
if (!serializedState) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return JSON.parse(serializedState);
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
export const saveState = state => {
|
||||
try {
|
||||
const serializedState = JSON.stringify(state);
|
||||
SessionStorageApi.setItem(sessionStorageKey, serializedState);
|
||||
} catch (e) {}
|
||||
};
|
||||
|
||||
const sessionStorage = {
|
||||
saveState,
|
||||
loadState,
|
||||
};
|
||||
|
||||
export default sessionStorage;
|
||||
@@ -9,6 +9,7 @@ import studyMetadataManager from './studyMetadataManager';
|
||||
import updateMetaDataManager from './updateMetaDataManager.js';
|
||||
import writeScript from './writeScript.js';
|
||||
import DicomLoaderService from './dicomLoaderService.js';
|
||||
import * as urlUtil from './urlUtil';
|
||||
|
||||
const utils = {
|
||||
guid,
|
||||
@@ -23,6 +24,7 @@ const utils = {
|
||||
updateMetaDataManager,
|
||||
DICOMTagDescriptions,
|
||||
DicomLoaderService,
|
||||
urlUtil,
|
||||
};
|
||||
|
||||
export {
|
||||
@@ -38,6 +40,7 @@ export {
|
||||
updateMetaDataManager,
|
||||
DICOMTagDescriptions,
|
||||
DicomLoaderService,
|
||||
urlUtil,
|
||||
};
|
||||
|
||||
export default utils;
|
||||
@@ -15,6 +15,7 @@ describe('Top level exports', () => {
|
||||
'updateMetaDataManager',
|
||||
'DICOMTagDescriptions',
|
||||
'DicomLoaderService',
|
||||
'urlUtil'
|
||||
].sort();
|
||||
|
||||
const exports = Object.keys(utils.default).sort();
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import lib from 'query-string';
|
||||
|
||||
const PARAM_SEPARATOR = ';';
|
||||
const PARAM_PATTERN_IDENTIFIER = ':';
|
||||
|
||||
function toLowerCaseFirstLetter(word) {
|
||||
return word[0].toLowerCase() + word.slice(1);
|
||||
}
|
||||
const getFilters = (location = {}) => {
|
||||
const { search } = location;
|
||||
|
||||
if (!search) {
|
||||
return;
|
||||
}
|
||||
|
||||
const searchParameters = parse(search);
|
||||
const filters = {};
|
||||
|
||||
Object.entries(searchParameters).forEach(([key, value]) => {
|
||||
filters[toLowerCaseFirstLetter(key)] = value;
|
||||
});
|
||||
|
||||
return filters;
|
||||
};
|
||||
|
||||
const decode = (strToDecode = '') => {
|
||||
try {
|
||||
const decoded = window.atob(strToDecode);
|
||||
return decoded;
|
||||
} catch (e) {
|
||||
return strToDecode;
|
||||
}
|
||||
};
|
||||
|
||||
const parse = toParse => {
|
||||
if (toParse) {
|
||||
return lib.parse(toParse);
|
||||
}
|
||||
|
||||
return {};
|
||||
};
|
||||
const parseParam = paramStr => {
|
||||
const _paramDecoded = decode(paramStr);
|
||||
if (_paramDecoded && typeof _paramDecoded === 'string') {
|
||||
return _paramDecoded.split(PARAM_SEPARATOR);
|
||||
}
|
||||
};
|
||||
|
||||
const replaceParam = (path = '', paramKey, paramValue) => {
|
||||
const paramPattern = `${PARAM_PATTERN_IDENTIFIER}${paramKey}`;
|
||||
if (paramValue) {
|
||||
return path.replace(paramPattern, paramValue);
|
||||
}
|
||||
|
||||
return path;
|
||||
};
|
||||
|
||||
const queryString = {
|
||||
getQueryFilters: getFilters,
|
||||
};
|
||||
|
||||
const paramString = {
|
||||
parseParam: parseParam,
|
||||
replaceParam: replaceParam,
|
||||
};
|
||||
|
||||
export { parse, queryString, paramString };
|
||||
@@ -3,6 +3,33 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.6.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.5.4...@ohif/viewer@1.6.0) (2019-10-11)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 🎸 Improve usability of Google Cloud adapter, including direct routes to studies ([#989](https://github.com/OHIF/Viewers/issues/989)) ([2bc361c](https://github.com/OHIF/Viewers/commit/2bc361c))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.5.4](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.5.3...@ohif/viewer@1.5.4) (2019-10-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.5.3](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.5.2...@ohif/viewer@1.5.3) (2019-10-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.5.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.5.1...@ohif/viewer@1.5.2) (2019-10-10)
|
||||
|
||||
|
||||
|
||||
@@ -1,407 +1,457 @@
|
||||
describe('OHIF Cornerstone Toolbar', () => {
|
||||
|
||||
before(() => {
|
||||
cy.openStudy("MISTER^MR");
|
||||
cy.waitDicomImage();
|
||||
cy.openStudy('MISTER^MR');
|
||||
cy.waitDicomImage();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.initCornerstoneToolsAliases();
|
||||
cy.initCommonElementsAliases();
|
||||
//Following best practices, reset should be done before each test
|
||||
cy.resetViewport();
|
||||
});
|
||||
|
||||
it('checks if all primary buttons are being displayed', () => {
|
||||
cy.get('@stackScrollBtn')
|
||||
.should('be.visible')
|
||||
.contains('Stack Scroll');
|
||||
cy.get('@zoomBtn')
|
||||
.should('be.visible')
|
||||
.contains('Zoom');
|
||||
cy.get('@levelsBtn')
|
||||
.should('be.visible')
|
||||
.contains('Levels');
|
||||
cy.get('@panBtn')
|
||||
.should('be.visible')
|
||||
.contains('Pan');
|
||||
cy.get('@lengthBtn')
|
||||
.should('be.visible')
|
||||
.contains('Length');
|
||||
cy.get('@annotateBtn')
|
||||
.should('be.visible')
|
||||
.contains('Annotate');
|
||||
cy.get('@angleBtn')
|
||||
.should('be.visible')
|
||||
.contains('Angle');
|
||||
cy.get('@resetBtn')
|
||||
.should('be.visible')
|
||||
.contains('Reset');
|
||||
cy.get('@cineBtn')
|
||||
.should('be.visible')
|
||||
.contains('CINE');
|
||||
cy.get('@moreBtn')
|
||||
.should('be.visible')
|
||||
.contains('More');
|
||||
cy.get('@twodmprBtn')
|
||||
.should('be.visible')
|
||||
.contains('2D MPR');
|
||||
cy.get('@layoutBtn')
|
||||
.should('be.visible')
|
||||
.contains('Layout');
|
||||
});
|
||||
|
||||
it('checks if Stack Scroll tool will navigate across all series in the viewport', () => {
|
||||
//Click on button and vefiry if icon is active on toolbar
|
||||
cy.get('@stackScrollBtn')
|
||||
.click()
|
||||
.then($stackScrollBtn => {
|
||||
cy.wrap($stackScrollBtn).should('have.class', 'active');
|
||||
});
|
||||
|
||||
//drags the mouse inside the viewport to be able to interact with series
|
||||
cy.get('@viewport')
|
||||
.trigger('mousedown', 'top', { which: 1 })
|
||||
.trigger('mousemove', 'center', { which: 1 })
|
||||
.trigger('mouseup');
|
||||
|
||||
const expectedText =
|
||||
'Ser: 1Img: 14 14/26256 x 256Loc: 0.00 mm Thick: 5.00 mm';
|
||||
cy.get('@viewportInfoBottomLeft').should('have.text', expectedText);
|
||||
});
|
||||
|
||||
it('checks if Zoom tool will zoom in/out an image in the viewport', () => {
|
||||
//Click on button and vefiry if icon is active on toolbar
|
||||
cy.get('@zoomBtn')
|
||||
.click()
|
||||
.then($zoomBtn => {
|
||||
cy.wrap($zoomBtn).should('have.class', 'active');
|
||||
});
|
||||
|
||||
//drags the mouse inside the viewport to be able to interact with series
|
||||
cy.get('@viewport')
|
||||
.trigger('mousedown', 'top', { which: 1 })
|
||||
.trigger('mousemove', 'center', { which: 1 })
|
||||
.trigger('mouseup');
|
||||
|
||||
const expectedText = 'Zoom: 884%W: 820 L: 410Lossless / Uncompressed';
|
||||
cy.get('@viewportInfoBottomRight').should('have.text', expectedText);
|
||||
});
|
||||
|
||||
it('checks if Levels tool will change the contrast and brightness of an image in the viewport', () => {
|
||||
//Click on button and vefiry if icon is active on toolbar
|
||||
cy.get('@levelsBtn')
|
||||
.click()
|
||||
.then($levelsBtn => {
|
||||
cy.wrap($levelsBtn).should('have.class', 'active');
|
||||
});
|
||||
|
||||
//drags the mouse inside the viewport to be able to interact with series
|
||||
cy.get('@viewport')
|
||||
.trigger('mousedown', 'top', { which: 1 })
|
||||
.trigger('mousemove', 'center', { which: 1 })
|
||||
.trigger('mouseup')
|
||||
.trigger('mousedown', 'center', { which: 1 })
|
||||
.trigger('mousemove', 'left', { which: 1 })
|
||||
.trigger('mouseup');
|
||||
|
||||
const expectedText = 'Zoom: 211%W: 544 L: 626Lossless / Uncompressed';
|
||||
cy.get('@viewportInfoBottomRight').should('have.text', expectedText);
|
||||
});
|
||||
|
||||
it('checks if Pan tool will move the image inside the viewport', () => {
|
||||
//Click on button and vefiry if icon is active on toolbar
|
||||
cy.get('@panBtn')
|
||||
.click()
|
||||
.then($panBtn => {
|
||||
cy.wrap($panBtn).should('have.class', 'active');
|
||||
});
|
||||
|
||||
//Get image position from cornerstone and check if y axis was modified
|
||||
let cornerstone;
|
||||
let currentPan;
|
||||
|
||||
cy.window()
|
||||
.its('cornerstone')
|
||||
.then(c => {
|
||||
cornerstone = c;
|
||||
currentPan = () =>
|
||||
cornerstone.getEnabledElements()[0].viewport.translation;
|
||||
});
|
||||
|
||||
cy.get('@viewport')
|
||||
.trigger('mousedown', 'center', { which: 1 })
|
||||
.trigger('mousemove', 'bottom', { which: 1 })
|
||||
.trigger('mouseup', 'bottom')
|
||||
.then(() => {
|
||||
expect(currentPan().y > 0).to.eq(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('checks if Length annotation can be added on viewport and on measurements panel', () => {
|
||||
//Click on button and vefiry if icon is active on toolbar
|
||||
cy.get('@lengthBtn')
|
||||
.click()
|
||||
.then($lengthbtn => {
|
||||
cy.wrap($lengthbtn).should('have.class', 'active');
|
||||
});
|
||||
|
||||
//Add annotation on the viewport
|
||||
const firstClick = [150, 100];
|
||||
const secondClick = [130, 170];
|
||||
cy.addLine('.cornerstone-canvas', firstClick, secondClick);
|
||||
|
||||
//Verify if measurement annotation was added into the measurements panel
|
||||
cy.get('@measurementsBtn')
|
||||
.click()
|
||||
.then($measurementsBtn => {
|
||||
cy.get('@measurementsPanel').should('be.visible');
|
||||
|
||||
cy.get('.measurementItem')
|
||||
.its('length')
|
||||
.should('be.eq', 1);
|
||||
|
||||
cy.wrap($measurementsBtn).click();
|
||||
});
|
||||
});
|
||||
|
||||
it('checks if Angle annotation can be added on viewport and on measurements panel', () => {
|
||||
//Click on button and vefiry if icon is active on toolbar
|
||||
cy.get('@angleBtn')
|
||||
.click()
|
||||
.then($angleBtn => {
|
||||
cy.wrap($angleBtn).should('have.class', 'active');
|
||||
});
|
||||
|
||||
//Add annotation on the viewport
|
||||
const initPos = [180, 390];
|
||||
const midPos = [300, 410];
|
||||
const finalPos = [180, 450];
|
||||
cy.addAngle('.cornerstone-canvas', initPos, midPos, finalPos);
|
||||
|
||||
//Verify if measurement annotation was added into the measurements panel
|
||||
cy.get('@measurementsBtn')
|
||||
.click()
|
||||
.then($measurementsBtn => {
|
||||
cy.get('@measurementsPanel').should('be.visible');
|
||||
|
||||
cy.get('.measurementItem')
|
||||
.its('length')
|
||||
.should('be.eq', 1);
|
||||
|
||||
cy.wrap($measurementsBtn).click();
|
||||
});
|
||||
});
|
||||
|
||||
it('checks if Reset tool will reset all changes made on the image', () => {
|
||||
//Make some changes by zooming in and rotating the image
|
||||
cy.imageZoomIn();
|
||||
cy.imageContrast();
|
||||
|
||||
//Click on reset button
|
||||
cy.get('@resetBtn').click();
|
||||
|
||||
const expectedText = 'Zoom: 211%W: 820 L: 410Lossless / Uncompressed';
|
||||
cy.get('@viewportInfoBottomRight').should('have.text', expectedText);
|
||||
});
|
||||
|
||||
it('checks if CINE tool will prompt a modal with working controls', () => {
|
||||
//Click on button
|
||||
cy.get('@cineBtn').click();
|
||||
//Vefiry if cine control overlay is being displayed
|
||||
cy.get('.cine-controls')
|
||||
.as('cineControls')
|
||||
.should('be.visible');
|
||||
|
||||
//Test PLAY button
|
||||
cy.get('[title="Play / Stop"]')
|
||||
.click()
|
||||
.wait(100)
|
||||
.click();
|
||||
|
||||
let expectedText = 'Img: 1 1/26';
|
||||
cy.get('@viewportInfoBottomLeft').should('not.have.text', expectedText);
|
||||
|
||||
//Test SKIP TO FIRST IMAGE button
|
||||
cy.get('[title="Skip to first Image"]').click();
|
||||
cy.get('@viewportInfoBottomLeft').should('contain.text', expectedText);
|
||||
|
||||
//Test NEXT IMAGE button
|
||||
cy.get('[title="Next Image"]').click();
|
||||
expectedText = 'Img: 2 2/26';
|
||||
cy.get('@viewportInfoBottomLeft').should('contain.text', expectedText);
|
||||
|
||||
//Test SKIP TO LAST IMAGE button
|
||||
cy.get('[title="Skip to last Image"]').click();
|
||||
expectedText = 'Img: 27 26/26';
|
||||
cy.get('@viewportInfoBottomLeft').should('contain.text', expectedText);
|
||||
|
||||
//Test PREVIOUS IMAGE button
|
||||
cy.get('[title="Previous Image"]').click();
|
||||
expectedText = 'Img: 26 25/26';
|
||||
cy.get('@viewportInfoBottomLeft').should('contain.text', expectedText);
|
||||
|
||||
//Click on Cine button
|
||||
cy.get('@cineBtn').click();
|
||||
//Vefiry if cine control overlay is hidden
|
||||
cy.get('@cineControls').should('not.be.visible');
|
||||
});
|
||||
|
||||
it('checks if More button will prompt a modal with secondary tools', () => {
|
||||
cy.get('@moreBtn').click();
|
||||
//Verify if overlay is displayed
|
||||
cy.get('.tooltip-toolbar-overlay')
|
||||
.as('toolbarOverlay')
|
||||
.should('be.visible');
|
||||
|
||||
let iconName;
|
||||
//Click on one of the secondary tools from the overlay
|
||||
cy.get('.tooltip-inner > :nth-child(1)')
|
||||
.click()
|
||||
.then($magnifyBtn => {
|
||||
cy.wrap($magnifyBtn)
|
||||
.should('have.class', 'active')
|
||||
.find('svg')
|
||||
.then($icon => {
|
||||
iconName = $icon.text();
|
||||
});
|
||||
});
|
||||
|
||||
//Check if More button is active and if it has same icon as the secondary tool selected
|
||||
cy.get('@moreBtn')
|
||||
.click()
|
||||
.then($moreBtn => {
|
||||
cy.wrap($moreBtn)
|
||||
.should('have.class', 'active')
|
||||
.contains(iconName);
|
||||
});
|
||||
|
||||
//Verify if overlay is hidden
|
||||
cy.get('@toolbarOverlay').should('not.be.visible');
|
||||
});
|
||||
|
||||
it('checks if Layout tool will multiply the number of viewports displayed', () => {
|
||||
//Click on Layout button and verify if overlay is displayed
|
||||
cy.get('@layoutBtn')
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.get('.layoutChooser')
|
||||
.as('layoutChooser')
|
||||
.should('be.visible')
|
||||
.find('td')
|
||||
.its('length')
|
||||
.should('be.eq', 9);
|
||||
});
|
||||
|
||||
//verify if layout has changed to 2 viewports
|
||||
cy.get('tbody > :nth-child(1) > :nth-child(2)').click();
|
||||
cy.get('.viewport-container').then($viewport => {
|
||||
cy.wrap($viewport)
|
||||
.its('length')
|
||||
.should('be.eq', 2);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
//Creating aliases for Cornerstone tools buttons
|
||||
cy.get('.toolbar-button:nth-child(2)').as('stackScrollBtn');
|
||||
cy.get('.toolbar-button:nth-child(3)').as('zoomBtn');
|
||||
cy.get('.toolbar-button:nth-child(4)').as('levelsBtn');
|
||||
cy.get('.toolbar-button:nth-child(5)').as('panBtn');
|
||||
cy.get('.toolbar-button:nth-child(6)').as('lengthBtn');
|
||||
cy.get('.toolbar-button:nth-child(7)').as('annotateBtn');
|
||||
cy.get('.toolbar-button:nth-child(8)').as('angleBtn');
|
||||
cy.get('.toolbar-button:nth-child(9)').as('resetBtn');
|
||||
cy.get('.toolbar-button:nth-child(10)').as('cineBtn');
|
||||
cy.get('.expandableToolMenu').as('moreBtn');
|
||||
cy.get('.PluginSwitch > .toolbar-button').as('twodmprBtn');
|
||||
cy.get('.btn-group > .toolbar-button').as('layoutBtn');
|
||||
cy.get('.pull-right > .RoundedButtonGroup > .roundedButtonWrapper > .roundedButton').as('measurementsBtn');
|
||||
cy.get('.viewport-element').as('viewport');
|
||||
cy.get('section.sidepanel.from-right').as('measurementsPanel')
|
||||
//Following best practices, reset should be done before each test
|
||||
cy.resetViewport();
|
||||
})
|
||||
|
||||
|
||||
it('checks if all primary buttons are being displayed', () => {
|
||||
cy.get('@stackScrollBtn').should('be.visible').contains('Stack Scroll');
|
||||
cy.get('@zoomBtn').should('be.visible').contains('Zoom');
|
||||
cy.get('@levelsBtn').should('be.visible').contains('Levels');
|
||||
cy.get('@panBtn').should('be.visible').contains('Pan');
|
||||
cy.get('@lengthBtn').should('be.visible').contains('Length');
|
||||
cy.get('@annotateBtn').should('be.visible').contains('Annotate');
|
||||
cy.get('@angleBtn').should('be.visible').contains('Angle');
|
||||
cy.get('@resetBtn').should('be.visible').contains('Reset');
|
||||
cy.get('@cineBtn').should('be.visible').contains('CINE');
|
||||
cy.get('@moreBtn').should('be.visible').contains('More');
|
||||
cy.get('@twodmprBtn').should('be.visible').contains('2D MPR');
|
||||
cy.get('@layoutBtn').should('be.visible').contains('Layout');
|
||||
cy.get('@layoutBtn').click();
|
||||
cy.get('tbody > :nth-child(2) > :nth-child(1)').click();
|
||||
cy.get('.viewport-container').then($viewport => {
|
||||
cy.wrap($viewport)
|
||||
.its('length')
|
||||
.should('be.eq', 2);
|
||||
});
|
||||
|
||||
|
||||
it('checks if Stack Scroll tool will navigate across all series in the viewport', () => {
|
||||
//Click on button and vefiry if icon is active on toolbar
|
||||
cy.get('@stackScrollBtn')
|
||||
.click()
|
||||
.then(($stackScrollBtn) =>{
|
||||
cy.wrap($stackScrollBtn)
|
||||
.should('have.class', 'active')
|
||||
})
|
||||
|
||||
//drags the mouse inside the viewport to be able to interact with series
|
||||
cy.get('@viewport')
|
||||
.trigger('mousedown', 'top', { which: 1 })
|
||||
.trigger('mousemove', 'center', { which: 1 })
|
||||
.trigger('mouseup');
|
||||
|
||||
const overlaySeriesInformation = 'div.ViewportOverlay > div.bottom-left.overlay-element > div';
|
||||
const expectedText = 'Ser: 1Img: 14 14/26256 x 256Loc: 0.00 mm Thick: 5.00 mm';
|
||||
cy.get(overlaySeriesInformation)
|
||||
.should('have.text', expectedText);
|
||||
//verify if layout has changed to 3 viewports
|
||||
cy.get('@layoutBtn').click();
|
||||
cy.get('tbody > :nth-child(1) > :nth-child(3)').click();
|
||||
cy.get('.viewport-container').then($viewport => {
|
||||
cy.wait(1000);
|
||||
cy.wrap($viewport)
|
||||
.its('length')
|
||||
.should('be.eq', 3);
|
||||
});
|
||||
|
||||
|
||||
it('checks if Zoom tool will zoom in/out an image in the viewport', () => {
|
||||
//Click on button and vefiry if icon is active on toolbar
|
||||
cy.get('@zoomBtn')
|
||||
.click()
|
||||
.then(($zoomBtn) =>{
|
||||
cy.wrap($zoomBtn)
|
||||
.should('have.class', 'active')
|
||||
})
|
||||
|
||||
//drags the mouse inside the viewport to be able to interact with series
|
||||
cy.get('@viewport')
|
||||
.trigger('mousedown', 'top', { which: 1 })
|
||||
.trigger('mousemove', 'center', { which: 1 })
|
||||
.trigger('mouseup');
|
||||
|
||||
const overlaySeriesInformation = 'div.ViewportOverlay > div.bottom-right.overlay-element > div';
|
||||
const expectedText = 'Zoom: 884%W: 820 L: 410Lossless / Uncompressed';
|
||||
cy.get(overlaySeriesInformation)
|
||||
.should('have.text', expectedText);
|
||||
cy.get('@layoutBtn').click();
|
||||
cy.get('tbody > :nth-child(3) > :nth-child(1)').click();
|
||||
cy.get('.viewport-container').then($viewport => {
|
||||
cy.wrap($viewport)
|
||||
.its('length')
|
||||
.should('be.eq', 3);
|
||||
});
|
||||
|
||||
|
||||
it('checks if Levels tool will change the contrast and brightness of an image in the viewport', () => {
|
||||
//Click on button and vefiry if icon is active on toolbar
|
||||
cy.get('@levelsBtn')
|
||||
.click()
|
||||
.then(($levelsBtn) =>{
|
||||
cy.wrap($levelsBtn)
|
||||
.should('have.class', 'active')
|
||||
})
|
||||
|
||||
//drags the mouse inside the viewport to be able to interact with series
|
||||
cy.get('@viewport')
|
||||
.trigger('mousedown', 'top', { which: 1 })
|
||||
.trigger('mousemove', 'center', { which: 1 })
|
||||
.trigger('mouseup')
|
||||
.trigger('mousedown', 'center', { which: 1 })
|
||||
.trigger('mousemove', 'left', { which: 1 })
|
||||
.trigger('mouseup');
|
||||
|
||||
const overlaySeriesInformation = 'div.ViewportOverlay > div.bottom-right.overlay-element > div';
|
||||
const expectedText = 'Zoom: 211%W: 544 L: 626Lossless / Uncompressed';
|
||||
cy.get(overlaySeriesInformation)
|
||||
.should('have.text', expectedText);
|
||||
});
|
||||
|
||||
|
||||
it('checks if Pan tool will move the image inside the viewport', () => {
|
||||
//Click on button and vefiry if icon is active on toolbar
|
||||
cy.get('@panBtn')
|
||||
.click()
|
||||
.then(($panBtn) =>{
|
||||
cy.wrap($panBtn)
|
||||
.should('have.class', 'active')
|
||||
})
|
||||
|
||||
//Get image position from cornerstone and check if y axis was modified
|
||||
let cornerstone;
|
||||
let currentPan;
|
||||
|
||||
cy.window()
|
||||
.its('cornerstone')
|
||||
.then((c) => {
|
||||
cornerstone = c;
|
||||
currentPan = () => cornerstone.getEnabledElements()[0].viewport.translation;
|
||||
});
|
||||
|
||||
cy.get('@viewport')
|
||||
.trigger('mousedown', 'center', { which: 1 })
|
||||
.trigger('mousemove', 'bottom', { which: 1 })
|
||||
.trigger('mouseup', 'bottom')
|
||||
.then(() => {
|
||||
expect(currentPan().y > 0).to.eq(true);
|
||||
});
|
||||
//verify if layout has changed to 4 viewports
|
||||
cy.get('@layoutBtn').click();
|
||||
cy.get('tbody > :nth-child(2) > :nth-child(2)').click();
|
||||
cy.get('.viewport-container').then($viewport => {
|
||||
cy.wrap($viewport)
|
||||
.its('length')
|
||||
.should('be.eq', 4);
|
||||
});
|
||||
|
||||
|
||||
it('checks if Length annotation can be added on viewport and on measurements panel', () => {
|
||||
//Click on button and vefiry if icon is active on toolbar
|
||||
cy.get('@lengthBtn')
|
||||
.click()
|
||||
.then(($lengthbtn) =>{
|
||||
cy.wrap($lengthbtn)
|
||||
.should('have.class', 'active')
|
||||
})
|
||||
|
||||
//Add annotation on the viewport
|
||||
const firstClick = [150, 100];
|
||||
const secondClick = [130, 170];
|
||||
cy.addLine('.cornerstone-canvas', firstClick, secondClick)
|
||||
|
||||
//Verify if measurement annotation was added into the measurements panel
|
||||
cy.get('@measurementsBtn')
|
||||
.click()
|
||||
.then($measurementsBtn => {
|
||||
cy.get('@measurementsPanel')
|
||||
.should('be.visible');
|
||||
|
||||
cy.get('.measurementItem')
|
||||
.its('length')
|
||||
.should('be.eq', 1);
|
||||
|
||||
cy.wrap($measurementsBtn)
|
||||
.click();
|
||||
})
|
||||
//verify if layout has changed to 6 viewports
|
||||
cy.get('@layoutBtn').click();
|
||||
cy.get('tbody > :nth-child(2) > :nth-child(3)').click();
|
||||
cy.get('.viewport-container').then($viewport => {
|
||||
cy.wrap($viewport)
|
||||
.its('length')
|
||||
.should('be.eq', 6);
|
||||
});
|
||||
|
||||
|
||||
it('checks if Angle annotation can be added on viewport and on measurements panel', () => {
|
||||
//Click on button and vefiry if icon is active on toolbar
|
||||
cy.get('@angleBtn')
|
||||
.click()
|
||||
.then(($angleBtn) =>{
|
||||
cy.wrap($angleBtn)
|
||||
.should('have.class', 'active')
|
||||
})
|
||||
|
||||
//Add annotation on the viewport
|
||||
const initPos = [180, 390];
|
||||
const midPos = [300, 410];
|
||||
const finalPos = [180, 450];
|
||||
cy.addAngle('.cornerstone-canvas', initPos, midPos, finalPos);
|
||||
|
||||
//Verify if measurement annotation was added into the measurements panel
|
||||
cy.get('@measurementsBtn')
|
||||
.click()
|
||||
.then($measurementsBtn => {
|
||||
cy.get('@measurementsPanel')
|
||||
.should('be.visible');
|
||||
|
||||
cy.get('.measurementItem')
|
||||
.its('length')
|
||||
.should('be.eq', 2);
|
||||
|
||||
cy.wrap($measurementsBtn)
|
||||
.click();
|
||||
})
|
||||
cy.get('@layoutBtn').click();
|
||||
cy.get('tbody > :nth-child(3) > :nth-child(2)').click();
|
||||
cy.get('.viewport-container').then($viewport => {
|
||||
cy.wrap($viewport)
|
||||
.its('length')
|
||||
.should('be.eq', 6);
|
||||
});
|
||||
|
||||
|
||||
it('checks if Reset tool will reset all changes made on the image', () => {
|
||||
//Make some changes by zooming in and rotating the image
|
||||
cy.imageZoomIn();
|
||||
cy.imageContrast();
|
||||
|
||||
//Click on reset button
|
||||
cy.get('@resetBtn').click()
|
||||
|
||||
const overlaySeriesInformation = 'div.ViewportOverlay > div.bottom-right.overlay-element > div';
|
||||
const expectedText = 'Zoom: 211%W: 820 L: 410Lossless / Uncompressed';
|
||||
cy.get(overlaySeriesInformation)
|
||||
.should('have.text', expectedText);
|
||||
});
|
||||
|
||||
|
||||
it('checks if CINE tool will prompt a modal with working controls', () => {
|
||||
//Click on button
|
||||
cy.get('@cineBtn')
|
||||
.click();
|
||||
//Vefiry if cine control overlay is being displayed
|
||||
cy.get('.cine-controls').as('cineControls')
|
||||
.should('be.visible');
|
||||
|
||||
//Test PLAY button
|
||||
cy.get('[title="Play / Stop"]')
|
||||
.click()
|
||||
.wait(100)
|
||||
.click();
|
||||
|
||||
const overlaySeriesInformation = 'div.ViewportOverlay > div.bottom-left.overlay-element > div';
|
||||
let expectedText = 'Img: 1 1/26';
|
||||
cy.get(overlaySeriesInformation)
|
||||
.should('not.have.text', expectedText);
|
||||
|
||||
//Test SKIP TO FIRST IMAGE button
|
||||
cy.get('[title="Skip to first Image"]')
|
||||
.click()
|
||||
cy.get(overlaySeriesInformation)
|
||||
.should('contain.text', expectedText);
|
||||
|
||||
//Test NEXT IMAGE button
|
||||
cy.get('[title="Next Image"]') //Title is wrong and was reported on bug #995: https://github.com/OHIF/Viewers/issues/995
|
||||
.click()
|
||||
expectedText = 'Img: 2 2/26';
|
||||
cy.get(overlaySeriesInformation)
|
||||
.should('contain.text', expectedText);
|
||||
|
||||
//Test SKIP TO LAST IMAGE button
|
||||
cy.get('[title="Skip to last Image"]') //Title is wrong and was reported on bug #995: https://github.com/OHIF/Viewers/issues/995
|
||||
.click()
|
||||
expectedText = 'Img: 27 26/26';
|
||||
cy.get(overlaySeriesInformation)
|
||||
.should('contain.text', expectedText);
|
||||
|
||||
//Test PREVIOUS IMAGE button
|
||||
cy.get('[title="Previous Image"]')
|
||||
.click()
|
||||
expectedText = 'Img: 26 25/26';
|
||||
cy.get(overlaySeriesInformation)
|
||||
.should('contain.text', expectedText);
|
||||
|
||||
//Click on Cine button
|
||||
cy.get('@cineBtn')
|
||||
.click();
|
||||
//Vefiry if cine control overlay is hidden
|
||||
cy.get('@cineControls')
|
||||
.should('not.be.visible');
|
||||
});
|
||||
|
||||
|
||||
it('checks if More button will prompt a modal with secondary tools', () => {
|
||||
cy.get('@moreBtn')
|
||||
.click();
|
||||
//Verify if overlay is displayed
|
||||
cy.get('.tooltip-toolbar-overlay').as('toolbarOverlay')
|
||||
.should('be.visible');
|
||||
|
||||
let iconName;
|
||||
//Click on one of the secondary tools from the overlay
|
||||
cy.get('.tooltip-inner > :nth-child(1)')
|
||||
.click()
|
||||
.then(($magnifyBtn) =>{
|
||||
cy.wrap($magnifyBtn)
|
||||
.should('have.class', 'active')
|
||||
.find('svg').then(($icon)=>{
|
||||
iconName = $icon.text();
|
||||
})
|
||||
})
|
||||
|
||||
//Check if More button is active and if it has same icon as the secondary tool selected
|
||||
cy.get('@moreBtn')
|
||||
.click()
|
||||
.then(($moreBtn) =>{
|
||||
cy.wrap($moreBtn)
|
||||
.should('have.class', 'active')
|
||||
.contains(iconName);
|
||||
})
|
||||
|
||||
//Verify if overlay is hidden
|
||||
cy.get('@toolbarOverlay')
|
||||
.should('not.be.visible');
|
||||
//verify if layout has changed to 9 viewports
|
||||
cy.get('@layoutBtn').click();
|
||||
cy.get('tbody > :nth-child(3) > :nth-child(3)').click();
|
||||
cy.get('.viewport-container').then($viewport => {
|
||||
cy.wrap($viewport)
|
||||
.its('length')
|
||||
.should('be.eq', 9);
|
||||
});
|
||||
|
||||
//Commented this to avoid throwing an wrong exception at the end of "Layout button" test
|
||||
//this commented section should be uncommented once issue #999 is fixed. (https://github.com/OHIF/Viewers/issues/999)
|
||||
|
||||
it('checks if Layout tool will multiply the number of viewports displayed', () => {
|
||||
//Click on Layout button and verify if overlay is displayed
|
||||
cy.get('@layoutBtn')
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.get('.layoutChooser').as('layoutChooser')
|
||||
.should('be.visible')
|
||||
.find('td')
|
||||
.its('length')
|
||||
.should('be.eq', 9);
|
||||
})
|
||||
|
||||
//verify if layout has changed to 2 viewports
|
||||
cy.get('tbody > :nth-child(1) > :nth-child(2)').click();
|
||||
cy.get('.viewport-container').then(($viewport) =>{
|
||||
cy.wrap($viewport)
|
||||
.its('length')
|
||||
.should('be.eq', 2);
|
||||
})
|
||||
|
||||
cy.get('@layoutBtn').click();
|
||||
cy.get('tbody > :nth-child(2) > :nth-child(1)').click();
|
||||
cy.get('.viewport-container').then(($viewport) =>{
|
||||
cy.wrap($viewport)
|
||||
.its('length')
|
||||
.should('be.eq', 2);
|
||||
})
|
||||
//verify if layout has changed to 1 viewport
|
||||
// cy.get('@layoutBtn').click();
|
||||
// cy.get('tbody > :nth-child(1) > :nth-child(1)').click();
|
||||
// cy.get('.viewport-container').then(($viewport) =>{
|
||||
// cy.wrap($viewport)
|
||||
// .its('length')
|
||||
// .should('be.eq', 1);
|
||||
// })
|
||||
cy.reload();
|
||||
cy.waitDicomImage();
|
||||
});
|
||||
|
||||
//verify if layout has changed to 3 viewports
|
||||
cy.get('@layoutBtn').click();
|
||||
cy.get('tbody > :nth-child(1) > :nth-child(3)').click();
|
||||
cy.get('.viewport-container').then(($viewport) =>{
|
||||
cy.wait(1000);
|
||||
cy.wrap($viewport)
|
||||
.its('length')
|
||||
.should('be.eq', 3);
|
||||
})
|
||||
|
||||
cy.get('@layoutBtn').click();
|
||||
cy.get('tbody > :nth-child(3) > :nth-child(1)').click();
|
||||
cy.get('.viewport-container').then(($viewport) =>{
|
||||
cy.wrap($viewport)
|
||||
.its('length')
|
||||
.should('be.eq', 3);
|
||||
})
|
||||
it('checks if Clear tool will delete all measurements added in the viewport', () => {
|
||||
//Add measurements in the viewport
|
||||
cy.get('@lengthBtn').click();
|
||||
const firstClick = [150, 100];
|
||||
const secondClick = [130, 170];
|
||||
cy.addLine('.cornerstone-canvas', firstClick, secondClick);
|
||||
|
||||
//verify if layout has changed to 4 viewports
|
||||
cy.get('@layoutBtn').click();
|
||||
cy.get('tbody > :nth-child(2) > :nth-child(2)').click();
|
||||
cy.get('.viewport-container').then(($viewport) =>{
|
||||
cy.wrap($viewport)
|
||||
.its('length')
|
||||
.should('be.eq', 4);
|
||||
})
|
||||
cy.get('@angleBtn').click();
|
||||
const initPos = [180, 390];
|
||||
const midPos = [300, 410];
|
||||
const finalPos = [180, 450];
|
||||
cy.addAngle('.cornerstone-canvas', initPos, midPos, finalPos);
|
||||
|
||||
//verify if layout has changed to 6 viewports
|
||||
cy.get('@layoutBtn').click();
|
||||
cy.get('tbody > :nth-child(2) > :nth-child(3)').click();
|
||||
cy.get('.viewport-container').then(($viewport) =>{
|
||||
cy.wrap($viewport)
|
||||
.its('length')
|
||||
.should('be.eq', 6);
|
||||
})
|
||||
//Verify if measurement annotation was added into the measurements panel
|
||||
cy.get('@measurementsBtn').click();
|
||||
cy.get('.measurementItem')
|
||||
.its('length')
|
||||
.should('be.eq', 2);
|
||||
|
||||
cy.get('@layoutBtn').click();
|
||||
cy.get('tbody > :nth-child(3) > :nth-child(2)').click();
|
||||
cy.get('.viewport-container').then(($viewport) =>{
|
||||
cy.wrap($viewport)
|
||||
.its('length')
|
||||
.should('be.eq', 6);
|
||||
})
|
||||
//Click on More button
|
||||
cy.get('@moreBtn').click();
|
||||
//Verify if overlay is displayed
|
||||
cy.get('.tooltip-toolbar-overlay')
|
||||
.as('toolbarOverlay')
|
||||
.should('be.visible');
|
||||
//Click on Clear button
|
||||
cy.get('.tooltip-inner > :nth-child(10)').click();
|
||||
|
||||
//verify if layout has changed to 9 viewports
|
||||
cy.get('@layoutBtn').click();
|
||||
cy.get('tbody > :nth-child(3) > :nth-child(3)').click();
|
||||
cy.get('.viewport-container').then(($viewport) =>{
|
||||
cy.wrap($viewport)
|
||||
.its('length')
|
||||
.should('be.eq', 9);
|
||||
})
|
||||
//Verify if measurements were removed from the measurements panel
|
||||
cy.get('.measurementItem').should('not.exist');
|
||||
|
||||
//Commented this to avoid throwing an wrong exception at the end of "Layout button" test
|
||||
//this commented section should be uncommented once issue #999 is fixed. (https://github.com/OHIF/Viewers/issues/999)
|
||||
|
||||
//verify if layout has changed to 1 viewport
|
||||
// cy.get('@layoutBtn').click();
|
||||
// cy.get('tbody > :nth-child(1) > :nth-child(1)').click();
|
||||
// cy.get('.viewport-container').then(($viewport) =>{
|
||||
// cy.wrap($viewport)
|
||||
// .its('length')
|
||||
// .should('be.eq', 1);
|
||||
// })
|
||||
//Close More button overlay
|
||||
cy.get('@moreBtn').click();
|
||||
|
||||
//Close the measurements panel
|
||||
cy.get('@measurementsBtn').then($btn => {
|
||||
$btn.click();
|
||||
cy.get('@measurementsPanel').should('not.be.enabled');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
it('checks if Eraser tool will remove the measurements added in the viewport', () => {
|
||||
//Add measurements in the viewport
|
||||
cy.get('@lengthBtn').click();
|
||||
const firstClick = [150, 100];
|
||||
const secondClick = [130, 170];
|
||||
cy.addLine('.cornerstone-canvas', firstClick, secondClick);
|
||||
|
||||
cy.get('@angleBtn').click();
|
||||
const initPos = [180, 390];
|
||||
const midPos = [300, 410];
|
||||
const finalPos = [180, 450];
|
||||
cy.addAngle('.cornerstone-canvas', initPos, midPos, finalPos);
|
||||
|
||||
//Verify if measurement annotation was added into the measurements panel
|
||||
cy.get('@measurementsBtn').click();
|
||||
cy.get('.measurementItem')
|
||||
.its('length')
|
||||
.should('be.eq', 2);
|
||||
cy.get('@measurementsBtn').click();
|
||||
|
||||
//Click More button
|
||||
cy.get('@moreBtn').click();
|
||||
//Click Eraser button
|
||||
cy.get('.tooltip-inner > :nth-child(12)').click();
|
||||
|
||||
//Erase measurement #1 and Verify if it was removed from the measurements panel
|
||||
const [x1, y1] = firstClick;
|
||||
cy.get('@viewport').click(x1, y1, { force: true });
|
||||
cy.get('.measurementItem')
|
||||
.its('length')
|
||||
.should('be.eq', 1);
|
||||
|
||||
//Erase measurement #2 and Verify if it was removed from the measurements panel
|
||||
const [x2, y2] = initPos;
|
||||
cy.get('@viewport').click(x2, y2, { force: true });
|
||||
cy.get('.measurementItem').should('not.exist');
|
||||
});
|
||||
});
|
||||
@@ -4,45 +4,90 @@ describe('OHIF Study Viewer Page', () => {
|
||||
cy.waitDicomImage();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.initCommonElementsAliases();
|
||||
//Following best practices, reset should be done before each test
|
||||
cy.resetViewport();
|
||||
});
|
||||
|
||||
it('checks if series thumbnails are being displayed', () => {
|
||||
cy.get('[data-cy="thumbnail-list"]')
|
||||
cy.get('.ThumbnailEntryContainer')
|
||||
.its('length')
|
||||
.should('be.gt', 1);
|
||||
});
|
||||
|
||||
it('drags and drop a series thumbnail into viewport', () => {
|
||||
cy.get('[data-cy="thumbnail-list"]:nth-child(2)') //element to be dragged
|
||||
cy.get('.ThumbnailEntryContainer:nth-child(2)') //element to be dragged
|
||||
.drag('.cornerstone-canvas'); //dropzone element
|
||||
|
||||
const overlaySeriesInformation =
|
||||
'div.ViewportOverlay > div.bottom-left.overlay-element > div';
|
||||
const expectedText =
|
||||
'Ser: 2Img: 1 1/13512 x 512Loc: -17.60 mm Thick: 3.00 mm';
|
||||
|
||||
cy.get(overlaySeriesInformation).should('have.text', expectedText);
|
||||
cy.get('@viewportInfoBottomLeft').should('contain.text', expectedText);
|
||||
});
|
||||
|
||||
it('checks if Series left panel can be hidden/displayed', () => {
|
||||
const seriesButton =
|
||||
'.pull-left > .RoundedButtonGroup > .roundedButtonWrapper > .roundedButton';
|
||||
const leftPanel = 'section.sidepanel.from-left';
|
||||
cy.get('@seriesBtn').click();
|
||||
cy.get('@seriesPanel').should('not.be.enabled');
|
||||
|
||||
cy.get(seriesButton).click();
|
||||
cy.get(leftPanel).should('not.be.enabled');
|
||||
|
||||
cy.get(seriesButton).click();
|
||||
cy.get(leftPanel).should('be.visible');
|
||||
cy.get('@seriesBtn').click();
|
||||
cy.get('@seriesPanel').should('be.visible');
|
||||
});
|
||||
|
||||
it('checks if Measurements right panel can be hidden/displayed', () => {
|
||||
const measurementsButton =
|
||||
'.pull-right > .RoundedButtonGroup > .roundedButtonWrapper > .roundedButton';
|
||||
const rightPanel = 'section.sidepanel.from-right';
|
||||
cy.get('@measurementsBtn').click();
|
||||
cy.get('@measurementsPanel').should('be.visible');
|
||||
|
||||
cy.get(measurementsButton).click();
|
||||
cy.get(rightPanel).should('be.visible');
|
||||
|
||||
cy.get(measurementsButton).click();
|
||||
cy.get(rightPanel).should('not.be.enabled');
|
||||
cy.get('@measurementsBtn').click();
|
||||
cy.get('@measurementsPanel').should('not.be.enabled');
|
||||
});
|
||||
|
||||
it('checks if measurement item can be Relabeled under Measurements panel', () => {
|
||||
cy.addLengthMeasurement(); //Adding measurement in the viewport
|
||||
cy.get('@measurementsBtn').click();
|
||||
cy.get('.measurementItem').click();
|
||||
|
||||
// Click "Relabel"
|
||||
cy.get('.btnAction')
|
||||
.contains('Relabel')
|
||||
.click();
|
||||
|
||||
// Search for "Bone"
|
||||
cy.get('.searchInput').type('Bone');
|
||||
|
||||
// Select "Bone" Result
|
||||
cy.get('.treeInputs > .wrapperLabel')
|
||||
.contains('Bone')
|
||||
.click();
|
||||
|
||||
// Confirm Selection
|
||||
cy.get('.checkIconWrapper').click();
|
||||
|
||||
//Verify if 'Bone' label was added
|
||||
cy.get('.measurementLocation').should('contain.text', 'Bone');
|
||||
});
|
||||
|
||||
//TO-DO: Test case will fail due to issue #1013: https://github.com/OHIF/Viewers/issues/1013
|
||||
|
||||
// it('checks if Description can be added to measurement item under Measurements panel', () => {
|
||||
// cy.addLengthMeasurement(); //Adding measurement in the viewport
|
||||
// cy.get('@measurementsBtn').click();
|
||||
// cy.get('.measurementItem').click();
|
||||
//
|
||||
// // Click "Description"
|
||||
// cy.get('.btnAction')
|
||||
// .contains('Description')
|
||||
// .click();
|
||||
//
|
||||
// // Enter description text
|
||||
// const descriptionText = 'Adding text for description test';
|
||||
// cy.get('#description')
|
||||
// .type(descriptionText);
|
||||
//
|
||||
// // Confirm
|
||||
// cy.get('.btn-confirm').click();
|
||||
//
|
||||
// //Verify if descriptionText was added
|
||||
// cy.get('.measurementLocation')
|
||||
// .should('contain.text', descriptionText);
|
||||
// });
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
//Creating aliases for Cornerstone tools buttons
|
||||
export function initCornerstoneToolsAliases() {
|
||||
cy.get('.ToolbarRow > :nth-child(2)').as('stackScrollBtn');
|
||||
cy.get('.ToolbarRow > :nth-child(3)').as('zoomBtn');
|
||||
cy.get('.ToolbarRow > :nth-child(4)').as('levelsBtn');
|
||||
cy.get('.ToolbarRow > :nth-child(5)').as('panBtn');
|
||||
cy.get('.ToolbarRow > :nth-child(6)').as('lengthBtn');
|
||||
cy.get('.ToolbarRow > :nth-child(7)').as('annotateBtn');
|
||||
cy.get('.ToolbarRow > :nth-child(8)').as('angleBtn');
|
||||
cy.get('.ToolbarRow > :nth-child(9)').as('resetBtn');
|
||||
cy.get('.ToolbarRow > :nth-child(10)').as('cineBtn');
|
||||
cy.get('.expandableToolMenu').as('moreBtn');
|
||||
cy.get('.PluginSwitch > .toolbar-button').as('twodmprBtn');
|
||||
cy.get('.btn-group > .toolbar-button').as('layoutBtn');
|
||||
}
|
||||
|
||||
//Creating aliases for Common page elements
|
||||
export function initCommonElementsAliases() {
|
||||
cy.get(
|
||||
'.pull-right > .RoundedButtonGroup > .roundedButtonWrapper > .roundedButton'
|
||||
).as('measurementsBtn');
|
||||
cy.get('.viewport-element').as('viewport');
|
||||
cy.get('section.sidepanel.from-right').as('measurementsPanel');
|
||||
cy.get(
|
||||
'.pull-left > .RoundedButtonGroup > .roundedButtonWrapper > .roundedButton'
|
||||
).as('seriesBtn');
|
||||
cy.get('section.sidepanel.from-left').as('seriesPanel');
|
||||
cy.get('div.ViewportOverlay > div.bottom-left.overlay-element > div').as(
|
||||
'viewportInfoBottomLeft'
|
||||
);
|
||||
cy.get('div.ViewportOverlay > div.bottom-right.overlay-element > div').as(
|
||||
'viewportInfoBottomRight'
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import { DragSimulator } from '../helpers/DragSimulator.js';
|
||||
import { doesNotReject } from 'assert';
|
||||
import { disconnect } from 'cluster';
|
||||
import {
|
||||
initCornerstoneToolsAliases,
|
||||
initCommonElementsAliases,
|
||||
} from './aliases.js';
|
||||
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
@@ -28,6 +30,11 @@ import { disconnect } from 'cluster';
|
||||
// -- This is will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
||||
|
||||
/**
|
||||
* Command to search for a patient name and open his/her study.
|
||||
*
|
||||
* @param {string} PatientName - Patient name that we would like to search for
|
||||
*/
|
||||
Cypress.Commands.add('openStudy', patientName => {
|
||||
cy.visit('/');
|
||||
cy.get('#patientName').type(patientName);
|
||||
@@ -39,6 +46,12 @@ Cypress.Commands.add('openStudy', patientName => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Command to perform a drag and drop action. Before using this command, we must get the element that should be dragged first.
|
||||
* Example of usage: cy.get(element-to-be-dragged).drag(dropzone-element)
|
||||
*
|
||||
* @param {*} element - Selector for element that we want to use as dropzone
|
||||
*/
|
||||
Cypress.Commands.add('drag', { prevSubject: 'element' }, (...args) =>
|
||||
DragSimulator.simulate(...args)
|
||||
);
|
||||
@@ -119,12 +132,26 @@ Cypress.Commands.add('waitDicomImage', (timeout = 20000) => {
|
||||
});
|
||||
});
|
||||
|
||||
//Command to reset the viewport changes throught the cornerstone method
|
||||
//Command to reset and clear all the changes made to the viewport
|
||||
Cypress.Commands.add('resetViewport', () => {
|
||||
cy.initCornerstoneToolsAliases();
|
||||
cy.get('@resetBtn').click();
|
||||
//Click on More button
|
||||
cy.get('@moreBtn').click();
|
||||
//Verify if overlay is displayed
|
||||
cy.get('body').then(body => {
|
||||
if (body.find('.tooltip-toolbar-overlay').length == 0) {
|
||||
cy.get('@moreBtn').click();
|
||||
}
|
||||
});
|
||||
//Click on Clear button
|
||||
cy.get('.tooltip-inner > :nth-child(10)')
|
||||
.as('clearBtn')
|
||||
.click();
|
||||
});
|
||||
|
||||
Cypress.Commands.add('imageZoomIn', () => {
|
||||
cy.initCornerstoneToolsAliases();
|
||||
cy.get('@zoomBtn').click();
|
||||
|
||||
//drags the mouse inside the viewport to be able to interact with series
|
||||
@@ -135,6 +162,7 @@ Cypress.Commands.add('imageZoomIn', () => {
|
||||
});
|
||||
|
||||
Cypress.Commands.add('imageContrast', () => {
|
||||
cy.initCornerstoneToolsAliases();
|
||||
cy.get('@levelsBtn').click();
|
||||
|
||||
//drags the mouse inside the viewport to be able to interact with series
|
||||
@@ -143,3 +171,32 @@ Cypress.Commands.add('imageContrast', () => {
|
||||
.trigger('mousemove', 'top', { which: 1 })
|
||||
.trigger('mouseup');
|
||||
});
|
||||
|
||||
//Initialize aliases for Cornerstone tools buttons
|
||||
Cypress.Commands.add('initCornerstoneToolsAliases', () => {
|
||||
initCornerstoneToolsAliases();
|
||||
});
|
||||
|
||||
//Initialize aliases for Common page elements
|
||||
Cypress.Commands.add('initCommonElementsAliases', () => {
|
||||
initCommonElementsAliases();
|
||||
});
|
||||
|
||||
//Add measurements in the viewport
|
||||
Cypress.Commands.add('addLengthMeasurement', () => {
|
||||
cy.initCornerstoneToolsAliases();
|
||||
cy.get('@lengthBtn').click();
|
||||
const firstClick = [150, 100];
|
||||
const secondClick = [130, 170];
|
||||
cy.addLine('.cornerstone-canvas', firstClick, secondClick);
|
||||
});
|
||||
|
||||
//Add measurements in the viewport
|
||||
Cypress.Commands.add('addAngleMeasurement', () => {
|
||||
cy.initCornerstoneToolsAliases();
|
||||
cy.get('@angleBtn').click();
|
||||
const initPos = [180, 390];
|
||||
const midPos = [300, 410];
|
||||
const finalPos = [180, 450];
|
||||
cy.addAngle('.cornerstone-canvas', initPos, midPos, finalPos);
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/viewer",
|
||||
"version": "1.5.2",
|
||||
"version": "1.6.0",
|
||||
"description": "OHIF Viewer",
|
||||
"author": "OHIF Contributors",
|
||||
"license": "MIT",
|
||||
@@ -46,12 +46,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"@ohif/core": "^1.2.0",
|
||||
"@ohif/core": "^1.3.0",
|
||||
"@ohif/extension-cornerstone": "^2.0.0",
|
||||
"@ohif/extension-dicom-html": "^1.0.0",
|
||||
"@ohif/extension-dicom-microscopy": "^0.50.6",
|
||||
"@ohif/extension-dicom-pdf": "^1.0.0",
|
||||
"@ohif/extension-vtk": "^0.52.4",
|
||||
"@ohif/extension-vtk": "^0.52.6",
|
||||
"@ohif/i18n": "^0.50.5",
|
||||
"@ohif/ui": "^0.53.3",
|
||||
"@tanem/react-nprogress": "^1.1.25",
|
||||
|
||||
@@ -8,44 +8,18 @@ import { connect } from 'react-redux';
|
||||
import { ViewerbaseDragDropContext } from '@ohif/ui';
|
||||
import { SignoutCallbackComponent } from 'redux-oidc';
|
||||
import asyncComponent from './components/AsyncComponent.js';
|
||||
import * as RoutesUtil from './routes/routesUtil';
|
||||
|
||||
import NotFound from './routes/NotFound.js';
|
||||
import { Bar, Container } from './components/LoadingBar/';
|
||||
import './OHIFStandaloneViewer.css';
|
||||
import './variables.css';
|
||||
import './theme-tide.css';
|
||||
|
||||
// Contexts
|
||||
import AppContext from './context/AppContext';
|
||||
|
||||
// Dynamic Import Routes (CodeSplitting)
|
||||
const IHEInvokeImageDisplay = asyncComponent(() =>
|
||||
import(
|
||||
/* webpackChunkName: "IHEInvokeImageDisplay" */ './routes/IHEInvokeImageDisplay.js'
|
||||
)
|
||||
);
|
||||
const ViewerRouting = asyncComponent(() =>
|
||||
import(/* webpackChunkName: "ViewerRouting" */ './routes/ViewerRouting.js')
|
||||
);
|
||||
const StudyListRouting = asyncComponent(() =>
|
||||
import(
|
||||
/* webpackChunkName: "StudyListRouting" */ './studylist/StudyListRouting.js'
|
||||
)
|
||||
);
|
||||
const StandaloneRouting = asyncComponent(() =>
|
||||
import(
|
||||
/* webpackChunkName: "StandaloneRouting" */ './routes/StandaloneRouting.js'
|
||||
)
|
||||
);
|
||||
const CallbackPage = asyncComponent(() =>
|
||||
import(/* webpackChunkName: "CallbackPage" */ './routes/CallbackPage.js')
|
||||
);
|
||||
const ViewerLocalFileData = asyncComponent(() =>
|
||||
import(
|
||||
/* webpackChunkName: "ViewerLocalFileData" */ './connectedComponents/ViewerLocalFileData.js'
|
||||
)
|
||||
);
|
||||
|
||||
const reload = () => window.location.reload();
|
||||
|
||||
class OHIFStandaloneViewer extends Component {
|
||||
static contextType = AppContext;
|
||||
@@ -86,7 +60,11 @@ class OHIFStandaloneViewer extends Component {
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route exact path="/silent-refresh.html" onEnter={reload} />
|
||||
<Route
|
||||
exact
|
||||
path="/silent-refresh.html"
|
||||
onEnter={RoutesUtil.reload}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/logout-redirect"
|
||||
@@ -128,41 +106,7 @@ class OHIFStandaloneViewer extends Component {
|
||||
*
|
||||
* See http://reactcommunity.org/react-transition-group/with-react-router/
|
||||
*/
|
||||
const routes = [
|
||||
{
|
||||
path: '/local',
|
||||
Component: ViewerLocalFileData,
|
||||
},
|
||||
{
|
||||
path: '/viewer',
|
||||
Component: StandaloneRouting,
|
||||
},
|
||||
{
|
||||
path: '/viewer/:studyInstanceUids',
|
||||
Component: ViewerRouting,
|
||||
},
|
||||
{
|
||||
path: '/study/:studyInstanceUids/series/:seriesInstanceUids',
|
||||
Component: ViewerRouting,
|
||||
},
|
||||
{
|
||||
path: '/IHEInvokeImageDisplay',
|
||||
Component: IHEInvokeImageDisplay,
|
||||
},
|
||||
];
|
||||
|
||||
const showStudyList =
|
||||
appConfig.showStudyList !== undefined ? appConfig.showStudyList : true;
|
||||
if (showStudyList) {
|
||||
routes.push({
|
||||
path: '/studylist',
|
||||
Component: StudyListRouting,
|
||||
});
|
||||
routes.push({
|
||||
path: '/',
|
||||
Component: StudyListRouting,
|
||||
});
|
||||
}
|
||||
const routes = RoutesUtil.getRoutes(appConfig);
|
||||
|
||||
const currentPath = this.props.location.pathname;
|
||||
const noMatchingRoutes = !routes.find(r =>
|
||||
@@ -184,8 +128,8 @@ class OHIFStandaloneViewer extends Component {
|
||||
</Container>
|
||||
)}
|
||||
</NProgress>
|
||||
<Route exact path="/silent-refresh.html" onEnter={reload} />
|
||||
<Route exact path="/logout-redirect.html" onEnter={reload} />
|
||||
<Route exact path="/silent-refresh.html" onEnter={RoutesUtil.reload} />
|
||||
<Route exact path="/logout-redirect.html" onEnter={RoutesUtil.reload} />
|
||||
{!noMatchingRoutes &&
|
||||
routes.map(({ path, Component }) => (
|
||||
<Route key={path} exact path={path}>
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import React, { useContext } from 'react';
|
||||
import GoogleCloudApi from '../googleCloud/api/GoogleCloudApi';
|
||||
|
||||
import * as GoogleCloudUtilServers from '../googleCloud/utils/getServers';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
|
||||
// Contexts
|
||||
import AppContext from '../context/AppContext';
|
||||
|
||||
const getActiveServer = servers => {
|
||||
const isActive = a => a.active === true;
|
||||
|
||||
return servers && servers.servers && servers.servers.find(isActive);
|
||||
};
|
||||
|
||||
const getServers = (appConfig, project, location, dataset, dicomStore) => {
|
||||
let servers = [];
|
||||
if (appConfig.enableGoogleCloudAdapter) {
|
||||
const pathUrl = GoogleCloudApi.getUrlBaseDicomWeb(
|
||||
project,
|
||||
location,
|
||||
dataset,
|
||||
dicomStore
|
||||
);
|
||||
const data = {
|
||||
project,
|
||||
location,
|
||||
dataset,
|
||||
dicomStore,
|
||||
wadoUriRoot: pathUrl,
|
||||
qidoRoot: pathUrl,
|
||||
wadoRoot: pathUrl,
|
||||
};
|
||||
servers = GoogleCloudUtilServers.getServers(data, dicomStore);
|
||||
}
|
||||
|
||||
return servers;
|
||||
};
|
||||
|
||||
const updateServer = (
|
||||
appConfig,
|
||||
dispatch,
|
||||
project,
|
||||
location,
|
||||
dataset,
|
||||
dicomStore
|
||||
) => {
|
||||
const servers = getServers(appConfig, project, location, dataset, dicomStore);
|
||||
|
||||
if (servers && servers.length) {
|
||||
const action = {
|
||||
type: 'SET_SERVERS',
|
||||
servers,
|
||||
};
|
||||
dispatch(action);
|
||||
}
|
||||
};
|
||||
|
||||
export default function useServer({
|
||||
project,
|
||||
location,
|
||||
dataset,
|
||||
dicomStore,
|
||||
} = {}) {
|
||||
// Hooks
|
||||
const servers = useSelector(state => state && state.servers);
|
||||
const dispatch = useDispatch();
|
||||
const { appConfig = {} } = useContext(AppContext);
|
||||
|
||||
const server = getActiveServer(servers);
|
||||
|
||||
if (!server) {
|
||||
updateServer(appConfig, dispatch, project, location, dataset, dicomStore);
|
||||
} else {
|
||||
return server;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import Modal from 'react-bootstrap-modal';
|
||||
import DatasetSelector from './DatasetSelector';
|
||||
import './googleCloud.css';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import * as GoogleCloudUtilServers from './utils/getServers';
|
||||
|
||||
class DicomStorePickerModal extends Component {
|
||||
static propTypes = {
|
||||
@@ -19,21 +20,7 @@ class DicomStorePickerModal extends Component {
|
||||
};
|
||||
|
||||
handleEvent = data => {
|
||||
const servers = [
|
||||
{
|
||||
name: data.dicomStore,
|
||||
imageRendering: 'wadors',
|
||||
thumbnailRendering: 'wadors',
|
||||
qidoSupportsIncludeField: false,
|
||||
type: 'dicomWeb',
|
||||
qidoRoot: data.qidoRoot,
|
||||
wadoRoot: data.wadoRoot,
|
||||
wadoUriRoot: data.wadoUriRoot,
|
||||
active: true,
|
||||
supportsFuzzyMatching: false,
|
||||
},
|
||||
];
|
||||
|
||||
const servers = GoogleCloudUtilServers.getServers(data, data.dicomstore);
|
||||
this.props.setServers(servers);
|
||||
};
|
||||
|
||||
|
||||
@@ -22,6 +22,13 @@ class GoogleCloudApi {
|
||||
return this.urlBase + `/projects`;
|
||||
}
|
||||
|
||||
getUrlBaseDicomWeb(project, location, dataset, dicomStore) {
|
||||
return (
|
||||
this.urlBase +
|
||||
`/projects/${project}/locations/${location}/datasets/${dataset}/dicomStores/${dicomStore}/dicomWeb`
|
||||
);
|
||||
}
|
||||
|
||||
async doRequest(urlStr, config = {}, params = {}) {
|
||||
const url = new URL(urlStr);
|
||||
let data = null;
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
const getServers = (data, name) => {
|
||||
const {
|
||||
wadoUriRoot,
|
||||
qidoRoot,
|
||||
wadoRoot,
|
||||
dataset = '',
|
||||
dicomStore = '',
|
||||
location = '',
|
||||
project = '',
|
||||
} = data;
|
||||
|
||||
return [
|
||||
{
|
||||
name: name,
|
||||
dataset,
|
||||
dicomStore,
|
||||
location,
|
||||
project,
|
||||
imageRendering: 'wadors',
|
||||
thumbnailRendering: 'wadors',
|
||||
type: 'dicomWeb',
|
||||
active: true,
|
||||
wadoUriRoot,
|
||||
qidoRoot,
|
||||
wadoRoot,
|
||||
supportsFuzzyMatching: false,
|
||||
qidoSupportsIncludeField: false,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export { getServers };
|
||||
@@ -1,29 +1,16 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import queryString from 'query-string';
|
||||
import ConnectedViewerRetrieveStudyData from '../connectedComponents/ConnectedViewerRetrieveStudyData.js';
|
||||
|
||||
function decodeStudyUids(studyUids) {
|
||||
const decodedData = window.atob(studyUids);
|
||||
|
||||
return decodedData.split(';');
|
||||
}
|
||||
|
||||
function getQueryParameters(location) {
|
||||
if (location) {
|
||||
return queryString.parse(location.search);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
import OHIF from '@ohif/core';
|
||||
const { urlUtil: UrlUtil } = OHIF.utils;
|
||||
|
||||
function IHEInvokeImageDisplay({ location }) {
|
||||
const {
|
||||
// patientID,
|
||||
requestType,
|
||||
studyUID,
|
||||
} = getQueryParameters(location);
|
||||
} = UrlUtil.parse(location.search);
|
||||
|
||||
switch (requestType) {
|
||||
case 'STUDY':
|
||||
@@ -36,7 +23,7 @@ function IHEInvokeImageDisplay({ location }) {
|
||||
case 'STUDYBASE64':
|
||||
return (
|
||||
<ConnectedViewerRetrieveStudyData
|
||||
studyInstanceUids={decodeStudyUids(studyUID)}
|
||||
studyInstanceUids={UrlUtil.paramString.parseParam(studyUID)}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@@ -1,26 +1,47 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ConnectedViewerRetrieveStudyData from '../connectedComponents/ConnectedViewerRetrieveStudyData';
|
||||
import useServer from '../customHooks/useServer';
|
||||
import OHIF from '@ohif/core';
|
||||
const { urlUtil: UrlUtil } = OHIF.utils;
|
||||
|
||||
function ViewerRouting({ match }) {
|
||||
const { studyInstanceUids, seriesInstanceUids } = match.params;
|
||||
/**
|
||||
* Get array of seriesUIDs from param or from queryString
|
||||
* @param {*} seriesInstanceUIDs
|
||||
* @param {*} location
|
||||
*/
|
||||
const getSeriesInstanceUIDs = (seriesInstanceUIDs, routeLocation) => {
|
||||
const queryFilters = UrlUtil.queryString.getQueryFilters(routeLocation);
|
||||
const querySeriesUIDs = queryFilters && queryFilters['SeriesInstanceUID'];
|
||||
const _seriesInstanceUIDs = seriesInstanceUIDs || querySeriesUIDs;
|
||||
|
||||
let studyUIDs;
|
||||
let seriesUIDs;
|
||||
return UrlUtil.paramString.parseParam(_seriesInstanceUIDs);
|
||||
};
|
||||
|
||||
if (studyInstanceUids && !seriesInstanceUids) {
|
||||
studyUIDs = studyInstanceUids.split(';');
|
||||
} else if (studyInstanceUids && seriesInstanceUids) {
|
||||
studyUIDs = [studyInstanceUids];
|
||||
seriesUIDs = match.params.seriesInstanceUids.split(';');
|
||||
function ViewerRouting({ match: routeMatch, location: routeLocation }) {
|
||||
const {
|
||||
project,
|
||||
location,
|
||||
dataset,
|
||||
dicomStore,
|
||||
studyInstanceUids,
|
||||
seriesInstanceUids,
|
||||
} = routeMatch.params;
|
||||
const server = useServer({ project, location, dataset, dicomStore });
|
||||
|
||||
const studyUIDs = UrlUtil.paramString.parseParam(studyInstanceUids);
|
||||
const seriesUIDs = getSeriesInstanceUIDs(seriesInstanceUids, routeLocation);
|
||||
|
||||
if (server && studyUIDs) {
|
||||
return (
|
||||
<ConnectedViewerRetrieveStudyData
|
||||
studyInstanceUids={studyUIDs}
|
||||
seriesInstanceUids={seriesUIDs}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ConnectedViewerRetrieveStudyData
|
||||
studyInstanceUids={studyUIDs}
|
||||
seriesInstanceUids={seriesUIDs}
|
||||
/>
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
ViewerRouting.propTypes = {
|
||||
@@ -28,6 +49,10 @@ ViewerRouting.propTypes = {
|
||||
params: PropTypes.shape({
|
||||
studyInstanceUids: PropTypes.string.isRequired,
|
||||
seriesInstanceUids: PropTypes.string,
|
||||
dataset: PropTypes.string,
|
||||
dicomStore: PropTypes.string,
|
||||
location: PropTypes.string,
|
||||
project: PropTypes.string,
|
||||
}),
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
import asyncComponent from '../components/AsyncComponent.js';
|
||||
|
||||
import OHIF from '@ohif/core';
|
||||
const { urlUtil: UrlUtil } = OHIF.utils;
|
||||
|
||||
// Dynamic Import Routes (CodeSplitting)
|
||||
const IHEInvokeImageDisplay = asyncComponent(() =>
|
||||
import(
|
||||
/* webpackChunkName: "IHEInvokeImageDisplay" */ './IHEInvokeImageDisplay.js'
|
||||
)
|
||||
);
|
||||
const ViewerRouting = asyncComponent(() =>
|
||||
import(/* webpackChunkName: "ViewerRouting" */ './ViewerRouting.js')
|
||||
);
|
||||
|
||||
const StudyListRouting = asyncComponent(() =>
|
||||
import(
|
||||
/* webpackChunkName: "StudyListRouting" */ '../studylist/StudyListRouting.js'
|
||||
)
|
||||
);
|
||||
const StandaloneRouting = asyncComponent(() =>
|
||||
import(/* webpackChunkName: "StandaloneRouting" */ './StandaloneRouting.js')
|
||||
);
|
||||
const ViewerLocalFileData = asyncComponent(() =>
|
||||
import(
|
||||
/* webpackChunkName: "ViewerLocalFileData" */ '../connectedComponents/ViewerLocalFileData.js'
|
||||
)
|
||||
);
|
||||
|
||||
const reload = () => window.location.reload();
|
||||
|
||||
const ROUTES_DEF = {
|
||||
default: {
|
||||
viewer: {
|
||||
path: '/viewer/:studyInstanceUids',
|
||||
component: ViewerRouting,
|
||||
},
|
||||
standaloneViewer: {
|
||||
path: '/viewer',
|
||||
component: StandaloneRouting,
|
||||
},
|
||||
list: {
|
||||
path: ['/studylist', '/'],
|
||||
component: StudyListRouting,
|
||||
condition: appConfig => {
|
||||
return appConfig.showStudyList !== undefined
|
||||
? appConfig.showStudyList
|
||||
: true;
|
||||
},
|
||||
},
|
||||
local: {
|
||||
path: '/local',
|
||||
component: ViewerLocalFileData,
|
||||
},
|
||||
IHEInvokeImageDisplay: {
|
||||
path: '/IHEInvokeImageDisplay',
|
||||
},
|
||||
},
|
||||
gcloud: {
|
||||
viewer: {
|
||||
path:
|
||||
'/projects/:project/locations/:location/datasets/:dataset/dicomStores/:dicomStore/study/:studyInstanceUids',
|
||||
component: ViewerRouting,
|
||||
condition: appConfig => {
|
||||
return !!appConfig.enableGoogleCloudAdapter;
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const getRoutes = appConfig => {
|
||||
const routes = [];
|
||||
for (let keyConfig in ROUTES_DEF) {
|
||||
const routesConfig = ROUTES_DEF[keyConfig];
|
||||
|
||||
for (let routeKey in routesConfig) {
|
||||
const route = routesConfig[routeKey];
|
||||
const validRoute =
|
||||
typeof route.condition === 'function'
|
||||
? route.condition(appConfig)
|
||||
: true;
|
||||
|
||||
if (validRoute) {
|
||||
routes.push({
|
||||
path: route.path,
|
||||
Component: route.component,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return routes;
|
||||
};
|
||||
|
||||
const parseViewerPath = (appConfig = {}, server = {}, params) => {
|
||||
let viewerPath = ROUTES_DEF.default.viewer.path;
|
||||
if (appConfig.enableGoogleCloudAdapter) {
|
||||
viewerPath = ROUTES_DEF.gcloud.viewer.path;
|
||||
}
|
||||
|
||||
const _paramsCopy = Object.assign({}, server, params);
|
||||
|
||||
for (let key in _paramsCopy) {
|
||||
viewerPath = UrlUtil.paramString.replaceParam(
|
||||
viewerPath,
|
||||
key,
|
||||
_paramsCopy[key]
|
||||
);
|
||||
}
|
||||
|
||||
return viewerPath;
|
||||
};
|
||||
|
||||
export { getRoutes, parseViewerPath, reload };
|
||||
@@ -13,7 +13,7 @@ import thunkMiddleware from 'redux-thunk';
|
||||
|
||||
// Combine our @ohif/core, ui, and oidc reducers
|
||||
// Set init data, using values found in localStorage
|
||||
const { reducers, localStorage } = redux;
|
||||
const { reducers, localStorage, sessionStorage } = redux;
|
||||
const middleware = [thunkMiddleware];
|
||||
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
||||
|
||||
@@ -21,9 +21,14 @@ reducers.ui = layoutReducers;
|
||||
reducers.oidc = oidcReducer;
|
||||
|
||||
const rootReducer = combineReducers(reducers);
|
||||
const preloadedState = {
|
||||
...localStorage.loadState(),
|
||||
...sessionStorage.loadState(),
|
||||
};
|
||||
|
||||
const store = createStore(
|
||||
rootReducer,
|
||||
localStorage.loadState(), // preloadedState
|
||||
preloadedState,
|
||||
composeEnhancers(applyMiddleware(...middleware))
|
||||
);
|
||||
|
||||
@@ -33,6 +38,9 @@ store.subscribe(() => {
|
||||
localStorage.saveState({
|
||||
preferences: store.getState().preferences,
|
||||
});
|
||||
sessionStorage.saveState({
|
||||
servers: store.getState().servers,
|
||||
});
|
||||
});
|
||||
|
||||
export default store;
|
||||
@@ -1,33 +1,18 @@
|
||||
import React, { useContext } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import queryString from 'query-string';
|
||||
import ConnectedStudyList from './ConnectedStudyList';
|
||||
|
||||
import OHIF from '@ohif/core';
|
||||
const { urlUtil: UrlUtil } = OHIF.utils;
|
||||
|
||||
// Contexts
|
||||
import AppContext from '../context/AppContext';
|
||||
|
||||
// TODO: Move to @ohif/ui
|
||||
|
||||
function toLowerCaseFirstLetter(word) {
|
||||
return word[0].toLowerCase() + word.slice(1);
|
||||
}
|
||||
|
||||
function getFilters({ search }) {
|
||||
const searchParameters = queryString.parse(search);
|
||||
const filters = {};
|
||||
|
||||
Object.entries(searchParameters).forEach(([key, value]) => {
|
||||
filters[toLowerCaseFirstLetter(key)] = value;
|
||||
});
|
||||
|
||||
return filters;
|
||||
}
|
||||
|
||||
function StudyListRouting({ location }) {
|
||||
const { appConfig = {} } = useContext(AppContext);
|
||||
|
||||
const filters = location ? getFilters(location) : undefined;
|
||||
const filters = UrlUtil.queryString.getQueryFilters(location);
|
||||
|
||||
let studyListFunctionsEnabled = false;
|
||||
if (appConfig.studyListFunctionsEnabled) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { withRouter } from 'react-router-dom';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import { StudyList } from '@ohif/ui';
|
||||
import ConnectedHeader from '../connectedComponents/ConnectedHeader.js';
|
||||
import * as RoutesUtil from '../routes/routesUtil';
|
||||
import moment from 'moment';
|
||||
import ConnectedDicomFilesUploader from '../googleCloud/ConnectedDicomFilesUploader';
|
||||
import ConnectedDicomStorePicker from '../googleCloud/ConnectedDicomStorePicker';
|
||||
@@ -175,7 +176,12 @@ class StudyListWithData extends Component {
|
||||
};
|
||||
|
||||
onSelectItem = studyInstanceUID => {
|
||||
this.props.history.push(`/viewer/${studyInstanceUID}`);
|
||||
const { appConfig = {} } = this.context;
|
||||
const { server } = this.props;
|
||||
const viewerPath = RoutesUtil.parseViewerPath(appConfig, server, {
|
||||
studyInstanceUids: studyInstanceUID,
|
||||
});
|
||||
this.props.history.push(viewerPath);
|
||||
};
|
||||
|
||||
onSearch = searchData => {
|
||||
|
||||
@@ -15078,10 +15078,10 @@ react-transition-group@^4.1.1:
|
||||
loose-envify "^1.4.0"
|
||||
prop-types "^15.6.2"
|
||||
|
||||
react-vtkjs-viewport@0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/react-vtkjs-viewport/-/react-vtkjs-viewport-0.1.4.tgz#f5ecc7dd52ab065d54656f36851f49b058ba683f"
|
||||
integrity sha512-07hVFJJ9LTy+Z9dgdPiPkaohVb013bgjNgH29MVafnjwH92YOXJzfFbf8/yzzVIZsYCETFf7rVw+oMGQJs5TAA==
|
||||
react-vtkjs-viewport@^0.1.6:
|
||||
version "0.1.6"
|
||||
resolved "https://registry.yarnpkg.com/react-vtkjs-viewport/-/react-vtkjs-viewport-0.1.6.tgz#d9bfb2c27c850c21c07c587405d0d3edcbd31d45"
|
||||
integrity sha512-NO5Dx7MXnS6NTG+crIwcGlm1uBFYMTR/tXtLKfTfPwWNakazkqlUuhqpsw2dEcMqEB+v/RmAX6bzrArcjv4M4A==
|
||||
dependencies:
|
||||
date-fns "^2.2.1"
|
||||
gl-matrix "^3.1.0"
|
||||
|
||||
Reference in new issue
Block a user