Compare commits
29
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
225da81135 | ||
|
|
686d12da5c | ||
|
|
c22756051b | ||
|
|
8fdfcc0c7d | ||
|
|
44ab65c981 | ||
|
|
b9574b6efc | ||
|
|
96a8e26786 | ||
|
|
80f5b23622 | ||
|
|
2b4950e954 | ||
|
|
e16f14089d | ||
|
|
c3807a89ed | ||
|
|
049ef4a666 | ||
|
|
d5fa880a29 | ||
|
|
1b9a6db73f | ||
|
|
ed2692908e | ||
|
|
0c6c5a5d51 | ||
|
|
b449f8eb0e | ||
|
|
cf5160fb13 | ||
|
|
4c1c91c006 | ||
|
|
9ed39c149b | ||
|
|
b3f0fe029d | ||
|
|
a1b78afdb9 | ||
|
|
3d3cfb1b41 | ||
|
|
b812fe03d2 | ||
|
|
84f087bca4 | ||
|
|
a7e612eb88 | ||
|
|
9e3afda2f1 | ||
|
|
4d7a91a584 | ||
|
|
1dc320edcd |
No files matched your search
@@ -459,8 +459,7 @@ workflows:
|
|||||||
working_directory: platform/viewer
|
working_directory: platform/viewer
|
||||||
build: npx cross-env QUICK_BUILD=true yarn run build
|
build: npx cross-env QUICK_BUILD=true yarn run build
|
||||||
# start server --> verify running --> percy + chrome + cypress
|
# start server --> verify running --> percy + chrome + cypress
|
||||||
start: yarn run test:e2e:dist
|
command: yarn run test:e2e:dist
|
||||||
spec: 'cypress/integration/visual-regression/**/*'
|
|
||||||
cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
|
cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
|
||||||
no-workspace: true # Don't persist workspace
|
no-workspace: true # Don't persist workspace
|
||||||
post-steps:
|
post-steps:
|
||||||
|
|||||||
+2
-1
@@ -12,10 +12,11 @@ exemptLabels:
|
|||||||
- 'Task: CI/Tooling :robot:'
|
- 'Task: CI/Tooling :robot:'
|
||||||
- 'Task: Docs 📖'
|
- 'Task: Docs 📖'
|
||||||
- 'Task: Docs :book:'
|
- 'Task: Docs :book:'
|
||||||
- 'Task: Refactor 🛠'
|
- 'Task: Refactor :hammer_and_wrench:'
|
||||||
- 'Task: Tests :microscope:'
|
- 'Task: Tests :microscope:'
|
||||||
- 'PR: Awaiting Review 👀'
|
- 'PR: Awaiting Review 👀'
|
||||||
- 'Triage :white_flag:'
|
- 'Triage :white_flag:'
|
||||||
|
- 'Extension: Discussion'
|
||||||
- 'Announcement 🎉'
|
- 'Announcement 🎉'
|
||||||
# Label to use when marking an issue as stale
|
# Label to use when marking an issue as stale
|
||||||
staleLabel: 'Stale :baguette_bread:'
|
staleLabel: 'Stale :baguette_bread:'
|
||||||
|
|||||||
@@ -3,6 +3,14 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [2.2.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.2.1...@ohif/extension-cornerstone@2.2.2) (2020-01-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [2.2.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.2.0...@ohif/extension-cornerstone@2.2.1) (2019-12-20)
|
## [2.2.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.2.0...@ohif/extension-cornerstone@2.2.1) (2019-12-20)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/extension-cornerstone",
|
"name": "@ohif/extension-cornerstone",
|
||||||
"version": "2.2.1",
|
"version": "2.2.2",
|
||||||
"description": "OHIF extension for Cornerstone",
|
"description": "OHIF extension for Cornerstone",
|
||||||
"author": "OHIF",
|
"author": "OHIF",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
"@ohif/ui": "^0.50.0",
|
"@ohif/ui": "^0.50.0",
|
||||||
"cornerstone-core": "^2.2.8",
|
"cornerstone-core": "^2.2.8",
|
||||||
"cornerstone-math": "^0.1.8",
|
"cornerstone-math": "^0.1.8",
|
||||||
"cornerstone-tools": "^4.9.0",
|
"cornerstone-tools": "^4.12.0",
|
||||||
"cornerstone-wado-image-loader": "^3.0.0",
|
"cornerstone-wado-image-loader": "^3.0.0",
|
||||||
"dcmjs": "^0.8.2",
|
"dcmjs": "^0.8.2",
|
||||||
"dicom-parser": "^1.8.3",
|
"dicom-parser": "^1.8.3",
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ export default function init({ servicesManager, configuration }) {
|
|||||||
csTools.MagnifyTool,
|
csTools.MagnifyTool,
|
||||||
csTools.StackScrollTool,
|
csTools.StackScrollTool,
|
||||||
csTools.StackScrollMouseWheelTool,
|
csTools.StackScrollMouseWheelTool,
|
||||||
|
csTools.OverlayTool,
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -179,4 +180,5 @@ export default function init({ servicesManager, configuration }) {
|
|||||||
csTools.setToolActive('StackScrollMouseWheel', {}); // TODO: Empty options should not be required
|
csTools.setToolActive('StackScrollMouseWheel', {}); // TODO: Empty options should not be required
|
||||||
csTools.setToolActive('PanMultiTouch', { pointers: 2 }); // TODO: Better error if no options
|
csTools.setToolActive('PanMultiTouch', { pointers: 2 }); // TODO: Better error if no options
|
||||||
csTools.setToolActive('ZoomTouchPinch', {});
|
csTools.setToolActive('ZoomTouchPinch', {});
|
||||||
|
csTools.setToolEnabled('Overlay', {});
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,57 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.2.13](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.12...@ohif/extension-vtk@1.2.13) (2020-01-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Set VTK viewport as active by interaction ([#1139](https://github.com/OHIF/Viewers/issues/1139)) ([686d12d](https://github.com/OHIF/Viewers/commit/686d12da5c9d3d435b1e326c2a5caee36e2ed27c))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.2.12](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.11...@ohif/extension-vtk@1.2.12) (2020-01-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.2.11](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.10...@ohif/extension-vtk@1.2.11) (2020-01-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.2.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.9...@ohif/extension-vtk@1.2.10) (2020-01-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.2.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.8...@ohif/extension-vtk@1.2.9) (2020-01-27)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.2.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.7...@ohif/extension-vtk@1.2.8) (2020-01-24)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [1.2.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.6...@ohif/extension-vtk@1.2.7) (2020-01-08)
|
## [1.2.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.6...@ohif/extension-vtk@1.2.7) (2020-01-08)
|
||||||
|
|
||||||
**Note:** Version bump only for package @ohif/extension-vtk
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/extension-vtk",
|
"name": "@ohif/extension-vtk",
|
||||||
"version": "1.2.7",
|
"version": "1.2.13",
|
||||||
"description": "OHIF extension for VTK.js",
|
"description": "OHIF extension for VTK.js",
|
||||||
"author": "OHIF",
|
"author": "OHIF",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -49,12 +49,12 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.5.5",
|
"@babel/runtime": "^7.5.5",
|
||||||
"lodash.throttle": "^4.1.1",
|
"lodash.throttle": "^4.1.1",
|
||||||
"react-vtkjs-viewport": "^0.3.9"
|
"react-vtkjs-viewport": "^0.8.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ohif/core": "^2.3.2",
|
"@ohif/core": "^2.3.7",
|
||||||
"@ohif/ui": "^1.1.8",
|
"@ohif/ui": "^1.1.9",
|
||||||
"cornerstone-tools": "^4.9.0",
|
"cornerstone-tools": "^4.12.0",
|
||||||
"cornerstone-wado-image-loader": "^3.0.0",
|
"cornerstone-wado-image-loader": "^3.0.0",
|
||||||
"dicom-parser": "^1.8.3",
|
"dicom-parser": "^1.8.3",
|
||||||
"gh-pages": "^2.0.1",
|
"gh-pages": "^2.0.1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import OHIF from '@ohif/core';
|
import OHIF from '@ohif/core';
|
||||||
import { View2D } from 'react-vtkjs-viewport';
|
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import VTKViewport from './VTKViewport';
|
||||||
|
|
||||||
const { setViewportActive, setViewportSpecificData } = OHIF.redux.actions;
|
const { setViewportActive, setViewportSpecificData } = OHIF.redux.actions;
|
||||||
|
|
||||||
@@ -18,6 +18,7 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
const pluginDetails = viewportLayout.vtk || {};
|
const pluginDetails = viewportLayout.vtk || {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
activeViewportIndex: state.viewports.activeViewportIndex,
|
||||||
layout: state.viewports.layout,
|
layout: state.viewports.layout,
|
||||||
isActive,
|
isActive,
|
||||||
...pluginDetails,
|
...pluginDetails,
|
||||||
@@ -75,6 +76,6 @@ const ConnectedVTKViewport = connect(
|
|||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
mapDispatchToProps,
|
mapDispatchToProps,
|
||||||
mergeProps
|
mergeProps
|
||||||
)(View2D);
|
)(VTKViewport);
|
||||||
|
|
||||||
export default ConnectedVTKViewport;
|
export default ConnectedVTKViewport;
|
||||||
@@ -77,6 +77,11 @@ class OHIFVTKViewport extends Component {
|
|||||||
}),
|
}),
|
||||||
viewportIndex: PropTypes.number,
|
viewportIndex: PropTypes.number,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
|
onScroll: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
onScroll: () => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
static id = 'OHIFVTKViewport';
|
static id = 'OHIFVTKViewport';
|
||||||
@@ -386,6 +391,7 @@ class OHIFVTKViewport extends Component {
|
|||||||
this.state.paintFilterBackgroundImageData
|
this.state.paintFilterBackgroundImageData
|
||||||
}
|
}
|
||||||
viewportIndex={this.props.viewportIndex}
|
viewportIndex={this.props.viewportIndex}
|
||||||
|
onScroll={this.props.onScroll}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
.vtk-viewport-handler svg {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import React, { useEffect, useCallback } from 'react';
|
||||||
|
import { View2D } from 'react-vtkjs-viewport';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import './VTKViewport.css';
|
||||||
|
|
||||||
|
const VTKViewport = props => {
|
||||||
|
const style = { width: '100%', height: '100%', position: 'relative' };
|
||||||
|
|
||||||
|
const setViewportActiveHandler = useCallback(() => {
|
||||||
|
const { setViewportActive, viewportIndex, activeViewportIndex } = props;
|
||||||
|
|
||||||
|
if (viewportIndex !== activeViewportIndex) {
|
||||||
|
// set in Connected
|
||||||
|
setViewportActive();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleScrollEvent = evt => {
|
||||||
|
const vtkViewportApiReference = props.onScroll(props.viewportIndex) || {};
|
||||||
|
const viewportUid = vtkViewportApiReference.uid;
|
||||||
|
const viewportWasScrolled = viewportUid === evt.detail.uid;
|
||||||
|
|
||||||
|
if (viewportWasScrolled) {
|
||||||
|
setViewportActiveHandler();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('vtkscrollevent', handleScrollEvent);
|
||||||
|
return () =>
|
||||||
|
window.removeEventListener('vtkscrollevent', handleScrollEvent);
|
||||||
|
}, [props, props.onScroll, props.viewportIndex, setViewportActiveHandler]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="vtk-viewport-handler"
|
||||||
|
style={style}
|
||||||
|
onClick={setViewportActiveHandler}
|
||||||
|
>
|
||||||
|
<View2D {...props} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
VTKViewport.propTypes = {
|
||||||
|
setViewportActive: PropTypes.func.isRequired,
|
||||||
|
viewportIndex: PropTypes.number.isRequired,
|
||||||
|
activeViewportIndex: PropTypes.number.isRequired,
|
||||||
|
/* Receives viewportIndex */
|
||||||
|
onScroll: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
VTKViewport.defaultProps = {
|
||||||
|
onScroll: () => {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default VTKViewport;
|
||||||
@@ -103,6 +103,9 @@ const commandsModule = ({ commandsManager }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
|
getVtkApis: ({ index }) => {
|
||||||
|
return apis[index];
|
||||||
|
},
|
||||||
axial: async ({ viewports }) => {
|
axial: async ({ viewports }) => {
|
||||||
const api = await _getActiveViewportVTKApi(viewports);
|
const api = await _getActiveViewportVTKApi(viewports);
|
||||||
|
|
||||||
@@ -266,11 +269,12 @@ const commandsModule = ({ commandsManager }) => {
|
|||||||
'crosshairsWidget'
|
'crosshairsWidget'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const uid = api.uid;
|
||||||
const istyle = vtkInteractorStyleMPRCrosshairs.newInstance();
|
const istyle = vtkInteractorStyleMPRCrosshairs.newInstance();
|
||||||
|
|
||||||
api.setInteractorStyle({
|
api.setInteractorStyle({
|
||||||
istyle,
|
istyle,
|
||||||
configuration: { apis, apiIndex },
|
configuration: { apis, apiIndex, uid },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -296,55 +300,45 @@ const commandsModule = ({ commandsManager }) => {
|
|||||||
},
|
},
|
||||||
enableRotateTool: {
|
enableRotateTool: {
|
||||||
commandFn: actions.enableRotateTool,
|
commandFn: actions.enableRotateTool,
|
||||||
storeContexts: [],
|
|
||||||
options: {},
|
options: {},
|
||||||
},
|
},
|
||||||
enableCrosshairsTool: {
|
enableCrosshairsTool: {
|
||||||
commandFn: actions.enableCrosshairsTool,
|
commandFn: actions.enableCrosshairsTool,
|
||||||
storeContexts: [],
|
|
||||||
options: {},
|
options: {},
|
||||||
},
|
},
|
||||||
enableLevelTool: {
|
enableLevelTool: {
|
||||||
commandFn: actions.enableLevelTool,
|
commandFn: actions.enableLevelTool,
|
||||||
storeContexts: [],
|
|
||||||
options: {},
|
options: {},
|
||||||
},
|
},
|
||||||
setBlendModeToComposite: {
|
setBlendModeToComposite: {
|
||||||
commandFn: actions.setBlendModeToComposite,
|
commandFn: actions.setBlendModeToComposite,
|
||||||
storeContexts: [],
|
|
||||||
options: { blendMode: BlendMode.COMPOSITE_BLEND },
|
options: { blendMode: BlendMode.COMPOSITE_BLEND },
|
||||||
},
|
},
|
||||||
setBlendModeToMaximumIntensity: {
|
setBlendModeToMaximumIntensity: {
|
||||||
commandFn: actions.setBlendModeToMaximumIntensity,
|
commandFn: actions.setBlendModeToMaximumIntensity,
|
||||||
storeContexts: [],
|
|
||||||
options: { blendMode: BlendMode.MAXIMUM_INTENSITY_BLEND },
|
options: { blendMode: BlendMode.MAXIMUM_INTENSITY_BLEND },
|
||||||
},
|
},
|
||||||
setBlendModeToMinimumIntensity: {
|
setBlendModeToMinimumIntensity: {
|
||||||
commandFn: actions.setBlendMode,
|
commandFn: actions.setBlendMode,
|
||||||
storeContexts: [],
|
|
||||||
options: { blendMode: BlendMode.MINIMUM_INTENSITY_BLEND },
|
options: { blendMode: BlendMode.MINIMUM_INTENSITY_BLEND },
|
||||||
},
|
},
|
||||||
setBlendModeToAverageIntensity: {
|
setBlendModeToAverageIntensity: {
|
||||||
commandFn: actions.setBlendMode,
|
commandFn: actions.setBlendMode,
|
||||||
storeContexts: [],
|
|
||||||
options: { blendMode: BlendMode.AVERAGE_INTENSITY_BLEND },
|
options: { blendMode: BlendMode.AVERAGE_INTENSITY_BLEND },
|
||||||
},
|
},
|
||||||
setSlabThickness: {
|
setSlabThickness: {
|
||||||
// TODO: How do we pass in a function argument?
|
// TODO: How do we pass in a function argument?
|
||||||
commandFn: actions.setSlabThickness,
|
commandFn: actions.setSlabThickness,
|
||||||
storeContexts: [],
|
|
||||||
options: {},
|
options: {},
|
||||||
},
|
},
|
||||||
increaseSlabThickness: {
|
increaseSlabThickness: {
|
||||||
commandFn: actions.changeSlabThickness,
|
commandFn: actions.changeSlabThickness,
|
||||||
storeContexts: [],
|
|
||||||
options: {
|
options: {
|
||||||
change: 3,
|
change: 3,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
decreaseSlabThickness: {
|
decreaseSlabThickness: {
|
||||||
commandFn: actions.changeSlabThickness,
|
commandFn: actions.changeSlabThickness,
|
||||||
storeContexts: [],
|
|
||||||
options: {
|
options: {
|
||||||
change: -3,
|
change: -3,
|
||||||
},
|
},
|
||||||
@@ -355,6 +349,10 @@ const commandsModule = ({ commandsManager }) => {
|
|||||||
options: {},
|
options: {},
|
||||||
context: 'VIEWER',
|
context: 'VIEWER',
|
||||||
},
|
},
|
||||||
|
getVtkApiForViewportIndex: {
|
||||||
|
commandFn: actions.getVtkApis,
|
||||||
|
context: 'VIEWER',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import asyncComponent from './asyncComponent.js';
|
import asyncComponent from './asyncComponent.js';
|
||||||
import commandsModule from './commandsModule.js';
|
import commandsModule from './commandsModule.js';
|
||||||
import toolbarModule from './toolbarModule.js';
|
import toolbarModule from './toolbarModule.js';
|
||||||
|
import withCommandsManager from './withCommandsManager.js';
|
||||||
// This feels weird
|
// This feels weird
|
||||||
// import loadLocales from './loadLocales';
|
// import loadLocales from './loadLocales';
|
||||||
|
|
||||||
@@ -14,8 +15,8 @@ const vtkExtension = {
|
|||||||
*/
|
*/
|
||||||
id: 'vtk',
|
id: 'vtk',
|
||||||
|
|
||||||
getViewportModule() {
|
getViewportModule({ commandsManager }) {
|
||||||
return OHIFVTKViewport;
|
return withCommandsManager(OHIFVTKViewport, commandsManager);
|
||||||
},
|
},
|
||||||
getToolbarModule() {
|
getToolbarModule() {
|
||||||
return toolbarModule;
|
return toolbarModule;
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default function withCommandsManager(Component, commandsManager = {}) {
|
||||||
|
return class WithCommandsManager extends React.Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Component
|
||||||
|
{...this.props}
|
||||||
|
onScroll={viewportIndex =>
|
||||||
|
commandsManager.runCommand('getVtkApiForViewportIndex', {
|
||||||
|
index: viewportIndex,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
+1
-1
@@ -59,6 +59,7 @@
|
|||||||
"copy-webpack-plugin": "^5.0.3",
|
"copy-webpack-plugin": "^5.0.3",
|
||||||
"cross-env": "^5.2.0",
|
"cross-env": "^5.2.0",
|
||||||
"css-loader": "^3.2.0",
|
"css-loader": "^3.2.0",
|
||||||
|
"cypress-file-upload": "^3.5.3",
|
||||||
"dotenv": "^8.1.0",
|
"dotenv": "^8.1.0",
|
||||||
"eslint": "5.16.0",
|
"eslint": "5.16.0",
|
||||||
"eslint-config-prettier": "^6.4.0",
|
"eslint-config-prettier": "^6.4.0",
|
||||||
@@ -72,7 +73,6 @@
|
|||||||
"eslint-plugin-react": "7.x",
|
"eslint-plugin-react": "7.x",
|
||||||
"eslint-plugin-react-hooks": "1.5.0",
|
"eslint-plugin-react-hooks": "1.5.0",
|
||||||
"extract-css-chunks-webpack-plugin": "^4.5.4",
|
"extract-css-chunks-webpack-plugin": "^4.5.4",
|
||||||
"favicons-webpack-plugin": "^1.0.2",
|
|
||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
"husky": "^3.0.0",
|
"husky": "^3.0.0",
|
||||||
"jest": "^24.8.0",
|
"jest": "^24.8.0",
|
||||||
|
|||||||
@@ -3,6 +3,49 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [2.3.7](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.6...@ohif/core@2.3.7) (2020-01-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Set VTK viewport as active by interaction ([#1139](https://github.com/OHIF/Viewers/issues/1139)) ([686d12d](https://github.com/OHIF/Viewers/commit/686d12da5c9d3d435b1e326c2a5caee36e2ed27c))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.3.6](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.5...@ohif/core@2.3.6) (2020-01-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/core
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.3.5](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.4...@ohif/core@2.3.5) (2020-01-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/core
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.3.4](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.3...@ohif/core@2.3.4) (2020-01-27)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/core
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.3.3](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.2...@ohif/core@2.3.3) (2020-01-24)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/core
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [2.3.2](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.1...@ohif/core@2.3.2) (2020-01-06)
|
## [2.3.2](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.1...@ohif/core@2.3.2) (2020-01-06)
|
||||||
|
|
||||||
**Note:** Version bump only for package @ohif/core
|
**Note:** Version bump only for package @ohif/core
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/core",
|
"name": "@ohif/core",
|
||||||
"version": "2.3.2",
|
"version": "2.3.7",
|
||||||
"description": "Generic business logic for web-based medical imaging applications",
|
"description": "Generic business logic for web-based medical imaging applications",
|
||||||
"author": "OHIF Core Team",
|
"author": "OHIF Core Team",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"cornerstone-core": "^2.2.8",
|
"cornerstone-core": "^2.2.8",
|
||||||
"cornerstone-tools": "^4.9.0",
|
"cornerstone-tools": "^4.12.0",
|
||||||
"cornerstone-wado-image-loader": "^3.0.0",
|
"cornerstone-wado-image-loader": "^3.0.0",
|
||||||
"dicom-parser": "^1.8.3"
|
"dicom-parser": "^1.8.3"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ export class CommandsManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { commandFn, storeContexts } = definition;
|
const { commandFn, storeContexts = [] } = definition;
|
||||||
const definitionOptions = definition.options;
|
const definitionOptions = definition.options;
|
||||||
|
|
||||||
let commandParams = {};
|
let commandParams = {};
|
||||||
|
|||||||
@@ -11,6 +11,16 @@ const servers = (state = defaultState, action) => {
|
|||||||
servers.forEach(s => (s.active = true));
|
servers.forEach(s => (s.active = true));
|
||||||
return { ...state, servers };
|
return { ...state, servers };
|
||||||
|
|
||||||
|
case 'ACTIVATE_SERVER': {
|
||||||
|
const newServer = { ...action.server, active: true };
|
||||||
|
const newServers = state.servers;
|
||||||
|
newServers.forEach(s => (s.active = false));
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
servers: uniqBy([...newServers, newServer], 'wadoRoot'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
case 'SET_SERVERS':
|
case 'SET_SERVERS':
|
||||||
return { ...state, servers: action.servers };
|
return { ...state, servers: action.servers };
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { api } from 'dicomweb-client';
|
||||||
import DICOMWeb from '../../../DICOMWeb';
|
import DICOMWeb from '../../../DICOMWeb';
|
||||||
|
|
||||||
const WADOProxy = {
|
const WADOProxy = {
|
||||||
@@ -21,6 +22,43 @@ function parseFloatArray(obj) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple cache schema for retrieved color palettes.
|
||||||
|
*/
|
||||||
|
const paletteColorCache = {
|
||||||
|
count: 0,
|
||||||
|
maxAge: 24 * 60 * 60 * 1000, // 24h cache?
|
||||||
|
entries: {},
|
||||||
|
isValidUID: function(paletteUID) {
|
||||||
|
return typeof paletteUID === 'string' && paletteUID.length > 0;
|
||||||
|
},
|
||||||
|
get: function(paletteUID) {
|
||||||
|
let entry = null;
|
||||||
|
if (this.entries.hasOwnProperty(paletteUID)) {
|
||||||
|
entry = this.entries[paletteUID];
|
||||||
|
// check how the entry is...
|
||||||
|
if (Date.now() - entry.time > this.maxAge) {
|
||||||
|
// entry is too old... remove entry.
|
||||||
|
delete this.entries[paletteUID];
|
||||||
|
this.count--;
|
||||||
|
entry = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return entry;
|
||||||
|
},
|
||||||
|
add: function(entry) {
|
||||||
|
if (this.isValidUID(entry.uid)) {
|
||||||
|
let paletteUID = entry.uid;
|
||||||
|
if (this.entries.hasOwnProperty(paletteUID) !== true) {
|
||||||
|
this.count++; // increment cache entry count...
|
||||||
|
}
|
||||||
|
entry.time = Date.now();
|
||||||
|
this.entries[paletteUID] = entry;
|
||||||
|
// @TODO: Add logic to get rid of old entries and reduce memory usage...
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a plain JS object that describes a study (a study descriptor object)
|
* Create a plain JS object that describes a study (a study descriptor object)
|
||||||
* @param {Object} server Object with server configuration parameters
|
* @param {Object} server Object with server configuration parameters
|
||||||
@@ -299,40 +337,41 @@ async function makeSOPInstance(server, study, instance) {
|
|||||||
return sopInstance;
|
return sopInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert String to ArrayBuffer
|
||||||
|
*
|
||||||
|
* @param {String} str Input String
|
||||||
|
* @return {ArrayBuffer} Output converted ArrayBuffer
|
||||||
|
*/
|
||||||
|
function str2ab(str) {
|
||||||
|
const strLen = str.length;
|
||||||
|
const bytes = new Uint8Array(strLen);
|
||||||
|
|
||||||
|
for (let i = 0; i < strLen; i++) {
|
||||||
|
bytes[i] = str.charCodeAt(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes.buffer;
|
||||||
|
}
|
||||||
|
|
||||||
function getPaletteColor(server, instance, tag, lutDescriptor) {
|
function getPaletteColor(server, instance, tag, lutDescriptor) {
|
||||||
const numLutEntries = lutDescriptor[0];
|
const numLutEntries = lutDescriptor[0];
|
||||||
const bits = lutDescriptor[2];
|
const bits = lutDescriptor[2];
|
||||||
|
|
||||||
let uri = WADOProxy.convertURL(instance[tag].BulkDataURI, server);
|
|
||||||
|
|
||||||
// TODO: Workaround for dcm4chee behind SSL-terminating proxy returning
|
|
||||||
// incorrect bulk data URIs
|
|
||||||
if (server.wadoRoot.indexOf('https') === 0 && !uri.includes('https')) {
|
|
||||||
uri = uri.replace('http', 'https');
|
|
||||||
}
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
url: server.wadoRoot, //BulkDataURI is absolute, so this isn't used
|
|
||||||
headers: DICOMWeb.getAuthorizationHeader(server),
|
|
||||||
};
|
|
||||||
const dicomWeb = new api.DICOMwebClient(config);
|
|
||||||
const options = {
|
|
||||||
BulkDataURI: uri,
|
|
||||||
};
|
|
||||||
|
|
||||||
const readUInt16 = (byteArray, position) => {
|
const readUInt16 = (byteArray, position) => {
|
||||||
return byteArray[position] + byteArray[position + 1] * 256;
|
return byteArray[position] + byteArray[position + 1] * 256;
|
||||||
};
|
};
|
||||||
|
|
||||||
const arrayBufferToPaletteColorLUT = result => {
|
const arrayBufferToPaletteColorLUT = arraybuffer => {
|
||||||
const arraybuffer = result[0];
|
|
||||||
const byteArray = new Uint8Array(arraybuffer);
|
const byteArray = new Uint8Array(arraybuffer);
|
||||||
const lut = [];
|
const lut = [];
|
||||||
|
|
||||||
for (let i = 0; i < numLutEntries; i++) {
|
if (bits === 16) {
|
||||||
if (bits === 16) {
|
for (let i = 0; i < numLutEntries; i++) {
|
||||||
lut[i] = readUInt16(byteArray, i * 2);
|
lut[i] = readUInt16(byteArray, i * 2);
|
||||||
} else {
|
}
|
||||||
|
} else {
|
||||||
|
for (let i = 0; i < numLutEntries; i++) {
|
||||||
lut[i] = byteArray[i];
|
lut[i] = byteArray[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -340,7 +379,38 @@ function getPaletteColor(server, instance, tag, lutDescriptor) {
|
|||||||
return lut;
|
return lut;
|
||||||
};
|
};
|
||||||
|
|
||||||
return dicomWeb.retrieveBulkData(options).then(arrayBufferToPaletteColorLUT);
|
if (instance[tag].BulkDataURI) {
|
||||||
|
let uri = WADOProxy.convertURL(instance[tag].BulkDataURI, server);
|
||||||
|
|
||||||
|
// TODO: Workaround for dcm4chee behind SSL-terminating proxy returning
|
||||||
|
// incorrect bulk data URIs
|
||||||
|
if (server.wadoRoot.indexOf('https') === 0 && !uri.includes('https')) {
|
||||||
|
uri = uri.replace('http', 'https');
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
url: server.wadoRoot, //BulkDataURI is absolute, so this isn't used
|
||||||
|
headers: DICOMWeb.getAuthorizationHeader(server),
|
||||||
|
};
|
||||||
|
const dicomWeb = new api.DICOMwebClient(config);
|
||||||
|
const options = {
|
||||||
|
BulkDataURI: uri,
|
||||||
|
};
|
||||||
|
|
||||||
|
return dicomWeb
|
||||||
|
.retrieveBulkData(options)
|
||||||
|
.then(result => result[0])
|
||||||
|
.then(arrayBufferToPaletteColorLUT);
|
||||||
|
} else if (instance[tag].InlineBinary) {
|
||||||
|
const inlineBinaryData = atob(instance[tag].InlineBinary);
|
||||||
|
const arraybuf = str2ab(inlineBinaryData);
|
||||||
|
|
||||||
|
return arrayBufferToPaletteColorLUT(arraybuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
'Palette Color LUT was not provided as InlineBinary or BulkDataURI'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,6 +3,17 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [0.52.5](https://github.com/OHIF/Viewers/compare/@ohif/i18n@0.52.4...@ohif/i18n@0.52.5) (2020-01-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* download tool fixes & improvements ([#1235](https://github.com/OHIF/Viewers/issues/1235)) ([b9574b6](https://github.com/OHIF/Viewers/commit/b9574b6efcfeb85cde35b5cae63282f8e1b35be6))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [0.52.4](https://github.com/OHIF/Viewers/compare/@ohif/i18n@0.52.3...@ohif/i18n@0.52.4) (2019-12-16)
|
## [0.52.4](https://github.com/OHIF/Viewers/compare/@ohif/i18n@0.52.3...@ohif/i18n@0.52.4) (2019-12-16)
|
||||||
|
|
||||||
**Note:** Version bump only for package @ohif/i18n
|
**Note:** Version bump only for package @ohif/i18n
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/i18n",
|
"name": "@ohif/i18n",
|
||||||
"version": "0.52.4",
|
"version": "0.52.5",
|
||||||
"description": "Internationalization library for The OHIF Viewer",
|
"description": "Internationalization library for The OHIF Viewer",
|
||||||
"author": "OHIF",
|
"author": "OHIF",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"emptyFilenameError": "The file name cannot be empty.",
|
||||||
|
"fileType": "File Type",
|
||||||
|
"filename": "File Name",
|
||||||
|
"formTitle": "Please specify the dimensions, filename, and desired type for the output image.",
|
||||||
|
"imageHeight": "Image height (px)",
|
||||||
|
"imagePreview": "Image Preview",
|
||||||
|
"imageWidth": "Image width (px)",
|
||||||
|
"keepAspectRatio": "Keep aspect ratio",
|
||||||
|
"loadingPreview": "Loading Image Preview...",
|
||||||
|
"minHeightError": "The minimum valid height is 100px.",
|
||||||
|
"minWidthError": "The minimum valid width is 100px.",
|
||||||
|
"showAnnotations": "Show Annotations"
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import Header from './Header.json';
|
|||||||
import MeasurementTable from './MeasurementTable.json';
|
import MeasurementTable from './MeasurementTable.json';
|
||||||
import StudyList from './StudyList.json';
|
import StudyList from './StudyList.json';
|
||||||
import UserPreferencesModal from './UserPreferencesModal.json';
|
import UserPreferencesModal from './UserPreferencesModal.json';
|
||||||
|
import ViewportDownloadForm from './ViewportDownloadForm.json';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
'en-US': {
|
'en-US': {
|
||||||
@@ -19,5 +20,6 @@ export default {
|
|||||||
MeasurementTable,
|
MeasurementTable,
|
||||||
StudyList,
|
StudyList,
|
||||||
UserPreferencesModal,
|
UserPreferencesModal,
|
||||||
|
ViewportDownloadForm,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -3,6 +3,17 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.1.9](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.1.8...@ohif/ui@1.1.9) (2020-01-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* download tool fixes & improvements ([#1235](https://github.com/OHIF/Viewers/issues/1235)) ([b9574b6](https://github.com/OHIF/Viewers/commit/b9574b6efcfeb85cde35b5cae63282f8e1b35be6))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [1.1.8](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.1.7...@ohif/ui@1.1.8) (2020-01-08)
|
## [1.1.8](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.1.7...@ohif/ui@1.1.8) (2020-01-08)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/ui",
|
"name": "@ohif/ui",
|
||||||
"version": "1.1.8",
|
"version": "1.1.9",
|
||||||
"description": "A set of React components for Medical Imaging Viewers",
|
"description": "A set of React components for Medical Imaging Viewers",
|
||||||
"author": "OHIF Contributors",
|
"author": "OHIF Contributors",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
import React, { useEffect, useState, createRef } from 'react';
|
import React, {
|
||||||
|
useRef,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
createRef,
|
||||||
|
} from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import './ViewportDownloadForm.styl';
|
import './ViewportDownloadForm.styl';
|
||||||
import { TextInput, Select } from '@ohif/ui';
|
import { TextInput, Select, Icon } from '@ohif/ui';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
|
||||||
const FILE_TYPE_OPTIONS = [
|
const FILE_TYPE_OPTIONS = [
|
||||||
{
|
{
|
||||||
@@ -17,6 +24,7 @@ const FILE_TYPE_OPTIONS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const DEFAULT_FILENAME = 'image';
|
const DEFAULT_FILENAME = 'image';
|
||||||
|
const REFRESH_VIEWPORT_TIMEOUT = 1000;
|
||||||
|
|
||||||
const ViewportDownloadForm = ({
|
const ViewportDownloadForm = ({
|
||||||
activeViewport,
|
activeViewport,
|
||||||
@@ -32,7 +40,7 @@ const ViewportDownloadForm = ({
|
|||||||
maximumSize,
|
maximumSize,
|
||||||
canvasClass,
|
canvasClass,
|
||||||
}) => {
|
}) => {
|
||||||
const [t] = useTranslation('Buttons');
|
const [t] = useTranslation('ViewportDownloadForm');
|
||||||
|
|
||||||
const [filename, setFilename] = useState(DEFAULT_FILENAME);
|
const [filename, setFilename] = useState(DEFAULT_FILENAME);
|
||||||
const [fileType, setFileType] = useState('jpg');
|
const [fileType, setFileType] = useState('jpg');
|
||||||
@@ -44,6 +52,12 @@ const ViewportDownloadForm = ({
|
|||||||
|
|
||||||
const [showAnnotations, setShowAnnotations] = useState(true);
|
const [showAnnotations, setShowAnnotations] = useState(true);
|
||||||
|
|
||||||
|
const [keepAspect, setKeepAspect] = useState(true);
|
||||||
|
const [aspectMultiplier, setAspectMultiplier] = useState({
|
||||||
|
width: 1,
|
||||||
|
height: 1,
|
||||||
|
});
|
||||||
|
|
||||||
const [viewportElement, setViewportElement] = useState();
|
const [viewportElement, setViewportElement] = useState();
|
||||||
const [viewportElementDimensions, setViewportElementDimensions] = useState({
|
const [viewportElementDimensions, setViewportElementDimensions] = useState({
|
||||||
width: defaultSize,
|
width: defaultSize,
|
||||||
@@ -62,41 +76,129 @@ const ViewportDownloadForm = ({
|
|||||||
height: defaultSize,
|
height: defaultSize,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Cornerstone's `enable/disable`
|
const [error, setError] = useState({
|
||||||
useEffect(() => {
|
width: false,
|
||||||
enableViewport(viewportElement);
|
height: false,
|
||||||
|
filename: false,
|
||||||
|
});
|
||||||
|
|
||||||
return () => {
|
const hasError = Object.values(error).includes(true);
|
||||||
disableViewport(viewportElement);
|
|
||||||
};
|
|
||||||
}, [disableViewport, enableViewport, viewportElement]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
const refreshViewport = useRef(null);
|
||||||
const { width, height } = viewportElementDimensions;
|
|
||||||
const validSize = value => (value >= minimumSize ? value : minimumSize);
|
|
||||||
const loadAndUpdateViewports = async () => {
|
|
||||||
await loadImage(activeViewport, viewportElement, width, height);
|
|
||||||
toggleAnnotations(showAnnotations, viewportElement);
|
|
||||||
|
|
||||||
const {
|
const downloadImage = () => {
|
||||||
dataUrl,
|
downloadBlob(
|
||||||
width: viewportElementWidth,
|
filename || DEFAULT_FILENAME,
|
||||||
height: viewportElementHeight,
|
fileType,
|
||||||
} = await updateViewportPreview(
|
viewportElement,
|
||||||
viewportElement,
|
downloadCanvas.ref.current
|
||||||
downloadCanvas.ref.current,
|
);
|
||||||
fileType
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} event - Input change event
|
||||||
|
* @param {string} dimension - "height" | "width"
|
||||||
|
*/
|
||||||
|
const onDimensionsChange = (event, dimension) => {
|
||||||
|
const oppositeDimension = dimension === 'height' ? 'width' : 'height';
|
||||||
|
const sanitizedTargetValue = event.target.value.replace(/\D/, '');
|
||||||
|
const isEmpty = sanitizedTargetValue === '';
|
||||||
|
const newDimensions = { ...dimensions };
|
||||||
|
const updatedDimension = isEmpty
|
||||||
|
? ''
|
||||||
|
: Math.min(sanitizedTargetValue, maximumSize);
|
||||||
|
|
||||||
|
if (updatedDimension === dimensions[dimension]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
newDimensions[dimension] = updatedDimension;
|
||||||
|
|
||||||
|
if (keepAspect && newDimensions[oppositeDimension] !== '') {
|
||||||
|
newDimensions[oppositeDimension] = Math.round(
|
||||||
|
newDimensions[dimension] * aspectMultiplier[oppositeDimension]
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
setViewportPreview(state => ({
|
// In current code, keepAspect is always `true`
|
||||||
|
// And we always start w/ a square width/height
|
||||||
|
setDimensions(newDimensions);
|
||||||
|
|
||||||
|
// Only update if value is non-empty
|
||||||
|
if (!isEmpty) {
|
||||||
|
setViewportElementDimensions(newDimensions);
|
||||||
|
setDownloadCanvas(state => ({
|
||||||
...state,
|
...state,
|
||||||
src: dataUrl,
|
...newDimensions,
|
||||||
width: validSize(viewportElementWidth),
|
|
||||||
height: validSize(viewportElementHeight),
|
|
||||||
}));
|
}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const error_messages = {
|
||||||
|
width: t('minWidthError'),
|
||||||
|
height: t('minHeightError'),
|
||||||
|
filename: t('emptyFilenameError'),
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderErrorHandler = errorType => {
|
||||||
|
if (!error[errorType]) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="input-error">{error_messages[errorType]}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onKeepAspectToggle = () => {
|
||||||
|
const { width, height } = dimensions;
|
||||||
|
const aspectMultiplier = { ...aspectMultiplier };
|
||||||
|
if (!keepAspect) {
|
||||||
|
const base = Math.min(width, height);
|
||||||
|
aspectMultiplier.width = width / base;
|
||||||
|
aspectMultiplier.height = height / base;
|
||||||
|
setAspectMultiplier(aspectMultiplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
setKeepAspect(!keepAspect);
|
||||||
|
};
|
||||||
|
|
||||||
|
const validSize = value => (value >= minimumSize ? value : minimumSize);
|
||||||
|
const loadAndUpdateViewports = useCallback(async () => {
|
||||||
|
const { width: scaledWidth, height: scaledHeight } = await loadImage(
|
||||||
|
activeViewport,
|
||||||
|
viewportElement,
|
||||||
|
dimensions.width,
|
||||||
|
dimensions.height
|
||||||
|
);
|
||||||
|
|
||||||
|
toggleAnnotations(showAnnotations, viewportElement);
|
||||||
|
|
||||||
|
const scaledDimensions = {
|
||||||
|
height: validSize(scaledHeight),
|
||||||
|
width: validSize(scaledWidth),
|
||||||
};
|
};
|
||||||
|
|
||||||
loadAndUpdateViewports();
|
setViewportElementDimensions(scaledDimensions);
|
||||||
|
setDownloadCanvas(state => ({
|
||||||
|
...state,
|
||||||
|
...scaledDimensions,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const {
|
||||||
|
dataUrl,
|
||||||
|
width: viewportElementWidth,
|
||||||
|
height: viewportElementHeight,
|
||||||
|
} = await updateViewportPreview(
|
||||||
|
viewportElement,
|
||||||
|
downloadCanvas.ref.current,
|
||||||
|
fileType
|
||||||
|
);
|
||||||
|
|
||||||
|
setViewportPreview(state => ({
|
||||||
|
...state,
|
||||||
|
src: dataUrl,
|
||||||
|
width: validSize(viewportElementWidth),
|
||||||
|
height: validSize(viewportElementHeight),
|
||||||
|
}));
|
||||||
}, [
|
}, [
|
||||||
activeViewport,
|
activeViewport,
|
||||||
viewportElement,
|
viewportElement,
|
||||||
@@ -111,76 +213,96 @@ const ViewportDownloadForm = ({
|
|||||||
viewportElementDimensions,
|
viewportElementDimensions,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/**
|
useEffect(() => {
|
||||||
* @param {object} event - Input change event
|
enableViewport(viewportElement);
|
||||||
* @param {string} dimension - "height" | "width"
|
|
||||||
*/
|
|
||||||
const onDimensionsChange = (event, dimension) => {
|
|
||||||
const sanitizedTargetValue = event.target.value.replace(/\D/, '');
|
|
||||||
const isEmpty = sanitizedTargetValue === '';
|
|
||||||
const updatedDimension = isEmpty
|
|
||||||
? ''
|
|
||||||
: Math.min(sanitizedTargetValue, maximumSize);
|
|
||||||
|
|
||||||
if (updatedDimension === dimensions.width) {
|
return () => {
|
||||||
return;
|
disableViewport(viewportElement);
|
||||||
|
};
|
||||||
|
}, [disableViewport, enableViewport, viewportElement]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (refreshViewport.current !== null) {
|
||||||
|
clearTimeout(refreshViewport.current);
|
||||||
}
|
}
|
||||||
|
|
||||||
// In current code, keepAspect is always `true`
|
refreshViewport.current = setTimeout(() => {
|
||||||
// And we always start w/ a square width/height
|
refreshViewport.current = null;
|
||||||
setDimensions({
|
loadAndUpdateViewports();
|
||||||
width: updatedDimension,
|
}, REFRESH_VIEWPORT_TIMEOUT);
|
||||||
height: updatedDimension,
|
}, [
|
||||||
});
|
activeViewport,
|
||||||
|
viewportElement,
|
||||||
|
showAnnotations,
|
||||||
|
dimensions,
|
||||||
|
loadImage,
|
||||||
|
toggleAnnotations,
|
||||||
|
updateViewportPreview,
|
||||||
|
fileType,
|
||||||
|
downloadCanvas.ref,
|
||||||
|
minimumSize,
|
||||||
|
maximumSize,
|
||||||
|
]);
|
||||||
|
|
||||||
// Only update if value is non-empty
|
useEffect(() => {
|
||||||
if (!isEmpty) {
|
const { width, height } = dimensions;
|
||||||
setViewportElementDimensions({
|
const hasError = {
|
||||||
height: updatedDimension,
|
width: width < minimumSize,
|
||||||
width: updatedDimension,
|
height: height < minimumSize,
|
||||||
});
|
filename: !filename,
|
||||||
setDownloadCanvas(state => ({
|
};
|
||||||
...state,
|
|
||||||
height: updatedDimension,
|
|
||||||
width: updatedDimension,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const downloadImage = () => {
|
setError({ ...hasError });
|
||||||
downloadBlob(
|
}, [dimensions, filename, minimumSize]);
|
||||||
filename || DEFAULT_FILENAME,
|
|
||||||
fileType,
|
|
||||||
viewportElement,
|
|
||||||
downloadCanvas.ref.current
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="ViewportDownloadForm">
|
<div className="ViewportDownloadForm">
|
||||||
<div className="title">
|
<div className="title">{t('formTitle')}</div>
|
||||||
{t(
|
|
||||||
'Please specify the dimensions, filename, and desired type for the output image.'
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="file-info-container" data-cy="file-info-container">
|
<div className="file-info-container" data-cy="file-info-container">
|
||||||
<div className="col">
|
<div className="dimension-wrapper">
|
||||||
<div className="width">
|
<div className="dimensions">
|
||||||
<TextInput
|
<div className="width">
|
||||||
data-cy="image-width"
|
<TextInput
|
||||||
value={dimensions.width}
|
type="number"
|
||||||
label={t('Image width (px)')}
|
min={minimumSize}
|
||||||
onChange={evt => onDimensionsChange(evt, 'height')}
|
max={maximumSize}
|
||||||
/>
|
value={dimensions.width}
|
||||||
|
label={t('imageWidth')}
|
||||||
|
onChange={evt => onDimensionsChange(evt, 'width')}
|
||||||
|
data-cy="image-width"
|
||||||
|
/>
|
||||||
|
{renderErrorHandler('width')}
|
||||||
|
</div>
|
||||||
|
<div className="height">
|
||||||
|
<TextInput
|
||||||
|
type="number"
|
||||||
|
min={minimumSize}
|
||||||
|
max={maximumSize}
|
||||||
|
value={dimensions.height}
|
||||||
|
label={t('imageHeight')}
|
||||||
|
onChange={evt => onDimensionsChange(evt, 'height')}
|
||||||
|
data-cy="image-height"
|
||||||
|
/>
|
||||||
|
{renderErrorHandler('height')}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="height">
|
<div className="keep-aspect-wrapper">
|
||||||
<TextInput
|
<button
|
||||||
data-cy="image-height"
|
id="keep-aspect"
|
||||||
value={dimensions.height}
|
className={classnames(
|
||||||
label={t('Image height (px)')}
|
'form-button btn',
|
||||||
onChange={evt => onDimensionsChange(evt, 'width')}
|
keepAspect ? 'active' : ''
|
||||||
/>
|
)}
|
||||||
|
data-cy="keep-aspect"
|
||||||
|
alt={t('keepAspectRatio')}
|
||||||
|
onClick={onKeepAspectToggle}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
name={keepAspect ? 'link' : 'unlink'}
|
||||||
|
alt={keepAspect ? 'Dismiss Aspect' : 'Keep Aspect'}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -191,9 +313,10 @@ const ViewportDownloadForm = ({
|
|||||||
data-cy="file-name"
|
data-cy="file-name"
|
||||||
value={filename}
|
value={filename}
|
||||||
onChange={event => setFilename(event.target.value)}
|
onChange={event => setFilename(event.target.value)}
|
||||||
label={t('File name')}
|
label={t('filename')}
|
||||||
id="file-name"
|
id="file-name"
|
||||||
/>
|
/>
|
||||||
|
{renderErrorHandler('filename')}
|
||||||
</div>
|
</div>
|
||||||
<div className="file-type">
|
<div className="file-type">
|
||||||
<Select
|
<Select
|
||||||
@@ -201,7 +324,7 @@ const ViewportDownloadForm = ({
|
|||||||
data-cy="file-type"
|
data-cy="file-type"
|
||||||
onChange={event => setFileType(event.target.value)}
|
onChange={event => setFileType(event.target.value)}
|
||||||
options={FILE_TYPE_OPTIONS}
|
options={FILE_TYPE_OPTIONS}
|
||||||
label={t('File type')}
|
label={t('fileType')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -217,7 +340,7 @@ const ViewportDownloadForm = ({
|
|||||||
checked={showAnnotations}
|
checked={showAnnotations}
|
||||||
onChange={event => setShowAnnotations(event.target.checked)}
|
onChange={event => setShowAnnotations(event.target.checked)}
|
||||||
/>
|
/>
|
||||||
{t('Show Annotations')}
|
{t('showAnnotations')}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -245,15 +368,23 @@ const ViewportDownloadForm = ({
|
|||||||
></canvas>
|
></canvas>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="preview" data-cy="image-preview">
|
{viewportPreview.src ? (
|
||||||
<h4> {t('Image Preview')}</h4>
|
<div className="preview" data-cy="image-preview">
|
||||||
<img
|
<div className="preview-header"> {t('imagePreview')}</div>
|
||||||
className="viewport-preview"
|
<img
|
||||||
src={viewportPreview.src}
|
className="viewport-preview"
|
||||||
alt="Viewport Preview"
|
src={viewportPreview.src}
|
||||||
data-cy="viewport-preview-img"
|
alt={t('imagePreview')}
|
||||||
/>
|
data-cy="image-preview"
|
||||||
</div>
|
data-cy="viewport-preview-img"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="loading-image">
|
||||||
|
<Icon name="circle-notch" className="icon-spin" />
|
||||||
|
{t('loadingPreview')}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="actions">
|
<div className="actions">
|
||||||
<div className="action-cancel">
|
<div className="action-cancel">
|
||||||
@@ -263,16 +394,17 @@ const ViewportDownloadForm = ({
|
|||||||
className="btn btn-danger"
|
className="btn btn-danger"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
>
|
>
|
||||||
{t('Cancel')}
|
{t('Buttons:Cancel')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="action-save">
|
<div className="action-save">
|
||||||
<button
|
<button
|
||||||
|
disabled={hasError}
|
||||||
onClick={downloadImage}
|
onClick={downloadImage}
|
||||||
className="btn btn-primary"
|
className="btn btn-primary"
|
||||||
data-cy="download-btn"
|
data-cy="download-btn"
|
||||||
>
|
>
|
||||||
{t('Download')}
|
{t('Buttons:Download')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,6 +10,9 @@
|
|||||||
input, select
|
input, select
|
||||||
max-height: 30px;
|
max-height: 30px;
|
||||||
|
|
||||||
|
#keep-aspect svg
|
||||||
|
margin-top: 3px;
|
||||||
|
|
||||||
.title
|
.title
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -17,85 +20,117 @@
|
|||||||
.file-info-container
|
.file-info-container
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
justify-content: space-around;
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding: 20px 10px 0;
|
padding: 20px 10px;
|
||||||
background-color: #16202b;
|
background-color: var(--ui-gray-dark);
|
||||||
|
|
||||||
.form-control.input-ohif
|
|
||||||
padding: 6px 12px;
|
|
||||||
|
|
||||||
@media screen and (max-width: 1023px)
|
@media screen and (max-width: 1023px)
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
.col
|
.width,
|
||||||
flex-grow: 1;
|
.height,
|
||||||
|
.file-name,
|
||||||
|
.file-type
|
||||||
|
height: 56px;
|
||||||
|
|
||||||
.input-ohif
|
.input-ohif
|
||||||
margin-left: 15px;
|
margin: 0 5px;
|
||||||
|
|
||||||
|
.file-name,
|
||||||
|
.file-type
|
||||||
|
.select-ohif, .input-ohif
|
||||||
|
width: 170px;
|
||||||
|
|
||||||
|
.input-ohif-label, .select-ohif-label
|
||||||
|
width: 90px;
|
||||||
|
display: inline-block;
|
||||||
@media screen and (max-width: 1023px)
|
@media screen and (max-width: 1023px)
|
||||||
margin-left: 0;
|
width: 120px;
|
||||||
margin-top: 5px;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
.file-type
|
.dimension-wrapper
|
||||||
.select-ohif
|
display: flex;
|
||||||
margin-left: 17px;
|
flex-direction: row;
|
||||||
|
|
||||||
|
.dimensions
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.input-ohif-label
|
||||||
|
width: 120px;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
.input-ohif
|
||||||
@media screen and (max-width: 1023px)
|
@media screen and (max-width: 1023px)
|
||||||
margin-left: 0;
|
width: 170px;
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
.show-annotations
|
.keep-aspect-wrapper
|
||||||
font-weight: bold;
|
display: flex;
|
||||||
line-height: 30px;
|
justify-content: center;
|
||||||
input
|
align-items: center;
|
||||||
margin-right: 7px;
|
padding: 0 10px;
|
||||||
vertical-align: middle;
|
height: 86px;
|
||||||
label
|
|
||||||
display: flex;
|
.show-annotations
|
||||||
justify-content: center;
|
font-weight: bold;
|
||||||
align-items: center;
|
line-height: 30px;
|
||||||
|
input
|
||||||
|
margin-right: 7px;
|
||||||
|
vertical-align: middle;
|
||||||
|
label
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.loading-image
|
||||||
|
height: 580px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
color: var(--active-color);
|
||||||
|
font-size: 20px;
|
||||||
|
|
||||||
|
.icon-spin
|
||||||
|
margin-right: 15px;
|
||||||
|
|
||||||
.preview
|
.preview
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction column;
|
flex-direction: column;
|
||||||
height: fit-content;
|
background-color: var(--ui-gray-dark);
|
||||||
background-color: #16202b;
|
|
||||||
width: fit-content;
|
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
align-self: center;
|
justify-content: flex-start;
|
||||||
margin-bottom: 20px;
|
align-items: center;
|
||||||
@media screen and (max-width: 1023px)
|
height: 580px;
|
||||||
width: 100%;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.viewport-preview
|
.viewport-preview
|
||||||
max-height: 512px;
|
max-height: 512px;
|
||||||
max-width: 512px;
|
max-width: 512px;
|
||||||
|
|
||||||
h4
|
.preview-header
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align center;
|
text-align center;
|
||||||
font-size: 1.3em;
|
font-size: 1.3em;
|
||||||
margin: 0 0 10px;
|
margin: 0 0 10px;
|
||||||
|
|
||||||
.preview-container
|
|
||||||
width: auto;
|
|
||||||
height: 100%;
|
|
||||||
max-height: 400px;
|
|
||||||
object-fit contain;
|
|
||||||
|
|
||||||
.actions
|
.actions
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
.action-cancel
|
margin-top: 20px;
|
||||||
margin: 0 20px;
|
|
||||||
.actions-save
|
.btn
|
||||||
margin: 0 0 0 10px;
|
margin: 0 10px;
|
||||||
|
|
||||||
|
.input-error
|
||||||
|
font-size: 12px;
|
||||||
|
color: red;
|
||||||
|
text-align: center;
|
||||||
|
margin: 3px 0;
|
||||||
|
|
||||||
|
|
||||||
.modal-dialog
|
.modal-dialog
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
@@ -10,7 +10,10 @@ export class TableListItem extends Component {
|
|||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
itemClass: PropTypes.string,
|
itemClass: PropTypes.string,
|
||||||
itemIndex: PropTypes.number,
|
itemIndex: PropTypes.number,
|
||||||
itemKey: PropTypes.oneOfType(['number', 'string']),
|
itemKey: PropTypes.oneOfType([
|
||||||
|
PropTypes.string,
|
||||||
|
PropTypes.number,
|
||||||
|
]),
|
||||||
onItemClick: PropTypes.func.isRequired,
|
onItemClick: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
.icon-pulse
|
.icon-pulse
|
||||||
fa-spin 1s infinite steps(8)
|
fa-spin 1s infinite steps(8)
|
||||||
|
|
||||||
|
.icon-spin {
|
||||||
|
animation: spin 2s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes fa-spin{
|
@keyframes fa-spin{
|
||||||
0%{ transform:rotate(0deg) }
|
0%{ transform:rotate(0deg) }
|
||||||
to{ transform:rotate(1turn) }
|
to{ transform:rotate(1turn) }
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ import thLarge from './icons/th-large.svg';
|
|||||||
import thList from './icons/th-list.svg';
|
import thList from './icons/th-list.svg';
|
||||||
import times from './icons/times.svg';
|
import times from './icons/times.svg';
|
||||||
import trash from './icons/trash.svg';
|
import trash from './icons/trash.svg';
|
||||||
|
import unlink from './icons/unlink.svg';
|
||||||
import user from './icons/user.svg';
|
import user from './icons/user.svg';
|
||||||
import youtube from './icons/youtube.svg';
|
import youtube from './icons/youtube.svg';
|
||||||
|
|
||||||
@@ -158,6 +159,7 @@ const ICONS = {
|
|||||||
rotate,
|
rotate,
|
||||||
'rotate-right': rotateRight,
|
'rotate-right': rotateRight,
|
||||||
trash,
|
trash,
|
||||||
|
unlink,
|
||||||
'exclamation-circle': exclamationCircle,
|
'exclamation-circle': exclamationCircle,
|
||||||
link,
|
link,
|
||||||
'exclamation-triangle': exclamationTriangle,
|
'exclamation-triangle': exclamationTriangle,
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
aria-labelledby="unlink"
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
width="1em"
|
||||||
|
height="1em"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<title id="title">Unlink</title>
|
||||||
|
<path d="M304.083 388.936c4.686 4.686 4.686 12.284 0 16.971l-65.057 65.056c-54.709 54.711-143.27 54.721-197.989 0-54.713-54.713-54.719-143.27 0-197.989l65.056-65.057c4.686-4.686 12.284-4.686 16.971 0l22.627 22.627c4.686 4.686 4.686 12.284 0 16.971L81.386 311.82c-34.341 34.341-33.451 88.269.597 120.866 32.577 31.187 84.788 31.337 117.445-1.32l65.057-65.056c4.686-4.686 12.284-4.686 16.971 0l22.627 22.626zm-56.568-243.245l64.304-64.304c34.346-34.346 88.286-33.453 120.882.612 31.18 32.586 31.309 84.785-1.335 117.43l-65.056 65.057c-4.686 4.686-4.686 12.284 0 16.971l22.627 22.627c4.686 4.686 12.284 4.686 16.971 0l65.056-65.057c54.711-54.709 54.721-143.271 0-197.99-54.71-54.711-143.27-54.72-197.989 0l-65.057 65.057c-4.686 4.686-4.686 12.284 0 16.971l22.627 22.627c4.685 4.685 12.283 4.685 16.97-.001zm238.343 362.794l22.627-22.627c4.686-4.686 4.686-12.284 0-16.971L43.112 3.515c-4.686-4.686-12.284-4.686-16.971 0L3.515 26.142c-4.686 4.686-4.686 12.284 0 16.971l465.373 465.373c4.686 4.686 12.284 4.686 16.97-.001z"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -29,18 +29,18 @@ class Select extends Component {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="select-ohif-container">
|
<div className="select-ohif-container">
|
||||||
<label className="select-ohif-label" htmlFor={this.id}>
|
{this.props.label && (
|
||||||
{this.props.label}
|
<label className="select-ohif-label" htmlFor={this.id}>{this.props.label}</label>
|
||||||
<select className="form-control select-ohif" {...this.props}>
|
)}
|
||||||
{this.props.options.map(({ key, value }) => {
|
<select className="form-control select-ohif" {...this.props}>
|
||||||
return (
|
{this.props.options.map(({ key, value }) => {
|
||||||
<option key={key} value={value}>
|
return (
|
||||||
{key}
|
<option key={key} value={value}>
|
||||||
</option>
|
{key}
|
||||||
);
|
</option>
|
||||||
})}
|
);
|
||||||
</select>
|
})}
|
||||||
</label>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class TextInput extends React.Component {
|
|||||||
PropTypes.number
|
PropTypes.number
|
||||||
]),
|
]),
|
||||||
id: PropTypes.string,
|
id: PropTypes.string,
|
||||||
label:PropTypes.string,
|
label: PropTypes.string,
|
||||||
type: PropTypes.string,
|
type: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -28,15 +28,15 @@ class TextInput extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="input-ohif-container">
|
<div className="input-ohif-container">
|
||||||
<label className="input-ohif-label" htmlFor={this.props.id}>
|
{this.props.label && (
|
||||||
{this.props.label}
|
<label className="input-ohif-label" htmlFor={this.props.id}>{this.props.label}</label>
|
||||||
<input
|
)}
|
||||||
type={this.props.type}
|
<input
|
||||||
id={this.props.id}
|
type={this.props.type}
|
||||||
className="form-control input-ohif"
|
id={this.props.id}
|
||||||
{...this.props}
|
className="form-control input-ohif"
|
||||||
/>
|
{...this.props}
|
||||||
</label>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
|
|||||||
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
||||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||||
const ExtractCssChunksPlugin = require('extract-css-chunks-webpack-plugin');
|
const ExtractCssChunksPlugin = require('extract-css-chunks-webpack-plugin');
|
||||||
// const FaviconsWebpackPlugin = require('favicons-webpack-plugin');
|
|
||||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||||
const { InjectManifest } = require('workbox-webpack-plugin');
|
const { InjectManifest } = require('workbox-webpack-plugin');
|
||||||
const TerserJSPlugin = require('terser-webpack-plugin');
|
const TerserJSPlugin = require('terser-webpack-plugin');
|
||||||
|
|||||||
@@ -3,6 +3,97 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [3.4.13](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.12...@ohif/viewer@3.4.13) (2020-01-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [3.4.12](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.11...@ohif/viewer@3.4.12) (2020-01-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [3.4.11](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.10...@ohif/viewer@3.4.11) (2020-01-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* download tool fixes & improvements ([#1235](https://github.com/OHIF/Viewers/issues/1235)) ([b9574b6](https://github.com/OHIF/Viewers/commit/b9574b6efcfeb85cde35b5cae63282f8e1b35be6))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [3.4.10](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.9...@ohif/viewer@3.4.10) (2020-01-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [3.4.9](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.8...@ohif/viewer@3.4.9) (2020-01-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [3.4.8](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.7...@ohif/viewer@3.4.8) (2020-01-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [3.4.7](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.6...@ohif/viewer@3.4.7) (2020-01-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [3.4.6](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.5...@ohif/viewer@3.4.6) (2020-01-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [3.4.5](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.4...@ohif/viewer@3.4.5) (2020-01-27)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [3.4.4](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.3...@ohif/viewer@3.4.4) (2020-01-27)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [3.4.3](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.2...@ohif/viewer@3.4.3) (2020-01-24)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [3.4.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.1...@ohif/viewer@3.4.2) (2020-01-17)
|
## [3.4.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.1...@ohif/viewer@3.4.2) (2020-01-17)
|
||||||
|
|
||||||
**Note:** Version bump only for package @ohif/viewer
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|||||||
Binary file not shown.
@@ -16,14 +16,14 @@ describe('OHIF Download Snapshot File', () => {
|
|||||||
.click();
|
.click();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('checks displayed information for Tablet experience', function() {
|
it('checks displayed information for Tablet experience', function () {
|
||||||
// Set Tablet resolution
|
// Set Tablet resolution
|
||||||
cy.viewport(1000, 660);
|
cy.viewport(1000, 660);
|
||||||
// Visual comparison
|
// Visual comparison
|
||||||
cy.screenshot('Download Image Modal - Tablet experience');
|
cy.screenshot('Download Image Modal - Tablet experience');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('checks displayed information for Desktop experience', function() {
|
it('checks displayed information for Desktop experience', function () {
|
||||||
// Set Desktop resolution
|
// Set Desktop resolution
|
||||||
cy.viewport(1750, 720);
|
cy.viewport(1750, 720);
|
||||||
// Visual comparison
|
// Visual comparison
|
||||||
@@ -52,7 +52,7 @@ describe('OHIF Download Snapshot File', () => {
|
|||||||
.should('be.visible');
|
.should('be.visible');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('cancel changes on download modal', function() {
|
it('cancel changes on download modal', function () {
|
||||||
//Change Image Width, Filename and File Type
|
//Change Image Width, Filename and File Type
|
||||||
cy.get('[data-cy="image-width"]')
|
cy.get('[data-cy="image-width"]')
|
||||||
.clear()
|
.clear()
|
||||||
@@ -91,7 +91,7 @@ describe('OHIF Download Snapshot File', () => {
|
|||||||
// //Check error message
|
// //Check error message
|
||||||
// });
|
// });
|
||||||
|
|
||||||
it('checks if "Show Annotations" checkbox will display annotations', function() {
|
it('checks if "Show Annotations" checkbox will display annotations', function () {
|
||||||
// Close modal that is initially opened
|
// Close modal that is initially opened
|
||||||
cy.get('[data-cy="close-button"]').click();
|
cy.get('[data-cy="close-button"]').click();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
describe('OHIF Load Local File', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.visit('/local');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks if user can navigate to /local', () => {
|
||||||
|
cy.get('.drag-drop-contents').should(
|
||||||
|
'contain',
|
||||||
|
'Drag and Drop DICOM files here to load them in the Viewer'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('loads an invalid file and verify if viewer is empty', () => {
|
||||||
|
const fileName = 'example.json';
|
||||||
|
|
||||||
|
cy.fixture(fileName).then(fileContent => {
|
||||||
|
cy.get('input[type=file]')
|
||||||
|
.first()
|
||||||
|
.upload({
|
||||||
|
fileContent,
|
||||||
|
fileName,
|
||||||
|
mimeType: 'application/json',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//Verify if there is no thumbnail visible
|
||||||
|
cy.expectMinimumThumbnails(0);
|
||||||
|
//Verify if DOC thumnails is displayed and load it on viewport
|
||||||
|
cy.get('[data-cy="thumbnail-list"]').should('not.exist');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -43,7 +43,7 @@ describe('OHIF Study List', function() {
|
|||||||
//Wait result list to be displayed
|
//Wait result list to be displayed
|
||||||
cy.waitStudyList();
|
cy.waitStudyList();
|
||||||
cy.get('@searchResult').should($list => {
|
cy.get('@searchResult').should($list => {
|
||||||
expect($list.length).to.be.eq(17);
|
expect($list.length).to.be.eq(16);
|
||||||
expect($list).to.contain('MR');
|
expect($list).to.contain('MR');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -132,7 +132,7 @@ describe('OHIF Study List', function() {
|
|||||||
//Wait result list to be displayed
|
//Wait result list to be displayed
|
||||||
cy.waitStudyList();
|
cy.waitStudyList();
|
||||||
cy.get('@searchResult').should($list => {
|
cy.get('@searchResult').should($list => {
|
||||||
expect($list.length).to.be.eq(17);
|
expect($list.length).to.be.eq(16);
|
||||||
expect($list).to.contain('MR');
|
expect($list).to.contain('MR');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
describe('OHIF Load Local PDF File', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.visit('/local');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('loads a PDF DICOM file and visualize it', () => {
|
||||||
|
const fileName = 'PDFDICOMfile.pdf';
|
||||||
|
|
||||||
|
cy.fixture(fileName).then(fileContent => {
|
||||||
|
cy.get('input[type=file]')
|
||||||
|
.first()
|
||||||
|
.upload({
|
||||||
|
fileContent,
|
||||||
|
fileName,
|
||||||
|
mimeType: 'application/pdf',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//Verify if 1 thumbnail is visible
|
||||||
|
cy.expectMinimumThumbnails(1);
|
||||||
|
//Verify if DOC thumnails is displayed and load it on viewport
|
||||||
|
cy.get('[data-cy="thumbnail-list"]')
|
||||||
|
.contains('DOC')
|
||||||
|
.click({ force: true });
|
||||||
|
|
||||||
|
//Verify if PDF document is displayed on the viewport
|
||||||
|
cy.get('#pdf-canvas-container').should('be.visible');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,16 +1,11 @@
|
|||||||
describe('OHIF Save Measurements', function() {
|
describe('OHIF Save Measurements', function() {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.openStudy('Fall 1');
|
cy.openStudy('MISTER^MR');
|
||||||
cy.expectMinimumThumbnails(2);
|
cy.waitDicomImage();
|
||||||
|
cy.expectMinimumThumbnails(5);
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Drags Study thumbnail into viewport
|
|
||||||
cy.get('[data-cy="thumbnail-list"]')
|
|
||||||
.contains('Rheuma')
|
|
||||||
.scrollIntoView()
|
|
||||||
.drag('.viewport-drop-target');
|
|
||||||
|
|
||||||
// Wait image to load on viewport
|
// Wait image to load on viewport
|
||||||
cy.wait(2000);
|
cy.wait(2000);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import '@percy/cypress';
|
import '@percy/cypress';
|
||||||
|
import 'cypress-file-upload';
|
||||||
import { DragSimulator } from '../helpers/DragSimulator.js';
|
import { DragSimulator } from '../helpers/DragSimulator.js';
|
||||||
import {
|
import {
|
||||||
initCornerstoneToolsAliases,
|
initCornerstoneToolsAliases,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/viewer",
|
"name": "@ohif/viewer",
|
||||||
"version": "3.4.2",
|
"version": "3.4.13",
|
||||||
"description": "OHIF Viewer",
|
"description": "OHIF Viewer",
|
||||||
"author": "OHIF Contributors",
|
"author": "OHIF Contributors",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -45,21 +45,20 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.5.5",
|
"@babel/runtime": "^7.5.5",
|
||||||
"@ffmpeg-installer/ffmpeg": "1.0.20",
|
"@ohif/core": "^2.3.7",
|
||||||
"@ohif/core": "^2.3.2",
|
"@ohif/extension-cornerstone": "^2.2.2",
|
||||||
"@ohif/extension-cornerstone": "^2.2.1",
|
|
||||||
"@ohif/extension-dicom-html": "^1.1.0",
|
"@ohif/extension-dicom-html": "^1.1.0",
|
||||||
"@ohif/extension-dicom-microscopy": "^0.50.6",
|
"@ohif/extension-dicom-microscopy": "^0.50.6",
|
||||||
"@ohif/extension-dicom-pdf": "^1.0.1",
|
"@ohif/extension-dicom-pdf": "^1.0.1",
|
||||||
"@ohif/extension-vtk": "^1.2.7",
|
"@ohif/extension-vtk": "^1.2.13",
|
||||||
"@ohif/i18n": "^0.52.4",
|
"@ohif/i18n": "^0.52.5",
|
||||||
"@ohif/ui": "^1.1.8",
|
"@ohif/ui": "^1.1.9",
|
||||||
"@tanem/react-nprogress": "^1.1.25",
|
"@tanem/react-nprogress": "^1.1.25",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"core-js": "^3.2.1",
|
"core-js": "^3.2.1",
|
||||||
"cornerstone-core": "^2.2.8",
|
"cornerstone-core": "^2.2.8",
|
||||||
"cornerstone-math": "^0.1.8",
|
"cornerstone-math": "^0.1.8",
|
||||||
"cornerstone-tools": "^4.9.0",
|
"cornerstone-tools": "^4.12.0",
|
||||||
"cornerstone-wado-image-loader": "^3.0.0",
|
"cornerstone-wado-image-loader": "^3.0.0",
|
||||||
"dcmjs": "^0.8.2",
|
"dcmjs": "^0.8.2",
|
||||||
"dicom-parser": "^1.8.3",
|
"dicom-parser": "^1.8.3",
|
||||||
@@ -89,7 +88,7 @@
|
|||||||
"cypress": "^3.8.0",
|
"cypress": "^3.8.0",
|
||||||
"gh-pages": "2.0.1",
|
"gh-pages": "2.0.1",
|
||||||
"identity-obj-proxy": "3.0.x",
|
"identity-obj-proxy": "3.0.x",
|
||||||
"lodash": "4.17.11",
|
"lodash": "4.17.15",
|
||||||
"lodash.clonedeep": "4.5.0"
|
"lodash.clonedeep": "4.5.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,6 +83,44 @@ class OHIFStandaloneViewer extends Component {
|
|||||||
path="/callback"
|
path="/callback"
|
||||||
render={() => <CallbackPage userManager={userManager} />}
|
render={() => <CallbackPage userManager={userManager} />}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path="/login"
|
||||||
|
component={() => {
|
||||||
|
const queryParams = new URLSearchParams(
|
||||||
|
this.props.location.search
|
||||||
|
);
|
||||||
|
const iss = queryParams.get('iss');
|
||||||
|
const loginHint = queryParams.get('login_hint');
|
||||||
|
const targetLinkUri = queryParams.get('target_link_uri');
|
||||||
|
const oidcAuthority =
|
||||||
|
appConfig.oidc !== null && appConfig.oidc[0].authority;
|
||||||
|
if (iss !== oidcAuthority) {
|
||||||
|
console.error(
|
||||||
|
'iss of /login does not match the oidc authority'
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
userManager.removeUser().then(() => {
|
||||||
|
if (targetLinkUri !== null) {
|
||||||
|
sessionStorage.setItem(
|
||||||
|
'ohif-redirect-to',
|
||||||
|
new URL(targetLinkUri).pathname
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
sessionStorage.setItem('ohif-redirect-to', '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loginHint !== null) {
|
||||||
|
userManager.signinRedirect({ login_hint: loginHint });
|
||||||
|
} else {
|
||||||
|
userManager.signinRedirect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<Route
|
<Route
|
||||||
component={() => {
|
component={() => {
|
||||||
userManager.getUser().then(user => {
|
userManager.getUser().then(user => {
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { connect } from 'react-redux';
|
||||||
|
import StandaloneRouting from '../routes/StandaloneRouting';
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => {
|
||||||
|
return {
|
||||||
|
activateServer: server => {
|
||||||
|
const action = {
|
||||||
|
type: 'ACTIVATE_SERVER',
|
||||||
|
server,
|
||||||
|
};
|
||||||
|
dispatch(action);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const ConnectedStandaloneRouting = connect(
|
||||||
|
null,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(StandaloneRouting);
|
||||||
|
|
||||||
|
export default ConnectedStandaloneRouting;
|
||||||
@@ -2,23 +2,21 @@ import { connect } from 'react-redux';
|
|||||||
import ViewerRetrieveStudyData from './ViewerRetrieveStudyData.js';
|
import ViewerRetrieveStudyData from './ViewerRetrieveStudyData.js';
|
||||||
import OHIF from "@ohif/core";
|
import OHIF from "@ohif/core";
|
||||||
|
|
||||||
const {
|
const { clearViewportSpecificData } = OHIF.redux.actions;
|
||||||
clearViewportSpecificData
|
|
||||||
} = OHIF.redux.actions;
|
|
||||||
const isActive = a => a.active === true;
|
const isActive = a => a.active === true;
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = (state, ownProps) => {
|
||||||
const activeServer = state.servers.servers.find(isActive);
|
const activeServer = state.servers.servers.find(isActive);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
server: activeServer,
|
server: ownProps.server || activeServer,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
const mapDispatchToProps = dispatch => {
|
const mapDispatchToProps = dispatch => {
|
||||||
return {
|
return {
|
||||||
clearViewportSpecificData: () => {
|
clearViewportSpecificData: () => {
|
||||||
dispatch(clearViewportSpecificData());
|
dispatch(clearViewportSpecificData());
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const DICOMFileLoader = new (class extends FileLoader {
|
|||||||
dicomData.meta
|
dicomData.meta
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('Error on getting dicom file dataset. It defaults to empty');
|
console.error('Error reading dicom file', e);
|
||||||
}
|
}
|
||||||
// Set imageId on dataset to be consumed later on
|
// Set imageId on dataset to be consumed later on
|
||||||
dataset.imageId = imageId;
|
dataset.imageId = imageId;
|
||||||
|
|||||||
@@ -2,14 +2,16 @@ import React from 'react';
|
|||||||
import './NotFound.css';
|
import './NotFound.css';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
export default function NotFound() {
|
export default function NotFound({ message = 'Sorry, this page does not exist.', showGoBackButton = true }) {
|
||||||
return (
|
return (
|
||||||
<div className={'not-found'}>
|
<div className={'not-found'}>
|
||||||
<div>
|
<div>
|
||||||
<h4>Sorry, this page does not exist.</h4>
|
<h4>{message}</h4>
|
||||||
<h5>
|
{showGoBackButton && (
|
||||||
<Link to={'/'}>Go back to the Study List</Link>
|
<h5>
|
||||||
</h5>
|
<Link to={'/'}>Go back to the Study List</Link>
|
||||||
|
</h5>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,29 +1,33 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { log, metadata, studies, utils } from '@ohif/core';
|
import { log, metadata, utils } from '@ohif/core';
|
||||||
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import ConnectedViewer from '../connectedComponents/ConnectedViewer';
|
|
||||||
import { extensionManager } from './../App.js';
|
|
||||||
import qs from 'querystring';
|
import qs from 'querystring';
|
||||||
|
|
||||||
const { OHIFStudyMetadata } = metadata;
|
import { extensionManager } from './../App.js';
|
||||||
const { retrieveStudiesMetadata } = studies;
|
import ConnectedViewer from '../connectedComponents/ConnectedViewer';
|
||||||
|
import ConnectedViewerRetrieveStudyData from '../connectedComponents/ConnectedViewerRetrieveStudyData';
|
||||||
|
import NotFound from '../routes/NotFound';
|
||||||
|
|
||||||
const { studyMetadataManager, updateMetaDataManager } = utils;
|
const { studyMetadataManager, updateMetaDataManager } = utils;
|
||||||
|
const { OHIFStudyMetadata } = metadata;
|
||||||
|
|
||||||
class StandaloneRouting extends Component {
|
class StandaloneRouting extends Component {
|
||||||
state = {
|
state = {
|
||||||
studies: null,
|
studies: null,
|
||||||
|
server: null,
|
||||||
|
studyInstanceUids: null,
|
||||||
|
seriesInstanceUids: null,
|
||||||
error: null,
|
error: null,
|
||||||
|
loading: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
studyInstanceUids = [];
|
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
location: PropTypes.object,
|
location: PropTypes.object,
|
||||||
store: PropTypes.object,
|
store: PropTypes.object,
|
||||||
|
setServers: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
static parseQueryAndFetchStudies(query) {
|
parseQueryAndRetrieveDICOMWebData(query) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const url = query.url;
|
const url = query.url;
|
||||||
|
|
||||||
@@ -43,7 +47,11 @@ class StandaloneRouting extends Component {
|
|||||||
|
|
||||||
// When the JSON has been returned, parse it into a JavaScript Object
|
// When the JSON has been returned, parse it into a JavaScript Object
|
||||||
// and render the OHIF Viewer with this data
|
// and render the OHIF Viewer with this data
|
||||||
oReq.addEventListener('load', () => {
|
oReq.addEventListener('load', event => {
|
||||||
|
if (event.target.status === 404) {
|
||||||
|
reject(new Error('No JSON data found'));
|
||||||
|
}
|
||||||
|
|
||||||
// Parse the response content
|
// Parse the response content
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseText
|
// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseText
|
||||||
if (!oReq.responseText) {
|
if (!oReq.responseText) {
|
||||||
@@ -54,27 +62,24 @@ class StandaloneRouting extends Component {
|
|||||||
log.info(JSON.stringify(oReq.responseText, null, 2));
|
log.info(JSON.stringify(oReq.responseText, null, 2));
|
||||||
|
|
||||||
const data = JSON.parse(oReq.responseText);
|
const data = JSON.parse(oReq.responseText);
|
||||||
if (data.servers && query.studyInstanceUids) {
|
if (data.servers) {
|
||||||
|
if (!query.studyInstanceUids) {
|
||||||
|
log.warn('No study instance uids specified');
|
||||||
|
reject(new Error('No study instance uids specified'));
|
||||||
|
}
|
||||||
|
|
||||||
const server = data.servers.dicomWeb[0];
|
const server = data.servers.dicomWeb[0];
|
||||||
server.type = 'dicomWeb';
|
server.type = 'dicomWeb';
|
||||||
|
|
||||||
const studyInstanceUids = query.studyInstanceUids.split(';');
|
log.warn('Activating server', server);
|
||||||
const seriesInstanceUids = [];
|
this.props.activateServer(server);
|
||||||
|
|
||||||
retrieveStudiesMetadata(
|
const studyInstanceUids = query.studyInstanceUids.split(';');
|
||||||
server,
|
const seriesInstanceUids = query.seriesInstanceUids ? query.seriesInstanceUids.split(';') : [];
|
||||||
studyInstanceUids,
|
|
||||||
seriesInstanceUids
|
resolve({ server, studyInstanceUids, seriesInstanceUids });
|
||||||
).then(
|
|
||||||
studies => {
|
|
||||||
resolve(studies);
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
resolve(data.studies);
|
resolve({ studies: data.studies, studyInstanceUids: [] });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -96,56 +101,80 @@ class StandaloneRouting extends Component {
|
|||||||
|
|
||||||
// Remove ? prefix which is included for some reason
|
// Remove ? prefix which is included for some reason
|
||||||
search = search.slice(1, search.length);
|
search = search.slice(1, search.length);
|
||||||
|
|
||||||
const query = qs.parse(search);
|
const query = qs.parse(search);
|
||||||
const studies = await StandaloneRouting.parseQueryAndFetchStudies(query);
|
|
||||||
|
|
||||||
studyMetadataManager.purge();
|
let {
|
||||||
|
server,
|
||||||
|
studies,
|
||||||
|
studyInstanceUids,
|
||||||
|
seriesInstanceUids,
|
||||||
|
} = await this.parseQueryAndRetrieveDICOMWebData(query);
|
||||||
|
|
||||||
// Map studies to new format, update metadata manager?
|
if (studies) {
|
||||||
const uniqueStudyUids = new Set();
|
const {
|
||||||
const updatedStudies = studies.map(study => {
|
studies: updatedStudies,
|
||||||
const studyMetadata = new OHIFStudyMetadata(
|
studyInstanceUids: updatedStudiesInstanceUids,
|
||||||
study,
|
} = _mapStudiesToNewFormat(studies);
|
||||||
study.studyInstanceUid
|
studies = updatedStudies;
|
||||||
);
|
studyInstanceUids = updatedStudiesInstanceUids;
|
||||||
const sopClassHandlerModules =
|
}
|
||||||
extensionManager.modules['sopClassHandlerModule'];
|
|
||||||
|
|
||||||
study.displaySets =
|
this.setState({
|
||||||
study.displaySets ||
|
studies,
|
||||||
studyMetadata.createDisplaySets(sopClassHandlerModules);
|
server,
|
||||||
studyMetadata.setDisplaySets(study.displaySets);
|
studyInstanceUids,
|
||||||
|
seriesInstanceUids,
|
||||||
// Updates WADO-RS metaDataManager
|
loading: false,
|
||||||
updateMetaDataManager(study);
|
|
||||||
|
|
||||||
studyMetadataManager.add(studyMetadata);
|
|
||||||
uniqueStudyUids.add(study.studyInstanceUid);
|
|
||||||
|
|
||||||
return study;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.studyInstanceUids = Array.from(uniqueStudyUids);
|
|
||||||
this.setState({ studies: updatedStudies });
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.setState({ error });
|
this.setState({ error: error.message, loading: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.state.error) {
|
const message = this.state.error ? `Error: ${JSON.stringify(this.state.error)}` : 'Loading...';
|
||||||
return <div>Error: {JSON.stringify(this.state.error)}</div>;
|
if (this.state.error || this.state.loading) {
|
||||||
} else if (!this.state.studies) {
|
return <NotFound message={message} showGoBackButton={this.state.error} />;
|
||||||
return <div>Loading...</div>;
|
|
||||||
}
|
}
|
||||||
return (
|
|
||||||
<ConnectedViewer
|
return this.state.studies ? (
|
||||||
studies={this.state.studies}
|
<ConnectedViewer studies={this.state.studies} />
|
||||||
studyInstanceUids={this.studyInstanceUids}
|
) : (
|
||||||
/>
|
<ConnectedViewerRetrieveStudyData
|
||||||
);
|
studyInstanceUids={this.state.studyInstanceUids}
|
||||||
|
seriesInstanceUids={this.state.seriesInstanceUids}
|
||||||
|
server={this.state.server}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _mapStudiesToNewFormat = studies => {
|
||||||
|
studyMetadataManager.purge();
|
||||||
|
|
||||||
|
/* Map studies to new format, update metadata manager? */
|
||||||
|
const uniqueStudyUids = new Set();
|
||||||
|
const updatedStudies = studies.map(study => {
|
||||||
|
const studyMetadata = new OHIFStudyMetadata(study, study.studyInstanceUid);
|
||||||
|
|
||||||
|
const sopClassHandlerModules = extensionManager.modules['sopClassHandlerModule'];
|
||||||
|
study.displaySets = study.displaySets ||
|
||||||
|
studyMetadata.createDisplaySets(sopClassHandlerModules);
|
||||||
|
studyMetadata.setDisplaySets(study.displaySets);
|
||||||
|
|
||||||
|
/* Updates WADO-RS metaDataManager */
|
||||||
|
updateMetaDataManager(study);
|
||||||
|
|
||||||
|
studyMetadataManager.add(studyMetadata);
|
||||||
|
uniqueStudyUids.add(study.studyInstanceUid);
|
||||||
|
|
||||||
|
return study;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
studies: updatedStudies,
|
||||||
|
studyInstanceUids: Array.from(uniqueStudyUids),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export default StandaloneRouting;
|
export default StandaloneRouting;
|
||||||
@@ -19,7 +19,7 @@ const StudyListRouting = asyncComponent(() =>
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
const StandaloneRouting = asyncComponent(() =>
|
const StandaloneRouting = asyncComponent(() =>
|
||||||
import(/* webpackChunkName: "StandaloneRouting" */ './StandaloneRouting.js')
|
import(/* webpackChunkName: "ConnectedStandaloneRouting" */ '../connectedComponents/ConnectedStandaloneRouting.js')
|
||||||
);
|
);
|
||||||
const ViewerLocalFileData = asyncComponent(() =>
|
const ViewerLocalFileData = asyncComponent(() =>
|
||||||
import(
|
import(
|
||||||
|
|||||||
Reference in new issue
Block a user