Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8bbbf3a889 | ||
|
|
d5bf72851a | ||
|
|
2914c83032 | ||
|
|
31e542dfd9 | ||
|
|
25599a2e2a | ||
|
|
1cf7015511 | ||
|
|
756f70cc54 | ||
|
|
ded2e61913 | ||
|
|
b58aa4575a | ||
|
|
f78abe2afa | ||
|
|
f48157117f | ||
|
|
6f8bd0dff8 | ||
|
|
943af89df2 | ||
|
|
cd2da349e5 |
No files matched your search
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
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:
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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,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",
|
||||||
|
|||||||
@@ -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 });
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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%;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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,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",
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
|||||||
@@ -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;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -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,
|
||||||
};
|
};
|
||||||
@@ -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,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",
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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,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,
|
||||||
|
|||||||
+5
-6
@@ -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';
|
||||||
@@ -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;
|
|
||||||
@@ -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';
|
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in new issue
Block a user