_bringToFront(id)}
>
diff --git a/platform/ui/src/contextProviders/DialogProvider.styl b/platform/ui/src/contextProviders/DialogProvider.styl
index e3a45c481..13db9a19b 100644
--- a/platform/ui/src/contextProviders/DialogProvider.styl
+++ b/platform/ui/src/contextProviders/DialogProvider.styl
@@ -1,8 +1,8 @@
-.DraggableItem
+.DraggableItem.draggable
div
cursor: grab !important
-.DraggableItem.dragging
+.DraggableItem.draggable.dragging
div
cursor: grabbing !important
diff --git a/platform/ui/src/contextProviders/LabellingFlowProvider.js b/platform/ui/src/contextProviders/LabellingFlowProvider.js
new file mode 100644
index 000000000..a5c266d8f
--- /dev/null
+++ b/platform/ui/src/contextProviders/LabellingFlowProvider.js
@@ -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 (
+
+ {children}
+
+ );
+};
+
+/**
+ * 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 (
+
+ );
+ };
+};
+
+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;
diff --git a/platform/ui/src/contextProviders/index.js b/platform/ui/src/contextProviders/index.js
index e4ca06143..821b6ce9e 100644
--- a/platform/ui/src/contextProviders/index.js
+++ b/platform/ui/src/contextProviders/index.js
@@ -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';
diff --git a/platform/ui/src/index.js b/platform/ui/src/index.js
index 6af64c47d..1672d4b13 100644
--- a/platform/ui/src/index.js
+++ b/platform/ui/src/index.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,
diff --git a/platform/viewer/public/config/default.js b/platform/viewer/public/config/default.js
index 37d807738..a8d44e7ef 100644
--- a/platform/viewer/public/config/default.js
+++ b/platform/viewer/public/config/default.js
@@ -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: {} },
};
diff --git a/platform/viewer/public/config/demo.js b/platform/viewer/public/config/demo.js
index 7cdf103ca..72272ed24 100644
--- a/platform/viewer/public/config/demo.js
+++ b/platform/viewer/public/config/demo.js
@@ -1,5 +1,6 @@
window.config = {
routerBasename: '/',
+ whiteLabelling: {},
extensions: [],
showStudyList: true,
servers: {
diff --git a/platform/viewer/public/config/docker_nginx-orthanc.js b/platform/viewer/public/config/docker_nginx-orthanc.js
index 812972475..5caddff2b 100644
--- a/platform/viewer/public/config/docker_nginx-orthanc.js
+++ b/platform/viewer/public/config/docker_nginx-orthanc.js
@@ -1,5 +1,6 @@
window.config = {
routerBasename: '/',
+ whiteLabelling: {},
showStudyList: true,
servers: {
dicomWeb: [
diff --git a/platform/viewer/public/config/docker_openresty-orthanc-keycloak.js b/platform/viewer/public/config/docker_openresty-orthanc-keycloak.js
index a872f4a44..c6fd6596e 100644
--- a/platform/viewer/public/config/docker_openresty-orthanc-keycloak.js
+++ b/platform/viewer/public/config/docker_openresty-orthanc-keycloak.js
@@ -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
diff --git a/platform/viewer/public/config/docker_openresty-orthanc.js b/platform/viewer/public/config/docker_openresty-orthanc.js
index d23915fc7..32f6aec21 100644
--- a/platform/viewer/public/config/docker_openresty-orthanc.js
+++ b/platform/viewer/public/config/docker_openresty-orthanc.js
@@ -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
diff --git a/platform/viewer/public/config/google.js b/platform/viewer/public/config/google.js
index 64e991d5e..b5d0c5904 100644
--- a/platform/viewer/public/config/google.js
+++ b/platform/viewer/public/config/google.js
@@ -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,
-}
+};
diff --git a/platform/viewer/public/config/local_dcm4chee.js b/platform/viewer/public/config/local_dcm4chee.js
index 1dc87f528..afc9429ce 100644
--- a/platform/viewer/public/config/local_dcm4chee.js
+++ b/platform/viewer/public/config/local_dcm4chee.js
@@ -1,6 +1,7 @@
window.config = {
// default: '/'
routerBasename: '/',
+ whiteLabelling: {},
// default: ''
showStudyList: true,
servers: {
diff --git a/platform/viewer/public/config/netlify.js b/platform/viewer/public/config/netlify.js
index 0365b414f..41e969760 100644
--- a/platform/viewer/public/config/netlify.js
+++ b/platform/viewer/public/config/netlify.js
@@ -1,5 +1,6 @@
window.config = {
routerBasename: '/pwa',
+ whiteLabelling: {},
showStudyList: true,
servers: {
dicomWeb: [
diff --git a/platform/viewer/public/config/public_dicomweb.js b/platform/viewer/public/config/public_dicomweb.js
index 0569b5045..c6c30463a 100644
--- a/platform/viewer/public/config/public_dicomweb.js
+++ b/platform/viewer/public/config/public_dicomweb.js
@@ -1,5 +1,6 @@
window.config = {
routerBasename: '/',
+ whiteLabelling: {},
showStudyList: true,
servers: {
dicomWeb: [
diff --git a/platform/viewer/public/html-templates/index.html b/platform/viewer/public/html-templates/index.html
index de7f23d4f..a01ca7aa3 100644
--- a/platform/viewer/public/html-templates/index.html
+++ b/platform/viewer/public/html-templates/index.html
@@ -1,74 +1,102 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
OHIF Viewer
+
+
+
+
+
+
-
-
+
OHIF Viewer
-
-
+
+
+
+
-
+
-
-
You need to enable JavaScript to run this app.
+
+
You need to enable JavaScript to run this app.
+
+
+
-
-
diff --git a/platform/viewer/public/html-templates/rollbar.html b/platform/viewer/public/html-templates/rollbar.html
index 7101cf875..ae8e9c695 100644
--- a/platform/viewer/public/html-templates/rollbar.html
+++ b/platform/viewer/public/html-templates/rollbar.html
@@ -1,86 +1,117 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
OHIF Viewer
+
+
+
+
+
+
-
-
+
OHIF Viewer
-
-
+
+
+
+
-
-
-
You need to enable JavaScript to run this app.
+
-
+
+
You need to enable JavaScript to run this app.
-
-
+
+
diff --git a/platform/viewer/src/App.js b/platform/viewer/src/App.js
index 3ae9dd4c8..b1cf92739 100644
--- a/platform/viewer/src/App.js
+++ b/platform/viewer/src/App.js
@@ -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 (
-
+
-
-
+
+
@@ -137,7 +180,21 @@ class App extends Component {
modal={OHIFModal}
service={UIModalService}
>
-
+
+
+
+
+
@@ -152,7 +209,7 @@ class App extends Component {
}
return (
-
+
@@ -160,7 +217,19 @@ class App extends Component {
-
+
+
+
+
+
@@ -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;
diff --git a/platform/viewer/src/appCustomProviders/ContextMenuProvider/ContextMenuProvider.js b/platform/viewer/src/appCustomProviders/ContextMenuProvider/ContextMenuProvider.js
new file mode 100644
index 000000000..4cd75d9f8
--- /dev/null
+++ b/platform/viewer/src/appCustomProviders/ContextMenuProvider/ContextMenuProvider.js
@@ -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 (
+
+ {children}
+
+ );
+};
+
+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;
diff --git a/platform/viewer/src/appCustomProviders/LabellingFlowProvider/LabellingFlowProvider.js b/platform/viewer/src/appCustomProviders/LabellingFlowProvider/LabellingFlowProvider.js
new file mode 100644
index 000000000..097a0350f
--- /dev/null
+++ b/platform/viewer/src/appCustomProviders/LabellingFlowProvider/LabellingFlowProvider.js
@@ -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 (
+
+ {children}
+
+ );
+};
+
+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;
diff --git a/platform/viewer/src/appCustomProviders/index.js b/platform/viewer/src/appCustomProviders/index.js
new file mode 100644
index 000000000..f66c8e462
--- /dev/null
+++ b/platform/viewer/src/appCustomProviders/index.js
@@ -0,0 +1,6 @@
+export {
+ default as LabellingFlowProvider,
+} from './LabellingFlowProvider/LabellingFlowProvider.js';
+export {
+ default as ContextMenuProvider,
+} from './ContextMenuProvider/ContextMenuProvider.js';
diff --git a/platform/viewer/src/appExtensions/MeasurementsPanel/ConnectedMeasurementTable.js b/platform/viewer/src/appExtensions/MeasurementsPanel/ConnectedMeasurementTable.js
index c4092c455..265a2e585 100644
--- a/platform/viewer/src/appExtensions/MeasurementsPanel/ConnectedMeasurementTable.js
+++ b/platform/viewer/src/appExtensions/MeasurementsPanel/ConnectedMeasurementTable.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,
diff --git a/platform/viewer/src/appExtensions/MeasurementsPanel/actions.js b/platform/viewer/src/appExtensions/MeasurementsPanel/actions.js
deleted file mode 100644
index 7f978e318..000000000
--- a/platform/viewer/src/appExtensions/MeasurementsPanel/actions.js
+++ /dev/null
@@ -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,
-};
diff --git a/platform/viewer/src/appExtensions/MeasurementsPanel/getMeasurementLocationCallback.js b/platform/viewer/src/appExtensions/MeasurementsPanel/getMeasurementLocationCallback.js
deleted file mode 100644
index 4e77e94fb..000000000
--- a/platform/viewer/src/appExtensions/MeasurementsPanel/getMeasurementLocationCallback.js
+++ /dev/null
@@ -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
- );
-}
diff --git a/platform/viewer/src/appExtensions/MeasurementsPanel/index.js b/platform/viewer/src/appExtensions/MeasurementsPanel/index.js
index 3e4ab4288..d240971da 100644
--- a/platform/viewer/src/appExtensions/MeasurementsPanel/index.js
+++ b/platform/viewer/src/appExtensions/MeasurementsPanel/index.js
@@ -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 = () => (
+ {
+ 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'],
diff --git a/platform/viewer/src/appExtensions/MeasurementsPanel/init.js b/platform/viewer/src/appExtensions/MeasurementsPanel/init.js
index 559cff154..0c299b81a 100644
--- a/platform/viewer/src/appExtensions/MeasurementsPanel/init.js
+++ b/platform/viewer/src/appExtensions/MeasurementsPanel/init.js
@@ -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);
}
diff --git a/platform/viewer/src/appExtensions/MeasurementsPanel/labelingFlowCallbacks.js b/platform/viewer/src/appExtensions/MeasurementsPanel/labelingFlowCallbacks.js
deleted file mode 100644
index 81fae1f0b..000000000
--- a/platform/viewer/src/appExtensions/MeasurementsPanel/labelingFlowCallbacks.js
+++ /dev/null
@@ -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,
-};
diff --git a/platform/viewer/src/appExtensions/MeasurementsPanel/updateTableWithNewMeasurementData.js b/platform/viewer/src/appExtensions/MeasurementsPanel/updateTableWithNewMeasurementData.js
deleted file mode 100644
index 7339cd219..000000000
--- a/platform/viewer/src/appExtensions/MeasurementsPanel/updateTableWithNewMeasurementData.js
+++ /dev/null
@@ -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);
- });
-}
diff --git a/platform/viewer/src/components/EditDescriptionDialog/EditDescriptionDialog.css b/platform/viewer/src/components/EditDescriptionDialog/EditDescriptionDialog.css
index 9d3de80fd..53a6e294a 100644
--- a/platform/viewer/src/components/EditDescriptionDialog/EditDescriptionDialog.css
+++ b/platform/viewer/src/components/EditDescriptionDialog/EditDescriptionDialog.css
@@ -1,5 +1,5 @@
.editDescriptionDialog {
- position: absolute;
+ position: relative;
z-index: 300;
width: 320px;
transition: all 300ms linear;
diff --git a/platform/viewer/src/components/EditDescriptionDialog/EditDescriptionDialog.js b/platform/viewer/src/components/EditDescriptionDialog/EditDescriptionDialog.js
index ff8e16bdc..9bea72359 100644
--- a/platform/viewer/src/components/EditDescriptionDialog/EditDescriptionDialog.js
+++ b/platform/viewer/src/components/EditDescriptionDialog/EditDescriptionDialog.js
@@ -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 (
{
- 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 (
<>
-
);
} 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);
- };
}
diff --git a/platform/viewer/src/components/Labelling/LabellingManager.css b/platform/viewer/src/components/Labelling/LabellingManager.css
index dde9058c9..684264870 100644
--- a/platform/viewer/src/components/Labelling/LabellingManager.css
+++ b/platform/viewer/src/components/Labelling/LabellingManager.css
@@ -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;
}
diff --git a/platform/viewer/src/components/Labelling/LabellingManager.js b/platform/viewer/src/components/Labelling/LabellingManager.js
index e573eb793..8b0148886 100644
--- a/platform/viewer/src/components/Labelling/LabellingManager.js
+++ b/platform/viewer/src/components/Labelling/LabellingManager.js
@@ -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 {
);
}
if (editLocation || editDescription) {
- return (
-
- );
+ return ;
}
};
@@ -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,
- };
-}
diff --git a/platform/viewer/src/components/Labelling/labellingPositionUtils.js b/platform/viewer/src/components/Labelling/labellingPositionUtils.js
deleted file mode 100644
index f581791f7..000000000
--- a/platform/viewer/src/components/Labelling/labellingPositionUtils.js
+++ /dev/null
@@ -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;
-}
diff --git a/platform/viewer/src/connectedComponents/ConnectedLabellingOverlay.js b/platform/viewer/src/connectedComponents/ConnectedLabellingOverlay.js
deleted file mode 100644
index 0354e1159..000000000
--- a/platform/viewer/src/connectedComponents/ConnectedLabellingOverlay.js
+++ /dev/null
@@ -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;
diff --git a/platform/viewer/src/connectedComponents/ConnectedToolContextMenu.js b/platform/viewer/src/connectedComponents/ConnectedToolContextMenu.js
deleted file mode 100644
index 8ab97531a..000000000
--- a/platform/viewer/src/connectedComponents/ConnectedToolContextMenu.js
+++ /dev/null
@@ -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;
diff --git a/platform/viewer/src/connectedComponents/ConnectedUserPreferencesForm.js b/platform/viewer/src/connectedComponents/ConnectedUserPreferencesForm.js
index a0c29857d..26c6a237d 100644
--- a/platform/viewer/src/connectedComponents/ConnectedUserPreferencesForm.js
+++ b/platform/viewer/src/connectedComponents/ConnectedUserPreferencesForm.js
@@ -35,7 +35,10 @@ const mapDispatchToProps = (dispatch, ownProps) => {
// set new language
i18n.changeLanguage(language);
- ownProps.hide();
+ if (ownProps.hide) {
+ ownProps.hide();
+ }
+
dispatch(
setUserPreferences({
windowLevelData,
diff --git a/platform/viewer/src/connectedComponents/LabellingOverlay.js b/platform/viewer/src/connectedComponents/LabellingOverlay.js
deleted file mode 100644
index cf976424a..000000000
--- a/platform/viewer/src/connectedComponents/LabellingOverlay.js
+++ /dev/null
@@ -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 ;
- }
-}
-
-export default LabellingOverlay;
diff --git a/platform/viewer/src/connectedComponents/ToolContextMenu.css b/platform/viewer/src/connectedComponents/ToolContextMenu.css
index 42bd2ca6f..66fa17495 100644
--- a/platform/viewer/src/connectedComponents/ToolContextMenu.css
+++ b/platform/viewer/src/connectedComponents/ToolContextMenu.css
@@ -1,5 +1,5 @@
.ToolContextMenu {
- position: absolute;
+ position: relative;
background-color: white;
border: 1px solid white;
border-radius: 5px;
diff --git a/platform/viewer/src/connectedComponents/ToolContextMenu.js b/platform/viewer/src/connectedComponents/ToolContextMenu.js
index ee92bdbd9..18bb57684 100644
--- a/platform/viewer/src/connectedComponents/ToolContextMenu.js
+++ b/platform/viewer/src/connectedComponents/ToolContextMenu.js
@@ -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 (
-
-
- {item.text}
-
-
- );
- });
+ 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 (
-
-
+ dropdownItems.push(item);
+ });
+ }
+
+ return dropdownItems;
+ };
+
+ const itemOnClickHandler = (action, params, onClose) => {
+ action(params);
+ if (onClose) {
+ onClose();
+ }
+ };
+
+ const dropdownItems = getDropdownItems(eventData, isTouchEvent);
+
+ return (
+ dropdownItems.length &&
+ eventData && (
+
+
+ {dropdownItems.map(({ params, action, text, actionType }) => (
+
+ itemOnClickHandler(action, params, onClose)}
+ >
+ {text}
+
+
+ ))}
+
- );
- }
+ )
+ );
+};
- 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;
diff --git a/platform/viewer/src/connectedComponents/Viewer.js b/platform/viewer/src/connectedComponents/Viewer.js
index 3cc112c63..d112ac502 100644
--- a/platform/viewer/src/connectedComponents/Viewer.js
+++ b/platform/viewer/src/connectedComponents/Viewer.js
@@ -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 {
)}
-
>
);
}
diff --git a/platform/viewer/src/connectedComponents/ViewerMain.js b/platform/viewer/src/connectedComponents/ViewerMain.js
index 250857fb1..34fa43d06 100644
--- a/platform/viewer/src/connectedComponents/ViewerMain.js
+++ b/platform/viewer/src/connectedComponents/ViewerMain.js
@@ -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 */}
-
)}
diff --git a/platform/viewer/src/index-umd.js b/platform/viewer/src/index-umd.js
index 810097fce..34a7a9590 100644
--- a/platform/viewer/src/index-umd.js
+++ b/platform/viewer/src/index-umd.js
@@ -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(