Compare commits

..
Author SHA1 Message Date
ohif-bot 8bbbf3a889 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.1.1
 - @ohif/viewer@3.1.11
2019-12-16 16:49:50 +00:00
arthur-miller d5bf72851a fix: 🐛 add WwwcRegionTool to cornerstone tools initialization (#1302) 2019-12-16 11:47:02 -05:00
ohif-bot 2914c83032 chore(release): publish [skip ci]
- @ohif/extension-dicom-pdf@1.0.1
 - @ohif/viewer@3.1.10
2019-12-16 16:13:00 +00:00
Igor Octaviano 31e542dfd9 fix: 🐛 Update ohif pdf extension to optionally use pdfjs (#1162)
* fix: 🐛 Update ohif pdf extension to optionally use pdfjs

Update ohif pdf extension of optionally use pdfjs

Closes: #1049

* Update branch and fix scrolling

* Dynamically import component

* Fix wrong file change

* Fix import

* Add multi opened pdf configuration

* Refactor connected component

* Specify worker explicitly

* Specify worker explicitly

* CR Update: Remove dead code

* CR Update: Refactor state

* CR Update: use refs

* CR Update: use refs
2019-12-16 11:10:10 -05:00
ohif-bot 25599a2e2a chore(release): publish [skip ci]
- @ohif/i18n@0.52.4
 - @ohif/viewer@3.1.9
2019-12-16 15:13:14 +00:00
Danny Brown 1cf7015511 docs: stub environment variables docs 2019-12-16 09:47:12 -05:00
Igor Octaviano 756f70cc54 refactor: 💡 Use env variables to set locize (#1299)
Instead of relying in dynamic configuration, use dedicated env variables
to set locize api key / configuration

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

* Refactor init to display labelling

* Cleanup labelling flow in measurements init

* Cleanup viewer imports

* Add classname to toolcontextmenu

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

* fix: consistently use `defaultSize` for intial values
2019-12-13 10:09:18 -05:00
49 changed files with 851 additions and 861 deletions

No files matched your search

+5
View File
@@ -72,12 +72,17 @@ module.exports = (env, argv, { SRC_DIR, DIST_DIR }) => {
}, },
plugins: [ plugins: [
new webpack.DefinePlugin({ new webpack.DefinePlugin({
/* Application */
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
'process.env.DEBUG': JSON.stringify(process.env.DEBUG), 'process.env.DEBUG': JSON.stringify(process.env.DEBUG),
'process.env.APP_CONFIG': JSON.stringify(process.env.APP_CONFIG || ''), 'process.env.APP_CONFIG': JSON.stringify(process.env.APP_CONFIG || ''),
'process.env.PUBLIC_URL': JSON.stringify(process.env.PUBLIC_URL || '/'), 'process.env.PUBLIC_URL': JSON.stringify(process.env.PUBLIC_URL || '/'),
'process.env.VERSION_NUMBER': JSON.stringify(PACKAGE.version || ''), 'process.env.VERSION_NUMBER': JSON.stringify(PACKAGE.version || ''),
'process.env.BUILD_NUM': JSON.stringify(BUILD_NUM), 'process.env.BUILD_NUM': JSON.stringify(BUILD_NUM),
/* i18n */
'process.env.USE_LOCIZE': JSON.stringify(process.env.USE_LOCIZE || ''),
'process.env.LOCIZE_PROJECTID': JSON.stringify(process.env.LOCIZE_PROJECTID || ''),
'process.env.LOCIZE_API_KEY': JSON.stringify(process.env.LOCIZE_API_KEY || ''),
}), }),
], ],
// Fix: https://github.com/webpack-contrib/css-loader/issues/447#issuecomment-285598881 // Fix: https://github.com/webpack-contrib/css-loader/issues/447#issuecomment-285598881
-2
View File
@@ -1,7 +1,6 @@
# OHIF Viewers # OHIF Viewers
- [Our Process](our-process.md) - [Our Process](our-process.md)
- [Quick Start](quick-start.md)
- Development - Development
- [Getting Started](development/getting-started.md) - [Getting Started](development/getting-started.md)
- [Contributing](development/contributing.md) - [Contributing](development/contributing.md)
@@ -31,7 +30,6 @@
- [OHIF Maintained](extensions/index.md#maintained-extensions) - [OHIF Maintained](extensions/index.md#maintained-extensions)
- [Services](services/index.md) - [Services](services/index.md)
- [Default](services/default/index.md) - [Default](services/default/index.md)
- [Measurements](services/default/measurements.md)
- [UI](services/ui/index.md) - [UI](services/ui/index.md)
- [Dialog Service](services/ui/ui-dialog-service.md) - [Dialog Service](services/ui/ui-dialog-service.md)
- [Modal Service](services/ui/ui-modal-service.md) - [Modal Service](services/ui/ui-modal-service.md)
+1 -1
View File
@@ -13,7 +13,7 @@ like user preferences and previous query paramaters. Because of this, the Viewer
has been built to be highly configurable to work with almost any web accessible has been built to be highly configurable to work with almost any web accessible
data source. data source.
![scope-of-project diagram](./assets/img/scope-of-project.png) ![scope-of-project diagram](./../assets/img/scope-of-project.png)
To be more specific, the OHIF Viewer is a collection of HTML, JS, and CSS files. To be more specific, the OHIF Viewer is a collection of HTML, JS, and CSS files.
These can be delivered to your end users however you would like: These can be delivered to your end users however you would like:
+54 -1
View File
@@ -1,3 +1,56 @@
# Viewer: Configuration # Viewer: Configuration
... We maintain a number of common viewer application configurations at [`<root>/platform/viewer/public/configs`][config-dir]. How these values are passed to the viewer depend on how it's deployed, but the two most common paths are:
- `index.html` looks for `https://your-website.com/app-config.js` OR
- `index.html` passes the values to `OHIF.installViewer()`
```js
window.config = {
routerBasename: '/',
whiteLabelling: {},
extensions: [],
showStudyList: true,
filterQueryParam: false,
servers: {
dicomWeb: [
{
name: 'DCM4CHEE',
wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado',
qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
qidoSupportsIncludeField: true,
imageRendering: 'wadors',
thumbnailRendering: 'wadors',
enableStudyLazyLoad: true,
},
],
},
// Supported Keys: https://craig.is/killing/mice
hotkeys: [
{ commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] },
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
{ commandName: 'invertViewport', label: 'Invert', keys: ['i'] },
{
commandName: 'flipViewportVertical',
label: 'Flip Horizontally',
keys: ['h'],
},
{
commandName: 'flipViewportHorizontal',
label: 'Flip Vertically',
keys: ['v'],
},
],
// Config to pass to the bundled cornerstone extension
cornerstoneExtensionConfig: {},
};
```
<!--
LINKS
-->
<!-- prettier-ignore-start -->
[config-dir]: https://github.com/OHIF/Viewers/tree/master/platform/viewer/public/config
<!-- prettier-ignore-end -->
@@ -0,0 +1,23 @@
# Environment Variables
There are a number of environment variables we use at build time to influence the output application's behavior.
```bash
# Application
NODE_ENV=< production | development >
DEBUG=< true | false >
APP_CONFIG=< relative path to application configuration file >
PUBLIC_URL=<>
VERSION_NUMBER=<Set by CircleCI>
BUILD_NUM=<Set by CircleCI>
# i18n
USE_LOCIZE=<false>
LOCIZE_PROJECTID=<ProjectID to pull translations for>
LOCIZE_API_KEY=<To enable Locize live editing of translations>
```
## Setting Environment Variables
- `npx cross-env`
- `.env` files
- env variables on build machine, or for terminal session
+11
View File
@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.1.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.1.0...@ohif/extension-cornerstone@2.1.1) (2019-12-16)
### Bug Fixes
* 🐛 add WwwcRegionTool to cornerstone tools initialization ([#1302](https://github.com/OHIF/Viewers/issues/1302)) ([d5bf728](https://github.com/OHIF/Viewers/commit/d5bf72851a32dff9fd3fc09332ea5250bc7e6114))
# [2.1.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.0.2...@ohif/extension-cornerstone@2.1.0) (2019-12-11) # [2.1.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.0.2...@ohif/extension-cornerstone@2.1.0) (2019-12-11)
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-cornerstone", "name": "@ohif/extension-cornerstone",
"version": "2.1.0", "version": "2.1.1",
"description": "OHIF extension for Cornerstone", "description": "OHIF extension for Cornerstone",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
+5 -4
View File
@@ -1,10 +1,10 @@
import OHIF from '@ohif/core'; import OHIF from '@ohif/core';
import { SimpleDialog } from '@ohif/ui';
import cornerstone from 'cornerstone-core'; import cornerstone from 'cornerstone-core';
import csTools from 'cornerstone-tools'; import csTools from 'cornerstone-tools';
import initCornerstoneTools from './initCornerstoneTools.js';
import queryString from 'query-string';
import { SimpleDialog } from '@ohif/ui';
import merge from 'lodash.merge'; import merge from 'lodash.merge';
import queryString from 'query-string';
import initCornerstoneTools from './initCornerstoneTools.js';
function fallbackMetaDataProvider(type, imageId) { function fallbackMetaDataProvider(type, imageId) {
if (!imageId.includes('wado?requestType=WADO')) { if (!imageId.includes('wado?requestType=WADO')) {
@@ -81,6 +81,7 @@ export default function init({ servicesManager, configuration }) {
csTools.PanTool, csTools.PanTool,
csTools.ZoomTool, csTools.ZoomTool,
csTools.WwwcTool, csTools.WwwcTool,
csTools.WwwcRegionTool,
csTools.MagnifyTool, csTools.MagnifyTool,
csTools.StackScrollTool, csTools.StackScrollTool,
csTools.StackScrollMouseWheelTool, csTools.StackScrollMouseWheelTool,
@@ -132,7 +133,7 @@ export default function init({ servicesManager, configuration }) {
csTools.setToolPassive(toolName); // there may be a better place to determine name; may not be on uninstantiated class csTools.setToolPassive(toolName); // there may be a better place to determine name; may not be on uninstantiated class
} }
}); });
csTools.setToolActive('Pan', { mouseButtonMask: 4 }); csTools.setToolActive('Pan', { mouseButtonMask: 4 });
csTools.setToolActive('Zoom', { mouseButtonMask: 2 }); csTools.setToolActive('Zoom', { mouseButtonMask: 2 });
csTools.setToolActive('Wwwc', { mouseButtonMask: 1 }); csTools.setToolActive('Wwwc', { mouseButtonMask: 1 });
+11
View File
@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.0.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@1.0.0...@ohif/extension-dicom-pdf@1.0.1) (2019-12-16)
### Bug Fixes
* 🐛 Update ohif pdf extension to optionally use pdfjs ([#1162](https://github.com/OHIF/Viewers/issues/1162)) ([31e542d](https://github.com/OHIF/Viewers/commit/31e542dfd973c3e982ede6d02d79bfe65175e792)), closes [#1049](https://github.com/OHIF/Viewers/issues/1049)
# [1.0.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@0.50.4...@ohif/extension-dicom-pdf@1.0.0) (2019-09-27) # [1.0.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@0.50.4...@ohif/extension-dicom-pdf@1.0.0) (2019-09-27)
+3 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-dicom-pdf", "name": "@ohif/extension-dicom-pdf",
"version": "1.0.0", "version": "1.0.1",
"description": "OHIF extension for Dicom PDF", "description": "OHIF extension for Dicom PDF",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -37,7 +37,8 @@
"dependencies": { "dependencies": {
"@babel/runtime": "^7.5.5", "@babel/runtime": "^7.5.5",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"lodash.isequal": "^4.5.0" "lodash.isequal": "^4.5.0",
"pdfjs-dist": "^2.2.228"
}, },
"gitHead": "a5baa9228c0eda0df880136bde4420d78e6f8706" "gitHead": "a5baa9228c0eda0df880136bde4420d78e6f8706"
} }
@@ -0,0 +1,27 @@
import OHIF from '@ohif/core';
import { connect } from 'react-redux';
import OHIFDicomPDFViewport from './OHIFDicomPDFViewport';
const { setViewportActive } = OHIF.redux.actions;
const mapStateToProps = (state, ownProps) => {
const { activeViewportIndex } = state.viewports;
return { activeViewportIndex };
};
const mapDispatchToProps = (dispatch, ownProps) => {
const { viewportIndex } = ownProps;
return {
setViewportActive: () => {
dispatch(setViewportActive(viewportIndex));
},
};
};
const ConnectedOHIFDicomPDFViewer = connect(
mapStateToProps,
mapDispatchToProps
)(OHIFDicomPDFViewport);
export default ConnectedOHIFDicomPDFViewer;
@@ -0,0 +1,62 @@
.DicomPDFViewport {
--header-height: 50px;
}
.DicomPDFViewport #toolbar {
display: flex;
align-items: center;
color: #fff;
padding: 0.5em;
border-bottom: var(--ui-border-thickness) solid var(--ui-border-color);
height: var(--header-height);
}
.DicomPDFViewport #canvas {
height: calc(100% - var(--header-height));
overflow-y: scroll;
}
.DicomPDFViewport #text-layer {
height: 100%;
margin-top: var(--header-height);
}
.DicomPDFViewport #pdf-canvas-container {
position: relative;
}
.DicomPDFViewport canvas, #text-layer {
display: block;
margin: 0 auto;
overflow: hidden;
}
.DicomPDFViewport #toolbar button {
color: currentColor;
background-color: transparent;
font: inherit;
border: var(--ui-border-thickness) solid var(--ui-border-color);
border-radius: 3px;
padding: 0.25em 0.5em;
margin-right: 0.5em;
cursor: pointer;
}
.DicomPDFViewport #text-layer {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
overflow: hidden;
opacity: 0.2;
line-height: 1;
}
.DicomPDFViewport #text-layer>span {
color: transparent;
position: absolute;
white-space: pre;
cursor: text;
transform-origin: 0% 0%;
}
+197 -23
View File
@@ -1,6 +1,14 @@
import React, { Component } from 'react'; import React, { Component, createRef } from 'react';
import dicomParser from 'dicom-parser'; import dicomParser from 'dicom-parser';
import PDFJS from 'pdfjs-dist';
import PropTypes from 'prop-types';
import TypedArrayProp from './TypedArrayProp'; import TypedArrayProp from './TypedArrayProp';
import './DicomPDFViewport.css';
import pdfjsBuild from 'pdfjs-dist/build/pdf';
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.entry';
pdfjsBuild.GlobalWorkerOptions.workerSrc = pdfjsWorker;
// TODO: Should probably use dcmjs for this // TODO: Should probably use dcmjs for this
const SOP_CLASS_UIDS = { const SOP_CLASS_UIDS = {
@@ -8,16 +16,88 @@ const SOP_CLASS_UIDS = {
}; };
class DicomPDFViewport extends Component { class DicomPDFViewport extends Component {
state = { constructor(props) {
fileURL: null, super(props);
error: null,
}; this.state = {
fileURL: null,
error: null,
currentPageIndex: 1,
pdf: null,
scale: 1,
};
this.canvas = createRef();
this.textLayer = createRef();
}
static propTypes = { static propTypes = {
byteArray: TypedArrayProp.uint8, byteArray: TypedArrayProp.uint8,
useNative: PropTypes.bool,
viewportData: PropTypes.object,
activeViewportIndex: PropTypes.number,
setViewportActive: PropTypes.func,
viewportIndex: PropTypes.number,
}; };
renderPDF = (dataSet, byteArray) => { static defaultProps = {
useNative: false,
};
async componentDidMount() {
const dataSet = this.parseByteArray(this.props.byteArray);
const fileURL = this.getPDFFileUrl(dataSet, this.props.byteArray);
this.setState(state => ({ ...state, fileURL }));
if (!this.props.useNative) {
const pdf = await PDFJS.getDocument(fileURL).promise;
this.setState(state => ({ ...state, pdf }), () => this.updatePDFCanvas());
}
}
updatePDFCanvas = async () => {
const { pdf, scale, currentPageIndex } = this.state;
const context = this.canvas.getContext('2d');
const page = await pdf.getPage(currentPageIndex);
let viewport = page.getViewport({ scale });
this.canvas.height = viewport.height;
this.canvas.width = viewport.width;
const renderContext = {
canvasContext: context,
viewport: viewport,
};
await page.render(renderContext);
const textContent = await page.getTextContent();
this.textLayer.innerHTML = '';
this.textLayer.style.height = viewport.height + 'px';
this.textLayer.style.width = viewport.width + 'px';
PDFJS.renderTextLayer({
textContent,
container: this.textLayer,
viewport,
textDivs: [],
});
};
componentDidUpdate(prevProps, prevState) {
const { currentPageIndex, scale } = this.state;
const newValidScale = prevState.scale !== scale && scale > 0;
const newValidPageNumber =
prevState.currentPageIndex !== currentPageIndex && currentPageIndex > 0;
if (newValidScale || newValidPageNumber) {
this.updatePDFCanvas();
}
}
getPDFFileUrl = (dataSet, byteArray) => {
let pdfByteArray = byteArray; let pdfByteArray = byteArray;
if (dataSet) { if (dataSet) {
@@ -36,50 +116,144 @@ class DicomPDFViewport extends Component {
const PDF = new Blob([pdfByteArray], { type: 'application/pdf' }); const PDF = new Blob([pdfByteArray], { type: 'application/pdf' });
const fileURL = URL.createObjectURL(PDF); const fileURL = URL.createObjectURL(PDF);
this.setState({ return fileURL;
fileURL, };
});
onPageChange = async event => {
const { currentPageIndex, pdf } = this.state;
let newPageIndex = currentPageIndex;
const action = event.target.getAttribute('data-pager');
if (action === 'prev') {
if (currentPageIndex === 1) {
return;
}
newPageIndex -= 1;
if (currentPageIndex < 0) {
newPageIndex = 0;
}
}
if (action === 'next') {
if (currentPageIndex === pdf.numPages - 1) {
return;
}
newPageIndex += 1;
if (currentPageIndex > pdf.numPages - 1) {
newPageIndex = pdf.numPages - 1;
}
}
this.setState(state => ({ ...state, currentPageIndex: newPageIndex }));
};
onZoomChange = () => {
let newZoomValue = this.state.scale;
const action = event.target.getAttribute('data-pager');
if (action === '+') {
newZoomValue += 0.25;
}
if (action === '-') {
newZoomValue -= 0.25;
}
this.setState(state => ({ ...state, scale: newZoomValue }));
}; };
parseByteArray = byteArray => { parseByteArray = byteArray => {
const options = { const options = { untilTag: '' };
untilTag: '',
};
let dataSet; let dataSet;
try { try {
dataSet = dicomParser.parseDicom(byteArray, options); dataSet = dicomParser.parseDicom(byteArray, options);
} catch (error) { } catch (error) {
this.setState({ this.setState(state => ({ ...state, error }));
error,
});
} }
return dataSet; return dataSet;
}; };
componentDidMount() { setViewportActiveHandler = () => {
const dataSet = this.parseByteArray(this.props.byteArray); const {
setViewportActive,
viewportIndex,
activeViewportIndex,
} = this.props;
this.renderPDF(dataSet, this.props.byteArray); if (viewportIndex !== activeViewportIndex) {
} setViewportActive(viewportIndex);
}
};
downloadPDFCanvas = () => {
const { fileURL } = this.state;
const a = document.createElement('a');
a.href = fileURL;
a.download = fileURL.substr(fileURL.lastIndexOf('/') + 1);
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
};
render() { render() {
const { fileURL, pdf, error } = this.state;
return ( return (
<div <div
className={'DicomPDFViewport'} className={'DicomPDFViewport'}
onClick={this.setViewportActiveHandler}
onScroll={this.setViewportActiveHandler}
style={{ width: '100%', height: '100%' }} style={{ width: '100%', height: '100%' }}
> >
{this.state.fileURL && ( {!this.props.useNative ? (
<>
<div id="toolbar">
<div id="pager">
{pdf && pdf.numPages > 1 && (
<>
<button data-pager="prev" onClick={this.onPageChange}>
{`<`}
</button>
<button data-pager="next" onClick={this.onPageChange}>
{`>`}
</button>
</>
)}
<button data-pager="-" onClick={this.onZoomChange}>
{`-`}
</button>
<button data-pager="+" onClick={this.onZoomChange}>
{`+`}
</button>
<button onClick={this.downloadPDFCanvas}>Download</button>
</div>
</div>
<div id="canvas">
<div id="pdf-canvas-container">
<canvas
id="pdf-canvas"
ref={canvas => (this.canvas = canvas)}
/>
<div
id="text-layer"
ref={textLayer => (this.textLayer = textLayer)}
></div>
</div>
</div>
</>
) : (
<object <object
data={this.state.fileURL} aria-label="PDF Viewer"
data={fileURL}
type="application/pdf" type="application/pdf"
width="100%" width="100%"
height="100%" height="100%"
/> />
)} )}
{this.state.error && <h2>{JSON.stringify(this.state.error)}</h2>} {error && <h2>{JSON.stringify(error)}</h2>}
</div> </div>
); );
} }
@@ -11,6 +11,9 @@ class OHIFDicomPDFViewport extends Component {
studies: PropTypes.object, studies: PropTypes.object,
displaySet: PropTypes.object, displaySet: PropTypes.object,
viewportIndex: PropTypes.number, viewportIndex: PropTypes.number,
viewportData: PropTypes.object,
activeViewportIndex: PropTypes.number,
setViewportActive: PropTypes.func,
}; };
state = { state = {
@@ -30,42 +33,36 @@ class OHIFDicomPDFViewport extends Component {
componentDidMount() { componentDidMount() {
const { displaySet, studies } = this.props.viewportData; const { displaySet, studies } = this.props.viewportData;
const {
studyInstanceUid,
seriesInstanceUid,
sopInstanceUid,
wadoRoot,
wadoUri,
authorizationHeaders,
} = displaySet;
DicomLoaderService.findDicomDataPromise(displaySet, studies).then( DicomLoaderService.findDicomDataPromise(displaySet, studies).then(
data => { data => this.setState({ byteArray: new Uint8Array(data) }),
const byteArray = new Uint8Array(data);
this.setState({
byteArray: byteArray,
});
},
error => { error => {
this.setState({ this.setState({ error });
error,
});
throw new Error(error); throw new Error(error);
} }
); );
} }
render() { render() {
const {
setViewportActive,
viewportIndex,
activeViewportIndex,
} = this.props;
const { byteArray, error } = this.state;
const { id, init, destroy } = OHIFDicomPDFViewport; const { id, init, destroy } = OHIFDicomPDFViewport;
const pluginProps = { id, init, destroy }; const pluginProps = { id, init, destroy };
return ( return (
<OHIFComponentPlugin {...pluginProps}> <OHIFComponentPlugin {...pluginProps}>
{this.state.byteArray && ( {byteArray && (
<DicomPDFViewport byteArray={this.state.byteArray} /> <DicomPDFViewport
byteArray={byteArray}
setViewportActive={setViewportActive}
viewportIndex={viewportIndex}
activeViewportIndex={activeViewportIndex}
/>
)} )}
{this.state.error && <h2>{JSON.stringify(this.state.error)}</h2>} {error && <h2>{JSON.stringify(error)}</h2>}
</OHIFComponentPlugin> </OHIFComponentPlugin>
); );
} }
@@ -0,0 +1,37 @@
/**
* We use this component to leverage "Code Splitting"
*
* Link: https://serverless-stack.com/chapters/code-splitting-in-create-react-app.html
*/
import React, { Component } from 'react';
export default function asyncComponent(importComponent) {
class AsyncComponent extends Component {
constructor(props) {
super(props);
this.state = {
component: null,
};
}
async componentDidMount() {
// Add dynamically loaded component to state
const { default: component } = await importComponent();
this.setState({
component: component,
});
}
render() {
const C = this.state.component;
// Render the loaded component, or null
return C ? <C {...this.props} /> : null;
}
}
return AsyncComponent;
}
+9 -4
View File
@@ -1,16 +1,21 @@
import asyncComponent from './asyncComponent.js';
import OHIFDicomPDFSopClassHandler from './OHIFDicomPDFSopClassHandler.js'; import OHIFDicomPDFSopClassHandler from './OHIFDicomPDFSopClassHandler.js';
import OHIFDicomPDFViewport from './OHIFDicomPDFViewport.js';
const ConnectedOHIFDicomPDFViewer = asyncComponent(() =>
import(
/* webpackChunkName: "ConnectedOHIFDicomPDFViewer" */ './ConnectedOHIFDicomPDFViewer'
)
);
export default { export default {
/** /**
* Only required property. Should be a unique value across all extensions. * Only required property. Should be a unique value across all extensions.
*/ */
id: 'pdf', id: 'pdf',
getViewportModule() { getViewportModule() {
return OHIFDicomPDFViewport; return ConnectedOHIFDicomPDFViewer;
}, },
getSopClassHandlerModule() { getSopClassHandlerModule() {
return OHIFDicomPDFSopClassHandler; return OHIFDicomPDFSopClassHandler;
} },
}; };
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.1.3...@ohif/extension-vtk@1.1.4) (2019-12-16)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.1.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.1.2...@ohif/extension-vtk@1.1.3) (2019-12-13)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.1.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.1.1...@ohif/extension-vtk@1.1.2) (2019-12-12) ## [1.1.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.1.1...@ohif/extension-vtk@1.1.2) (2019-12-12)
**Note:** Version bump only for package @ohif/extension-vtk **Note:** Version bump only for package @ohif/extension-vtk
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-vtk", "name": "@ohif/extension-vtk",
"version": "1.1.2", "version": "1.1.4",
"description": "OHIF extension for VTK.js", "description": "OHIF extension for VTK.js",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -52,8 +52,8 @@
"react-vtkjs-viewport": "^0.3.9" "react-vtkjs-viewport": "^0.3.9"
}, },
"devDependencies": { "devDependencies": {
"@ohif/core": "^2.1.0", "@ohif/core": "^2.1.1",
"@ohif/ui": "^1.1.2", "@ohif/ui": "^1.1.4",
"cornerstone-tools": "^4.8.0", "cornerstone-tools": "^4.8.0",
"cornerstone-wado-image-loader": "^3.0.0", "cornerstone-wado-image-loader": "^3.0.0",
"dicom-parser": "^1.8.3", "dicom-parser": "^1.8.3",
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.1.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.1.0...@ohif/core@2.1.1) (2019-12-16)
**Note:** Version bump only for package @ohif/core
# [2.1.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.0.2...@ohif/core@2.1.0) (2019-12-11) # [2.1.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.0.2...@ohif/core@2.1.0) (2019-12-11)
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/core", "name": "@ohif/core",
"version": "2.1.0", "version": "2.1.1",
"description": "Generic business logic for web-based medical imaging applications", "description": "Generic business logic for web-based medical imaging applications",
"author": "OHIF Core Team", "author": "OHIF Core Team",
"license": "MIT", "license": "MIT",
-6
View File
@@ -23,8 +23,6 @@ import {
UINotificationService, UINotificationService,
UIModalService, UIModalService,
UIDialogService, UIDialogService,
UIContextMenuService,
UILabellingFlowService,
} from './services'; } from './services';
const OHIF = { const OHIF = {
@@ -55,8 +53,6 @@ const OHIF = {
UINotificationService, UINotificationService,
UIModalService, UIModalService,
UIDialogService, UIDialogService,
UIContextMenuService,
UILabellingFlowService,
}; };
export { export {
@@ -86,8 +82,6 @@ export {
UINotificationService, UINotificationService,
UIModalService, UIModalService,
UIDialogService, UIDialogService,
UIContextMenuService,
UILabellingFlowService,
}; };
export { OHIF }; export { OHIF };
-2
View File
@@ -13,8 +13,6 @@ describe('Top level exports', () => {
'UINotificationService', 'UINotificationService',
'UIModalService', 'UIModalService',
'UIDialogService', 'UIDialogService',
'UIContextMenuService',
'UILabellingFlowService',
// //
'utils', 'utils',
'studies', 'studies',
@@ -1,66 +0,0 @@
/**
* UI Context Menu
*
* @typedef {Object} ContextMenuProps
* @property {Event} event The event with tool information.
*/
const name = 'UIContextMenuService';
const publicAPI = {
name,
hide: _hide,
show: _show,
setServiceImplementation,
};
const serviceImplementation = {
_show: () => console.warn('show() NOT IMPLEMENTED'),
_hide: () => console.warn('hide() NOT IMPLEMENTED'),
};
/**
* Show a new UI ContextMenu dialog;
*
* @param {ContextMenuProps} props { event }
*/
function _show({ event }) {
return serviceImplementation._show({
event,
});
}
/**
* Hide a UI ContextMenu dialog;
*
*/
function _hide() {
return serviceImplementation._hide();
}
/**
*
*
* @param {*} {
* show: showImplementation,
* hide: hideImplementation,
* }
*/
function setServiceImplementation({
show: showImplementation,
hide: hideImplementation,
}) {
if (showImplementation) {
serviceImplementation._show = showImplementation;
}
if (hideImplementation) {
serviceImplementation._hide = hideImplementation;
}
}
export default {
name,
create: ({ configuration = {} }) => {
return publicAPI;
},
};
@@ -1,71 +0,0 @@
/**
* UI Labelling Flow
*
* @typedef {Object} LabellingFlowProps
* @property {Object} defaultPosition The position of the labelling dialog.
* @property {boolean} centralize conditional to center the labelling dialog.
* @property {Object} props The labelling props.
*
*/
const name = 'UILabellingFlowService';
const publicAPI = {
name,
show: _show,
hide: _hide,
setServiceImplementation,
};
const serviceImplementation = {
_show: () => console.warn('show() NOT IMPLEMENTED'),
_hide: () => console.warn('hide() NOT IMPLEMENTED'),
};
/**
* Hide a UI LabellingFlow dialog;
*
*/
function _hide() {
return serviceImplementation._hide();
}
/**
* Show a new UI LabellingFlow dialog;
*
* @param {LabellingFlowProps} props { defaultPosition, centralize, props }
*/
function _show({ defaultPosition, centralize, props }) {
return serviceImplementation._show({
defaultPosition,
centralize,
props,
});
}
/**
*
*
* @param {*} {
* show: showImplementation,
* hide: hideImplementation,
* }
*/
function setServiceImplementation({
show: showImplementation,
hide: hideImplementation,
}) {
if (showImplementation) {
serviceImplementation._show = showImplementation;
}
if (hideImplementation) {
serviceImplementation._hide = hideImplementation;
}
}
export default {
name,
create: ({ configuration = {} }) => {
return publicAPI;
},
};
-4
View File
@@ -2,14 +2,10 @@ import ServicesManager from './ServicesManager.js';
import UINotificationService from './UINotificationService'; import UINotificationService from './UINotificationService';
import UIModalService from './UIModalService'; import UIModalService from './UIModalService';
import UIDialogService from './UIDialogService'; import UIDialogService from './UIDialogService';
import UIContextMenuService from './UIContextMenuService';
import UILabellingFlowService from './UILabellingFlowService';
export { export {
UINotificationService, UINotificationService,
UIModalService, UIModalService,
UIDialogService, UIDialogService,
UIContextMenuService,
UILabellingFlowService,
ServicesManager, ServicesManager,
}; };
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [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
## [0.52.3](https://github.com/OHIF/Viewers/compare/@ohif/i18n@0.52.2...@ohif/i18n@0.52.3) (2019-12-12) ## [0.52.3](https://github.com/OHIF/Viewers/compare/@ohif/i18n@0.52.2...@ohif/i18n@0.52.3) (2019-12-12)
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/i18n", "name": "@ohif/i18n",
"version": "0.52.3", "version": "0.52.4",
"description": "Internationalization library for The OHIF Viewer", "description": "Internationalization library for The OHIF Viewer",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
+9 -8
View File
@@ -29,19 +29,20 @@ function addLocales(newLocales) {
customDebug(resourceBundle, 'info'); customDebug(resourceBundle, 'info');
} }
// Note: Developers can add the API key to use the in-context editor. /*
// DO NOT commit the API key * Note: Developers can add the API key to use the
const config = (window.config && window.config.i18n) || {}; * in-context editor using environment variables.
* (DO NOT commit the API key)
*/
const locizeOptions = { const locizeOptions = {
projectId: config.LOCIZE_PROJECTID, projectId: process.env.LOCIZE_PROJECTID,
apiKey: config.LOCIZE_API_KEY, apiKey: process.env.LOCIZE_API_KEY,
referenceLng: 'en-US', referenceLng: 'en-US',
fallbacklng: 'en-US', fallbacklng: 'en-US',
}; };
const envUseLocize = !!config.USE_LOCIZE; const envUseLocize = !!process.env.USE_LOCIZE;
const envApiKeyAvailable = !!config.LOCIZE_API_KEY; const envApiKeyAvailable = !!process.env.LOCIZE_API_KEY;
const DEFAULT_LANGUAGE = 'en-US'; const DEFAULT_LANGUAGE = 'en-US';
function initI18n( function initI18n(
+19
View File
@@ -3,6 +3,25 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.4](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.1.3...@ohif/ui@1.1.4) (2019-12-16)
**Note:** Version bump only for package @ohif/ui
## [1.1.3](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.1.2...@ohif/ui@1.1.3) (2019-12-13)
### Bug Fixes
* allow empty values for dimensions ([#1295](https://github.com/OHIF/Viewers/issues/1295)) ([cd2da34](https://github.com/OHIF/Viewers/commit/cd2da349e5212cccdd8e65ffa3f7fdc6bad1057c))
## [1.1.2](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.1.1...@ohif/ui@1.1.2) (2019-12-12) ## [1.1.2](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.1.1...@ohif/ui@1.1.2) (2019-12-12)
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/ui", "name": "@ohif/ui",
"version": "1.1.2", "version": "1.1.4",
"description": "A set of React components for Medical Imaging Viewers", "description": "A set of React components for Medical Imaging Viewers",
"author": "OHIF Contributors", "author": "OHIF Contributors",
"license": "MIT", "license": "MIT",
@@ -37,64 +37,47 @@ const ViewportDownloadForm = ({
const [filename, setFilename] = useState(DEFAULT_FILENAME); const [filename, setFilename] = useState(DEFAULT_FILENAME);
const [fileType, setFileType] = useState('jpg'); const [fileType, setFileType] = useState('jpg');
const [height, setHeight] = useState(defaultSize); const [dimensions, setDimensions] = useState({
const [width, setWidth] = useState(defaultSize); width: defaultSize,
height: defaultSize,
});
const [showAnnotations, setShowAnnotations] = useState(true); const [showAnnotations, setShowAnnotations] = useState(true);
const [keepAspect, setKeepAspect] = useState(true);
const [lastImage, setLastImage] = useState();
const [viewportElement, setViewportElement] = useState(); const [viewportElement, setViewportElement] = useState();
const [viewportElementHeight, setViewportElementHeight] = useState( const [viewportElementDimensions, setViewportElementDimensions] = useState({
minimumSize width: defaultSize,
); height: defaultSize,
const [viewportElementWidth, setViewportElementWidth] = useState(minimumSize); });
const [downloadCanvas, setDownloadCanvas] = useState({ const [downloadCanvas, setDownloadCanvas] = useState({
ref: createRef(), ref: createRef(),
width: minimumSize, width: defaultSize,
height: minimumSize, height: defaultSize,
}); });
const [viewportPreview, setViewportPreview] = useState({ const [viewportPreview, setViewportPreview] = useState({
src: null, src: null,
width: minimumSize, width: defaultSize,
height: minimumSize, height: defaultSize,
}); });
// Cornerstone's `enable/disable`
useEffect(() => { useEffect(() => {
enableViewport(viewportElement); enableViewport(viewportElement);
return () => { return () => {
disableViewport(viewportElement); disableViewport(viewportElement);
setHeight(defaultSize);
setWidth(defaultSize);
}; };
}, [defaultSize, disableViewport, enableViewport, viewportElement]); }, [disableViewport, enableViewport, viewportElement]);
useEffect(() => { useEffect(() => {
const { width, height } = viewportElementDimensions;
const validSize = value => (value >= minimumSize ? value : minimumSize); const validSize = value => (value >= minimumSize ? value : minimumSize);
const loadAndUpdateViewports = async () => { const loadAndUpdateViewports = async () => {
const { await loadImage(activeViewport, viewportElement, width, height);
image,
width: scaledWidth,
height: scaledHeight,
} = await loadImage(activeViewport, viewportElement, width, height);
setLastImage(image);
toggleAnnotations(showAnnotations, viewportElement); toggleAnnotations(showAnnotations, viewportElement);
setViewportElementHeight(validSize(scaledHeight));
setViewportElementWidth(validSize(scaledWidth));
setDownloadCanvas(state => ({
...state,
height: validSize(scaledHeight),
width: validSize(scaledWidth),
}));
const { const {
dataUrl, dataUrl,
width: viewportElementWidth, width: viewportElementWidth,
@@ -118,8 +101,6 @@ const ViewportDownloadForm = ({
activeViewport, activeViewport,
viewportElement, viewportElement,
showAnnotations, showAnnotations,
height,
width,
loadImage, loadImage,
toggleAnnotations, toggleAnnotations,
updateViewportPreview, updateViewportPreview,
@@ -127,54 +108,41 @@ const ViewportDownloadForm = ({
downloadCanvas.ref, downloadCanvas.ref,
minimumSize, minimumSize,
maximumSize, maximumSize,
viewportElementDimensions,
]); ]);
const onHeightChange = event => { /**
const newHeight = Math.min(event.target.value, maximumSize); * @param {object} event - Input change event
setHeight(newHeight); * @param {string} dimension - "height" | "width"
*/
const onDimensionsChange = (event, dimension) => {
const sanitizedTargetValue = event.target.value.replace(/\D/, '');
const isEmpty = sanitizedTargetValue === '';
const updatedDimension = isEmpty
? ''
: Math.min(sanitizedTargetValue, maximumSize);
setViewportElementHeight(newHeight); if (updatedDimension === dimensions.width) {
return;
setDownloadCanvas(state => ({
...state,
height: newHeight,
}));
if (keepAspect) {
const multiplier = newHeight / lastImage.height;
const newWidth = Math.round(lastImage.width * multiplier);
setWidth(newWidth);
setViewportElementWidth(newWidth);
setDownloadCanvas(state => ({
...state,
width: newWidth,
}));
} }
};
const onWidthChange = event => { // In current code, keepAspect is always `true`
const newWidth = Math.min(event.target.value, maximumSize); // And we always start w/ a square width/height
setWidth(newWidth); setDimensions({
width: updatedDimension,
setViewportElementWidth(newWidth); height: updatedDimension,
});
setDownloadCanvas(state => ({
...state,
width: newWidth,
}));
if (keepAspect) {
const multiplier = newWidth / lastImage.width;
const newHeight = Math.round(lastImage.height * multiplier);
setHeight(newHeight);
setViewportElementHeight(newHeight);
// Only update if value is non-empty
if (!isEmpty) {
setViewportElementDimensions({
height: updatedDimension,
width: updatedDimension,
});
setDownloadCanvas(state => ({ setDownloadCanvas(state => ({
...state, ...state,
height: newHeight, height: updatedDimension,
width: updatedDimension,
})); }));
} }
}; };
@@ -200,24 +168,18 @@ const ViewportDownloadForm = ({
<div className="col"> <div className="col">
<div className="width"> <div className="width">
<TextInput <TextInput
type="number"
data-cy="image-width" data-cy="image-width"
min={minimumSize} value={dimensions.width}
max={maximumSize}
value={width}
label={t('Image width (px)')} label={t('Image width (px)')}
onChange={onWidthChange} onChange={evt => onDimensionsChange(evt, 'height')}
/> />
</div> </div>
<div className="height"> <div className="height">
<TextInput <TextInput
type="number"
data-cy="image-height" data-cy="image-height"
min={minimumSize} value={dimensions.height}
max={maximumSize}
value={height}
label={t('Image height (px)')} label={t('Image height (px)')}
onChange={onHeightChange} onChange={evt => onDimensionsChange(evt, 'width')}
/> />
</div> </div>
</div> </div>
@@ -263,8 +225,8 @@ const ViewportDownloadForm = ({
<div <div
style={{ style={{
height: viewportElementHeight, height: viewportElementDimensions.height,
width: viewportElementWidth, width: viewportElementDimensions.width,
position: 'absolute', position: 'absolute',
left: '9999px', left: '9999px',
}} }}
@@ -327,6 +289,7 @@ ViewportDownloadForm.propTypes = {
toggleAnnotations: PropTypes.func.isRequired, toggleAnnotations: PropTypes.func.isRequired,
loadImage: PropTypes.func.isRequired, loadImage: PropTypes.func.isRequired,
downloadBlob: PropTypes.func.isRequired, downloadBlob: PropTypes.func.isRequired,
/** A default width & height, between the minimum and maximum size */
defaultSize: PropTypes.number.isRequired, defaultSize: PropTypes.number.isRequired,
minimumSize: PropTypes.number.isRequired, minimumSize: PropTypes.number.isRequired,
maximumSize: PropTypes.number.isRequired, maximumSize: PropTypes.number.isRequired,
@@ -1,20 +1,19 @@
.ToolContextMenu { .ContextMenu {
position: relative; position: relative;
background-color: white; background-color: white;
border: 1px solid white;
border-radius: 5px; border-radius: 5px;
z-index: 1000; z-index: 1000;
display: block; display: block;
width: 170px; width: 170px;
} }
.ToolContextMenu > ul { .ContextMenu>ul {
list-style-type: none; list-style-type: none;
padding-left: 0; padding-left: 0;
margin: 0; margin: 0;
} }
.ToolContextMenu > ul > li > button { .ContextMenu>ul>li>button {
padding: 10px; padding: 10px;
font-size: 14px; font-size: 14px;
border: none; border: none;
@@ -25,10 +24,10 @@
background: none; background: none;
} }
.ToolContextMenu > ul > li > button:hover { .ContextMenu>ul>li>button:hover {
color: #16202b; color: #16202b;
} }
.ToolContextMenu > ul > li > button:active { .ContextMenu>ul>li>button:active {
color: #79f9fe; color: #79f9fe;
} }
@@ -0,0 +1,27 @@
import React from 'react';
import PropTypes from 'prop-types';
import './ContextMenu.css';
const ContextMenu = ({ items, onClick }) => {
return (
<div className="ContextMenu">
<ul>
{items.map((item, index) => (
<li key={index}>
<button className="form-action" onClick={() => onClick(item)}>
<span key={index}>{item.label}</span>
</button>
</li>
))}
</ul>
</div>
);
};
ContextMenu.propTypes = {
items: PropTypes.array.isRequired,
onClick: PropTypes.func.isRequired,
};
export default ContextMenu;
@@ -0,0 +1 @@
export { default as ContextMenu } from './ContextMenu.js';
+2
View File
@@ -17,6 +17,7 @@ import { RoundedButtonGroup } from './roundedButtonGroup';
import { SelectTree } from './selectTree'; import { SelectTree } from './selectTree';
import { SimpleDialog } from './simpleDialog'; import { SimpleDialog } from './simpleDialog';
import { OHIFModal } from './ohifModal'; import { OHIFModal } from './ohifModal';
import { ContextMenu } from './contextMenu';
import { import {
PageToolbar, PageToolbar,
StudyList, StudyList,
@@ -27,6 +28,7 @@ import { ToolbarSection } from './toolbarSection';
import { Tooltip } from './tooltip'; import { Tooltip } from './tooltip';
export { export {
ContextMenu,
Checkbox, Checkbox,
CineDialog, CineDialog,
ViewportDownloadForm, ViewportDownloadForm,
@@ -1,147 +0,0 @@
import React, {
createContext,
useContext,
useEffect,
useCallback,
} from 'react';
import PropTypes from 'prop-types';
import { useDialog } from '@ohif/ui';
import { useLabellingFlow } from '@ohif/ui';
const ContextMenuContext = createContext(null);
const { Provider } = ContextMenuContext;
export const useContextMenu = () => useContext(ContextMenuContext);
const ContextMenuProvider = ({
children,
service,
contextMenuComponent: ContextMenuComponent,
onDelete,
}) => {
const { create, dismiss } = useDialog();
const { show: showLabellingFlow } = useLabellingFlow();
/**
* Sets the implementation of a context menu service that can be used by extensions.
*
* @returns void
*/
useEffect(() => {
if (service) {
service.setServiceImplementation({
show,
hide,
});
}
}, [hide, service, show]);
const hide = useCallback(() => dismiss({ id: 'context-menu' }), [dismiss]);
/**
* Show the context menu and override its configuration props.
*
* @param {ContextMenuProps} props { eventData, isTouchEvent, onClose, visible }
* @returns void
*/
const show = useCallback(
({ event }) => {
hide();
create({
id: 'context-menu',
isDraggable: false,
preservePosition: false,
content: ContextMenuComponent,
contentProps: {
eventData: event,
onDelete: (nearbyToolData, eventData) =>
onDelete(nearbyToolData, eventData),
onClose: () => dismiss({ id: 'context-menu' }),
onSetLabel: (eventData, measurementData) =>
showLabellingFlow({
event: eventData,
centralize: true,
props: {
measurementData,
skipAddLabelButton: true,
editLocation: true,
},
}),
onSetDescription: (eventData, measurementData) =>
showLabellingFlow({
event: eventData,
centralize: false,
defaultPosition: _getDefaultPosition(eventData),
props: {
measurementData,
editDescriptionOnDialog: true,
},
}),
},
defaultPosition: _getDefaultPosition(event),
});
},
[ContextMenuComponent, create, dismiss, hide, onDelete, showLabellingFlow]
);
const _getDefaultPosition = event => ({
x: (event && event.currentPoints.client.x) || 0,
y: (event && event.currentPoints.client.y) || 0,
});
return (
<Provider
value={{
show,
hide,
}}
>
{children}
</Provider>
);
};
/**
* Higher Order Component to use the context menu methods through a Class Component.
*
* @returns
*/
export const withContextMenu = Component => {
return function WrappedComponent(props) {
const { show, hide } = useContextMenu();
return (
<Component
{...props}
modal={{
show,
hide,
}}
/>
);
};
};
ContextMenuProvider.defaultProps = {
service: null,
};
ContextMenuProvider.propTypes = {
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]).isRequired,
service: PropTypes.shape({
setServiceImplementation: PropTypes.func,
}),
contextMenuComponent: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
PropTypes.func,
]).isRequired,
onDelete: PropTypes.func.isRequired,
};
export default ContextMenuProvider;
export const ContextMenuConsumer = ContextMenuContext.Consumer;
@@ -1,136 +0,0 @@
import React, {
createContext,
useContext,
useEffect,
useCallback,
} from 'react';
import PropTypes from 'prop-types';
import { useDialog } from './DialogProvider';
const LabellingFlowContext = createContext(null);
const { Provider } = LabellingFlowContext;
export const useLabellingFlow = () => useContext(LabellingFlowContext);
const LabellingFlowProvider = ({
children,
service,
labellingComponent: LabellingComponent,
onUpdateLabelling,
}) => {
const { create, dismiss } = useDialog();
/**
* Sets the implementation of a labelling flow service that can be used by extensions.
*
* @returns void
*/
useEffect(() => {
if (service) {
service.setServiceImplementation({
show,
hide,
});
}
}, [hide, service, show]);
const hide = useCallback(() => dismiss({ id: 'labelling' }), [dismiss]);
const show = useCallback(
({ centralize, defaultPosition, props }) => {
hide();
create({
id: 'labelling',
centralize,
isDraggable: false,
showOverlay: true,
content: LabellingComponent,
defaultPosition,
contentProps: {
visible: true,
measurementData: props.measurementData,
labellingDoneCallback: () => dismiss({ id: 'labelling' }),
updateLabelling: labellingData =>
_updateLabellingHandler(labellingData, props.measurementData),
...props,
},
});
},
[LabellingComponent, _updateLabellingHandler, create, dismiss, hide]
);
const _updateLabellingHandler = useCallback(
(labellingData, measurementData) => {
const { location, description, response } = labellingData;
if (location) {
measurementData.location = location;
}
measurementData.description = description || '';
if (response) {
measurementData.response = response;
}
onUpdateLabelling(labellingData, measurementData);
},
[onUpdateLabelling]
);
return (
<Provider
value={{
show,
hide,
}}
>
{children}
</Provider>
);
};
/**
* Higher Order Component to use the labelling flow methods through a Class Component.
*
* @returns
*/
export const withLabellingFlow = Component => {
return function WrappedComponent(props) {
const { show, hide } = useLabellingFlow();
return (
<Component
{...props}
modal={{
show,
hide,
}}
/>
);
};
};
LabellingFlowProvider.defaultProps = {
service: null,
};
LabellingFlowProvider.propTypes = {
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]).isRequired,
service: PropTypes.shape({
setServiceImplementation: PropTypes.func,
}),
labellingComponent: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
PropTypes.func,
]).isRequired,
onUpdateLabelling: PropTypes.func.isRequired,
};
export default LabellingFlowProvider;
export const LabellingFlowConsumer = LabellingFlowContext.Consumer;
-12
View File
@@ -18,15 +18,3 @@ export {
withDialog, withDialog,
useDialog, useDialog,
} from './DialogProvider.js'; } from './DialogProvider.js';
export {
default as ContextMenuProvider,
withContextMenu,
useContextMenu,
ContextMenuConsumer,
} from './ContextMenuProvider.js';
export {
default as LabellingFlowProvider,
withLabellingFlow,
useLabellingFlow,
LabellingFlowConsumer,
} from './LabellingFlowProvider.js';
+2 -16
View File
@@ -1,4 +1,5 @@
import { import {
ContextMenu,
Checkbox, Checkbox,
CineDialog, CineDialog,
ViewportDownloadForm, ViewportDownloadForm,
@@ -60,14 +61,6 @@ import {
ModalConsumer, ModalConsumer,
useModal, useModal,
withModal, withModal,
ContextMenuProvider,
ContextMenuConsumer,
useContextMenu,
withContextMenu,
LabellingFlowProvider,
LabellingFlowConsumer,
useLabellingFlow,
withLabellingFlow,
} from './contextProviders'; } from './contextProviders';
export { export {
@@ -80,6 +73,7 @@ export {
TextArea, TextArea,
TextInput, TextInput,
CineDialog, CineDialog,
ContextMenu,
ViewportDownloadForm, ViewportDownloadForm,
ExpandableToolMenu, ExpandableToolMenu,
Icon, Icon,
@@ -125,14 +119,6 @@ export {
DialogProvider, DialogProvider,
withDialog, withDialog,
useDialog, useDialog,
ContextMenuProvider,
ContextMenuConsumer,
useContextMenu,
withContextMenu,
LabellingFlowProvider,
LabellingFlowConsumer,
useLabellingFlow,
withLabellingFlow,
// Hooks // Hooks
useDebounce, useDebounce,
useMedia, useMedia,
+40
View File
@@ -3,6 +3,46 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.1.11](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.1.10...@ohif/viewer@3.1.11) (2019-12-16)
**Note:** Version bump only for package @ohif/viewer
## [3.1.10](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.1.9...@ohif/viewer@3.1.10) (2019-12-16)
**Note:** Version bump only for package @ohif/viewer
## [3.1.9](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.1.8...@ohif/viewer@3.1.9) (2019-12-16)
**Note:** Version bump only for package @ohif/viewer
## [3.1.8](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.1.7...@ohif/viewer@3.1.8) (2019-12-16)
**Note:** Version bump only for package @ohif/viewer
## [3.1.7](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.1.6...@ohif/viewer@3.1.7) (2019-12-13)
**Note:** Version bump only for package @ohif/viewer
## [3.1.6](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.1.5...@ohif/viewer@3.1.6) (2019-12-13) ## [3.1.6](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.1.5...@ohif/viewer@3.1.6) (2019-12-13)
**Note:** Version bump only for package @ohif/viewer **Note:** Version bump only for package @ohif/viewer
+7 -7
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/viewer", "name": "@ohif/viewer",
"version": "3.1.6", "version": "3.1.11",
"description": "OHIF Viewer", "description": "OHIF Viewer",
"author": "OHIF Contributors", "author": "OHIF Contributors",
"license": "MIT", "license": "MIT",
@@ -45,14 +45,14 @@
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.5.5", "@babel/runtime": "^7.5.5",
"@ohif/core": "^2.1.0", "@ohif/core": "^2.1.1",
"@ohif/extension-cornerstone": "^2.1.0", "@ohif/extension-cornerstone": "^2.1.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.0", "@ohif/extension-dicom-pdf": "^1.0.1",
"@ohif/extension-vtk": "^1.1.2", "@ohif/extension-vtk": "^1.1.4",
"@ohif/i18n": "^0.52.3", "@ohif/i18n": "^0.52.4",
"@ohif/ui": "^1.1.2", "@ohif/ui": "^1.1.4",
"@tanem/react-nprogress": "^1.1.25", "@tanem/react-nprogress": "^1.1.25",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"core-js": "^3.2.1", "core-js": "^3.2.1",
+5 -47
View File
@@ -8,9 +8,6 @@ import { hot } from 'react-hot-loader/root';
import OHIFCornerstoneExtension from '@ohif/extension-cornerstone'; import OHIFCornerstoneExtension from '@ohif/extension-cornerstone';
import ToolContextMenu from './connectedComponents/ToolContextMenu';
import LabellingFlow from './components/Labelling/LabellingFlow';
import { import {
SnackbarProvider, SnackbarProvider,
ModalProvider, ModalProvider,
@@ -18,11 +15,6 @@ import {
OHIFModal, OHIFModal,
} from '@ohif/ui'; } from '@ohif/ui';
import {
LabellingFlowProvider,
ContextMenuProvider,
} from './appCustomProviders';
import { import {
CommandsManager, CommandsManager,
ExtensionManager, ExtensionManager,
@@ -31,8 +23,6 @@ import {
UINotificationService, UINotificationService,
UIModalService, UIModalService,
UIDialogService, UIDialogService,
UIContextMenuService,
UILabellingFlowService,
utils, utils,
redux as reduxOHIF, redux as reduxOHIF,
} from '@ohif/core'; } from '@ohif/core';
@@ -135,13 +125,7 @@ class App extends Component {
} = this._appConfig; } = this._appConfig;
this.initUserManager(oidc); this.initUserManager(oidc);
_initServices([ _initServices([UINotificationService, UIModalService, UIDialogService]);
UINotificationService,
UIModalService,
UIDialogService,
UIContextMenuService,
UILabellingFlowService,
]);
_initExtensions( _initExtensions(
[...defaultExtensions, ...extensions], [...defaultExtensions, ...extensions],
cornerstoneExtensionConfig cornerstoneExtensionConfig
@@ -161,9 +145,7 @@ class App extends Component {
const { const {
UINotificationService, UINotificationService,
UIDialogService, UIDialogService,
UILabellingFlowService,
UIModalService, UIModalService,
UIContextMenuService,
} = servicesManager.services; } = servicesManager.services;
if (this._userManager) { if (this._userManager) {
@@ -181,21 +163,9 @@ class App extends Component {
modal={OHIFModal} modal={OHIFModal}
service={UIModalService} service={UIModalService}
> >
<LabellingFlowProvider <OHIFStandaloneViewer
service={UILabellingFlowService} userManager={this._userManager}
labellingComponent={LabellingFlow} />
commandsManager={commandsManager}
>
<ContextMenuProvider
service={UIContextMenuService}
contextMenuComponent={ToolContextMenu}
commandsManager={commandsManager}
>
<OHIFStandaloneViewer
userManager={this._userManager}
/>
</ContextMenuProvider>
</LabellingFlowProvider>
</ModalProvider> </ModalProvider>
</DialogProvider> </DialogProvider>
</SnackbarProvider> </SnackbarProvider>
@@ -218,19 +188,7 @@ class App extends Component {
<SnackbarProvider service={UINotificationService}> <SnackbarProvider service={UINotificationService}>
<DialogProvider service={UIDialogService}> <DialogProvider service={UIDialogService}>
<ModalProvider modal={OHIFModal} service={UIModalService}> <ModalProvider modal={OHIFModal} service={UIModalService}>
<LabellingFlowProvider <OHIFStandaloneViewer />
service={UILabellingFlowService}
labellingComponent={LabellingFlow}
commandsManager={commandsManager}
>
<ContextMenuProvider
service={UIContextMenuService}
contextMenuComponent={ToolContextMenu}
commandsManager={commandsManager}
>
<OHIFStandaloneViewer />
</ContextMenuProvider>
</LabellingFlowProvider>
</ModalProvider> </ModalProvider>
</DialogProvider> </DialogProvider>
</SnackbarProvider> </SnackbarProvider>
@@ -1,51 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { ContextMenuProvider } from '@ohif/ui';
const CustomContextMenuProvider = ({
children,
service,
contextMenuComponent,
commandsManager,
}) => {
const onDeleteHandler = (nearbyToolData, eventData) => {
const element = eventData.element;
commandsManager.runCommand('removeToolState', {
element,
toolType: nearbyToolData.toolType,
tool: nearbyToolData.tool,
});
};
return (
<ContextMenuProvider
service={service}
contextMenuComponent={contextMenuComponent}
onDelete={onDeleteHandler}
>
{children}
</ContextMenuProvider>
);
};
CustomContextMenuProvider.defaultProps = {
service: null,
};
CustomContextMenuProvider.propTypes = {
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]).isRequired,
service: PropTypes.shape({
setServiceImplementation: PropTypes.func,
}),
contextMenuComponent: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
PropTypes.func,
]).isRequired,
commandsManager: PropTypes.object.isRequired,
};
export default CustomContextMenuProvider;
@@ -1,49 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { LabellingFlowProvider } from '@ohif/ui';
const CustomLabellingFlowProvider = ({
children,
service,
labellingComponent,
commandsManager,
}) => {
const onUpdateLabellingHandler = (labellingData, measurementData) => {
commandsManager.runCommand(
'updateTableWithNewMeasurementData',
measurementData
);
};
return (
<LabellingFlowProvider
service={service}
labellingComponent={labellingComponent}
onUpdateLabelling={onUpdateLabellingHandler}
>
{children}
</LabellingFlowProvider>
);
};
CustomLabellingFlowProvider.defaultProps = {
service: null,
};
CustomLabellingFlowProvider.propTypes = {
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]).isRequired,
service: PropTypes.shape({
setServiceImplementation: PropTypes.func,
}),
labellingComponent: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
PropTypes.func,
]).isRequired,
commandsManager: PropTypes.object.isRequired,
};
export default CustomLabellingFlowProvider;
@@ -1,6 +0,0 @@
export {
default as LabellingFlowProvider,
} from './LabellingFlowProvider/LabellingFlowProvider.js';
export {
default as ContextMenuProvider,
} from './ContextMenuProvider/ContextMenuProvider.js';
@@ -2,6 +2,8 @@ import React from 'react';
import ConnectedMeasurementTable from './ConnectedMeasurementTable.js'; import ConnectedMeasurementTable from './ConnectedMeasurementTable.js';
import init from './init.js'; import init from './init.js';
import LabellingFlow from '../../components/Labelling/LabellingFlow';
export default { export default {
/** /**
* Only required property. Should be a unique value across all extensions. * Only required property. Should be a unique value across all extensions.
@@ -13,32 +15,51 @@ export default {
}, },
getPanelModule({ servicesManager, commandsManager }) { getPanelModule({ servicesManager, commandsManager }) {
const { UILabellingFlowService, UINotificationService } = servicesManager.services; const { UINotificationService, UIDialogService } = servicesManager.services;
const showLabellingDialog = (props, measurementData) => {
if (!UIDialogService) {
console.warn('Unable to show dialog; no UI Dialog Service available.');
return;
}
UIDialogService.dismiss({ id: 'labelling' });
UIDialogService.create({
id: 'labelling',
centralize: true,
isDraggable: false,
showOverlay: true,
content: LabellingFlow,
contentProps: {
measurementData,
labellingDoneCallback: () =>
UIDialogService.dismiss({ id: 'labelling' }),
updateLabelling: ({ location, description, response }) => {
measurementData.location = location || measurementData.location;
measurementData.description = description || '';
measurementData.response = response || measurementData.response;
commandsManager.runCommand(
'updateTableWithNewMeasurementData',
measurementData
);
},
...props,
},
});
};
const ExtendedConnectedMeasurementTable = () => ( const ExtendedConnectedMeasurementTable = () => (
<ConnectedMeasurementTable <ConnectedMeasurementTable
onRelabel={tool => { onRelabel={tool =>
if (UILabellingFlowService) { showLabellingDialog(
UILabellingFlowService.show({ { editLocation: true, skipAddLabelButton: true },
centralize: true, tool
props: { )
skipAddLabelButton: true, }
editLocation: true, onEditDescription={tool =>
measurementData: tool, showLabellingDialog({ editDescriptionOnDialog: true }, tool)
}, }
});
}
}}
onEditDescription={tool => {
if (UILabellingFlowService) {
UILabellingFlowService.show({
centralize: true,
props: {
editDescriptionOnDialog: true,
measurementData: tool,
},
});
}
}}
onSaveComplete={message => { onSaveComplete={message => {
if (UINotificationService) { if (UINotificationService) {
UINotificationService.show(message); UINotificationService.show(message);
@@ -3,6 +3,9 @@ import cornerstone from 'cornerstone-core';
import csTools from 'cornerstone-tools'; import csTools from 'cornerstone-tools';
import throttle from 'lodash.throttle'; import throttle from 'lodash.throttle';
import LabellingFlow from '../../components/Labelling/LabellingFlow';
import ToolContextMenu from '../../connectedComponents/ToolContextMenu';
const { const {
onAdded, onAdded,
onRemoved, onRemoved,
@@ -29,10 +32,7 @@ export default function init({
commandsManager, commandsManager,
configuration, configuration,
}) { }) {
const { const { UIDialogService } = servicesManager.services;
UIContextMenuService,
UILabellingFlowService,
} = servicesManager.services;
// TODO: MEASUREMENT_COMPLETED (not present in initial implementation) // TODO: MEASUREMENT_COMPLETED (not present in initial implementation)
const onMeasurementsChanged = (action, event) => { const onMeasurementsChanged = (action, event) => {
@@ -46,21 +46,108 @@ export default function init({
'labelmapModified' 'labelmapModified'
); );
const onRightClick = event => { const _getDefaultPosition = event => ({
if (UIContextMenuService) { x: (event && event.currentPoints.client.x) || 0,
UIContextMenuService.show({ event: event.detail }); y: (event && event.currentPoints.client.y) || 0,
});
const _updateLabellingHandler = (labellingData, measurementData) => {
const { location, description, response } = labellingData;
if (location) {
measurementData.location = location;
} }
measurementData.description = description || '';
if (response) {
measurementData.response = response;
}
commandsManager.runCommand(
'updateTableWithNewMeasurementData',
measurementData
);
};
const showLabellingDialog = (props, contentProps, measurementData) => {
if (!UIDialogService) {
console.warn('Unable to show dialog; no UI Dialog Service available.');
return;
}
UIDialogService.create({
id: 'labelling',
isDraggable: false,
showOverlay: true,
content: LabellingFlow,
contentProps: {
measurementData,
labellingDoneCallback: () =>
UIDialogService.dismiss({ id: 'labelling' }),
updateLabelling: labellingData =>
_updateLabellingHandler(labellingData, measurementData),
...contentProps,
},
...props,
});
};
const onRightClick = event => {
if (!UIDialogService) {
console.warn('Unable to show dialog; no UI Dialog Service available.');
return;
}
UIDialogService.dismiss({ id: 'context-menu' });
UIDialogService.create({
id: 'context-menu',
isDraggable: false,
preservePosition: false,
defaultPosition: _getDefaultPosition(event.detail),
content: ToolContextMenu,
contentProps: {
eventData: event.detail,
onDelete: (nearbyToolData, eventData) => {
const element = eventData.element;
commandsManager.runCommand('removeToolState', {
element,
toolType: nearbyToolData.toolType,
tool: nearbyToolData.tool,
});
},
onClose: () => UIDialogService.dismiss({ id: 'context-menu' }),
onSetLabel: (eventData, measurementData) => {
showLabellingDialog(
{ centralize: true, isDraggable: false },
{ skipAddLabelButton: true, editLocation: true },
measurementData
);
},
onSetDescription: (eventData, measurementData) => {
showLabellingDialog(
{ defaultPosition: _getDefaultPosition(eventData) },
{ editDescriptionOnDialog: true },
measurementData
);
},
},
});
}; };
const onTouchPress = event => { const onTouchPress = event => {
if (UIContextMenuService) { if (!UIDialogService) {
UIContextMenuService.show({ console.warn('Unable to show dialog; no UI Dialog Service available.');
event: event.detail, return;
props: {
isTouchEvent: true,
},
});
} }
UIDialogService.create({
eventData: event.detail,
content: ToolContextMenu,
contentProps: {
isTouchEvent: true,
},
});
}; };
const onTouchStart = () => resetLabelligAndContextMenu(); const onTouchStart = () => resetLabelligAndContextMenu();
@@ -68,10 +155,13 @@ export default function init({
const onMouseClick = () => resetLabelligAndContextMenu(); const onMouseClick = () => resetLabelligAndContextMenu();
const resetLabelligAndContextMenu = () => { const resetLabelligAndContextMenu = () => {
if (UILabellingFlowService && UIContextMenuService) { if (!UIDialogService) {
UILabellingFlowService.hide(); console.warn('Unable to show dialog; no UI Dialog Service available.');
UIContextMenuService.hide(); return;
} }
UIDialogService.dismiss({ id: 'context-menu' });
UIDialogService.dismiss({ id: 'labelling' });
}; };
// TODO: This makes scrolling painfully slow // TODO: This makes scrolling painfully slow
@@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { commandsManager } from './../App.js'; import { commandsManager } from './../App.js';
import './ToolContextMenu.css'; import { ContextMenu } from '@ohif/ui';
const toolTypes = [ const toolTypes = [
'Angle', 'Angle',
@@ -24,11 +24,13 @@ const ToolContextMenu = ({
}) => { }) => {
const defaultDropdownItems = [ const defaultDropdownItems = [
{ {
label: 'Delete measurement',
actionType: 'Delete', actionType: 'Delete',
action: ({ nearbyToolData, eventData }) => action: ({ nearbyToolData, eventData }) =>
onDelete(nearbyToolData, eventData), onDelete(nearbyToolData, eventData),
}, },
{ {
label: 'Relabel',
actionType: 'setLabel', actionType: 'setLabel',
action: ({ nearbyToolData, eventData }) => { action: ({ nearbyToolData, eventData }) => {
const { tool: measurementData } = nearbyToolData; const { tool: measurementData } = nearbyToolData;
@@ -51,7 +53,10 @@ const ToolContextMenu = ({
availableToolTypes: toolTypes, availableToolTypes: toolTypes,
}); });
// Annotate tools for touch events already have a press handle to edit it, has a better UX for deleting it /*
* Annotate tools for touch events already have a press handle to edit it,
* has a better UX for deleting it.
*/
if ( if (
isTouchEvent && isTouchEvent &&
nearbyToolData && nearbyToolData &&
@@ -63,21 +68,10 @@ const ToolContextMenu = ({
let dropdownItems = []; let dropdownItems = [];
if (nearbyToolData) { if (nearbyToolData) {
defaultDropdownItems.forEach(item => { defaultDropdownItems.forEach(item => {
item.params = { item.params = { eventData, nearbyToolData };
eventData,
nearbyToolData,
};
if (item.actionType === 'Delete') {
item.text = 'Delete measurement';
}
if (item.actionType === 'setLabel') {
item.text = 'Relabel';
}
if (item.actionType === 'setDescription') { if (item.actionType === 'setDescription') {
item.text = `${ item.label = `${
nearbyToolData.tool.description ? 'Edit' : 'Add' nearbyToolData.tool.description ? 'Edit' : 'Add'
} Description`; } Description`;
} }
@@ -89,7 +83,7 @@ const ToolContextMenu = ({
return dropdownItems; return dropdownItems;
}; };
const itemOnClickHandler = (action, params, onClose) => { const onClickHandler = ({ action, params }) => {
action(params); action(params);
if (onClose) { if (onClose) {
onClose(); onClose();
@@ -99,23 +93,9 @@ const ToolContextMenu = ({
const dropdownItems = getDropdownItems(eventData, isTouchEvent); const dropdownItems = getDropdownItems(eventData, isTouchEvent);
return ( return (
dropdownItems.length && <div className="ToolContextMenu">
eventData && ( <ContextMenu items={dropdownItems} onClick={onClickHandler} />;
<div className="ToolContextMenu"> </div>
<ul className="bounded">
{dropdownItems.map(({ params, action, text, actionType }) => (
<li key={actionType}>
<button
className="form-action"
onClick={() => itemOnClickHandler(action, params, onClose)}
>
<span key={actionType}>{text}</span>
</button>
</li>
))}
</ul>
</div>
)
); );
}; };
@@ -123,6 +103,9 @@ ToolContextMenu.propTypes = {
isTouchEvent: PropTypes.bool.isRequired, isTouchEvent: PropTypes.bool.isRequired,
eventData: PropTypes.object, eventData: PropTypes.object,
onClose: PropTypes.func, onClose: PropTypes.func,
onSetDescription: PropTypes.func,
onSetLabel: PropTypes.func,
onDelete: PropTypes.func,
}; };
ToolContextMenu.defaultProps = { ToolContextMenu.defaultProps = {
+13
View File
@@ -13296,6 +13296,11 @@ node-dir@^0.1.10:
dependencies: dependencies:
minimatch "^3.0.2" minimatch "^3.0.2"
node-ensure@^0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/node-ensure/-/node-ensure-0.0.0.tgz#ecae764150de99861ec5c810fd5d096b183932a7"
integrity sha1-7K52QVDemYYexcgQ/V0Jaxg5Mqc=
node-fetch-npm@^2.0.2: node-fetch-npm@^2.0.2:
version "2.0.2" version "2.0.2"
resolved "https://registry.yarnpkg.com/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz#7258c9046182dca345b4208eda918daf33697ff7" resolved "https://registry.yarnpkg.com/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz#7258c9046182dca345b4208eda918daf33697ff7"
@@ -14423,6 +14428,14 @@ pbkdf2@^3.0.3:
safe-buffer "^5.0.1" safe-buffer "^5.0.1"
sha.js "^2.4.8" sha.js "^2.4.8"
pdfjs-dist@^2.2.228:
version "2.2.228"
resolved "https://registry.yarnpkg.com/pdfjs-dist/-/pdfjs-dist-2.2.228.tgz#777b068a0a16c96418433303807c183058b47aaa"
integrity sha512-W5LhYPMS2UKX0ELIa4u+CFCMoox5qQNQElt0bAK2mwz1V8jZL0rvLao+0tBujce84PK6PvWG36Nwr7agCCWFGQ==
dependencies:
node-ensure "^0.0.0"
worker-loader "^2.0.0"
pend@~1.2.0: pend@~1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"