feat(measurements): Provide for the Load (SR) measurements button to optionally clear existing measurements prior to loading the SR. (#4586)
This commit is contained in:
parent
7eec015ff1
commit
4d3d5e794c
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Icon, Tooltip } from '@ohif/ui';
|
import { Icon, Tooltip, ViewportActionButton } from '@ohif/ui';
|
||||||
import { Icons } from '@ohif/ui-next';
|
import { Icons } from '@ohif/ui-next';
|
||||||
|
|
||||||
export default function _getStatusComponent({ isHydrated, onStatusClick }) {
|
export default function _getStatusComponent({ isHydrated, onStatusClick }) {
|
||||||
@ -35,13 +35,7 @@ export default function _getStatusComponent({ isHydrated, onStatusClick }) {
|
|||||||
<span className="ml-1">RTSTRUCT</span>
|
<span className="ml-1">RTSTRUCT</span>
|
||||||
</div>
|
</div>
|
||||||
{!isHydrated && (
|
{!isHydrated && (
|
||||||
<div
|
<ViewportActionButton onInteraction={onStatusClick}>{loadStr}</ViewportActionButton>
|
||||||
className="bg-primary-main hover:bg-primary-light ml-1 cursor-pointer rounded px-1.5 hover:text-black"
|
|
||||||
// Using onMouseUp here because onClick is not working when the viewport is not active and is styled with pointer-events:none
|
|
||||||
onMouseUp={onStatusClick}
|
|
||||||
>
|
|
||||||
{loadStr}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Tooltip } from '@ohif/ui';
|
import { Tooltip, ViewportActionButton } from '@ohif/ui';
|
||||||
import { Icons } from '@ohif/ui-next';
|
import { Icons } from '@ohif/ui-next';
|
||||||
|
|
||||||
export default function _getStatusComponent({ isHydrated, onStatusClick }) {
|
export default function _getStatusComponent({ isHydrated, onStatusClick }) {
|
||||||
@ -35,13 +35,7 @@ export default function _getStatusComponent({ isHydrated, onStatusClick }) {
|
|||||||
<span className="ml-1">SEG</span>
|
<span className="ml-1">SEG</span>
|
||||||
</div>
|
</div>
|
||||||
{!isHydrated && (
|
{!isHydrated && (
|
||||||
<div
|
<ViewportActionButton onInteraction={onStatusClick}>{loadStr}</ViewportActionButton>
|
||||||
className="bg-primary-main hover:bg-primary-light ml-1 cursor-pointer rounded px-1.5 hover:text-black"
|
|
||||||
// Using onMouseUp here because onClick is not working when the viewport is not active and is styled with pointer-events:none
|
|
||||||
onMouseUp={onStatusClick}
|
|
||||||
>
|
|
||||||
{loadStr}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import dcmjs from 'dcmjs';
|
|||||||
import { adaptersSR } from '@cornerstonejs/adapters';
|
import { adaptersSR } from '@cornerstonejs/adapters';
|
||||||
|
|
||||||
import getFilteredCornerstoneToolState from './utils/getFilteredCornerstoneToolState';
|
import getFilteredCornerstoneToolState from './utils/getFilteredCornerstoneToolState';
|
||||||
|
import hydrateStructuredReport from './utils/hydrateStructuredReport';
|
||||||
|
|
||||||
const { MeasurementReport } = adaptersSR.Cornerstone3D;
|
const { MeasurementReport } = adaptersSR.Cornerstone3D;
|
||||||
const { log } = OHIF;
|
const { log } = OHIF;
|
||||||
@ -41,8 +42,8 @@ const _generateReport = (measurementData, additionalFindingTypes, options = {})
|
|||||||
};
|
};
|
||||||
|
|
||||||
const commandsModule = (props: withAppTypes) => {
|
const commandsModule = (props: withAppTypes) => {
|
||||||
const { servicesManager } = props;
|
const { servicesManager, extensionManager, commandsManager } = props;
|
||||||
const { customizationService } = servicesManager.services;
|
const { customizationService, displaySetService, viewportGridService } = servicesManager.services;
|
||||||
const actions = {
|
const actions = {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -123,6 +124,27 @@ const commandsModule = (props: withAppTypes) => {
|
|||||||
throw new Error(error.message || 'Error while saving the measurements.');
|
throw new Error(error.message || 'Error while saving the measurements.');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads measurements by hydrating and loading the SR for the given display set instance UID
|
||||||
|
* and displays it in the active viewport.
|
||||||
|
*/
|
||||||
|
loadSRMeasurements: ({ displaySetInstanceUID }) => {
|
||||||
|
const { SeriesInstanceUIDs } = hydrateStructuredReport(
|
||||||
|
{ servicesManager, extensionManager },
|
||||||
|
displaySetInstanceUID
|
||||||
|
);
|
||||||
|
|
||||||
|
const displaySets = displaySetService.getDisplaySetsForSeries(SeriesInstanceUIDs[0]);
|
||||||
|
if (displaySets.length) {
|
||||||
|
viewportGridService.setDisplaySetsForViewports([
|
||||||
|
{
|
||||||
|
viewportId: viewportGridService.getActiveViewportId(),
|
||||||
|
displaySetInstanceUIDs: [displaySets[0].displaySetInstanceUID],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const definitions = {
|
const definitions = {
|
||||||
@ -132,6 +154,9 @@ const commandsModule = (props: withAppTypes) => {
|
|||||||
storeMeasurements: {
|
storeMeasurements: {
|
||||||
commandFn: actions.storeMeasurements,
|
commandFn: actions.storeMeasurements,
|
||||||
},
|
},
|
||||||
|
loadSRMeasurements: {
|
||||||
|
commandFn: actions.loadSRMeasurements,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -1,13 +1,11 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { useCallback, useContext, useEffect, useState } from 'react';
|
import React, { useCallback, useContext, useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ExtensionManager } from '@ohif/core';
|
import { ExtensionManager, useToolbar } from '@ohif/core';
|
||||||
|
|
||||||
import { setTrackingUniqueIdentifiersForElement } from '../tools/modules/dicomSRModule';
|
import { setTrackingUniqueIdentifiersForElement } from '../tools/modules/dicomSRModule';
|
||||||
|
|
||||||
import { Icon, Tooltip, useViewportGrid, ViewportActionArrows } from '@ohif/ui';
|
import { Icon, Tooltip, useViewportGrid, ViewportActionArrows } from '@ohif/ui';
|
||||||
import hydrateStructuredReport from '../utils/hydrateStructuredReport';
|
|
||||||
import { useAppConfig } from '@state';
|
|
||||||
import createReferencedImageDisplaySet from '../utils/createReferencedImageDisplaySet';
|
import createReferencedImageDisplaySet from '../utils/createReferencedImageDisplaySet';
|
||||||
import { usePositionPresentationStore } from '@ohif/extension-cornerstone';
|
import { usePositionPresentationStore } from '@ohif/extension-cornerstone';
|
||||||
import { Icons } from '@ohif/ui-next';
|
import { Icons } from '@ohif/ui-next';
|
||||||
@ -19,10 +17,7 @@ function OHIFCornerstoneSRMeasurementViewport(props: withAppTypes) {
|
|||||||
const { children, dataSource, displaySets, viewportOptions, servicesManager, extensionManager } =
|
const { children, dataSource, displaySets, viewportOptions, servicesManager, extensionManager } =
|
||||||
props;
|
props;
|
||||||
|
|
||||||
const [appConfig] = useAppConfig();
|
const { displaySetService, viewportActionCornersService } = servicesManager.services;
|
||||||
|
|
||||||
const { displaySetService, measurementService, viewportActionCornersService } =
|
|
||||||
servicesManager.services;
|
|
||||||
|
|
||||||
const viewportId = viewportOptions.viewportId;
|
const viewportId = viewportOptions.viewportId;
|
||||||
|
|
||||||
@ -47,7 +42,6 @@ function OHIFCornerstoneSRMeasurementViewport(props: withAppTypes) {
|
|||||||
|
|
||||||
// Optional hook into tracking extension, if present.
|
// Optional hook into tracking extension, if present.
|
||||||
let trackedMeasurements;
|
let trackedMeasurements;
|
||||||
let sendTrackedMeasurementsEvent;
|
|
||||||
|
|
||||||
const hasMeasurementTrackingExtension = extensionManager.registeredExtensionIds.includes(
|
const hasMeasurementTrackingExtension = extensionManager.registeredExtensionIds.includes(
|
||||||
MEASUREMENT_TRACKING_EXTENSION_ID
|
MEASUREMENT_TRACKING_EXTENSION_ID
|
||||||
@ -60,29 +54,6 @@ function OHIFCornerstoneSRMeasurementViewport(props: withAppTypes) {
|
|||||||
|
|
||||||
const tracked = useContext(contextModule.context);
|
const tracked = useContext(contextModule.context);
|
||||||
trackedMeasurements = tracked?.[0];
|
trackedMeasurements = tracked?.[0];
|
||||||
sendTrackedMeasurementsEvent = tracked?.[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sendTrackedMeasurementsEvent) {
|
|
||||||
// if no panels from measurement-tracking extension is used, this code will run
|
|
||||||
trackedMeasurements = null;
|
|
||||||
sendTrackedMeasurementsEvent = (eventName, { displaySetInstanceUID }) => {
|
|
||||||
measurementService.clearMeasurements();
|
|
||||||
const { SeriesInstanceUIDs } = hydrateStructuredReport(
|
|
||||||
{ servicesManager, extensionManager, appConfig },
|
|
||||||
displaySetInstanceUID
|
|
||||||
);
|
|
||||||
|
|
||||||
const displaySets = displaySetService.getDisplaySetsForSeries(SeriesInstanceUIDs[0]);
|
|
||||||
if (displaySets.length) {
|
|
||||||
viewportGridService.setDisplaySetsForViewports([
|
|
||||||
{
|
|
||||||
viewportId: activeViewportId,
|
|
||||||
displaySetInstanceUIDs: [displaySets[0].displaySetInstanceUID],
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -296,8 +267,8 @@ function OHIFCornerstoneSRMeasurementViewport(props: withAppTypes) {
|
|||||||
viewportId,
|
viewportId,
|
||||||
isRehydratable: srDisplaySet.isRehydratable,
|
isRehydratable: srDisplaySet.isRehydratable,
|
||||||
isLocked,
|
isLocked,
|
||||||
sendTrackedMeasurementsEvent,
|
|
||||||
t,
|
t,
|
||||||
|
servicesManager,
|
||||||
}),
|
}),
|
||||||
indexPriority: -100,
|
indexPriority: -100,
|
||||||
location: viewportActionCornersService.LOCATIONS.topLeft,
|
location: viewportActionCornersService.LOCATIONS.topLeft,
|
||||||
@ -316,15 +287,7 @@ function OHIFCornerstoneSRMeasurementViewport(props: withAppTypes) {
|
|||||||
location: viewportActionCornersService.LOCATIONS.topRight,
|
location: viewportActionCornersService.LOCATIONS.topRight,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
}, [
|
}, [isLocked, onMeasurementChange, srDisplaySet, t, viewportActionCornersService, viewportId]);
|
||||||
isLocked,
|
|
||||||
onMeasurementChange,
|
|
||||||
sendTrackedMeasurementsEvent,
|
|
||||||
srDisplaySet,
|
|
||||||
t,
|
|
||||||
viewportActionCornersService,
|
|
||||||
viewportId,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
let childrenWithProps = null;
|
let childrenWithProps = null;
|
||||||
@ -412,16 +375,9 @@ function _getStatusComponent({
|
|||||||
viewportId,
|
viewportId,
|
||||||
isRehydratable,
|
isRehydratable,
|
||||||
isLocked,
|
isLocked,
|
||||||
sendTrackedMeasurementsEvent,
|
|
||||||
t,
|
t,
|
||||||
|
servicesManager,
|
||||||
}) {
|
}) {
|
||||||
const handleMouseUp = () => {
|
|
||||||
sendTrackedMeasurementsEvent('HYDRATE_SR', {
|
|
||||||
displaySetInstanceUID: srDisplaySet.displaySetInstanceUID,
|
|
||||||
viewportId,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const loadStr = t('LOAD');
|
const loadStr = t('LOAD');
|
||||||
|
|
||||||
// 1 - Incompatible
|
// 1 - Incompatible
|
||||||
@ -467,23 +423,46 @@ function _getStatusComponent({
|
|||||||
ToolTipMessage = () => <div>{`Click ${loadStr} to restore measurements.`}</div>;
|
ToolTipMessage = () => <div>{`Click ${loadStr} to restore measurements.`}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StatusArea = () => (
|
const StatusArea = () => {
|
||||||
<div className="flex h-6 cursor-default text-sm leading-6 text-white">
|
const { toolbarButtons: loadSRMeasurementsButtons, onInteraction } = useToolbar({
|
||||||
<div className="bg-customgray-100 flex min-w-[45px] items-center rounded-l-xl rounded-r p-1">
|
servicesManager,
|
||||||
<StatusIcon />
|
buttonSection: 'loadSRMeasurements',
|
||||||
<span className="ml-1">SR</span>
|
});
|
||||||
</div>
|
|
||||||
{state === 3 && (
|
const commandOptions = {
|
||||||
<div
|
displaySetInstanceUID: srDisplaySet.displaySetInstanceUID,
|
||||||
className="bg-primary-main hover:bg-primary-light ml-1 cursor-pointer rounded px-1.5 hover:text-black"
|
viewportId,
|
||||||
// Using onMouseUp here because onClick is not working when the viewport is not active and is styled with pointer-events:none
|
};
|
||||||
onMouseUp={handleMouseUp}
|
|
||||||
>
|
return (
|
||||||
{loadStr}
|
<div className="flex h-6 cursor-default text-sm leading-6 text-white">
|
||||||
|
<div className="bg-customgray-100 flex min-w-[45px] items-center rounded-l-xl rounded-r p-1">
|
||||||
|
<StatusIcon />
|
||||||
|
<span className="ml-1">SR</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
{state === 3 && (
|
||||||
</div>
|
<>
|
||||||
);
|
{loadSRMeasurementsButtons.map(toolDef => {
|
||||||
|
if (!toolDef) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const { id, Component, componentProps } = toolDef;
|
||||||
|
const tool = (
|
||||||
|
<Component
|
||||||
|
key={id}
|
||||||
|
id={id}
|
||||||
|
onInteraction={args => onInteraction({ ...args, ...commandOptions })}
|
||||||
|
{...componentProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return <div key={id}>{tool}</div>;
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import getSopClassHandlerModule from './getSopClassHandlerModule';
|
import getSopClassHandlerModule from './getSopClassHandlerModule';
|
||||||
import { srProtocol } from './getHangingProtocolModule';
|
import { srProtocol } from './getHangingProtocolModule';
|
||||||
import onModeEnter from './onModeEnter';
|
|
||||||
import getCommandsModule from './commandsModule';
|
import getCommandsModule from './commandsModule';
|
||||||
import preRegistration from './init';
|
import preRegistration from './init';
|
||||||
import { id } from './id.js';
|
import { id } from './id.js';
|
||||||
@ -9,6 +8,8 @@ import toolNames from './tools/toolNames';
|
|||||||
import hydrateStructuredReport from './utils/hydrateStructuredReport';
|
import hydrateStructuredReport from './utils/hydrateStructuredReport';
|
||||||
import createReferencedImageDisplaySet from './utils/createReferencedImageDisplaySet';
|
import createReferencedImageDisplaySet from './utils/createReferencedImageDisplaySet';
|
||||||
import Enums from './enums';
|
import Enums from './enums';
|
||||||
|
import { ViewportActionButton } from '@ohif/ui';
|
||||||
|
import i18n from '@ohif/i18n';
|
||||||
|
|
||||||
const Component = React.lazy(() => {
|
const Component = React.lazy(() => {
|
||||||
return import(/* webpackPrefetch: true */ './components/OHIFCornerstoneSRViewport');
|
return import(/* webpackPrefetch: true */ './components/OHIFCornerstoneSRViewport');
|
||||||
@ -30,7 +31,30 @@ const dicomSRExtension = {
|
|||||||
* Only required property. Should be a unique value across all extensions.
|
* Only required property. Should be a unique value across all extensions.
|
||||||
*/
|
*/
|
||||||
id,
|
id,
|
||||||
onModeEnter,
|
|
||||||
|
onModeEnter({ servicesManager }) {
|
||||||
|
const { toolbarService } = servicesManager.services;
|
||||||
|
|
||||||
|
toolbarService.addButtons([
|
||||||
|
{
|
||||||
|
// A base/default button for loading measurements. It is added to the toolbar below.
|
||||||
|
// Customizations to this button can be made in the mode or by another extension.
|
||||||
|
// For example, the button label can be changed and/or the command to clear
|
||||||
|
// the measurements can be dropped.
|
||||||
|
id: 'loadSRMeasurements',
|
||||||
|
component: props => (
|
||||||
|
<ViewportActionButton {...props}>{i18n.t('Common:LOAD')}</ViewportActionButton>
|
||||||
|
),
|
||||||
|
props: {
|
||||||
|
commands: ['clearMeasurements', 'loadSRMeasurements'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// The toolbar used in the viewport's status bar. Modes and extensions can further customize
|
||||||
|
// it to optionally add other buttons.
|
||||||
|
toolbarService.createButtonSection('loadSRMeasurements', ['loadSRMeasurements']);
|
||||||
|
},
|
||||||
|
|
||||||
preRegistration,
|
preRegistration,
|
||||||
|
|
||||||
|
|||||||
@ -41,7 +41,7 @@ const convertSites = (codingValues, sites) => {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export default function hydrateStructuredReport(
|
export default function hydrateStructuredReport(
|
||||||
{ servicesManager, extensionManager, appConfig }: withAppTypes,
|
{ servicesManager, extensionManager }: withAppTypes,
|
||||||
displaySetInstanceUID
|
displaySetInstanceUID
|
||||||
) {
|
) {
|
||||||
const annotationManager = CsAnnotation.state.getAnnotationManager();
|
const annotationManager = CsAnnotation.state.getAnnotationManager();
|
||||||
|
|||||||
@ -98,7 +98,7 @@ const commandsModule = ({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
clearMeasurements: () => {
|
clearMeasurements: () => {
|
||||||
measurementService.clear();
|
measurementService.clearMeasurements();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -272,6 +272,14 @@ function TrackedMeasurementsContextProvider(
|
|||||||
viewports,
|
viewports,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// The command needs to be bound to the context's sendTrackedMeasurementsEvent
|
||||||
|
// so the command has to be registered in a React component.
|
||||||
|
commandsManager.registerCommand('DEFAULT', 'loadTrackedSRMeasurements', {
|
||||||
|
commandFn: props => sendTrackedMeasurementsEvent('HYDRATE_SR', props),
|
||||||
|
});
|
||||||
|
}, [commandsManager, sendTrackedMeasurementsEvent]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TrackedMeasurementsContext.Provider
|
<TrackedMeasurementsContext.Provider
|
||||||
value={[trackedMeasurements, sendTrackedMeasurementsEvent]}
|
value={[trackedMeasurements, sendTrackedMeasurementsEvent]}
|
||||||
|
|||||||
@ -1,7 +1,11 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
import getContextModule from './getContextModule';
|
import getContextModule from './getContextModule';
|
||||||
import getPanelModule from './getPanelModule';
|
import getPanelModule from './getPanelModule';
|
||||||
import getViewportModule from './getViewportModule';
|
import getViewportModule from './getViewportModule';
|
||||||
import { id } from './id.js';
|
import { id } from './id.js';
|
||||||
|
import { ViewportActionButton } from '@ohif/ui';
|
||||||
|
import i18n from '@ohif/i18n';
|
||||||
|
|
||||||
const measurementTrackingExtension = {
|
const measurementTrackingExtension = {
|
||||||
/**
|
/**
|
||||||
@ -12,6 +16,28 @@ const measurementTrackingExtension = {
|
|||||||
getContextModule,
|
getContextModule,
|
||||||
getPanelModule,
|
getPanelModule,
|
||||||
getViewportModule,
|
getViewportModule,
|
||||||
|
|
||||||
|
onModeEnter({ servicesManager }) {
|
||||||
|
const { toolbarService } = servicesManager.services;
|
||||||
|
|
||||||
|
toolbarService.addButtons(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
// A button for loading tracked, SR measurements.
|
||||||
|
// Note that the command run is registered in TrackedMeasurementsContext
|
||||||
|
// because it must be bound to a React context's data.
|
||||||
|
id: 'loadSRMeasurements',
|
||||||
|
component: props => (
|
||||||
|
<ViewportActionButton {...props}>{i18n.t('Common:LOAD')}</ViewportActionButton>
|
||||||
|
),
|
||||||
|
props: {
|
||||||
|
commands: ['loadTrackedSRMeasurements'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
true // replace the button if it is already defined
|
||||||
|
);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default measurementTrackingExtension;
|
export default measurementTrackingExtension;
|
||||||
|
|||||||
@ -128,10 +128,11 @@ export default class ToolbarService extends PubSubService {
|
|||||||
/**
|
/**
|
||||||
* Adds buttons to the toolbar.
|
* Adds buttons to the toolbar.
|
||||||
* @param buttons - The buttons to be added.
|
* @param buttons - The buttons to be added.
|
||||||
|
* @param replace - Flag indicating if any existing button with the same id as one being added should be replaced
|
||||||
*/
|
*/
|
||||||
public addButtons(buttons: Button[]): void {
|
public addButtons(buttons: Button[], replace: boolean = false): void {
|
||||||
buttons.forEach(button => {
|
buttons.forEach(button => {
|
||||||
if (!this.state.buttons[button.id]) {
|
if (replace || !this.state.buttons[button.id]) {
|
||||||
if (!button.props) {
|
if (!button.props) {
|
||||||
button.props = {};
|
button.props = {};
|
||||||
}
|
}
|
||||||
@ -383,12 +384,18 @@ export default class ToolbarService extends PubSubService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a button section with the specified key and buttons.
|
* Creates a button section with the specified key and buttons.
|
||||||
|
* Buttons already in the section (i.e. with the same ids) will NOT be added twice.
|
||||||
* @param {string} key - The key of the button section.
|
* @param {string} key - The key of the button section.
|
||||||
* @param {Array} buttons - The buttons to be added to the section.
|
* @param {Array} buttons - The buttons to be added to the section.
|
||||||
*/
|
*/
|
||||||
createButtonSection(key, buttons) {
|
createButtonSection(key, buttons) {
|
||||||
if (this.state.buttonSections[key]) {
|
if (this.state.buttonSections[key]) {
|
||||||
this.state.buttonSections[key].push(...buttons);
|
this.state.buttonSections[key].push(
|
||||||
|
buttons.filter(
|
||||||
|
button =>
|
||||||
|
!this.state.buttonSections[key].find(sectionButton => sectionButton.id === button.id)
|
||||||
|
)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
this.state.buttonSections[key] = buttons;
|
this.state.buttonSections[key] = buttons;
|
||||||
}
|
}
|
||||||
@ -452,7 +459,7 @@ export default class ToolbarService extends PubSubService {
|
|||||||
|
|
||||||
const buttonType = buttonTypes[uiType];
|
const buttonType = buttonTypes[uiType];
|
||||||
|
|
||||||
if (!buttonType) {
|
if (!buttonType && !component) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,28 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
function ViewportActionButton({ onInteraction, commands, id, children }) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="bg-primary-main hover:bg-primary-light ml-1 cursor-pointer rounded px-1.5 hover:text-black"
|
||||||
|
// Using onMouseUp here because onClick is not working when the viewport is not active and is styled with pointer-events:none
|
||||||
|
onMouseUp={() => {
|
||||||
|
onInteraction({
|
||||||
|
itemId: id,
|
||||||
|
commands,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewportActionButton.propTypes = {
|
||||||
|
id: PropTypes.string,
|
||||||
|
onInteraction: PropTypes.func.isRequired,
|
||||||
|
commands: PropTypes.array,
|
||||||
|
children: PropTypes.node,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ViewportActionButton;
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
import ViewportActionButton from './ViewportActionButton';
|
||||||
|
export default ViewportActionButton;
|
||||||
@ -86,6 +86,7 @@ import LabellingFlow from './Labelling';
|
|||||||
import SwitchButton, { SwitchLabelLocation } from './SwitchButton';
|
import SwitchButton, { SwitchLabelLocation } from './SwitchButton';
|
||||||
import * as AllInOneMenu from './AllInOneMenu';
|
import * as AllInOneMenu from './AllInOneMenu';
|
||||||
import ViewportActionArrows from './ViewportActionArrows';
|
import ViewportActionArrows from './ViewportActionArrows';
|
||||||
|
import ViewportActionButton from './ViewportActionButton';
|
||||||
import HeaderPatientInfo from './HeaderPatientInfo';
|
import HeaderPatientInfo from './HeaderPatientInfo';
|
||||||
import LegacySplitButton from './LegacySplitButton';
|
import LegacySplitButton from './LegacySplitButton';
|
||||||
import { ToolSettings } from './AdvancedToolbox';
|
import { ToolSettings } from './AdvancedToolbox';
|
||||||
@ -180,6 +181,7 @@ export {
|
|||||||
Viewport,
|
Viewport,
|
||||||
ViewportActionArrows,
|
ViewportActionArrows,
|
||||||
ViewportActionBar,
|
ViewportActionBar,
|
||||||
|
ViewportActionButton,
|
||||||
ViewportActionCorners,
|
ViewportActionCorners,
|
||||||
ViewportActionCornersLocations,
|
ViewportActionCornersLocations,
|
||||||
ViewportDownloadForm,
|
ViewportDownloadForm,
|
||||||
|
|||||||
@ -115,6 +115,7 @@ export {
|
|||||||
Viewport,
|
Viewport,
|
||||||
ViewportActionArrows,
|
ViewportActionArrows,
|
||||||
ViewportActionBar,
|
ViewportActionBar,
|
||||||
|
ViewportActionButton,
|
||||||
ViewportActionCorners,
|
ViewportActionCorners,
|
||||||
ViewportActionCornersLocations,
|
ViewportActionCornersLocations,
|
||||||
ViewportDownloadForm,
|
ViewportDownloadForm,
|
||||||
|
|||||||
41
yarn.lock
41
yarn.lock
@ -3203,7 +3203,7 @@
|
|||||||
"@docusaurus/theme-search-algolia" "3.6.1"
|
"@docusaurus/theme-search-algolia" "3.6.1"
|
||||||
"@docusaurus/types" "3.6.1"
|
"@docusaurus/types" "3.6.1"
|
||||||
|
|
||||||
"@docusaurus/react-loadable@5.5.2":
|
"@docusaurus/react-loadable@5.5.2", "react-loadable@npm:@docusaurus/react-loadable@5.5.2":
|
||||||
version "5.5.2"
|
version "5.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz#81aae0db81ecafbdaee3651f12804580868fa6ce"
|
resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz#81aae0db81ecafbdaee3651f12804580868fa6ce"
|
||||||
integrity sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ==
|
integrity sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ==
|
||||||
@ -20853,14 +20853,6 @@ react-loadable-ssr-addon-v5-slorber@^1.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.10.3"
|
"@babel/runtime" "^7.10.3"
|
||||||
|
|
||||||
"react-loadable@npm:@docusaurus/react-loadable@5.5.2":
|
|
||||||
version "5.5.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz#81aae0db81ecafbdaee3651f12804580868fa6ce"
|
|
||||||
integrity sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ==
|
|
||||||
dependencies:
|
|
||||||
"@types/react" "*"
|
|
||||||
prop-types "^15.6.2"
|
|
||||||
|
|
||||||
"react-loadable@npm:@docusaurus/react-loadable@6.0.0":
|
"react-loadable@npm:@docusaurus/react-loadable@6.0.0":
|
||||||
version "6.0.0"
|
version "6.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-6.0.0.tgz#de6c7f73c96542bd70786b8e522d535d69069dc4"
|
resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-6.0.0.tgz#de6c7f73c96542bd70786b8e522d535d69069dc4"
|
||||||
@ -22773,7 +22765,7 @@ string-natural-compare@^3.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
|
resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
|
||||||
integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
|
integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
|
||||||
|
|
||||||
"string-width-cjs@npm:string-width@^4.2.0":
|
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||||
version "4.2.3"
|
version "4.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
@ -22791,15 +22783,6 @@ string-width@^1.0.1:
|
|||||||
is-fullwidth-code-point "^1.0.0"
|
is-fullwidth-code-point "^1.0.0"
|
||||||
strip-ansi "^3.0.0"
|
strip-ansi "^3.0.0"
|
||||||
|
|
||||||
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
|
||||||
version "4.2.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
|
||||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
|
||||||
dependencies:
|
|
||||||
emoji-regex "^8.0.0"
|
|
||||||
is-fullwidth-code-point "^3.0.0"
|
|
||||||
strip-ansi "^6.0.1"
|
|
||||||
|
|
||||||
string-width@^2.1.1:
|
string-width@^2.1.1:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
|
||||||
@ -22910,7 +22893,7 @@ stringify-object@^3.3.0:
|
|||||||
is-obj "^1.0.1"
|
is-obj "^1.0.1"
|
||||||
is-regexp "^1.0.0"
|
is-regexp "^1.0.0"
|
||||||
|
|
||||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||||
version "6.0.1"
|
version "6.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
@ -22931,13 +22914,6 @@ strip-ansi@^4.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ansi-regex "^3.0.0"
|
ansi-regex "^3.0.0"
|
||||||
|
|
||||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
|
||||||
version "6.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
|
||||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
|
||||||
dependencies:
|
|
||||||
ansi-regex "^5.0.1"
|
|
||||||
|
|
||||||
strip-ansi@^7.0.0, strip-ansi@^7.0.1:
|
strip-ansi@^7.0.0, strip-ansi@^7.0.1:
|
||||||
version "7.1.0"
|
version "7.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
|
||||||
@ -25255,7 +25231,7 @@ worker-loader@3.0.8, worker-loader@^3.0.8:
|
|||||||
loader-utils "^2.0.0"
|
loader-utils "^2.0.0"
|
||||||
schema-utils "^3.0.0"
|
schema-utils "^3.0.0"
|
||||||
|
|
||||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
||||||
version "7.0.0"
|
version "7.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||||
@ -25281,15 +25257,6 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0:
|
|||||||
string-width "^4.1.0"
|
string-width "^4.1.0"
|
||||||
strip-ansi "^6.0.0"
|
strip-ansi "^6.0.0"
|
||||||
|
|
||||||
wrap-ansi@^7.0.0:
|
|
||||||
version "7.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
|
||||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
|
||||||
dependencies:
|
|
||||||
ansi-styles "^4.0.0"
|
|
||||||
string-width "^4.1.0"
|
|
||||||
strip-ansi "^6.0.0"
|
|
||||||
|
|
||||||
wrap-ansi@^8.0.1, wrap-ansi@^8.1.0:
|
wrap-ansi@^8.0.1, wrap-ansi@^8.1.0:
|
||||||
version "8.1.0"
|
version "8.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user