feat(actions): simplify action corner api through the toolbarService (#5033)
This commit is contained in:
1 parent
f71b88e639
commit
7c15bb8901
246 files changed
+7930
-4270
No files matched your search
@@ -1,8 +1,4 @@
|
||||
const RESPONSE = {
|
||||
NO_NEVER: -1,
|
||||
CANCEL: 0,
|
||||
HYDRATE_SEG: 5,
|
||||
};
|
||||
import { utils, Types } from '@ohif/extension-cornerstone';
|
||||
|
||||
function promptHydrateSEG({
|
||||
servicesManager,
|
||||
@@ -10,75 +6,20 @@ function promptHydrateSEG({
|
||||
viewportId,
|
||||
preHydrateCallbacks,
|
||||
hydrateCallback,
|
||||
}: withAppTypes) {
|
||||
const { uiViewportDialogService, customizationService } = servicesManager.services;
|
||||
const extensionManager = servicesManager._extensionManager;
|
||||
const appConfig = extensionManager._appConfig;
|
||||
|
||||
return new Promise(async function (resolve, reject) {
|
||||
const promptResult = appConfig?.disableConfirmationPrompts
|
||||
? RESPONSE.HYDRATE_SEG
|
||||
: await _askHydrate(uiViewportDialogService, customizationService, viewportId);
|
||||
|
||||
if (promptResult === RESPONSE.HYDRATE_SEG) {
|
||||
preHydrateCallbacks?.forEach(callback => {
|
||||
callback();
|
||||
});
|
||||
|
||||
window.setTimeout(async () => {
|
||||
const isHydrated = await hydrateCallback({
|
||||
segDisplaySet,
|
||||
viewportId,
|
||||
});
|
||||
|
||||
resolve(isHydrated);
|
||||
}, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function _askHydrate(
|
||||
uiViewportDialogService: AppTypes.UIViewportDialogService,
|
||||
customizationService: AppTypes.CustomizationService,
|
||||
viewportId
|
||||
) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
const message = customizationService.getCustomization('viewportNotification.hydrateSEGMessage');
|
||||
const actions = [
|
||||
{
|
||||
id: 'no-hydrate',
|
||||
type: 'secondary',
|
||||
text: 'No',
|
||||
value: RESPONSE.CANCEL,
|
||||
},
|
||||
{
|
||||
id: 'yes-hydrate',
|
||||
type: 'primary',
|
||||
text: 'Yes',
|
||||
value: RESPONSE.HYDRATE_SEG,
|
||||
},
|
||||
];
|
||||
const onSubmit = result => {
|
||||
uiViewportDialogService.hide();
|
||||
resolve(result);
|
||||
};
|
||||
|
||||
uiViewportDialogService.show({
|
||||
viewportId,
|
||||
type: 'info',
|
||||
message,
|
||||
actions,
|
||||
onSubmit,
|
||||
onOutsideClick: () => {
|
||||
uiViewportDialogService.hide();
|
||||
resolve(RESPONSE.CANCEL);
|
||||
},
|
||||
onKeyPress: event => {
|
||||
if (event.key === 'Enter') {
|
||||
onSubmit(RESPONSE.HYDRATE_SEG);
|
||||
}
|
||||
},
|
||||
});
|
||||
}: {
|
||||
servicesManager: AppTypes.ServicesManager;
|
||||
segDisplaySet: AppTypes.DisplaySet;
|
||||
viewportId: string;
|
||||
preHydrateCallbacks?: Types.HydrationCallback[];
|
||||
hydrateCallback: Types.HydrationCallback;
|
||||
}) {
|
||||
return utils.promptHydrationDialog({
|
||||
servicesManager,
|
||||
viewportId,
|
||||
displaySet: segDisplaySet as AppTypes.DisplaySet,
|
||||
preHydrateCallbacks,
|
||||
hydrateCallback,
|
||||
type: 'SEG',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,36 +1,24 @@
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ViewportActionArrows } from '@ohif/ui-next';
|
||||
import { useViewportGrid } from '@ohif/ui-next';
|
||||
import createSEGToolGroupAndAddTools from '../utils/initSEGToolGroup';
|
||||
import promptHydrateSEG from '../utils/promptHydrateSEG';
|
||||
import _getStatusComponent from './_getStatusComponent';
|
||||
import { usePositionPresentationStore } from '@ohif/extension-cornerstone';
|
||||
import { usePositionPresentationStore, OHIFCornerstoneViewport } from '@ohif/extension-cornerstone';
|
||||
import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
|
||||
import { utils } from '@ohif/extension-cornerstone';
|
||||
import { useSystem } from '@ohif/core/src/contextProviders/SystemProvider';
|
||||
|
||||
const SEG_TOOLGROUP_BASE_NAME = 'SEGToolGroup';
|
||||
|
||||
function OHIFCornerstoneSEGViewport(props: withAppTypes) {
|
||||
const {
|
||||
children,
|
||||
displaySets,
|
||||
viewportOptions,
|
||||
servicesManager,
|
||||
extensionManager,
|
||||
commandsManager,
|
||||
} = props;
|
||||
|
||||
const { t } = useTranslation('SEGViewport');
|
||||
const { servicesManager, commandsManager } = useSystem();
|
||||
const { children, displaySets, viewportOptions } = props as {
|
||||
children: React.ReactNode;
|
||||
displaySets: AppTypes.DisplaySet[];
|
||||
viewportOptions: AppTypes.ViewportOptions;
|
||||
};
|
||||
const viewportId = viewportOptions.viewportId;
|
||||
|
||||
const {
|
||||
displaySetService,
|
||||
toolGroupService,
|
||||
segmentationService,
|
||||
customizationService,
|
||||
viewportActionCornersService,
|
||||
} = servicesManager.services;
|
||||
const { displaySetService, toolGroupService, segmentationService, customizationService } =
|
||||
servicesManager.services;
|
||||
|
||||
const LoadingIndicatorTotalPercent = customizationService.getCustomization(
|
||||
'ui.loadingIndicatorTotalPercent'
|
||||
@@ -47,7 +35,6 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) {
|
||||
const [viewportGrid, viewportGridService] = useViewportGrid();
|
||||
|
||||
// States
|
||||
const selectedSegmentObjectIndex: number = 0;
|
||||
const { setPositionPresentation } = usePositionPresentationStore();
|
||||
|
||||
// Hydration means that the SEG is opened and segments are loaded into the
|
||||
@@ -55,9 +42,7 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) {
|
||||
// same frameOfReferenceUID as the referencedSeriesUID of the SEG. However,
|
||||
// loading basically means SEG loading over network and bit unpacking of the
|
||||
// SEG data.
|
||||
const [isHydrated, setIsHydrated] = useState(segDisplaySet.isHydrated);
|
||||
const [segIsLoading, setSegIsLoading] = useState(!segDisplaySet.isLoaded);
|
||||
const [element, setElement] = useState(null);
|
||||
const [processingProgress, setProcessingProgress] = useState({
|
||||
percentComplete: null,
|
||||
totalSegments: null,
|
||||
@@ -82,36 +67,10 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) {
|
||||
displaySet: referencedDisplaySet,
|
||||
metadata: referencedDisplaySetMetadata,
|
||||
};
|
||||
/**
|
||||
* OnElementEnabled callback which is called after the cornerstoneExtension
|
||||
* has enabled the element. Note: we delegate all the image rendering to
|
||||
* cornerstoneExtension, so we don't need to do anything here regarding
|
||||
* the image rendering, element enabling etc.
|
||||
*/
|
||||
const onElementEnabled = evt => {
|
||||
setElement(evt.detail.element);
|
||||
};
|
||||
|
||||
const onElementDisabled = () => {
|
||||
setElement(null);
|
||||
};
|
||||
|
||||
const storePresentationState = useCallback(() => {
|
||||
viewportGrid?.viewports.forEach(({ viewportId }) => {
|
||||
commandsManager.runCommand('storePresentation', {
|
||||
viewportId,
|
||||
});
|
||||
});
|
||||
}, [viewportGrid]);
|
||||
|
||||
const getCornerstoneViewport = useCallback(() => {
|
||||
const { component: Component } = extensionManager.getModuleEntry(
|
||||
'@ohif/extension-cornerstone.viewportModule.cornerstone'
|
||||
);
|
||||
|
||||
// Todo: jump to the center of the first segment
|
||||
return (
|
||||
<Component
|
||||
<OHIFCornerstoneViewport
|
||||
{...props}
|
||||
displaySets={[segDisplaySet]}
|
||||
viewportOptions={{
|
||||
@@ -123,66 +82,35 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) {
|
||||
}}
|
||||
onElementEnabled={evt => {
|
||||
props.onElementEnabled?.(evt);
|
||||
onElementEnabled(evt);
|
||||
}}
|
||||
onElementDisabled={onElementDisabled}
|
||||
></Component>
|
||||
/>
|
||||
);
|
||||
}, [viewportId, segDisplaySet, toolGroupId]);
|
||||
|
||||
const onSegmentChange = useCallback(
|
||||
direction => {
|
||||
utils.handleSegmentChange({
|
||||
direction,
|
||||
segDisplaySet: segDisplaySet,
|
||||
viewportId,
|
||||
selectedSegmentObjectIndex,
|
||||
segmentationService,
|
||||
});
|
||||
},
|
||||
[selectedSegmentObjectIndex]
|
||||
);
|
||||
|
||||
const hydrateSEG = useCallback(() => {
|
||||
// update the previously stored segmentationPresentation with the new viewportId
|
||||
// presentation so that when we put the referencedDisplaySet back in the viewport
|
||||
// it will have the correct segmentation representation hydrated
|
||||
commandsManager.runCommand('updateStoredSegmentationPresentation', {
|
||||
displaySet: segDisplaySet,
|
||||
type: SegmentationRepresentations.Labelmap,
|
||||
});
|
||||
|
||||
// update the previously stored positionPresentation with the new viewportId
|
||||
// presentation so that when we put the referencedDisplaySet back in the viewport
|
||||
// it will be in the correct position zoom and pan
|
||||
commandsManager.runCommand('updateStoredPositionPresentation', {
|
||||
viewportId,
|
||||
displaySetInstanceUIDs: [referencedDisplaySet.displaySetInstanceUID],
|
||||
});
|
||||
|
||||
commandsManager.runCommand('loadSegmentationDisplaySetsForViewport', {
|
||||
viewportId,
|
||||
displaySetInstanceUIDs: [referencedDisplaySet.displaySetInstanceUID],
|
||||
});
|
||||
}, [commandsManager, viewportId, referencedDisplaySet, segDisplaySet]);
|
||||
}, [viewportId, segDisplaySet, toolGroupId, props, viewportOptions]);
|
||||
|
||||
useEffect(() => {
|
||||
if (segIsLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if not active viewport, return
|
||||
if (viewportId !== activeViewportId) {
|
||||
return;
|
||||
}
|
||||
|
||||
promptHydrateSEG({
|
||||
servicesManager,
|
||||
viewportId,
|
||||
segDisplaySet,
|
||||
preHydrateCallbacks: [storePresentationState],
|
||||
hydrateCallback: hydrateSEG,
|
||||
}).then(isHydrated => {
|
||||
if (isHydrated) {
|
||||
setIsHydrated(true);
|
||||
}
|
||||
hydrateCallback: async () => {
|
||||
await commandsManager.runCommand('hydrateSecondaryDisplaySet', {
|
||||
displaySet: segDisplaySet,
|
||||
viewportId,
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
});
|
||||
}, [servicesManager, viewportId, segDisplaySet, segIsLoading, hydrateSEG]);
|
||||
}, [servicesManager, viewportId, segDisplaySet, segIsLoading, commandsManager, activeViewportId]);
|
||||
|
||||
useEffect(() => {
|
||||
// on new seg display set, remove all segmentations from all viewports
|
||||
@@ -283,54 +211,6 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onStatusClick = useCallback(async () => {
|
||||
// Before hydrating a SEG and make it added to all viewports in the grid
|
||||
// that share the same frameOfReferenceUID, we need to store the viewport grid
|
||||
// presentation state, so that we can restore it after hydrating the SEG. This is
|
||||
// required if the user has changed the viewport (other viewport than SEG viewport)
|
||||
// presentation state (w/l and invert) and then opens the SEG. If we don't store
|
||||
// the presentation state, the viewport will be reset to the default presentation
|
||||
storePresentationState();
|
||||
hydrateSEG();
|
||||
}, [storePresentationState, hydrateSEG]);
|
||||
|
||||
useEffect(() => {
|
||||
viewportActionCornersService.addComponents([
|
||||
{
|
||||
viewportId,
|
||||
id: 'viewportStatusComponent',
|
||||
component: _getStatusComponent({
|
||||
isHydrated,
|
||||
onStatusClick,
|
||||
}),
|
||||
indexPriority: -100,
|
||||
location: viewportActionCornersService.LOCATIONS.topRight,
|
||||
},
|
||||
{
|
||||
viewportId,
|
||||
id: 'viewportActionArrowsComponent',
|
||||
component: (
|
||||
<ViewportActionArrows
|
||||
key="actionArrows"
|
||||
onArrowsClick={onSegmentChange}
|
||||
className={
|
||||
viewportId === activeViewportId ? 'visible' : 'invisible group-hover/pane:visible'
|
||||
}
|
||||
></ViewportActionArrows>
|
||||
),
|
||||
indexPriority: 0,
|
||||
location: viewportActionCornersService.LOCATIONS.topRight,
|
||||
},
|
||||
]);
|
||||
}, [
|
||||
activeViewportId,
|
||||
isHydrated,
|
||||
onSegmentChange,
|
||||
onStatusClick,
|
||||
viewportActionCornersService,
|
||||
viewportId,
|
||||
]);
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
let childrenWithProps = null;
|
||||
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ViewportActionButton } from '@ohif/ui-next';
|
||||
import { Icons, Tooltip, TooltipTrigger, TooltipContent } from '@ohif/ui-next';
|
||||
|
||||
export default function _getStatusComponent({ isHydrated, onStatusClick }) {
|
||||
let ToolTipMessage = null;
|
||||
let StatusIcon = null;
|
||||
|
||||
switch (isHydrated) {
|
||||
case true:
|
||||
StatusIcon = () => <Icons.ByName name="status-alert" />;
|
||||
ToolTipMessage = () => <div>This Segmentation is loaded in the segmentation panel</div>;
|
||||
break;
|
||||
case false:
|
||||
StatusIcon = () => (
|
||||
<Icons.ByName
|
||||
className="text-muted-foreground h-4 w-4"
|
||||
name="status-untracked"
|
||||
/>
|
||||
);
|
||||
ToolTipMessage = () => <div>Click LOAD to load segmentation.</div>;
|
||||
}
|
||||
|
||||
const StatusArea = () => {
|
||||
const { t } = useTranslation('Common');
|
||||
const loadStr = t('LOAD');
|
||||
|
||||
return (
|
||||
<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">SEG</span>
|
||||
</div>
|
||||
{!isHydrated && (
|
||||
<ViewportActionButton onInteraction={onStatusClick}>{loadStr}</ViewportActionButton>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{ToolTipMessage && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span>
|
||||
<StatusArea />
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">
|
||||
<ToolTipMessage />
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
{!ToolTipMessage && <StatusArea />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user