Merge branch 'master' of github.com:OHIF/Viewers into ups-master
This commit is contained in:
commit
994818faeb
@ -66,6 +66,52 @@ window.config = {
|
||||
};
|
||||
```
|
||||
|
||||
The configuration can also be written as a JS Function in case you need to inject dependencies like external services:
|
||||
|
||||
```js
|
||||
window.config = ({ servicesManager } = {}) => {
|
||||
const { UIDialogService } = servicesManager.services;
|
||||
return {
|
||||
cornerstoneExtensionConfig: {
|
||||
tools: {
|
||||
ArrowAnnotate: {
|
||||
configuration: {
|
||||
getTextCallback: (callback, eventDetails) => UIDialogService.create({...
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
routerBasename: '/',
|
||||
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',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
You can also create a new config file and specify its path relative to the build
|
||||
output's root by setting the `APP_CONFIG` environment variable. You can set the
|
||||
value of this environment variable a few different ways:
|
||||
|
||||
- ~[Add a temporary environment variable in your shell](https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables#adding-temporary-environment-variables-in-your-shell)~
|
||||
- Previous `react-scripts` functionality that we need to duplicate with
|
||||
`dotenv-webpack`
|
||||
- ~[Add environment specific variables in `.env` file(s)](https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables#adding-development-environment-variables-in-env)~
|
||||
- Previous `react-scripts` functionality that we need to duplicate with
|
||||
`dotenv-webpack`
|
||||
- Using the `cross-env` package in an npm script:
|
||||
- `"build": "cross-env APP_CONFIG=config/my-config.js react-scripts build"`
|
||||
|
||||
After updating the configuration, `yarn run build` to generate updated build
|
||||
output.
|
||||
|
||||
|
||||
@ -24,12 +24,12 @@ include tags. Here's how it works:
|
||||
</ul>
|
||||
|
||||
<ol start="2">
|
||||
<li>Create a JS Object to hold the OHIF Viewer's configuration. Here are some
|
||||
<li>Create a JS Object or Function to hold the OHIF Viewer's configuration. Here are some
|
||||
example values that would allow the viewer to hit our public PACS:</li>
|
||||
</ol>
|
||||
|
||||
```js
|
||||
// Set before importing `ohif-viewer`
|
||||
// Set before importing `ohif-viewer` (JS Object)
|
||||
window.config = {
|
||||
// default: '/'
|
||||
routerBasename: '/',
|
||||
@ -49,6 +49,9 @@ window.config = {
|
||||
};
|
||||
```
|
||||
|
||||
To learn more about how you can configure the OHIF Viewer, check out our
|
||||
[Configuration Guide](./index.md).
|
||||
|
||||
<ol start="3"><li>
|
||||
Render the viewer in the web page's target <code>div</code>
|
||||
</li></ol>
|
||||
|
||||
@ -12,8 +12,9 @@ export default {
|
||||
*/
|
||||
|
||||
preRegistration({
|
||||
servicesManager,
|
||||
configuration: extensionConfiguration,
|
||||
servicesManager = {},
|
||||
commandsManager = {},
|
||||
configuration = {},
|
||||
}) {},
|
||||
|
||||
/**
|
||||
|
||||
@ -3,6 +3,20 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.0.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.7.2...@ohif/extension-cornerstone@2.0.0) (2019-12-09)
|
||||
|
||||
|
||||
* feat!: Ability to configure cornerstone tools via extension configuration (#1229) ([55a5806](https://github.com/OHIF/Viewers/commit/55a580659ecb74ca6433461d8f9a05c2a2b69533)), closes [#1229](https://github.com/OHIF/Viewers/issues/1229)
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* modifies the exposed react <App /> components props. The contract for providing configuration for the app has changed. Please reference updated documentation for guidance.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.7.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.7.1...@ohif/extension-cornerstone@1.7.2) (2019-12-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
@ -67,6 +67,24 @@ Our Viewport wraps [cornerstonejs/react-cornerstone-viewport][react-viewport]
|
||||
and is connected the redux store. This module is the most prone to change as we
|
||||
hammer out our Viewport interface.
|
||||
|
||||
## Tool Configuration
|
||||
|
||||
Tools can be configured through extension configuration using the tools key:
|
||||
|
||||
```js
|
||||
...
|
||||
cornerstoneExtensionConfig: {
|
||||
tools: {
|
||||
ArrowAnnotate: {
|
||||
configuration: {
|
||||
getTextCallback: (callback, eventDetails) => callback(prompt('Enter your custom annotation')),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
...
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
### Repositories
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-cornerstone",
|
||||
"version": "1.7.2",
|
||||
"version": "2.0.0",
|
||||
"description": "OHIF extension for Cornerstone",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@ -47,6 +47,7 @@
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"classnames": "^2.2.6",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"query-string": "^6.8.3",
|
||||
"react-cornerstone-viewport": "2.x.x"
|
||||
|
||||
@ -248,10 +248,13 @@ class OHIFCornerstoneViewport extends Component {
|
||||
// TODO: Does it make more sense to use Context?
|
||||
if (this.props.children && this.props.children.length) {
|
||||
childrenWithProps = this.props.children.map((child, index) => {
|
||||
return React.cloneElement(child, {
|
||||
viewportIndex: this.props.viewportIndex,
|
||||
key: index,
|
||||
});
|
||||
return (
|
||||
child &&
|
||||
React.cloneElement(child, {
|
||||
viewportIndex: this.props.viewportIndex,
|
||||
key: index,
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -148,18 +148,118 @@ const commandsModule = ({ servicesManager }) => {
|
||||
showDownloadViewportModal: ({ title, viewports }) => {
|
||||
const activeViewportIndex = viewports.activeViewportIndex;
|
||||
const { UIModalService } = servicesManager.services;
|
||||
UIModalService.show({
|
||||
content: CornerstoneViewportDownloadForm,
|
||||
title,
|
||||
contentProps: {
|
||||
activeViewportIndex,
|
||||
onClose: UIModalService.hide,
|
||||
},
|
||||
if (UIModalService) {
|
||||
UIModalService.show({
|
||||
content: CornerstoneViewportDownloadForm,
|
||||
title,
|
||||
contentProps: {
|
||||
activeViewportIndex,
|
||||
onClose: UIModalService.hide,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
updateTableWithNewMeasurementData({
|
||||
toolType,
|
||||
measurementNumber,
|
||||
location,
|
||||
description,
|
||||
}) {
|
||||
// Update all measurements by measurement number
|
||||
const measurementApi = OHIF.measurements.MeasurementApi.Instance;
|
||||
const measurements = measurementApi.tools[toolType].filter(
|
||||
m => m.measurementNumber === measurementNumber
|
||||
);
|
||||
|
||||
measurements.forEach(measurement => {
|
||||
measurement.location = location;
|
||||
measurement.description = description;
|
||||
|
||||
measurementApi.updateMeasurement(measurement.toolType, measurement);
|
||||
});
|
||||
|
||||
measurementApi.syncMeasurementsAndToolData();
|
||||
|
||||
// Update images in all active viewports
|
||||
cornerstone.getEnabledElements().forEach(enabledElement => {
|
||||
cornerstone.updateImage(enabledElement.element);
|
||||
});
|
||||
},
|
||||
getNearbyToolData({ element, canvasCoordinates, availableToolTypes }) {
|
||||
const nearbyTool = {};
|
||||
let pointNearTool = false;
|
||||
|
||||
availableToolTypes.forEach(toolType => {
|
||||
const elementToolData = cornerstoneTools.getToolState(
|
||||
element,
|
||||
toolType
|
||||
);
|
||||
|
||||
if (!elementToolData) {
|
||||
return;
|
||||
}
|
||||
|
||||
elementToolData.data.forEach((toolData, index) => {
|
||||
let elementToolInstance = cornerstoneTools.getToolForElement(
|
||||
element,
|
||||
toolType
|
||||
);
|
||||
|
||||
if (!elementToolInstance) {
|
||||
elementToolInstance = cornerstoneTools.getToolForElement(
|
||||
element,
|
||||
`${toolType}Tool`
|
||||
);
|
||||
}
|
||||
|
||||
if (!elementToolInstance) {
|
||||
console.warn('Tool not found.');
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (
|
||||
elementToolInstance.pointNearTool(
|
||||
element,
|
||||
toolData,
|
||||
canvasCoordinates
|
||||
)
|
||||
) {
|
||||
pointNearTool = true;
|
||||
nearbyTool.tool = toolData;
|
||||
nearbyTool.index = index;
|
||||
nearbyTool.toolType = toolType;
|
||||
}
|
||||
});
|
||||
|
||||
if (pointNearTool) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return pointNearTool ? nearbyTool : undefined;
|
||||
},
|
||||
removeToolState: ({ element, toolType, tool }) => {
|
||||
cornerstoneTools.removeToolState(element, toolType, tool);
|
||||
cornerstone.updateImage(element);
|
||||
},
|
||||
};
|
||||
|
||||
const definitions = {
|
||||
getNearbyToolData: {
|
||||
commandFn: actions.getNearbyToolData,
|
||||
storeContexts: [],
|
||||
options: {},
|
||||
},
|
||||
removeToolState: {
|
||||
commandFn: actions.removeToolState,
|
||||
storeContexts: [],
|
||||
options: {},
|
||||
},
|
||||
updateTableWithNewMeasurementData: {
|
||||
commandFn: actions.updateTableWithNewMeasurementData,
|
||||
storeContexts: [],
|
||||
options: {},
|
||||
},
|
||||
showDownloadViewportModal: {
|
||||
commandFn: actions.showDownloadViewportModal,
|
||||
storeContexts: ['viewports'],
|
||||
|
||||
@ -4,6 +4,7 @@ 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';
|
||||
|
||||
function fallbackMetaDataProvider(type, imageId) {
|
||||
if (!imageId.includes('wado?requestType=WADO')) {
|
||||
@ -26,30 +27,33 @@ cornerstone.metaData.addProvider(fallbackMetaDataProvider, -1);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} configuration
|
||||
* @param {Object} servicesManager
|
||||
* @param {Object} configuration
|
||||
* @param {Object|Array} configuration.csToolsConfig
|
||||
*/
|
||||
export default function init({ servicesManager, configuration = {} }) {
|
||||
const { UIDialogService } = servicesManager.services;
|
||||
export default function init({ servicesManager, configuration }) {
|
||||
const callInputDialog = (data, event, callback) => {
|
||||
let dialogId = UIDialogService.create({
|
||||
content: SimpleDialog.InputDialog,
|
||||
defaultPosition: {
|
||||
x: (event && event.currentPoints.canvas.x) || 0,
|
||||
y: (event && event.currentPoints.canvas.y) || 0,
|
||||
},
|
||||
showOverlay: true,
|
||||
contentProps: {
|
||||
title: 'Enter your annotation',
|
||||
label: 'New label',
|
||||
measurementData: data ? { description: data.text } : {},
|
||||
onClose: () => UIDialogService.dismiss({ id: dialogId }),
|
||||
onSubmit: value => {
|
||||
callback(value);
|
||||
UIDialogService.dismiss({ id: dialogId });
|
||||
const { UIDialogService } = servicesManager.services;
|
||||
|
||||
if (UIDialogService) {
|
||||
let dialogId = UIDialogService.create({
|
||||
centralize: true,
|
||||
isDraggable: false,
|
||||
content: SimpleDialog.InputDialog,
|
||||
useLastPosition: false,
|
||||
showOverlay: true,
|
||||
contentProps: {
|
||||
title: 'Enter your annotation',
|
||||
label: 'New label',
|
||||
measurementData: data ? { description: data.text } : {},
|
||||
onClose: () => UIDialogService.dismiss({ id: dialogId }),
|
||||
onSubmit: value => {
|
||||
callback(value);
|
||||
UIDialogService.dismiss({ id: dialogId });
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const { csToolsConfig } = configuration;
|
||||
@ -73,61 +77,55 @@ export default function init({ servicesManager, configuration = {} }) {
|
||||
initCornerstoneTools(defaultCsToolsConfig);
|
||||
|
||||
// ~~ Toooools 🙌
|
||||
const {
|
||||
PanTool,
|
||||
ZoomTool,
|
||||
WwwcTool,
|
||||
MagnifyTool,
|
||||
StackScrollTool,
|
||||
StackScrollMouseWheelTool,
|
||||
// Touch
|
||||
PanMultiTouchTool,
|
||||
ZoomTouchPinchTool,
|
||||
// Annotations
|
||||
EraserTool,
|
||||
BidirectionalTool,
|
||||
LengthTool,
|
||||
AngleTool,
|
||||
FreehandRoiTool,
|
||||
EllipticalRoiTool,
|
||||
DragProbeTool,
|
||||
RectangleRoiTool,
|
||||
// Segmentation
|
||||
BrushTool,
|
||||
} = csTools;
|
||||
const tools = [
|
||||
PanTool,
|
||||
ZoomTool,
|
||||
WwwcTool,
|
||||
MagnifyTool,
|
||||
StackScrollTool,
|
||||
StackScrollMouseWheelTool,
|
||||
csTools.PanTool,
|
||||
csTools.ZoomTool,
|
||||
csTools.WwwcTool,
|
||||
csTools.MagnifyTool,
|
||||
csTools.StackScrollTool,
|
||||
csTools.StackScrollMouseWheelTool,
|
||||
// Touch
|
||||
PanMultiTouchTool,
|
||||
ZoomTouchPinchTool,
|
||||
csTools.PanMultiTouchTool,
|
||||
csTools.ZoomTouchPinchTool,
|
||||
// Annotations
|
||||
EraserTool,
|
||||
BidirectionalTool,
|
||||
LengthTool,
|
||||
AngleTool,
|
||||
FreehandRoiTool,
|
||||
EllipticalRoiTool,
|
||||
DragProbeTool,
|
||||
RectangleRoiTool,
|
||||
csTools.ArrowAnnotateTool,
|
||||
csTools.EraserTool,
|
||||
csTools.BidirectionalTool,
|
||||
csTools.LengthTool,
|
||||
csTools.AngleTool,
|
||||
csTools.FreehandRoiTool,
|
||||
csTools.EllipticalRoiTool,
|
||||
csTools.DragProbeTool,
|
||||
csTools.RectangleRoiTool,
|
||||
// Segmentation
|
||||
BrushTool,
|
||||
csTools.BrushTool,
|
||||
];
|
||||
|
||||
tools.forEach(tool => csTools.addTool(tool));
|
||||
|
||||
csTools.addTool(csTools.ArrowAnnotateTool, {
|
||||
configuration: {
|
||||
getTextCallback: (callback, eventDetails) =>
|
||||
callInputDialog(null, eventDetails, callback),
|
||||
changeTextCallback: (data, eventDetails, callback) =>
|
||||
callInputDialog(data, eventDetails, callback),
|
||||
/* Add extension tools configuration here. */
|
||||
const extensionToolsConfiguration = {
|
||||
ArrowAnnotate: {
|
||||
configuration: {
|
||||
getTextCallback: (callback, eventDetails) =>
|
||||
callInputDialog(null, eventDetails, callback),
|
||||
changeTextCallback: (data, eventDetails, callback) =>
|
||||
callInputDialog(data, eventDetails, callback),
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const isEmpty = obj => Object.keys(obj).length < 1;
|
||||
if (!isEmpty(configuration.tools) || !isEmpty(extensionToolsConfiguration)) {
|
||||
/* Add tools with its custom props through extension configuration. */
|
||||
tools.forEach(tool => {
|
||||
const toolName = tool.name.replace('Tool', '');
|
||||
const configurationToolProps = configuration.tools[toolName] || {};
|
||||
const extensionToolProps = extensionToolsConfiguration[toolName];
|
||||
let props = merge(extensionToolProps, configurationToolProps);
|
||||
csTools.addTool(tool, props);
|
||||
});
|
||||
} else {
|
||||
tools.forEach(tool => csTools.addTool(tool));
|
||||
}
|
||||
|
||||
csTools.setToolActive('Pan', { mouseButtonMask: 4 });
|
||||
csTools.setToolActive('Zoom', { mouseButtonMask: 2 });
|
||||
|
||||
@ -3,6 +3,20 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.0.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.54.6...@ohif/extension-vtk@1.0.0) (2019-12-09)
|
||||
|
||||
|
||||
* feat!: Ability to configure cornerstone tools via extension configuration (#1229) ([55a5806](https://github.com/OHIF/Viewers/commit/55a580659ecb74ca6433461d8f9a05c2a2b69533)), closes [#1229](https://github.com/OHIF/Viewers/issues/1229)
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* modifies the exposed react <App /> components props. The contract for providing configuration for the app has changed. Please reference updated documentation for guidance.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.54.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.54.5...@ohif/extension-vtk@0.54.6) (2019-12-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-vtk",
|
||||
"version": "0.54.6",
|
||||
"version": "1.0.0",
|
||||
"description": "OHIF extension for VTK.js",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@ -52,8 +52,8 @@
|
||||
"react-vtkjs-viewport": "^0.3.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ohif/core": "^1.13.3",
|
||||
"@ohif/ui": "^0.65.4",
|
||||
"@ohif/core": "^2.0.0",
|
||||
"@ohif/ui": "^1.0.0",
|
||||
"cornerstone-tools": "^4.8.0",
|
||||
"cornerstone-wado-image-loader": "^3.0.0",
|
||||
"dcmjs": "^0.6.1",
|
||||
|
||||
@ -358,10 +358,13 @@ class OHIFVTKViewport extends Component {
|
||||
// TODO: Does it make more sense to use Context?
|
||||
if (this.props.children && this.props.children.length) {
|
||||
childrenWithProps = this.props.children.map((child, index) => {
|
||||
return React.cloneElement(child, {
|
||||
viewportIndex: this.props.viewportIndex,
|
||||
key: index,
|
||||
});
|
||||
return (
|
||||
child &&
|
||||
React.cloneElement(child, {
|
||||
viewportIndex: this.props.viewportIndex,
|
||||
key: index,
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,20 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.0.0](https://github.com/OHIF/Viewers/compare/@ohif/core@1.13.3...@ohif/core@2.0.0) (2019-12-09)
|
||||
|
||||
|
||||
* feat!: Ability to configure cornerstone tools via extension configuration (#1229) ([55a5806](https://github.com/OHIF/Viewers/commit/55a580659ecb74ca6433461d8f9a05c2a2b69533)), closes [#1229](https://github.com/OHIF/Viewers/issues/1229)
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* modifies the exposed react <App /> components props. The contract for providing configuration for the app has changed. Please reference updated documentation for guidance.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.13.3](https://github.com/OHIF/Viewers/compare/@ohif/core@1.13.2...@ohif/core@1.13.3) (2019-12-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/core",
|
||||
"version": "1.13.3",
|
||||
"version": "2.0.0",
|
||||
"description": "Generic business logic for web-based medical imaging applications",
|
||||
"author": "OHIF Core Team",
|
||||
"license": "MIT",
|
||||
|
||||
@ -59,7 +59,7 @@ export class CommandsManager {
|
||||
*
|
||||
* @method
|
||||
* @param {string} contextName - Namespace for commands
|
||||
* @returs {Object} - the matched context
|
||||
* @returns {Object} - the matched context
|
||||
*/
|
||||
getContext(contextName) {
|
||||
const context = this.contexts[contextName];
|
||||
|
||||
@ -26,7 +26,7 @@ export default class ExtensionManager {
|
||||
const hasConfiguration = Array.isArray(extension);
|
||||
|
||||
if (hasConfiguration) {
|
||||
const [ohifExtension, configuration] = extensions;
|
||||
const [ohifExtension, configuration] = extension;
|
||||
this.registerExtension(ohifExtension, configuration);
|
||||
} else {
|
||||
this.registerExtension(extension);
|
||||
|
||||
@ -37,6 +37,24 @@ describe('ExtensionManager.js', () => {
|
||||
// Assert
|
||||
expect(extensionManager.registerExtension.mock.calls.length).toBe(3);
|
||||
});
|
||||
|
||||
it('calls registerExtension() for each extension passing its configuration if tuple', () => {
|
||||
const fakeConfiguration = { testing: true };
|
||||
extensionManager.registerExtension = jest.fn();
|
||||
|
||||
// SUT
|
||||
const fakeExtensions = [
|
||||
{ one: '1' },
|
||||
[{ two: '2' }, fakeConfiguration],
|
||||
{ three: '3 ' },
|
||||
];
|
||||
extensionManager.registerExtensions(fakeExtensions);
|
||||
|
||||
// Assert
|
||||
expect(extensionManager.registerExtension.mock.calls[1]).toContain(
|
||||
fakeConfiguration
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('registerExtension()', () => {
|
||||
|
||||
@ -23,6 +23,8 @@ import {
|
||||
createUINotificationService,
|
||||
createUIModalService,
|
||||
createUIDialogService,
|
||||
createUIContextMenuService,
|
||||
createUILabellingFlowService,
|
||||
} from './services';
|
||||
|
||||
const OHIF = {
|
||||
@ -53,6 +55,8 @@ const OHIF = {
|
||||
createUINotificationService,
|
||||
createUIModalService,
|
||||
createUIDialogService,
|
||||
createUIContextMenuService,
|
||||
createUILabellingFlowService,
|
||||
};
|
||||
|
||||
export {
|
||||
@ -82,6 +86,8 @@ export {
|
||||
createUINotificationService,
|
||||
createUIModalService,
|
||||
createUIDialogService,
|
||||
createUIContextMenuService,
|
||||
createUILabellingFlowService,
|
||||
};
|
||||
|
||||
export { OHIF };
|
||||
|
||||
@ -13,6 +13,8 @@ describe('Top level exports', () => {
|
||||
'createUINotificationService',
|
||||
'createUIModalService',
|
||||
'createUIDialogService',
|
||||
'createUIContextMenuService',
|
||||
'createUILabellingFlowService',
|
||||
//
|
||||
'utils',
|
||||
'studies',
|
||||
|
||||
63
platform/core/src/services/UIContextMenuService/index.js
Normal file
63
platform/core/src/services/UIContextMenuService/index.js
Normal file
@ -0,0 +1,63 @@
|
||||
/**
|
||||
* UI Context Menu
|
||||
*
|
||||
* @typedef {Object} ContextMenuProps
|
||||
* @property {Event} event The event with tool information.
|
||||
*/
|
||||
|
||||
const uiContextMenuServicePublicAPI = {
|
||||
name: 'UIContextMenuService',
|
||||
hide,
|
||||
show,
|
||||
setServiceImplementation,
|
||||
};
|
||||
|
||||
const uiContextMenuServiceImplementation = {
|
||||
_show: () => console.warn('show() NOT IMPLEMENTED'),
|
||||
_hide: () => console.warn('hide() NOT IMPLEMENTED'),
|
||||
};
|
||||
|
||||
function createUIContextMenuService() {
|
||||
return uiContextMenuServicePublicAPI;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a new UI ContextMenu dialog;
|
||||
*
|
||||
* @param {ContextMenuProps} props { event }
|
||||
*/
|
||||
function show({ event }) {
|
||||
return uiContextMenuServiceImplementation._show({
|
||||
event,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide a UI ContextMenu dialog;
|
||||
*
|
||||
*/
|
||||
function hide() {
|
||||
return uiContextMenuServiceImplementation._hide();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param {*} {
|
||||
* show: showImplementation,
|
||||
* hide: hideImplementation,
|
||||
* }
|
||||
*/
|
||||
function setServiceImplementation({
|
||||
show: showImplementation,
|
||||
hide: hideImplementation,
|
||||
}) {
|
||||
if (showImplementation) {
|
||||
uiContextMenuServiceImplementation._show = showImplementation;
|
||||
}
|
||||
if (hideImplementation) {
|
||||
uiContextMenuServiceImplementation._hide = hideImplementation;
|
||||
}
|
||||
}
|
||||
|
||||
export default createUIContextMenuService;
|
||||
@ -17,8 +17,9 @@
|
||||
* @property {Object} contentProps The dialog content props.
|
||||
* @property {boolean} [isDraggable=true] Controls if dialog content is draggable or not.
|
||||
* @property {boolean} [showOverlay=false] Controls dialog overlay.
|
||||
* @property {boolean} [centralize=false] Center the dialog on the screen.
|
||||
* @property {boolean} [preservePosition=true] Use last position instead of default.
|
||||
* @property {ElementPosition} defaultPosition Specifies the `x` and `y` that the dragged item should start at.
|
||||
* @property {ElementPosition} position If this property is present, the item becomes 'controlled' and is not responsive to user input.
|
||||
* @property {Function} onStart Called when dragging starts. If `false` is returned any handler, the action will cancel.
|
||||
* @property {Function} onStop Called when dragging stops.
|
||||
* @property {Function} onDrag Called while dragging.
|
||||
@ -45,7 +46,7 @@ function createUIDialogService() {
|
||||
/**
|
||||
* Show a new UI dialog;
|
||||
*
|
||||
* @param {DialogProps} props { id, content, contentProps, onStart, onDrag, onStop, isDraggable, showOverlay, defaultPosition, position }
|
||||
* @param {DialogProps} props { id, content, contentProps, onStart, onDrag, onStop, centralize, isDraggable, showOverlay, preservePosition, defaultPosition }
|
||||
*/
|
||||
function create({
|
||||
id,
|
||||
@ -54,10 +55,11 @@ function create({
|
||||
onStart,
|
||||
onDrag,
|
||||
onStop,
|
||||
centralize = false,
|
||||
preservePosition = true,
|
||||
isDraggable = true,
|
||||
showOverlay = false,
|
||||
defaultPosition,
|
||||
position,
|
||||
}) {
|
||||
return uiDialogServiceImplementation._create({
|
||||
id,
|
||||
@ -66,10 +68,11 @@ function create({
|
||||
onStart,
|
||||
onDrag,
|
||||
onStop,
|
||||
centralize,
|
||||
preservePosition,
|
||||
isDraggable,
|
||||
showOverlay,
|
||||
defaultPosition,
|
||||
position,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
68
platform/core/src/services/UILabellingFlowService/index.js
Normal file
68
platform/core/src/services/UILabellingFlowService/index.js
Normal file
@ -0,0 +1,68 @@
|
||||
/**
|
||||
* 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 uiLabellingFlowServicePublicAPI = {
|
||||
name: 'UILabellingFlowService',
|
||||
show,
|
||||
hide,
|
||||
setServiceImplementation,
|
||||
};
|
||||
|
||||
const uiLabellingFlowServiceImplementation = {
|
||||
_show: () => console.warn('show() NOT IMPLEMENTED'),
|
||||
_hide: () => console.warn('hide() NOT IMPLEMENTED'),
|
||||
};
|
||||
|
||||
function createUILabellingFlowService() {
|
||||
return uiLabellingFlowServicePublicAPI;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide a UI LabellingFlow dialog;
|
||||
*
|
||||
*/
|
||||
function hide() {
|
||||
return uiLabellingFlowServiceImplementation._hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a new UI LabellingFlow dialog;
|
||||
*
|
||||
* @param {LabellingFlowProps} props { defaultPosition, centralize, props }
|
||||
*/
|
||||
function show({ defaultPosition, centralize, props }) {
|
||||
return uiLabellingFlowServiceImplementation._show({
|
||||
defaultPosition,
|
||||
centralize,
|
||||
props,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param {*} {
|
||||
* show: showImplementation,
|
||||
* hide: hideImplementation,
|
||||
* }
|
||||
*/
|
||||
function setServiceImplementation({
|
||||
show: showImplementation,
|
||||
hide: hideImplementation,
|
||||
}) {
|
||||
if (showImplementation) {
|
||||
uiLabellingFlowServiceImplementation._show = showImplementation;
|
||||
}
|
||||
if (hideImplementation) {
|
||||
uiLabellingFlowServiceImplementation._hide = hideImplementation;
|
||||
}
|
||||
}
|
||||
|
||||
export default createUILabellingFlowService;
|
||||
@ -2,10 +2,14 @@ import ServicesManager from './ServicesManager.js';
|
||||
import createUINotificationService from './UINotificationService';
|
||||
import createUIModalService from './UIModalService';
|
||||
import createUIDialogService from './UIDialogService';
|
||||
import createUIContextMenuService from './UIContextMenuService';
|
||||
import createUILabellingFlowService from './UILabellingFlowService';
|
||||
|
||||
export {
|
||||
createUINotificationService,
|
||||
createUIModalService,
|
||||
createUIDialogService,
|
||||
createUIContextMenuService,
|
||||
createUILabellingFlowService,
|
||||
ServicesManager,
|
||||
};
|
||||
|
||||
@ -3,6 +3,20 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.0.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.65.4...@ohif/ui@1.0.0) (2019-12-09)
|
||||
|
||||
|
||||
* feat!: Ability to configure cornerstone tools via extension configuration (#1229) ([55a5806](https://github.com/OHIF/Viewers/commit/55a580659ecb74ca6433461d8f9a05c2a2b69533)), closes [#1229](https://github.com/OHIF/Viewers/issues/1229)
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* modifies the exposed react <App /> components props. The contract for providing configuration for the app has changed. Please reference updated documentation for guidance.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.65.4](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.65.3...@ohif/ui@0.65.4) (2019-12-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/ui
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/ui",
|
||||
"version": "0.65.4",
|
||||
"version": "1.0.0",
|
||||
"description": "A set of React components for Medical Imaging Viewers",
|
||||
"author": "OHIF Contributors",
|
||||
"license": "MIT",
|
||||
|
||||
@ -6,9 +6,7 @@
|
||||
position: relative
|
||||
|
||||
.simpleDialog
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
position: relative;
|
||||
z-index: 1000;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
|
||||
147
platform/ui/src/contextProviders/ContextMenuProvider.js
Normal file
147
platform/ui/src/contextProviders/ContextMenuProvider.js
Normal file
@ -0,0 +1,147 @@
|
||||
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;
|
||||
@ -20,7 +20,30 @@ export const useDialog = () => useContext(DialogContext);
|
||||
const DialogProvider = ({ children, service }) => {
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [dialogs, setDialogs] = useState([]);
|
||||
const [lastDialogId, setLastDialogId] = useState(null);
|
||||
const [lastDialogPosition, setLastDialogPosition] = useState(null);
|
||||
const [centerPositions, setCenterPositions] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
setCenterPositions(
|
||||
dialogs.map(dialog => ({
|
||||
id: dialog.id,
|
||||
...getCenterPosition(dialog.id),
|
||||
}))
|
||||
);
|
||||
}, [dialogs]);
|
||||
|
||||
const getCenterPosition = id => {
|
||||
const root = document.querySelector('#root');
|
||||
const centerX = root.offsetLeft + root.offsetWidth / 2;
|
||||
const centerY = root.offsetTop + root.offsetHeight / 2;
|
||||
const item = document.querySelector(`#draggableItem-${id}`);
|
||||
const itemBounds = item.getBoundingClientRect();
|
||||
return {
|
||||
x: centerX - itemBounds.width / 2,
|
||||
y: centerY - itemBounds.height / 2,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the implementation of a dialog service that can be used by extensions.
|
||||
@ -42,13 +65,16 @@ const DialogProvider = ({ children, service }) => {
|
||||
* @property {Object} contentProps The dialog content props.
|
||||
* @property {boolean} isDraggable Controls if dialog content is draggable or not.
|
||||
* @property {boolean} showOverlay Controls dialog overlay.
|
||||
* @property {boolean} centralize Center the dialog on the screen.
|
||||
* @property {boolean} preservePosition Use last position instead of default.
|
||||
* @property {ElementPosition} defaultPosition Specifies the `x` and `y` that the dragged item should start at.
|
||||
* @property {ElementPosition} position If this property is present, the item becomes 'controlled' and is not responsive to user input.
|
||||
* @property {Function} onStart Called when dragging starts. If `false` is returned any handler, the action will cancel.
|
||||
* @property {Function} onStop Called when dragging stops.
|
||||
* @property {Function} onDrag Called while dragging.
|
||||
*/
|
||||
|
||||
useEffect(() => _bringToFront(lastDialogId), [_bringToFront, lastDialogId]);
|
||||
|
||||
/**
|
||||
* Creates a new dialog and return its id.
|
||||
*
|
||||
@ -64,6 +90,7 @@ const DialogProvider = ({ children, service }) => {
|
||||
}
|
||||
|
||||
setDialogs(dialogs => [...dialogs, { ...props, id: dialogId }]);
|
||||
setLastDialogId(dialogId);
|
||||
|
||||
return dialogId;
|
||||
}, []);
|
||||
@ -75,9 +102,11 @@ const DialogProvider = ({ children, service }) => {
|
||||
* @property {string} props.id The dialog id.
|
||||
* @returns void
|
||||
*/
|
||||
const dismiss = useCallback(({ id }) => {
|
||||
setDialogs(dialogs => dialogs.filter(dialog => dialog.id !== id));
|
||||
}, []);
|
||||
const dismiss = useCallback(
|
||||
({ id }) =>
|
||||
setDialogs(dialogs => dialogs.filter(dialog => dialog.id !== id)),
|
||||
[]
|
||||
);
|
||||
|
||||
/**
|
||||
* Dismisses all dialogs.
|
||||
@ -101,14 +130,14 @@ const DialogProvider = ({ children, service }) => {
|
||||
* @param {string} id The dialog id.
|
||||
* @returns void
|
||||
*/
|
||||
const _bringToFront = id => {
|
||||
const _bringToFront = useCallback(id => {
|
||||
setDialogs(dialogs => {
|
||||
const topDialog = dialogs.find(dialog => dialog.id === id);
|
||||
return topDialog
|
||||
? [...dialogs.filter(dialog => dialog.id !== id), topDialog]
|
||||
: [];
|
||||
: dialogs;
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
|
||||
const renderDialogs = () =>
|
||||
dialogs.map(dialog => {
|
||||
@ -116,20 +145,27 @@ const DialogProvider = ({ children, service }) => {
|
||||
id,
|
||||
content: DialogContent,
|
||||
contentProps,
|
||||
position,
|
||||
defaultPosition,
|
||||
centralize = false,
|
||||
preservePosition = true,
|
||||
isDraggable = true,
|
||||
onStart,
|
||||
onStop,
|
||||
onDrag,
|
||||
} = dialog;
|
||||
|
||||
let position =
|
||||
(preservePosition && lastDialogPosition) || defaultPosition;
|
||||
if (centralize) {
|
||||
position = centerPositions.find(position => position.id === id);
|
||||
}
|
||||
|
||||
return (
|
||||
<Draggable
|
||||
key={id}
|
||||
disabled={!isDraggable}
|
||||
position={position}
|
||||
defaultPosition={lastDialogPosition || defaultPosition}
|
||||
defaultPosition={position}
|
||||
bounds="parent"
|
||||
onStart={event => {
|
||||
const e = event || window.event;
|
||||
@ -169,7 +205,11 @@ const DialogProvider = ({ children, service }) => {
|
||||
>
|
||||
<div
|
||||
id={`draggableItem-${id}`}
|
||||
className={classNames('DraggableItem', isDragging && 'dragging')}
|
||||
className={classNames(
|
||||
'DraggableItem',
|
||||
isDragging && 'dragging',
|
||||
isDraggable && 'draggable'
|
||||
)}
|
||||
style={{ zIndex: '999', position: 'absolute' }}
|
||||
onClick={() => _bringToFront(id)}
|
||||
>
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
.DraggableItem
|
||||
.DraggableItem.draggable
|
||||
div
|
||||
cursor: grab !important
|
||||
|
||||
.DraggableItem.dragging
|
||||
.DraggableItem.draggable.dragging
|
||||
div
|
||||
cursor: grabbing !important
|
||||
|
||||
|
||||
136
platform/ui/src/contextProviders/LabellingFlowProvider.js
Normal file
136
platform/ui/src/contextProviders/LabellingFlowProvider.js
Normal file
@ -0,0 +1,136 @@
|
||||
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,3 +18,15 @@ export {
|
||||
withDialog,
|
||||
useDialog,
|
||||
} from './DialogProvider.js';
|
||||
export {
|
||||
default as ContextMenuProvider,
|
||||
withContextMenu,
|
||||
useContextMenu,
|
||||
ContextMenuConsumer,
|
||||
} from './ContextMenuProvider.js';
|
||||
export {
|
||||
default as LabellingFlowProvider,
|
||||
withLabellingFlow,
|
||||
useLabellingFlow,
|
||||
LabellingFlowConsumer,
|
||||
} from './LabellingFlowProvider.js';
|
||||
|
||||
@ -60,6 +60,14 @@ import {
|
||||
ModalConsumer,
|
||||
useModal,
|
||||
withModal,
|
||||
ContextMenuProvider,
|
||||
ContextMenuConsumer,
|
||||
useContextMenu,
|
||||
withContextMenu,
|
||||
LabellingFlowProvider,
|
||||
LabellingFlowConsumer,
|
||||
useLabellingFlow,
|
||||
withLabellingFlow,
|
||||
} from './contextProviders';
|
||||
|
||||
export {
|
||||
@ -117,6 +125,14 @@ export {
|
||||
DialogProvider,
|
||||
withDialog,
|
||||
useDialog,
|
||||
ContextMenuProvider,
|
||||
ContextMenuConsumer,
|
||||
useContextMenu,
|
||||
withContextMenu,
|
||||
LabellingFlowProvider,
|
||||
LabellingFlowConsumer,
|
||||
useLabellingFlow,
|
||||
withLabellingFlow,
|
||||
// Hooks
|
||||
useDebounce,
|
||||
useMedia,
|
||||
|
||||
@ -3,6 +3,20 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [3.0.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@2.11.8...@ohif/viewer@3.0.0) (2019-12-09)
|
||||
|
||||
|
||||
* feat!: Ability to configure cornerstone tools via extension configuration (#1229) ([55a5806](https://github.com/OHIF/Viewers/commit/55a580659ecb74ca6433461d8f9a05c2a2b69533)), closes [#1229](https://github.com/OHIF/Viewers/issues/1229)
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* modifies the exposed react <App /> components props. The contract for providing configuration for the app has changed. Please reference updated documentation for guidance.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.11.8](https://github.com/OHIF/Viewers/compare/@ohif/viewer@2.11.7...@ohif/viewer@2.11.8) (2019-12-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/viewer",
|
||||
"version": "2.11.8",
|
||||
"version": "3.0.0",
|
||||
"description": "OHIF Viewer",
|
||||
"author": "OHIF Contributors",
|
||||
"license": "MIT",
|
||||
@ -45,14 +45,14 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"@ohif/core": "^1.13.3",
|
||||
"@ohif/core": "^2.0.0",
|
||||
"@ohif/extension-cornerstone": "^2.0.0",
|
||||
"@ohif/extension-dicom-html": "^1.0.2",
|
||||
"@ohif/extension-dicom-microscopy": "^0.50.6",
|
||||
"@ohif/extension-dicom-pdf": "^1.0.0",
|
||||
"@ohif/extension-vtk": "^0.54.6",
|
||||
"@ohif/extension-vtk": "^1.0.0",
|
||||
"@ohif/i18n": "^0.52.2",
|
||||
"@ohif/ui": "^0.65.4",
|
||||
"@ohif/ui": "^1.0.0",
|
||||
"@tanem/react-nprogress": "^1.1.25",
|
||||
"classnames": "^2.2.6",
|
||||
"core-js": "^3.2.1",
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
window.config = {
|
||||
// default: '/'
|
||||
routerBasename: '/',
|
||||
whiteLabelling: {},
|
||||
extensions: [],
|
||||
showStudyList: true,
|
||||
filterQueryParam: false,
|
||||
@ -69,4 +70,5 @@ window.config = {
|
||||
// ~ Cornerstone Tools
|
||||
{ commandName: 'setZoomTool', label: 'Zoom', keys: ['z'] },
|
||||
],
|
||||
cornerstoneExtensionConfig: { tools: {} },
|
||||
};
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
window.config = {
|
||||
routerBasename: '/',
|
||||
whiteLabelling: {},
|
||||
extensions: [],
|
||||
showStudyList: true,
|
||||
servers: {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
window.config = {
|
||||
routerBasename: '/',
|
||||
whiteLabelling: {},
|
||||
showStudyList: true,
|
||||
servers: {
|
||||
dicomWeb: [
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
window.config = {
|
||||
routerBasename: '/',
|
||||
whiteLabelling: {},
|
||||
showStudyList: true,
|
||||
servers: {
|
||||
// This is an array, but we'll only use the first entry for now
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
window.config = {
|
||||
routerBasename: '/',
|
||||
whiteLabelling: {},
|
||||
showStudyList: true,
|
||||
servers: {
|
||||
// This is an array, but we'll only use the first entry for now
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
window.config = {
|
||||
routerBasename: '/',
|
||||
whiteLabelling: {},
|
||||
enableGoogleCloudAdapter: true,
|
||||
servers: {
|
||||
// This is an array, but we'll only use the first entry for now
|
||||
@ -14,7 +15,8 @@ window.config = {
|
||||
client_id: 'YOURCLIENTID.apps.googleusercontent.com',
|
||||
redirect_uri: '/callback', // `OHIFStandaloneViewer.js`
|
||||
response_type: 'id_token token',
|
||||
scope: 'email profile openid https://www.googleapis.com/auth/cloudplatformprojects.readonly https://www.googleapis.com/auth/cloud-healthcare', // email profile openid
|
||||
scope:
|
||||
'email profile openid https://www.googleapis.com/auth/cloudplatformprojects.readonly https://www.googleapis.com/auth/cloud-healthcare', // email profile openid
|
||||
// ~ OPTIONAL
|
||||
post_logout_redirect_uri: '/logout-redirect.html',
|
||||
revoke_uri: 'https://accounts.google.com/o/oauth2/revoke?token=',
|
||||
@ -23,4 +25,4 @@ window.config = {
|
||||
},
|
||||
],
|
||||
studyListFunctionsEnabled: true,
|
||||
}
|
||||
};
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
window.config = {
|
||||
// default: '/'
|
||||
routerBasename: '/',
|
||||
whiteLabelling: {},
|
||||
// default: ''
|
||||
showStudyList: true,
|
||||
servers: {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
window.config = {
|
||||
routerBasename: '/pwa',
|
||||
whiteLabelling: {},
|
||||
showStudyList: true,
|
||||
servers: {
|
||||
dicomWeb: [
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
window.config = {
|
||||
routerBasename: '/',
|
||||
whiteLabelling: {},
|
||||
showStudyList: true,
|
||||
servers: {
|
||||
dicomWeb: [
|
||||
|
||||
@ -1,74 +1,102 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="application-name" content="OHIF Viewer">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<meta name="apple-mobile-web-app-title" content="@ohif/viewer">
|
||||
<meta name="msapplication-TileColor" content="#fff">
|
||||
<meta name="msapplication-TileImage" content="<%= PUBLIC_URL %>assets/mstile-144x144.png">
|
||||
<meta name="msapplication-config" content="<%= PUBLIC_URL %>assets/browserconfig.xml">
|
||||
<link rel="manifest" href="<%= PUBLIC_URL %>manifest.json">
|
||||
|
||||
<link rel="shortcut icon" href="<%= PUBLIC_URL %>assets/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="<%= PUBLIC_URL %>assets/favicon-16x16.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="<%= PUBLIC_URL %>assets/favicon-32x32.png">
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="<%= PUBLIC_URL %>assets/apple-touch-icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="<%= PUBLIC_URL %>assets/apple-touch-icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="<%= PUBLIC_URL %>assets/apple-touch-icon-72x72.png">
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="<%= PUBLIC_URL %>assets/apple-touch-icon-76x76.png">
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="<%= PUBLIC_URL %>assets/apple-touch-icon-114x114.png">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="<%= PUBLIC_URL %>assets/apple-touch-icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="<%= PUBLIC_URL %>assets/apple-touch-icon-144x144.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="<%= PUBLIC_URL %>assets/apple-touch-icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="167x167" href="<%= PUBLIC_URL %>assets/apple-touch-icon-167x167.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="<%= PUBLIC_URL %>assets/apple-touch-icon-180x180.png">
|
||||
<link rel="apple-touch-icon" sizes="1024x1024" href="<%= PUBLIC_URL %>assets/apple-touch-icon-1024x1024.png">
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 480px) and (-webkit-device-pixel-ratio: 1)" href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-320x460.png">
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 480px) and (-webkit-device-pixel-ratio: 2)" href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-640x920.png">
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2)" href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-640x1096.png">
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2)" href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-750x1294.png">
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (orientation: landscape) and (-webkit-device-pixel-ratio: 3)" href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-1182x2208.png">
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (orientation: portrait) and (-webkit-device-pixel-ratio: 3)" href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-1242x2148.png">
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (orientation: landscape) and (-webkit-device-pixel-ratio: 1)" href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-748x1024.png">
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (orientation: portrait) and (-webkit-device-pixel-ratio: 1)" href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-768x1004.png">
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (orientation: landscape) and (-webkit-device-pixel-ratio: 2)" href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-1496x2048.png">
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (orientation: portrait) and (-webkit-device-pixel-ratio: 2)" href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-1536x2008.png">
|
||||
<link rel="icon" type="image/png" sizes="228x228" href="<%= PUBLIC_URL %>assets/coast-228x228.png">
|
||||
<link rel="yandex-tableau-widget" href="<%= PUBLIC_URL %>assets/yandex-browser-manifest.json">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="application-name" content="OHIF Viewer">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<meta name="apple-mobile-web-app-title" content="@ohif/viewer">
|
||||
<meta name="msapplication-TileColor" content="#fff">
|
||||
<meta name="msapplication-TileImage" content="<%= PUBLIC_URL %>assets/mstile-144x144.png">
|
||||
<meta name="msapplication-config" content="<%= PUBLIC_URL %>assets/browserconfig.xml">
|
||||
<link rel="manifest" href="<%= PUBLIC_URL %>manifest.json">
|
||||
|
||||
<!-- Built with: https://polyfill.io/v3/url-builder/ -->
|
||||
<!-- Targets IE11 -->
|
||||
<script src="https://polyfill.io/v3/polyfill.min.js?flags=gated&features=default%2CObject.values%2CArray.prototype.flat%2CObject.entries%2CSymbol%2CArray.prototype.includes%2CString.prototype.repeat%2CArray.prototype.find"></script>
|
||||
<script type="text/javascript">
|
||||
window.PUBLIC_URL = '<%= PUBLIC_URL %>';
|
||||
</script>
|
||||
<script type="text/javascript" src="<%= PUBLIC_URL %>app-config.js"></script>
|
||||
<script type="module" src="<%= PUBLIC_URL %>init-service-worker.js"></script>
|
||||
<link rel="shortcut icon" href="<%= PUBLIC_URL %>assets/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="<%= PUBLIC_URL %>assets/favicon-16x16.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="<%= PUBLIC_URL %>assets/favicon-32x32.png">
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="<%= PUBLIC_URL %>assets/apple-touch-icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="<%= PUBLIC_URL %>assets/apple-touch-icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="<%= PUBLIC_URL %>assets/apple-touch-icon-72x72.png">
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="<%= PUBLIC_URL %>assets/apple-touch-icon-76x76.png">
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="<%= PUBLIC_URL %>assets/apple-touch-icon-114x114.png">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="<%= PUBLIC_URL %>assets/apple-touch-icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="<%= PUBLIC_URL %>assets/apple-touch-icon-144x144.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="<%= PUBLIC_URL %>assets/apple-touch-icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="167x167" href="<%= PUBLIC_URL %>assets/apple-touch-icon-167x167.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="<%= PUBLIC_URL %>assets/apple-touch-icon-180x180.png">
|
||||
<link rel="apple-touch-icon" sizes="1024x1024" href="<%= PUBLIC_URL %>assets/apple-touch-icon-1024x1024.png">
|
||||
<link rel="apple-touch-startup-image"
|
||||
media="(device-width: 320px) and (device-height: 480px) and (-webkit-device-pixel-ratio: 1)"
|
||||
href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-320x460.png">
|
||||
<link rel="apple-touch-startup-image"
|
||||
media="(device-width: 320px) and (device-height: 480px) and (-webkit-device-pixel-ratio: 2)"
|
||||
href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-640x920.png">
|
||||
<link rel="apple-touch-startup-image"
|
||||
media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2)"
|
||||
href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-640x1096.png">
|
||||
<link rel="apple-touch-startup-image"
|
||||
media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2)"
|
||||
href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-750x1294.png">
|
||||
<link rel="apple-touch-startup-image"
|
||||
media="(device-width: 414px) and (device-height: 736px) and (orientation: landscape) and (-webkit-device-pixel-ratio: 3)"
|
||||
href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-1182x2208.png">
|
||||
<link rel="apple-touch-startup-image"
|
||||
media="(device-width: 414px) and (device-height: 736px) and (orientation: portrait) and (-webkit-device-pixel-ratio: 3)"
|
||||
href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-1242x2148.png">
|
||||
<link rel="apple-touch-startup-image"
|
||||
media="(device-width: 768px) and (device-height: 1024px) and (orientation: landscape) and (-webkit-device-pixel-ratio: 1)"
|
||||
href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-748x1024.png">
|
||||
<link rel="apple-touch-startup-image"
|
||||
media="(device-width: 768px) and (device-height: 1024px) and (orientation: portrait) and (-webkit-device-pixel-ratio: 1)"
|
||||
href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-768x1004.png">
|
||||
<link rel="apple-touch-startup-image"
|
||||
media="(device-width: 768px) and (device-height: 1024px) and (orientation: landscape) and (-webkit-device-pixel-ratio: 2)"
|
||||
href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-1496x2048.png">
|
||||
<link rel="apple-touch-startup-image"
|
||||
media="(device-width: 768px) and (device-height: 1024px) and (orientation: portrait) and (-webkit-device-pixel-ratio: 2)"
|
||||
href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-1536x2008.png">
|
||||
<link rel="icon" type="image/png" sizes="228x228" href="<%= PUBLIC_URL %>assets/coast-228x228.png">
|
||||
<link rel="yandex-tableau-widget" href="<%= PUBLIC_URL %>assets/yandex-browser-manifest.json">
|
||||
|
||||
<title>OHIF Viewer</title>
|
||||
<!-- Built with: https://polyfill.io/v3/url-builder/ -->
|
||||
<!-- Targets IE11 -->
|
||||
<script
|
||||
src="https://polyfill.io/v3/polyfill.min.js?flags=gated&features=default%2CObject.values%2CArray.prototype.flat%2CObject.entries%2CSymbol%2CArray.prototype.includes%2CString.prototype.repeat%2CArray.prototype.find"></script>
|
||||
<script type="text/javascript">
|
||||
window.PUBLIC_URL = '<%= PUBLIC_URL %>';
|
||||
</script>
|
||||
<script type="text/javascript" src="<%= PUBLIC_URL %>app-config.js"></script>
|
||||
<script type="module" src="<%= PUBLIC_URL %>init-service-worker.js"></script>
|
||||
|
||||
<!-- WEB FONTS -->
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<title>OHIF Viewer</title>
|
||||
|
||||
<!-- EXTENSIONS -->
|
||||
<!-- <script type="text/javascript" src="path/to/some-extension.js"></script>
|
||||
<!-- WEB FONTS -->
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700&display=swap" rel="stylesheet" />
|
||||
|
||||
<!-- EXTENSIONS -->
|
||||
<!-- <script type="text/javascript" src="path/to/some-extension.js"></script>
|
||||
|
||||
<script>
|
||||
// If configuration file is a JS Object:
|
||||
window.config.extensions = [SomeExtension];
|
||||
|
||||
// If configuration file is a JS Function:
|
||||
window.config = () => {
|
||||
return {
|
||||
extensions: [SomeExtension]
|
||||
};
|
||||
};
|
||||
</script> -->
|
||||
</head>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript> You need to enable JavaScript to run this app. </noscript>
|
||||
<body>
|
||||
<noscript> You need to enable JavaScript to run this app. </noscript>
|
||||
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -4,9 +4,13 @@ import { I18nextProvider } from 'react-i18next';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Provider } from 'react-redux';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import OHIFCornerstoneExtension from '@ohif/extension-cornerstone';
|
||||
import { hot } from 'react-hot-loader/root';
|
||||
|
||||
import OHIFCornerstoneExtension from '@ohif/extension-cornerstone';
|
||||
|
||||
import ToolContextMenu from './connectedComponents/ToolContextMenu';
|
||||
import LabellingManager from './components/Labelling/LabellingManager';
|
||||
|
||||
import {
|
||||
SnackbarProvider,
|
||||
ModalProvider,
|
||||
@ -14,6 +18,11 @@ import {
|
||||
OHIFModal,
|
||||
} from '@ohif/ui';
|
||||
|
||||
import {
|
||||
LabellingFlowProvider,
|
||||
ContextMenuProvider,
|
||||
} from './appCustomProviders';
|
||||
|
||||
import {
|
||||
CommandsManager,
|
||||
ExtensionManager,
|
||||
@ -22,8 +31,10 @@ import {
|
||||
createUINotificationService,
|
||||
createUIModalService,
|
||||
createUIDialogService,
|
||||
createUIContextMenuService,
|
||||
createUILabellingFlowService,
|
||||
utils,
|
||||
redux as reduxOHIF
|
||||
redux as reduxOHIF,
|
||||
} from '@ohif/core';
|
||||
|
||||
import i18n from '@ohif/i18n';
|
||||
@ -46,12 +57,12 @@ import OHIFStandaloneViewer from './OHIFStandaloneViewer';
|
||||
/** Store */
|
||||
import { getActiveContexts } from './store/layout/selectors.js';
|
||||
import store from './store';
|
||||
const { setUserPreferences } = reduxOHIF.actions;
|
||||
|
||||
/** Contexts */
|
||||
import WhiteLabellingContext from './context/WhiteLabellingContext';
|
||||
import UserManagerContext from './context/UserManagerContext';
|
||||
import AppContext from './context/AppContext';
|
||||
const { setUserPreferences } = reduxOHIF.actions;
|
||||
|
||||
/** ~~~~~~~~~~~~~ Application Setup */
|
||||
const commandsManagerConfig = {
|
||||
@ -63,6 +74,8 @@ const commandsManagerConfig = {
|
||||
const UINotificationService = createUINotificationService();
|
||||
const UIModalService = createUIModalService();
|
||||
const UIDialogService = createUIDialogService();
|
||||
const UIContextMenuService = createUIContextMenuService();
|
||||
const UILabellingFlowService = createUILabellingFlowService();
|
||||
|
||||
/** Managers */
|
||||
const commandsManager = new CommandsManager(commandsManagerConfig);
|
||||
@ -79,23 +92,25 @@ window.store = store;
|
||||
|
||||
class App extends Component {
|
||||
static propTypes = {
|
||||
routerBasename: PropTypes.string.isRequired,
|
||||
servers: PropTypes.object.isRequired,
|
||||
//
|
||||
oidc: PropTypes.array,
|
||||
whiteLabelling: PropTypes.object,
|
||||
extensions: PropTypes.arrayOf(
|
||||
config: PropTypes.oneOfType([
|
||||
PropTypes.func,
|
||||
PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
})
|
||||
),
|
||||
hotkeys: PropTypes.array,
|
||||
routerBasename: PropTypes.string.isRequired,
|
||||
oidc: PropTypes.array,
|
||||
whiteLabelling: PropTypes.object,
|
||||
extensions: PropTypes.array,
|
||||
}),
|
||||
]).isRequired,
|
||||
defaultExtensions: PropTypes.array,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
whiteLabelling: {},
|
||||
oidc: [],
|
||||
extensions: [],
|
||||
config: {
|
||||
whiteLabelling: {},
|
||||
oidc: [],
|
||||
extensions: [],
|
||||
},
|
||||
defaultExtensions: [],
|
||||
};
|
||||
|
||||
_appConfig;
|
||||
@ -104,31 +119,59 @@ class App extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this._appConfig = props;
|
||||
const { config, defaultExtensions } = props;
|
||||
|
||||
const { servers, extensions, hotkeys, oidc } = props;
|
||||
const appDefaultConfig = {
|
||||
cornerstoneExtensionConfig: {},
|
||||
extensions: [],
|
||||
routerBasename: '/',
|
||||
whiteLabelling: {},
|
||||
};
|
||||
|
||||
this._appConfig = {
|
||||
...appDefaultConfig,
|
||||
...(typeof config === 'function' ? config({ servicesManager }) : config),
|
||||
};
|
||||
|
||||
const {
|
||||
servers,
|
||||
hotkeys,
|
||||
cornerstoneExtensionConfig,
|
||||
extensions,
|
||||
oidc,
|
||||
} = this._appConfig;
|
||||
|
||||
this.initUserManager(oidc);
|
||||
_initServices([UINotificationService, UIModalService, UIDialogService]);
|
||||
_initExtensions(extensions, hotkeys);
|
||||
_initServices([
|
||||
UINotificationService,
|
||||
UIModalService,
|
||||
UIDialogService,
|
||||
UIContextMenuService,
|
||||
UILabellingFlowService,
|
||||
]);
|
||||
_initExtensions(
|
||||
[...defaultExtensions, ...extensions],
|
||||
cornerstoneExtensionConfig
|
||||
);
|
||||
|
||||
/*
|
||||
* Must run after extension commands are registered
|
||||
* if there is no hotkeys from localStorage set up from config.
|
||||
*/
|
||||
_initHotkeys(hotkeys);
|
||||
_initServers(servers);
|
||||
initWebWorkers();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { whiteLabelling, routerBasename } = this.props;
|
||||
const userManager = this._userManager;
|
||||
const config = {
|
||||
appConfig: this._appConfig,
|
||||
};
|
||||
|
||||
if (userManager) {
|
||||
const { whiteLabelling, routerBasename } = this._appConfig;
|
||||
if (this._userManager) {
|
||||
return (
|
||||
<AppContext.Provider value={config}>
|
||||
<AppContext.Provider value={{ appConfig: this._appConfig }}>
|
||||
<Provider store={store}>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<OidcProvider store={store} userManager={userManager}>
|
||||
<UserManagerContext.Provider value={userManager}>
|
||||
<OidcProvider store={store} userManager={this._userManager}>
|
||||
<UserManagerContext.Provider value={this._userManager}>
|
||||
<Router basename={routerBasename}>
|
||||
<WhiteLabellingContext.Provider value={whiteLabelling}>
|
||||
<SnackbarProvider service={UINotificationService}>
|
||||
@ -137,7 +180,21 @@ class App extends Component {
|
||||
modal={OHIFModal}
|
||||
service={UIModalService}
|
||||
>
|
||||
<OHIFStandaloneViewer userManager={userManager} />
|
||||
<LabellingFlowProvider
|
||||
service={UILabellingFlowService}
|
||||
labellingComponent={LabellingManager}
|
||||
commandsManager={commandsManager}
|
||||
>
|
||||
<ContextMenuProvider
|
||||
service={UIContextMenuService}
|
||||
contextMenuComponent={ToolContextMenu}
|
||||
commandsManager={commandsManager}
|
||||
>
|
||||
<OHIFStandaloneViewer
|
||||
userManager={this._userManager}
|
||||
/>
|
||||
</ContextMenuProvider>
|
||||
</LabellingFlowProvider>
|
||||
</ModalProvider>
|
||||
</DialogProvider>
|
||||
</SnackbarProvider>
|
||||
@ -152,7 +209,7 @@ class App extends Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<AppContext.Provider value={config}>
|
||||
<AppContext.Provider value={{ appConfig: this._appConfig }}>
|
||||
<Provider store={store}>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<Router basename={routerBasename}>
|
||||
@ -160,7 +217,19 @@ class App extends Component {
|
||||
<SnackbarProvider service={UINotificationService}>
|
||||
<DialogProvider service={UIDialogService}>
|
||||
<ModalProvider modal={OHIFModal} service={UIModalService}>
|
||||
<OHIFStandaloneViewer />
|
||||
<LabellingFlowProvider
|
||||
service={UILabellingFlowService}
|
||||
labellingComponent={LabellingManager}
|
||||
commandsManager={commandsManager}
|
||||
>
|
||||
<ContextMenuProvider
|
||||
service={UIContextMenuService}
|
||||
contextMenuComponent={ToolContextMenu}
|
||||
commandsManager={commandsManager}
|
||||
>
|
||||
<OHIFStandaloneViewer />
|
||||
</ContextMenuProvider>
|
||||
</LabellingFlowProvider>
|
||||
</ModalProvider>
|
||||
</DialogProvider>
|
||||
</SnackbarProvider>
|
||||
@ -174,10 +243,10 @@ class App extends Component {
|
||||
|
||||
initUserManager(oidc) {
|
||||
if (oidc && !!oidc.length) {
|
||||
const firstOpenIdClient = this.props.oidc[0];
|
||||
const firstOpenIdClient = this._appConfig.oidc[0];
|
||||
|
||||
const { protocol, host } = window.location;
|
||||
const { routerBasename } = this.props;
|
||||
const { routerBasename } = this._appConfig;
|
||||
const baseUri = `${protocol}//${host}${routerBasename}`;
|
||||
|
||||
const redirect_uri = firstOpenIdClient.redirect_uri || '/callback';
|
||||
@ -213,22 +282,22 @@ function _initServices(services) {
|
||||
/**
|
||||
* @param
|
||||
*/
|
||||
function _initExtensions(extensions, hotkeys) {
|
||||
const defaultExtensions = [
|
||||
function _initExtensions(extensions, cornerstoneExtensionConfig) {
|
||||
const requiredExtensions = [
|
||||
GenericViewerCommands,
|
||||
OHIFCornerstoneExtension,
|
||||
// WARNING: MUST BE REGISTERED _AFTER_ OHIFCORNERSTONEEXTENSION
|
||||
[OHIFCornerstoneExtension, cornerstoneExtensionConfig],
|
||||
/* WARNING: MUST BE REGISTERED _AFTER_ OHIFCornerstoneExtension */
|
||||
MeasurementsPanel,
|
||||
];
|
||||
const mergedExtensions = defaultExtensions.concat(extensions);
|
||||
const mergedExtensions = requiredExtensions.concat(extensions);
|
||||
extensionManager.registerExtensions(mergedExtensions);
|
||||
}
|
||||
|
||||
function _initHotkeys(hotkeys) {
|
||||
const { hotkeyDefinitions = {} } = store.getState().preferences || {};
|
||||
let updateStore = false;
|
||||
let hotkeysToUse = hotkeyDefinitions;
|
||||
|
||||
// Must run after extension commands are registered
|
||||
// if there is no hotkeys from localStorate set up from config
|
||||
if (!Object.keys(hotkeyDefinitions).length) {
|
||||
hotkeysToUse = hotkeys;
|
||||
updateStore = true;
|
||||
@ -236,7 +305,8 @@ function _initExtensions(extensions, hotkeys) {
|
||||
|
||||
if (hotkeysToUse) {
|
||||
hotkeysManager.setHotkeys(hotkeysToUse);
|
||||
// set default based on app config
|
||||
|
||||
/* Set hotkeys default based on app config. */
|
||||
hotkeysManager.setDefaultHotKeys(hotkeys);
|
||||
|
||||
if (updateStore) {
|
||||
@ -264,7 +334,9 @@ function _makeAbsoluteIfNecessary(url, base_url) {
|
||||
return url;
|
||||
}
|
||||
|
||||
// Make sure base_url and url are not duplicating slashes
|
||||
/*
|
||||
* Make sure base_url and url are not duplicating slashes.
|
||||
*/
|
||||
if (base_url[base_url.length - 1] === '/') {
|
||||
base_url = base_url.slice(0, base_url.length - 1);
|
||||
}
|
||||
@ -272,7 +344,9 @@ function _makeAbsoluteIfNecessary(url, base_url) {
|
||||
return base_url + url;
|
||||
}
|
||||
|
||||
// Only wrap/use hot if in dev
|
||||
/*
|
||||
* Only wrap/use hot if in dev.
|
||||
*/
|
||||
const ExportedApp = process.env.NODE_ENV === 'development' ? hot(App) : App;
|
||||
|
||||
export default ExportedApp;
|
||||
|
||||
@ -0,0 +1,51 @@
|
||||
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;
|
||||
@ -0,0 +1,49 @@
|
||||
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;
|
||||
6
platform/viewer/src/appCustomProviders/index.js
Normal file
6
platform/viewer/src/appCustomProviders/index.js
Normal file
@ -0,0 +1,6 @@
|
||||
export {
|
||||
default as LabellingFlowProvider,
|
||||
} from './LabellingFlowProvider/LabellingFlowProvider.js';
|
||||
export {
|
||||
default as ContextMenuProvider,
|
||||
} from './ContextMenuProvider/ContextMenuProvider.js';
|
||||
@ -4,9 +4,7 @@ import OHIF from '@ohif/core';
|
||||
import moment from 'moment';
|
||||
import cornerstone from 'cornerstone-core';
|
||||
|
||||
//
|
||||
import jumpToRowItem from './jumpToRowItem.js';
|
||||
import getMeasurementLocationCallback from './getMeasurementLocationCallback';
|
||||
|
||||
const { setViewportSpecificData } = OHIF.redux.actions;
|
||||
const { MeasurementApi } = OHIF.measurements;
|
||||
@ -155,9 +153,11 @@ const mapStateToProps = state => {
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
const mapDispatchToProps = (dispatch, ownProps) => {
|
||||
return {
|
||||
dispatchRelabel: (event, measurementData, viewportsState) => {
|
||||
event.persist();
|
||||
|
||||
const activeViewportIndex =
|
||||
(viewportsState && viewportsState.activeViewportIndex) || 0;
|
||||
|
||||
@ -167,31 +167,21 @@ const mapDispatchToProps = dispatch => {
|
||||
return;
|
||||
}
|
||||
|
||||
const { element } = enabledElements[activeViewportIndex];
|
||||
|
||||
const eventData = {
|
||||
event: {
|
||||
clientX: event.clientX,
|
||||
clientY: event.clientY,
|
||||
},
|
||||
element,
|
||||
};
|
||||
|
||||
const { toolType, measurementId } = measurementData;
|
||||
const tool = MeasurementApi.Instance.tools[toolType].find(measurement => {
|
||||
return measurement._id === measurementId;
|
||||
});
|
||||
|
||||
const options = {
|
||||
skipAddLabelButton: true,
|
||||
editLocation: true,
|
||||
};
|
||||
|
||||
// Clone the tool not to set empty location initially
|
||||
const toolForLocation = Object.assign({}, tool, { location: null });
|
||||
getMeasurementLocationCallback(eventData, toolForLocation, options);
|
||||
|
||||
if (ownProps.onRelabel) {
|
||||
ownProps.onRelabel(toolForLocation);
|
||||
}
|
||||
},
|
||||
dispatchEditDescription: (event, measurementData, viewportsState) => {
|
||||
event.persist();
|
||||
|
||||
const activeViewportIndex =
|
||||
(viewportsState && viewportsState.activeViewportIndex) || 0;
|
||||
|
||||
@ -201,26 +191,14 @@ const mapDispatchToProps = dispatch => {
|
||||
return;
|
||||
}
|
||||
|
||||
const { element } = enabledElements[activeViewportIndex];
|
||||
|
||||
const eventData = {
|
||||
event: {
|
||||
clientX: event.clientX,
|
||||
clientY: event.clientY,
|
||||
},
|
||||
element,
|
||||
};
|
||||
|
||||
const { toolType, measurementId } = measurementData;
|
||||
const tool = MeasurementApi.Instance.tools[toolType].find(measurement => {
|
||||
return measurement._id === measurementId;
|
||||
});
|
||||
|
||||
const options = {
|
||||
editDescriptionOnDialog: true,
|
||||
};
|
||||
|
||||
getMeasurementLocationCallback(eventData, tool, options);
|
||||
if (ownProps.onEditDescription) {
|
||||
ownProps.onEditDescription(tool);
|
||||
}
|
||||
},
|
||||
dispatchJumpToRowItem: (
|
||||
measurementData,
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
const setLabellingFlowDataAction = labellingFlowData => ({
|
||||
type: 'SET_LABELLING_FLOW_DATA',
|
||||
labellingFlowData,
|
||||
});
|
||||
|
||||
const resetLabellingAndContextMenuAction = state => ({
|
||||
type: 'RESET_LABELLING_AND_CONTEXT_MENU',
|
||||
state,
|
||||
});
|
||||
|
||||
const setToolContextMenuDataAction = (viewportIndex, toolContextMenuData) => ({
|
||||
type: 'SET_TOOL_CONTEXT_MENU_DATA',
|
||||
viewportIndex,
|
||||
toolContextMenuData,
|
||||
});
|
||||
|
||||
export {
|
||||
resetLabellingAndContextMenuAction,
|
||||
setLabellingFlowDataAction,
|
||||
setToolContextMenuDataAction,
|
||||
};
|
||||
@ -1,33 +0,0 @@
|
||||
import cornerstoneTools from 'cornerstone-tools';
|
||||
import updateTableWithNewMeasurementData from './updateTableWithNewMeasurementData.js';
|
||||
|
||||
export default function getMeasurementLocationCallback(
|
||||
eventData,
|
||||
tool,
|
||||
options
|
||||
) {
|
||||
const { toolType } = tool;
|
||||
const { element } = eventData;
|
||||
const doneCallback = updateTableWithNewMeasurementData;
|
||||
|
||||
const ToolInstance = cornerstoneTools.getToolForElement(element, toolType);
|
||||
|
||||
if (
|
||||
!ToolInstance ||
|
||||
!ToolInstance.configuration ||
|
||||
!ToolInstance.configuration.getMeasurementLocationCallback
|
||||
) {
|
||||
console.warn(
|
||||
'Tool instance configuration is missing: getMeasurementLocationCallback'
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ToolInstance.configuration.getMeasurementLocationCallback(
|
||||
tool,
|
||||
eventData,
|
||||
doneCallback,
|
||||
options
|
||||
);
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
import React from 'react';
|
||||
import ConnectedMeasurementTable from './ConnectedMeasurementTable.js';
|
||||
import init from './init.js';
|
||||
|
||||
@ -7,10 +8,38 @@ export default {
|
||||
*/
|
||||
id: 'measurements-table',
|
||||
|
||||
preRegistration({ servicesManager, configuration = {} }) {
|
||||
init({ servicesManager, configuration });
|
||||
preRegistration({ servicesManager, commandsManager, configuration = {} }) {
|
||||
init({ servicesManager, commandsManager, configuration });
|
||||
},
|
||||
getPanelModule({ servicesManager }) {
|
||||
getPanelModule({ servicesManager, commandsManager }) {
|
||||
const { UILabellingFlowService } = servicesManager.services;
|
||||
const ExtendedConnectedMeasurementTable = () => (
|
||||
<ConnectedMeasurementTable
|
||||
onRelabel={tool => {
|
||||
if (UILabellingFlowService) {
|
||||
UILabellingFlowService.show({
|
||||
centralize: true,
|
||||
props: {
|
||||
skipAddLabelButton: true,
|
||||
editLocation: true,
|
||||
measurementData: tool,
|
||||
},
|
||||
});
|
||||
}
|
||||
}}
|
||||
onEditDescription={tool => {
|
||||
if (UILabellingFlowService) {
|
||||
UILabellingFlowService.show({
|
||||
centralize: true,
|
||||
props: {
|
||||
editDescriptionOnDialog: true,
|
||||
measurementData: tool,
|
||||
},
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
return {
|
||||
menuOptions: [
|
||||
{
|
||||
@ -22,7 +51,7 @@ export default {
|
||||
components: [
|
||||
{
|
||||
id: 'measurement-panel',
|
||||
component: ConnectedMeasurementTable,
|
||||
component: ExtendedConnectedMeasurementTable,
|
||||
},
|
||||
],
|
||||
defaultContext: ['VIEWER'],
|
||||
|
||||
@ -1,19 +1,7 @@
|
||||
import OHIF from '@ohif/core';
|
||||
import cornerstone from 'cornerstone-core';
|
||||
import csTools from 'cornerstone-tools';
|
||||
import {
|
||||
getToolLabellingFlowCallback,
|
||||
getOnRightClickCallback,
|
||||
getOnTouchPressCallback,
|
||||
getResetLabellingAndContextMenu,
|
||||
} from './labelingFlowCallbacks.js';
|
||||
import throttle from 'lodash.throttle';
|
||||
import { SimpleDialog } from '@ohif/ui';
|
||||
|
||||
// TODO: This only works because we have a hard dependency on this extension
|
||||
// We need to decouple and make stuff like this possible w/o bundling this at
|
||||
// build time
|
||||
import store from './../../store';
|
||||
|
||||
const {
|
||||
onAdded,
|
||||
@ -33,92 +21,18 @@ const MEASUREMENT_ACTION_MAP = {
|
||||
*
|
||||
*
|
||||
* @export
|
||||
* @param {*} configuration
|
||||
* @param {Object} servicesManager
|
||||
* @param {Object} configuration
|
||||
*/
|
||||
export default function init({ servicesManager, configuration = {} }) {
|
||||
const { UIDialogService } = servicesManager.services;
|
||||
const callInputDialog = (data, event, callback) => {
|
||||
let dialogId = UIDialogService.create({
|
||||
content: SimpleDialog.InputDialog,
|
||||
defaultPosition: {
|
||||
x: (event && event.currentPoints.canvas.x) || 0,
|
||||
y: (event && event.currentPoints.canvas.y) || 0,
|
||||
},
|
||||
showOverlay: true,
|
||||
contentProps: {
|
||||
title: 'Enter your annotation',
|
||||
label: 'New label',
|
||||
defaultValue: data ? data.text : '',
|
||||
onClose: () => UIDialogService.dismiss({ id: dialogId }),
|
||||
onSubmit: value => {
|
||||
callback(value);
|
||||
UIDialogService.dismiss({ id: dialogId });
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// If these tools were already added by a different extension, we want to replace
|
||||
// them with the same tools that have an alternative configuration. By passing in
|
||||
// our custom `getMeasurementLocationCallback`, we can...
|
||||
const toolLabellingFlowCallback = getToolLabellingFlowCallback(store);
|
||||
|
||||
// Removes all tools from all enabled elements w/ provided name
|
||||
// Not commonly used API, so :eyes: for unknown side-effects
|
||||
csTools.removeTool('Bidirectional');
|
||||
csTools.removeTool('Length');
|
||||
csTools.removeTool('Angle');
|
||||
csTools.removeTool('FreehandRoi');
|
||||
csTools.removeTool('EllipticalRoi');
|
||||
csTools.removeTool('CircleRoi');
|
||||
csTools.removeTool('RectangleRoi');
|
||||
csTools.removeTool('ArrowAnnotate');
|
||||
|
||||
// Re-add each tool w/ our custom configuration
|
||||
csTools.addTool(csTools.BidirectionalTool, {
|
||||
configuration: {
|
||||
getMeasurementLocationCallback: toolLabellingFlowCallback,
|
||||
},
|
||||
});
|
||||
csTools.addTool(csTools.LengthTool, {
|
||||
configuration: {
|
||||
getMeasurementLocationCallback: toolLabellingFlowCallback,
|
||||
},
|
||||
});
|
||||
csTools.addTool(csTools.AngleTool, {
|
||||
configuration: {
|
||||
getMeasurementLocationCallback: toolLabellingFlowCallback,
|
||||
},
|
||||
});
|
||||
csTools.addTool(csTools.FreehandRoiTool, {
|
||||
configuration: {
|
||||
getMeasurementLocationCallback: toolLabellingFlowCallback,
|
||||
},
|
||||
});
|
||||
csTools.addTool(csTools.EllipticalRoiTool, {
|
||||
configuration: {
|
||||
getMeasurementLocationCallback: toolLabellingFlowCallback,
|
||||
},
|
||||
});
|
||||
csTools.addTool(csTools.CircleRoiTool, {
|
||||
configuration: {
|
||||
getMeasurementLocationCallback: toolLabellingFlowCallback,
|
||||
},
|
||||
});
|
||||
csTools.addTool(csTools.RectangleRoiTool, {
|
||||
configuration: {
|
||||
getMeasurementLocationCallback: toolLabellingFlowCallback,
|
||||
},
|
||||
});
|
||||
csTools.addTool(csTools.ArrowAnnotateTool, {
|
||||
configuration: {
|
||||
getMeasurementLocationCallback: toolLabellingFlowCallback,
|
||||
getTextCallback: (callback, eventDetails) =>
|
||||
callInputDialog(null, eventDetails, callback),
|
||||
changeTextCallback: (data, eventDetails, callback) =>
|
||||
callInputDialog(data, eventDetails, callback),
|
||||
},
|
||||
});
|
||||
export default function init({
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
configuration,
|
||||
}) {
|
||||
const {
|
||||
UIContextMenuService,
|
||||
UILabellingFlowService,
|
||||
} = servicesManager.services;
|
||||
|
||||
// TODO: MEASUREMENT_COMPLETED (not present in initial implementation)
|
||||
const onMeasurementsChanged = (action, event) => {
|
||||
@ -131,15 +45,43 @@ export default function init({ servicesManager, configuration = {} }) {
|
||||
this,
|
||||
'labelmapModified'
|
||||
);
|
||||
//
|
||||
const onRightClick = getOnRightClickCallback(store);
|
||||
const onTouchPress = getOnTouchPressCallback(store);
|
||||
const onNewImage = getResetLabellingAndContextMenu(store);
|
||||
const onMouseClick = getResetLabellingAndContextMenu(store);
|
||||
const onTouchStart = getResetLabellingAndContextMenu(store);
|
||||
|
||||
// Because click gives us the native "mouse up", buttons will always be `0`
|
||||
// Need to fallback to event.which;
|
||||
const onRightClick = event => {
|
||||
if (UIContextMenuService) {
|
||||
UIContextMenuService.show({ event: event.detail });
|
||||
}
|
||||
};
|
||||
|
||||
const onTouchPress = event => {
|
||||
if (UIContextMenuService) {
|
||||
UIContextMenuService.show({
|
||||
event: event.detail,
|
||||
props: {
|
||||
isTouchEvent: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onTouchStart = () => resetLabelligAndContextMenu();
|
||||
|
||||
const onMouseClick = () => resetLabelligAndContextMenu();
|
||||
|
||||
const resetLabelligAndContextMenu = () => {
|
||||
if (UILabellingFlowService && UIContextMenuService) {
|
||||
UILabellingFlowService.hide();
|
||||
UIContextMenuService.hide();
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: This makes scrolling painfully slow
|
||||
// const onNewImage = ...
|
||||
|
||||
/*
|
||||
* Because click gives us the native "mouse up", buttons will always be `0`
|
||||
* Need to fallback to event.which;
|
||||
*
|
||||
*/
|
||||
const handleClick = cornerstoneMouseClickEvent => {
|
||||
const mouseUpEvent = cornerstoneMouseClickEvent.detail.event;
|
||||
const isRightClick = mouseUpEvent.which === 3;
|
||||
@ -170,10 +112,11 @@ export default function init({ servicesManager, configuration = {} }) {
|
||||
csTools.EVENTS.LABELMAP_MODIFIED,
|
||||
onLabelmapModified
|
||||
);
|
||||
//
|
||||
|
||||
element.addEventListener(csTools.EVENTS.TOUCH_PRESS, onTouchPress);
|
||||
element.addEventListener(csTools.EVENTS.MOUSE_CLICK, handleClick);
|
||||
element.addEventListener(csTools.EVENTS.TOUCH_START, onTouchStart);
|
||||
|
||||
// TODO: This makes scrolling painfully slow
|
||||
// element.addEventListener(cornerstone.EVENTS.NEW_IMAGE, onNewImage);
|
||||
}
|
||||
@ -197,10 +140,12 @@ export default function init({ servicesManager, configuration = {} }) {
|
||||
csTools.EVENTS.LABELMAP_MODIFIED,
|
||||
onLabelmapModified
|
||||
);
|
||||
//
|
||||
|
||||
element.removeEventListener(csTools.EVENTS.TOUCH_PRESS, onTouchPress);
|
||||
element.removeEventListener(csTools.EVENTS.MOUSE_CLICK, handleClick);
|
||||
element.removeEventListener(csTools.EVENTS.TOUCH_START, onTouchStart);
|
||||
|
||||
// TODO: This makes scrolling painfully slow
|
||||
// element.removeEventListener(cornerstone.EVENTS.NEW_IMAGE, onNewImage);
|
||||
}
|
||||
|
||||
|
||||
@ -1,144 +0,0 @@
|
||||
import {
|
||||
resetLabellingAndContextMenuAction,
|
||||
setToolContextMenuDataAction,
|
||||
setLabellingFlowDataAction,
|
||||
} from './actions.js';
|
||||
import updateTableWithNewMeasurementData from './updateTableWithNewMeasurementData.js';
|
||||
|
||||
const VIEWPORT_INDEX = 0;
|
||||
|
||||
function getOnRightClickCallback(store) {
|
||||
const setToolContextMenuData = (viewportIndex, toolContextMenuData) => {
|
||||
store.dispatch(resetLabellingAndContextMenuAction());
|
||||
store.dispatch(
|
||||
setToolContextMenuDataAction(viewportIndex, toolContextMenuData)
|
||||
);
|
||||
};
|
||||
|
||||
const getOnCloseCallback = viewportIndex => {
|
||||
return function onClose() {
|
||||
const toolContextMenuData = {
|
||||
visible: false,
|
||||
};
|
||||
|
||||
store.dispatch(
|
||||
setToolContextMenuDataAction(viewportIndex, toolContextMenuData)
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
return function onRightClick(event) {
|
||||
const eventData = event.detail;
|
||||
const viewportIndex = VIEWPORT_INDEX; // parseInt(eventData.element.dataset.viewportIndex, 10);
|
||||
|
||||
const toolContextMenuData = {
|
||||
eventData,
|
||||
isTouchEvent: false,
|
||||
onClose: getOnCloseCallback(viewportIndex),
|
||||
};
|
||||
|
||||
// setToolContextMenuData(viewportIndex, toolContextMenuData);
|
||||
setToolContextMenuData(0, toolContextMenuData);
|
||||
};
|
||||
}
|
||||
|
||||
function getOnTouchPressCallback(store) {
|
||||
const setToolContextMenuData = (viewportIndex, toolContextMenuData) => {
|
||||
store.dispatch(resetLabellingAndContextMenuAction());
|
||||
store.dispatch(
|
||||
setToolContextMenuDataAction(viewportIndex, toolContextMenuData)
|
||||
);
|
||||
};
|
||||
|
||||
const getOnCloseCallback = viewportIndex => {
|
||||
return function onClose() {
|
||||
const toolContextMenuData = {
|
||||
visible: false,
|
||||
};
|
||||
|
||||
store.dispatch(
|
||||
setToolContextMenuDataAction(viewportIndex, toolContextMenuData)
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
return function onTouchPress(event) {
|
||||
const eventData = event.detail;
|
||||
const viewportIndex = parseInt(eventData.element.dataset.viewportIndex, 10);
|
||||
|
||||
const toolContextMenuData = {
|
||||
eventData,
|
||||
isTouchEvent: true,
|
||||
onClose: getOnCloseCallback(viewportIndex),
|
||||
};
|
||||
|
||||
setToolContextMenuData(viewportIndex, toolContextMenuData);
|
||||
};
|
||||
}
|
||||
|
||||
function getResetLabellingAndContextMenu(store) {
|
||||
return function resetLabellingAndContextMenu() {
|
||||
store.dispatch(resetLabellingAndContextMenuAction());
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param {*} store
|
||||
* @returns
|
||||
*/
|
||||
function getToolLabellingFlowCallback(store) {
|
||||
const setLabellingFlowData = labellingFlowData => {
|
||||
store.dispatch(setLabellingFlowDataAction(labellingFlowData));
|
||||
};
|
||||
|
||||
return function toolLabellingFlowCallback(
|
||||
measurementData,
|
||||
eventData,
|
||||
doneCallback,
|
||||
options = {}
|
||||
) {
|
||||
const updateLabelling = ({ location, response, description }) => {
|
||||
// Update the measurement data with the labelling parameters
|
||||
|
||||
if (location) {
|
||||
measurementData.location = location;
|
||||
}
|
||||
|
||||
measurementData.description = description || '';
|
||||
|
||||
if (response) {
|
||||
measurementData.response = response;
|
||||
}
|
||||
|
||||
updateTableWithNewMeasurementData(measurementData);
|
||||
};
|
||||
|
||||
const labellingDoneCallback = () => {
|
||||
setLabellingFlowData({ visible: false });
|
||||
};
|
||||
|
||||
const labellingFlowData = {
|
||||
visible: true,
|
||||
eventData,
|
||||
measurementData,
|
||||
skipAddLabelButton: options.skipAddLabelButton,
|
||||
editLocation: options.editLocation,
|
||||
editDescription: options.editDescription,
|
||||
editResponse: options.editResponse,
|
||||
editDescriptionOnDialog: options.editDescriptionOnDialog,
|
||||
labellingDoneCallback,
|
||||
updateLabelling,
|
||||
};
|
||||
|
||||
setLabellingFlowData(labellingFlowData);
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
getToolLabellingFlowCallback,
|
||||
getOnRightClickCallback,
|
||||
getOnTouchPressCallback,
|
||||
getResetLabellingAndContextMenu,
|
||||
};
|
||||
@ -1,29 +0,0 @@
|
||||
import OHIF from '@ohif/core';
|
||||
import cornerstone from 'cornerstone-core';
|
||||
|
||||
export default function updateTableWithNewMeasurementData({
|
||||
toolType,
|
||||
measurementNumber,
|
||||
location,
|
||||
description,
|
||||
}) {
|
||||
// Update all measurements by measurement number
|
||||
const measurementApi = OHIF.measurements.MeasurementApi.Instance;
|
||||
const measurements = measurementApi.tools[toolType].filter(
|
||||
m => m.measurementNumber === measurementNumber
|
||||
);
|
||||
|
||||
measurements.forEach(measurement => {
|
||||
measurement.location = location;
|
||||
measurement.description = description;
|
||||
|
||||
measurementApi.updateMeasurement(measurement.toolType, measurement);
|
||||
});
|
||||
|
||||
measurementApi.syncMeasurementsAndToolData();
|
||||
|
||||
// Update images in all active viewports
|
||||
cornerstone.getEnabledElements().forEach(enabledElement => {
|
||||
cornerstone.updateImage(enabledElement.element);
|
||||
});
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
.editDescriptionDialog {
|
||||
position: absolute;
|
||||
position: relative;
|
||||
z-index: 300;
|
||||
width: 320px;
|
||||
transition: all 300ms linear;
|
||||
|
||||
@ -1,24 +1,15 @@
|
||||
import { Component } from 'react';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import SimpleDialog from '../SimpleDialog/SimpleDialog.js';
|
||||
|
||||
import bounding from '../../lib/utils/bounding.js';
|
||||
import { getDialogStyle } from './../Labelling/labellingPositionUtils.js';
|
||||
|
||||
import './EditDescriptionDialog.css';
|
||||
|
||||
export default class EditDescriptionDialog extends Component {
|
||||
static defaultProps = {
|
||||
componentRef: React.createRef(),
|
||||
componentStyle: {},
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
description: PropTypes.string,
|
||||
measurementData: PropTypes.object.isRequired,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
componentRef: PropTypes.object,
|
||||
componentStyle: PropTypes.object,
|
||||
onUpdate: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
@ -28,14 +19,8 @@ export default class EditDescriptionDialog extends Component {
|
||||
this.state = {
|
||||
description: props.measurementData.description || '',
|
||||
};
|
||||
|
||||
this.mainElement = React.createRef();
|
||||
}
|
||||
|
||||
componentDidMount = () => {
|
||||
bounding(this.mainElement);
|
||||
};
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.description !== prevProps.description) {
|
||||
this.setState({
|
||||
@ -45,16 +30,12 @@ export default class EditDescriptionDialog extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const style = getDialogStyle(this.props.componentStyle);
|
||||
|
||||
return (
|
||||
<SimpleDialog
|
||||
headerTitle="Edit Description"
|
||||
onClose={this.onClose}
|
||||
onConfirm={this.onConfirm}
|
||||
rootClass="editDescriptionDialog"
|
||||
componentRef={this.mainElement}
|
||||
componentStyle={style}
|
||||
>
|
||||
<input
|
||||
value={this.state.description}
|
||||
|
||||
@ -1,20 +1,16 @@
|
||||
import { Icon, SelectTree } from '@ohif/ui';
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cloneDeep from 'lodash.clonedeep';
|
||||
|
||||
import LabellingTransition from './LabellingTransition.js';
|
||||
import OHIFLabellingData from './OHIFLabellingData.js';
|
||||
import PropTypes from 'prop-types';
|
||||
import bounding from '../../lib/utils/bounding.js';
|
||||
import cloneDeep from 'lodash.clonedeep';
|
||||
import { getAddLabelButtonStyle } from './labellingPositionUtils.js';
|
||||
|
||||
export default class LabellingFlow extends Component {
|
||||
static propTypes = {
|
||||
eventData: PropTypes.object.isRequired,
|
||||
measurementData: PropTypes.object.isRequired,
|
||||
labellingDoneCallback: PropTypes.func.isRequired,
|
||||
updateLabelling: PropTypes.func.isRequired,
|
||||
|
||||
initialTopDistance: PropTypes.number,
|
||||
skipAddLabelButton: PropTypes.bool,
|
||||
editLocation: PropTypes.bool,
|
||||
@ -26,11 +22,6 @@ export default class LabellingFlow extends Component {
|
||||
|
||||
const { location, locationLabel, description } = props.measurementData;
|
||||
|
||||
let style = props.componentStyle;
|
||||
if (!props.skipAddLabelButton) {
|
||||
style = getAddLabelButtonStyle(props.measurementData, props.eventData);
|
||||
}
|
||||
|
||||
this.state = {
|
||||
location,
|
||||
locationLabel,
|
||||
@ -38,20 +29,17 @@ export default class LabellingFlow extends Component {
|
||||
skipAddLabelButton: props.skipAddLabelButton,
|
||||
editDescription: props.editDescription,
|
||||
editLocation: props.editLocation,
|
||||
componentStyle: style,
|
||||
confirmationState: false,
|
||||
displayComponent: true,
|
||||
};
|
||||
|
||||
this.mainElement = React.createRef();
|
||||
this.descriptionInput = React.createRef();
|
||||
|
||||
this.initialItems = OHIFLabellingData;
|
||||
this.currentItems = cloneDeep(this.initialItems);
|
||||
}
|
||||
|
||||
componentDidUpdate = () => {
|
||||
this.repositionComponent();
|
||||
if (this.state.editDescription) {
|
||||
this.descriptionInput.current.focus();
|
||||
}
|
||||
@ -63,36 +51,14 @@ export default class LabellingFlow extends Component {
|
||||
mainElementClassName += ' editDescription';
|
||||
}
|
||||
|
||||
const style = Object.assign({}, this.state.componentStyle);
|
||||
if (this.state.skipAddLabelButton) {
|
||||
if (style.left - 160 < 0) {
|
||||
style.left = 0;
|
||||
} else {
|
||||
style.left -= 160;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.state.editLocation) {
|
||||
style.maxHeight = '70vh';
|
||||
if (!this.initialTopDistance) {
|
||||
this.initialTopDistance = window.innerHeight - window.innerHeight * 0.3;
|
||||
style.top = `${this.state.componentStyle.top -
|
||||
this.initialTopDistance / 2}px`;
|
||||
} else {
|
||||
style.top = `${this.state.componentStyle.top}px`;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<LabellingTransition
|
||||
displayComponent={this.state.displayComponent}
|
||||
onTransitionExit={this.props.labellingDoneCallback}
|
||||
>
|
||||
<>
|
||||
<div className="labellingComponent-overlay"></div>
|
||||
<div
|
||||
className={mainElementClassName}
|
||||
style={style}
|
||||
ref={this.mainElement}
|
||||
onMouseLeave={this.fadeOutAndLeave}
|
||||
onMouseEnter={this.clearFadeOutTimer}
|
||||
@ -130,9 +96,8 @@ export default class LabellingFlow extends Component {
|
||||
<SelectTree
|
||||
items={this.currentItems}
|
||||
columns={1}
|
||||
onSelected={this.selectTreeSelectCalback}
|
||||
onSelected={this.selectTreeSelectCallback}
|
||||
selectTreeFirstTitle="Assign Label"
|
||||
onComponentChange={this.repositionComponent}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
@ -195,33 +160,17 @@ export default class LabellingFlow extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
relabel = event => {
|
||||
const viewportTopPosition = this.mainElement.current.offsetParent.offsetTop;
|
||||
const componentStyle = {
|
||||
top: event.nativeEvent.y - viewportTopPosition - 55,
|
||||
left: event.nativeEvent.x,
|
||||
};
|
||||
this.setState({
|
||||
editLocation: true,
|
||||
componentStyle,
|
||||
});
|
||||
};
|
||||
relabel = event => this.setState({ editLocation: true });
|
||||
|
||||
setDescriptionUpdateMode = () => {
|
||||
this.descriptionInput.current.focus();
|
||||
|
||||
this.setState({
|
||||
editDescription: true,
|
||||
});
|
||||
this.setState({ editDescription: true });
|
||||
};
|
||||
|
||||
descriptionCancel = () => {
|
||||
const { description = '' } = cloneDeep(this.state);
|
||||
this.descriptionInput.current.value = description;
|
||||
|
||||
this.setState({
|
||||
editDescription: false,
|
||||
});
|
||||
this.setState({ editDescription: false });
|
||||
};
|
||||
|
||||
handleKeyPress = e => {
|
||||
@ -240,22 +189,15 @@ export default class LabellingFlow extends Component {
|
||||
});
|
||||
};
|
||||
|
||||
selectTreeSelectCalback = (event, itemSelected) => {
|
||||
selectTreeSelectCallback = (event, itemSelected) => {
|
||||
const location = itemSelected.value;
|
||||
this.props.updateLabelling({ location });
|
||||
|
||||
const viewportTopPosition = this.mainElement.current.offsetParent.offsetTop;
|
||||
const componentStyle = {
|
||||
top: event.nativeEvent.y - viewportTopPosition - 25,
|
||||
left: event.nativeEvent.x,
|
||||
};
|
||||
|
||||
this.setState({
|
||||
editLocation: false,
|
||||
confirmationState: true,
|
||||
location: itemSelected.value,
|
||||
locationLabel: itemSelected.label,
|
||||
componentStyle,
|
||||
});
|
||||
|
||||
if (this.isTouchScreen) {
|
||||
@ -276,18 +218,13 @@ export default class LabellingFlow extends Component {
|
||||
|
||||
fadeOutAndLeave = () => {
|
||||
// Wait for 1 sec to dismiss the labelling component
|
||||
this.fadeOutTimer = setTimeout(() => {
|
||||
this.setState({
|
||||
displayComponent: false,
|
||||
});
|
||||
}, 1000);
|
||||
this.fadeOutTimer = setTimeout(
|
||||
() => this.setState({ displayComponent: false }),
|
||||
1000
|
||||
);
|
||||
};
|
||||
|
||||
fadeOutAndLeaveFast = () => {
|
||||
this.setState({
|
||||
displayComponent: false,
|
||||
});
|
||||
};
|
||||
fadeOutAndLeaveFast = () => this.setState({ displayComponent: false });
|
||||
|
||||
clearFadeOutTimer = () => {
|
||||
if (!this.fadeOutTimer) {
|
||||
@ -296,29 +233,4 @@ export default class LabellingFlow extends Component {
|
||||
|
||||
clearTimeout(this.fadeOutTimer);
|
||||
};
|
||||
|
||||
calculateTopDistance = () => {
|
||||
const height = window.innerHeight - window.innerHeight * 0.3;
|
||||
let top = this.state.componentStyle.top - height / 2 + 55;
|
||||
if (top < 0) {
|
||||
top = 0;
|
||||
} else {
|
||||
if (top + height > window.innerHeight) {
|
||||
top -= top + height - window.innerHeight;
|
||||
}
|
||||
}
|
||||
return top;
|
||||
};
|
||||
|
||||
repositionComponent = () => {
|
||||
// SetTimeout for the css animation to end.
|
||||
setTimeout(() => {
|
||||
bounding(this.mainElement);
|
||||
if (this.state.editLocation) {
|
||||
this.mainElement.current.style.maxHeight = '70vh';
|
||||
const top = this.calculateTopDistance();
|
||||
this.mainElement.current.style.top = `${top}px`;
|
||||
}
|
||||
}, 200);
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,18 +1,9 @@
|
||||
.labellingComponent-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 10;
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.labellingComponent {
|
||||
position: absolute;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
z-index: 999;
|
||||
transition: all 200ms linear;
|
||||
max-height: 500px;
|
||||
}
|
||||
|
||||
.labellingComponent .selectedLabel,
|
||||
@ -87,9 +78,7 @@
|
||||
border: none;
|
||||
}
|
||||
|
||||
.labellingComponent.editDescription
|
||||
.locationDescriptionWrapper
|
||||
#descriptionInput {
|
||||
.labellingComponent.editDescription .locationDescriptionWrapper #descriptionInput {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
|
||||
@ -1,21 +1,16 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import cloneDeep from 'lodash.clonedeep';
|
||||
|
||||
import EditDescriptionDialog from './../EditDescriptionDialog/EditDescriptionDialog.js';
|
||||
import LabellingFlow from './LabellingFlow.js';
|
||||
|
||||
import './LabellingManager.css';
|
||||
|
||||
export default class LabellingManager extends Component {
|
||||
static propTypes = {
|
||||
eventData: PropTypes.object.isRequired,
|
||||
measurementData: PropTypes.object.isRequired,
|
||||
|
||||
labellingDoneCallback: PropTypes.func.isRequired,
|
||||
updateLabelling: PropTypes.func.isRequired,
|
||||
|
||||
skipAddLabelButton: PropTypes.bool,
|
||||
editLocation: PropTypes.bool,
|
||||
editDescription: PropTypes.bool,
|
||||
@ -41,7 +36,6 @@ export default class LabellingManager extends Component {
|
||||
}
|
||||
|
||||
this.state = {
|
||||
componentStyle: getComponentPosition(props.eventData),
|
||||
skipAddLabelButton: props.skipAddLabelButton,
|
||||
editLocation: editLocation,
|
||||
editDescription: props.editDescription,
|
||||
@ -75,20 +69,13 @@ export default class LabellingManager extends Component {
|
||||
<EditDescriptionDialog
|
||||
onCancel={this.props.labellingDoneCallback}
|
||||
onUpdate={this.descriptionDialogUpdate}
|
||||
componentRef={this.editDescriptionDialog}
|
||||
componentStyle={this.state.componentStyle}
|
||||
measurementData={measurementData}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (editLocation || editDescription) {
|
||||
return (
|
||||
<LabellingFlow
|
||||
{...this.props}
|
||||
componentStyle={this.state.componentStyle}
|
||||
/>
|
||||
);
|
||||
return <LabellingFlow {...this.props} />;
|
||||
}
|
||||
};
|
||||
|
||||
@ -98,33 +85,19 @@ export default class LabellingManager extends Component {
|
||||
if (editDescription) {
|
||||
measurementData.description = undefined;
|
||||
}
|
||||
|
||||
if (editLocation) {
|
||||
measurementData.location = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
responseDialogUpdate = response => {
|
||||
this.props.updateLabelling({
|
||||
response,
|
||||
});
|
||||
this.props.updateLabelling({ response });
|
||||
this.props.labellingDoneCallback();
|
||||
};
|
||||
|
||||
descriptionDialogUpdate = description => {
|
||||
this.props.updateLabelling({
|
||||
description,
|
||||
});
|
||||
this.props.updateLabelling({ description });
|
||||
this.props.labellingDoneCallback();
|
||||
};
|
||||
}
|
||||
|
||||
function getComponentPosition(eventData) {
|
||||
const {
|
||||
event: { clientX: left, clientY: top },
|
||||
} = eventData;
|
||||
|
||||
return {
|
||||
left,
|
||||
top,
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,53 +0,0 @@
|
||||
import cornerstone from 'cornerstone-core';
|
||||
|
||||
const buttonSize = {
|
||||
width: 96,
|
||||
height: 28,
|
||||
};
|
||||
|
||||
export function getAddLabelButtonStyle(measurementData, eventData) {
|
||||
const { start, end } = measurementData.handles;
|
||||
const { client } = eventData.currentPoints;
|
||||
const clientStart = cornerstone.pixelToCanvas(eventData.element, start);
|
||||
const clientEnd = cornerstone.pixelToCanvas(eventData.element, end);
|
||||
const canvasOffSetLeft = client.x - clientStart.x;
|
||||
const canvasOffSetTop = client.y - clientStart.y;
|
||||
const position = {
|
||||
left: clientEnd.x + canvasOffSetLeft,
|
||||
top: clientEnd.y + canvasOffSetTop,
|
||||
};
|
||||
|
||||
if (start.y > end.y) {
|
||||
position.top -= buttonSize.height;
|
||||
}
|
||||
if (start.x > end.x) {
|
||||
position.left -= buttonSize.width;
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
export function getDialogStyle(componentStyle) {
|
||||
const style = Object.assign({}, componentStyle);
|
||||
const dialogProps = {
|
||||
width: 320,
|
||||
height: 230,
|
||||
};
|
||||
|
||||
// Get max values to avoid position out of the screen
|
||||
const maxLeft = window.innerWidth - dialogProps.width;
|
||||
const maxTop = window.innerHeight - dialogProps.height;
|
||||
|
||||
// Positioning the dialog with its center on the click event
|
||||
style.left -= dialogProps.width / 2;
|
||||
style.top -= dialogProps.height / 2;
|
||||
|
||||
if (style.left > maxLeft) {
|
||||
style.left = maxLeft;
|
||||
}
|
||||
if (style.top > maxTop) {
|
||||
style.top = maxTop;
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
import { connect } from 'react-redux';
|
||||
import LabellingOverlay from './LabellingOverlay';
|
||||
|
||||
const mapStateToProps = state => {
|
||||
if (!state.ui || !state.ui.labelling) {
|
||||
return {
|
||||
visible: false,
|
||||
};
|
||||
}
|
||||
|
||||
const labellingFlowData = state.ui.labelling;
|
||||
|
||||
return {
|
||||
visible: false,
|
||||
...labellingFlowData,
|
||||
};
|
||||
};
|
||||
|
||||
const ConnectedLabellingOverlay = connect(
|
||||
mapStateToProps,
|
||||
null
|
||||
)(LabellingOverlay);
|
||||
|
||||
export default ConnectedLabellingOverlay;
|
||||
@ -1,24 +0,0 @@
|
||||
import { connect } from 'react-redux';
|
||||
import ToolContextMenu from './ToolContextMenu';
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
if (!state.ui || !state.ui.contextMenu) {
|
||||
return {
|
||||
visible: false,
|
||||
};
|
||||
}
|
||||
|
||||
const { viewportIndex } = ownProps;
|
||||
const toolContextMenuData = state.ui.contextMenu[viewportIndex];
|
||||
|
||||
return {
|
||||
...toolContextMenuData,
|
||||
};
|
||||
};
|
||||
|
||||
const ConnectedToolContextMenu = connect(
|
||||
mapStateToProps,
|
||||
null
|
||||
)(ToolContextMenu);
|
||||
|
||||
export default ConnectedToolContextMenu;
|
||||
@ -35,7 +35,10 @@ const mapDispatchToProps = (dispatch, ownProps) => {
|
||||
// set new language
|
||||
i18n.changeLanguage(language);
|
||||
|
||||
ownProps.hide();
|
||||
if (ownProps.hide) {
|
||||
ownProps.hide();
|
||||
}
|
||||
|
||||
dispatch(
|
||||
setUserPreferences({
|
||||
windowLevelData,
|
||||
|
||||
@ -1,23 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import LabellingManager from '../components/Labelling/LabellingManager';
|
||||
|
||||
class LabellingOverlay extends Component {
|
||||
static propTypes = {
|
||||
visible: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
visible: false,
|
||||
};
|
||||
|
||||
render() {
|
||||
if (!this.props.visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <LabellingManager {...this.props} />;
|
||||
}
|
||||
}
|
||||
|
||||
export default LabellingOverlay;
|
||||
@ -1,5 +1,5 @@
|
||||
.ToolContextMenu {
|
||||
position: absolute;
|
||||
position: relative;
|
||||
background-color: white;
|
||||
border: 1px solid white;
|
||||
border-radius: 5px;
|
||||
|
||||
@ -1,9 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cornerstone from 'cornerstone-core';
|
||||
import cornerstoneTools from 'cornerstone-tools';
|
||||
// This whole component should live in the Measurements Extension :thinking:
|
||||
import getMeasurementLocationCallback from '../appExtensions/MeasurementsPanel/getMeasurementLocationCallback';
|
||||
import { commandsManager } from './../App.js';
|
||||
|
||||
import './ToolContextMenu.css';
|
||||
|
||||
@ -17,232 +14,119 @@ const toolTypes = [
|
||||
'RectangleRoi',
|
||||
];
|
||||
|
||||
let defaultDropdownItems = [
|
||||
{
|
||||
actionType: 'Delete',
|
||||
action: ({ nearbyToolData, eventData }) => {
|
||||
const element = eventData.element;
|
||||
|
||||
cornerstoneTools.removeToolState(
|
||||
element,
|
||||
nearbyToolData.toolType,
|
||||
nearbyToolData.tool
|
||||
);
|
||||
cornerstone.updateImage(element);
|
||||
const ToolContextMenu = ({
|
||||
onSetLabel,
|
||||
onSetDescription,
|
||||
isTouchEvent,
|
||||
eventData,
|
||||
onClose,
|
||||
onDelete,
|
||||
}) => {
|
||||
const defaultDropdownItems = [
|
||||
{
|
||||
actionType: 'Delete',
|
||||
action: ({ nearbyToolData, eventData }) =>
|
||||
onDelete(nearbyToolData, eventData),
|
||||
},
|
||||
},
|
||||
{
|
||||
actionType: 'setLabel',
|
||||
action: ({ nearbyToolData, eventData }) => {
|
||||
const { tool } = nearbyToolData;
|
||||
|
||||
const options = {
|
||||
skipAddLabelButton: true,
|
||||
editLocation: true,
|
||||
};
|
||||
|
||||
getMeasurementLocationCallback(eventData, tool, options);
|
||||
{
|
||||
actionType: 'setLabel',
|
||||
action: ({ nearbyToolData, eventData }) => {
|
||||
const { tool: measurementData } = nearbyToolData;
|
||||
onSetLabel(eventData, measurementData);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
actionType: 'setDescription',
|
||||
action: ({ nearbyToolData, eventData }) => {
|
||||
const { tool } = nearbyToolData;
|
||||
|
||||
const options = {
|
||||
editDescriptionOnDialog: true,
|
||||
};
|
||||
|
||||
getMeasurementLocationCallback(eventData, tool, options);
|
||||
{
|
||||
actionType: 'setDescription',
|
||||
action: ({ nearbyToolData, eventData }) => {
|
||||
const { tool: measurementData } = nearbyToolData;
|
||||
onSetDescription(eventData, measurementData);
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
];
|
||||
|
||||
function getNearbyToolData(element, coords, toolTypes) {
|
||||
const nearbyTool = {};
|
||||
let pointNearTool = false;
|
||||
const getDropdownItems = (eventData, isTouchEvent = false) => {
|
||||
const nearbyToolData = commandsManager.runCommand('getNearbyToolData', {
|
||||
element: eventData.element,
|
||||
canvasCoordinates: eventData.currentPoints.canvas,
|
||||
availableToolTypes: toolTypes,
|
||||
});
|
||||
|
||||
toolTypes.forEach(toolType => {
|
||||
const toolData = cornerstoneTools.getToolState(element, toolType);
|
||||
if (!toolData) {
|
||||
// Annotate tools for touch events already have a press handle to edit it, has a better UX for deleting it
|
||||
if (
|
||||
isTouchEvent &&
|
||||
nearbyToolData &&
|
||||
nearbyToolData.toolType === 'arrowAnnotate'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
toolData.data.forEach(function(data, index) {
|
||||
// TODO: Fix this, it's ugly
|
||||
let toolInterface = cornerstoneTools.getToolForElement(element, toolType);
|
||||
if (!toolInterface) {
|
||||
toolInterface = cornerstoneTools.getToolForElement(
|
||||
element,
|
||||
`${toolType}Tool`
|
||||
);
|
||||
}
|
||||
let dropdownItems = [];
|
||||
if (nearbyToolData) {
|
||||
defaultDropdownItems.forEach(item => {
|
||||
item.params = {
|
||||
eventData,
|
||||
nearbyToolData,
|
||||
};
|
||||
|
||||
if (!toolInterface) {
|
||||
throw new Error('Tool not found.');
|
||||
}
|
||||
|
||||
if (toolInterface.pointNearTool(element, data, coords)) {
|
||||
pointNearTool = true;
|
||||
nearbyTool.tool = data;
|
||||
nearbyTool.index = index;
|
||||
nearbyTool.toolType = toolType;
|
||||
}
|
||||
});
|
||||
|
||||
if (pointNearTool) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return pointNearTool ? nearbyTool : undefined;
|
||||
}
|
||||
|
||||
function getDropdownItems(eventData, isTouchEvent = false) {
|
||||
const nearbyToolData = getNearbyToolData(
|
||||
eventData.element,
|
||||
eventData.currentPoints.canvas,
|
||||
toolTypes
|
||||
);
|
||||
|
||||
// Annotate tools for touch events already have a press handle to edit it, has a better UX for deleting it
|
||||
if (
|
||||
isTouchEvent &&
|
||||
nearbyToolData &&
|
||||
nearbyToolData.toolType === 'arrowAnnotate'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
let dropdownItems = [];
|
||||
if (nearbyToolData) {
|
||||
defaultDropdownItems.forEach(function(item) {
|
||||
item.params = {
|
||||
eventData,
|
||||
nearbyToolData,
|
||||
};
|
||||
|
||||
if (item.actionType === 'Delete') {
|
||||
item.text = 'Delete measurement';
|
||||
}
|
||||
|
||||
if (item.actionType === 'setLabel') {
|
||||
item.text = 'Relabel';
|
||||
}
|
||||
|
||||
if (item.actionType === 'setDescription') {
|
||||
item.text = `${
|
||||
nearbyToolData.tool.description ? 'Edit' : 'Add'
|
||||
} Description`;
|
||||
}
|
||||
|
||||
dropdownItems.push(item);
|
||||
});
|
||||
}
|
||||
|
||||
return dropdownItems;
|
||||
}
|
||||
|
||||
class ToolContextMenu extends Component {
|
||||
static propTypes = {
|
||||
isTouchEvent: PropTypes.bool.isRequired,
|
||||
eventData: PropTypes.object,
|
||||
onClose: PropTypes.func,
|
||||
visible: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
visible: true,
|
||||
isTouchEvent: false,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.mainElement = React.createRef();
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.props.eventData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { isTouchEvent, eventData } = this.props;
|
||||
const dropdownItems = getDropdownItems(eventData, isTouchEvent);
|
||||
|
||||
// Skip if there is no dropdown item
|
||||
if (!dropdownItems.length) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const dropdownComponents = dropdownItems.map(item => {
|
||||
const itemOnClick = event => {
|
||||
item.action(item.params);
|
||||
if (this.props.onClose) {
|
||||
this.props.onClose();
|
||||
if (item.actionType === 'Delete') {
|
||||
item.text = 'Delete measurement';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<li key={item.actionType}>
|
||||
<button className="form-action" onClick={itemOnClick}>
|
||||
<span key={item.actionType}>{item.text}</span>
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
if (item.actionType === 'setLabel') {
|
||||
item.text = 'Relabel';
|
||||
}
|
||||
|
||||
const position = {
|
||||
top: `${eventData.currentPoints.canvas.y}px`,
|
||||
left: `${eventData.currentPoints.canvas.x}px`,
|
||||
};
|
||||
if (item.actionType === 'setDescription') {
|
||||
item.text = `${
|
||||
nearbyToolData.tool.description ? 'Edit' : 'Add'
|
||||
} Description`;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ToolContextMenu" style={position} ref={this.mainElement}>
|
||||
<ul className="bounded">{dropdownComponents}</ul>
|
||||
dropdownItems.push(item);
|
||||
});
|
||||
}
|
||||
|
||||
return dropdownItems;
|
||||
};
|
||||
|
||||
const itemOnClickHandler = (action, params, onClose) => {
|
||||
action(params);
|
||||
if (onClose) {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
const dropdownItems = getDropdownItems(eventData, isTouchEvent);
|
||||
|
||||
return (
|
||||
dropdownItems.length &&
|
||||
eventData && (
|
||||
<div className="ToolContextMenu">
|
||||
<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>
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
componentDidMount = () => {
|
||||
if (this.mainElement.current) {
|
||||
this.updateElementPosition();
|
||||
}
|
||||
};
|
||||
ToolContextMenu.propTypes = {
|
||||
isTouchEvent: PropTypes.bool.isRequired,
|
||||
eventData: PropTypes.object,
|
||||
onClose: PropTypes.func,
|
||||
};
|
||||
|
||||
componentDidUpdate = () => {
|
||||
if (this.mainElement.current) {
|
||||
this.updateElementPosition();
|
||||
}
|
||||
};
|
||||
|
||||
updateElementPosition = () => {
|
||||
const {
|
||||
offsetParent,
|
||||
offsetTop,
|
||||
offsetHeight,
|
||||
offsetWidth,
|
||||
offsetLeft,
|
||||
} = this.mainElement.current;
|
||||
|
||||
const { eventData } = this.props;
|
||||
|
||||
if (offsetTop + offsetHeight > offsetParent.offsetHeight) {
|
||||
const offBoundPixels =
|
||||
offsetTop + offsetHeight - offsetParent.offsetHeight;
|
||||
const top = eventData.currentPoints.canvas.y - offBoundPixels;
|
||||
|
||||
this.mainElement.current.style.top = `${top > 0 ? top : 0}px`;
|
||||
}
|
||||
|
||||
if (offsetLeft + offsetWidth > offsetParent.offsetWidth) {
|
||||
const offBoundPixels =
|
||||
offsetLeft + offsetWidth - offsetParent.offsetWidth;
|
||||
const left = eventData.currentPoints.canvas.x - offBoundPixels;
|
||||
|
||||
this.mainElement.current.style.left = `${left > 0 ? left : 0}px`;
|
||||
}
|
||||
};
|
||||
}
|
||||
ToolContextMenu.defaultProps = {
|
||||
isTouchEvent: false,
|
||||
};
|
||||
|
||||
export default ToolContextMenu;
|
||||
|
||||
@ -7,7 +7,6 @@ import OHIF from '@ohif/core';
|
||||
import moment from 'moment';
|
||||
import ConnectedHeader from './ConnectedHeader.js';
|
||||
import ConnectedToolbarRow from './ConnectedToolbarRow.js';
|
||||
import ConnectedLabellingOverlay from './ConnectedLabellingOverlay';
|
||||
import ConnectedStudyBrowser from './ConnectedStudyBrowser.js';
|
||||
import ConnectedViewerMain from './ConnectedViewerMain.js';
|
||||
import SidePanel from './../components/SidePanel.js';
|
||||
@ -317,7 +316,6 @@ class Viewer extends Component {
|
||||
)}
|
||||
</SidePanel>
|
||||
</div>
|
||||
<ConnectedLabellingOverlay />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -2,7 +2,6 @@ import './ViewerMain.css';
|
||||
|
||||
import { Component } from 'react';
|
||||
import { ConnectedViewportGrid } from './../components/ViewportGrid/index.js';
|
||||
import ConnectedToolContextMenu from './ConnectedToolContextMenu.js';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
@ -153,7 +152,6 @@ class ViewerMain extends Component {
|
||||
setViewportData={this.setViewportData}
|
||||
>
|
||||
{/* Children to add to each viewport that support children */}
|
||||
<ConnectedToolContextMenu />
|
||||
</ConnectedViewportGrid>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -7,7 +7,7 @@ import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App.js';
|
||||
|
||||
function installViewer(props, containerId = 'root', callback) {
|
||||
function installViewer(config, containerId = 'root', callback) {
|
||||
const container = document.getElementById(containerId);
|
||||
|
||||
if (!container) {
|
||||
@ -16,7 +16,7 @@ function installViewer(props, containerId = 'root', callback) {
|
||||
);
|
||||
}
|
||||
|
||||
return ReactDOM.render(<App {...props} />, container, callback);
|
||||
return ReactDOM.render(<App config={config} />, container, callback);
|
||||
}
|
||||
|
||||
export { App, installViewer };
|
||||
|
||||
@ -18,35 +18,36 @@ import ReactDOM from 'react-dom';
|
||||
* "baked in" to the published application.
|
||||
*
|
||||
* Depending on your use case/needs, you may want to consider not adding any extensions
|
||||
* by default HERE, and instead provide them via the configuration specified at
|
||||
* `window.config.extensions`, or by using the exported `App` component, and passing
|
||||
* in your extensions as props.
|
||||
* by default HERE, and instead provide them via the extensions configuration key or
|
||||
* by using the exported `App` component, and passing in your extensions as props using
|
||||
* the defaultExtensions property.
|
||||
*/
|
||||
import OHIFVTKExtension from '@ohif/extension-vtk';
|
||||
import OHIFDicomHtmlExtension from '@ohif/extension-dicom-html';
|
||||
import OHIFDicomMicroscopyExtension from '@ohif/extension-dicom-microscopy';
|
||||
import OHIFDicomPDFExtension from '@ohif/extension-dicom-pdf';
|
||||
|
||||
// Default Settings
|
||||
/*
|
||||
* Default Settings
|
||||
*/
|
||||
let config = {};
|
||||
const appDefaults = {
|
||||
routerBasename: '/',
|
||||
};
|
||||
|
||||
if (window) {
|
||||
config = window.config || {};
|
||||
config.extensions = [
|
||||
}
|
||||
|
||||
const appProps = {
|
||||
config,
|
||||
defaultExtensions: [
|
||||
OHIFVTKExtension,
|
||||
OHIFDicomHtmlExtension,
|
||||
OHIFDicomMicroscopyExtension,
|
||||
OHIFDicomPDFExtension,
|
||||
];
|
||||
}
|
||||
],
|
||||
};
|
||||
|
||||
const appProps = Object.assign({}, appDefaults, config);
|
||||
|
||||
// Create App
|
||||
/** Create App */
|
||||
const app = React.createElement(App, appProps, null);
|
||||
|
||||
// Render
|
||||
/** Render */
|
||||
ReactDOM.render(app, document.getElementById('root'));
|
||||
|
||||
@ -6,18 +6,16 @@ import {
|
||||
} from 'redux/es/redux.js';
|
||||
|
||||
// import { createLogger } from 'redux-logger';
|
||||
import layoutReducers from './layout/reducers.js';
|
||||
import { reducer as oidcReducer } from 'redux-oidc';
|
||||
import { redux } from '@ohif/core';
|
||||
import thunkMiddleware from 'redux-thunk';
|
||||
|
||||
// Combine our @ohif/core, ui, and oidc reducers
|
||||
// Combine our @ohif/core and oidc reducers
|
||||
// Set init data, using values found in localStorage
|
||||
const { reducers, localStorage, sessionStorage } = redux;
|
||||
const middleware = [thunkMiddleware];
|
||||
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
||||
|
||||
reducers.ui = layoutReducers;
|
||||
reducers.oidc = oidcReducer;
|
||||
|
||||
const rootReducer = combineReducers(reducers);
|
||||
|
||||
@ -1,33 +0,0 @@
|
||||
const defaultState = {
|
||||
labelling: {},
|
||||
contextMenu: {},
|
||||
};
|
||||
|
||||
const ui = (state = defaultState, action) => {
|
||||
switch (action.type) {
|
||||
case 'SET_LABELLING_FLOW_DATA': {
|
||||
const labelling = Object.assign({}, action.labellingFlowData);
|
||||
|
||||
return Object.assign({}, state, { labelling });
|
||||
}
|
||||
case 'SET_TOOL_CONTEXT_MENU_DATA': {
|
||||
const contextMenu = Object.assign({}, state.contextMenu);
|
||||
|
||||
contextMenu[action.viewportIndex] = Object.assign(
|
||||
{},
|
||||
action.toolContextMenuData
|
||||
);
|
||||
|
||||
return Object.assign({}, state, { contextMenu });
|
||||
}
|
||||
case 'RESET_LABELLING_AND_CONTEXT_MENU':
|
||||
return Object.assign({}, state, {
|
||||
labelling: defaultState.labelling,
|
||||
contextMenu: defaultState.contextMenu,
|
||||
});
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default ui;
|
||||
@ -12246,7 +12246,7 @@ lodash.memoize@^4.1.2:
|
||||
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
|
||||
integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
|
||||
|
||||
lodash.merge@^4.4.0, lodash.merge@^4.6.1:
|
||||
lodash.merge@^4.4.0, lodash.merge@^4.6.1, lodash.merge@^4.6.2:
|
||||
version "4.6.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
|
||||
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
|
||||
|
||||
Loading…
Reference in New Issue
Block a user