feat: Add customization support for more UI components (#4634)

Co-authored-by: ashikcn <ashik@L1140.trenser.com>
Co-authored-by: Devu Jayalekshmi <devu.jayalekshmi@trenser.com>
Co-authored-by: Arul Mozhi <arul.mozhi@trenser.com>
This commit is contained in:
authored and GitHub committed 2025-02-03 09:18:00 -05:00
1 parent 8f79ebafd9
commit f15eb44b4c
31 files changed
+288 -33

No files matched your search

@@ -1,6 +1,5 @@
import PropTypes from 'prop-types';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { LoadingIndicatorTotalPercent } from '@ohif/ui';
import { useViewportGrid } from '@ohif/ui-next';
function OHIFCornerstonePMAPViewport(props: withAppTypes) {
@@ -13,7 +12,7 @@ function OHIFCornerstonePMAPViewport(props: withAppTypes) {
extensionManager,
} = props;
const viewportId = viewportOptions.viewportId;
const { displaySetService, segmentationService, uiNotificationService } =
const { displaySetService, segmentationService, uiNotificationService, customizationService } =
servicesManager.services;
// PMAP viewport will always have a single display set
@@ -21,6 +20,10 @@ function OHIFCornerstonePMAPViewport(props: withAppTypes) {
throw new Error('PMAP viewport must have a single display set');
}
const LoadingIndicatorTotalPercent = customizationService.getCustomization(
'ui.loadingIndicatorTotalPercent'
);
const pmapDisplaySet = displaySets[0];
const [viewportGrid, viewportGridService] = useViewportGrid();
const referencedDisplaySetRef = useRef(null);
@@ -1,6 +1,6 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import { LoadingIndicatorTotalPercent, ViewportActionArrows } from '@ohif/ui';
import { ViewportActionArrows } from '@ohif/ui';
import { useViewportGrid } from '@ohif/ui-next';
import promptHydrateRT from '../utils/promptHydrateRT';
@@ -39,6 +39,10 @@ function OHIFCornerstoneRTViewport(props: withAppTypes) {
throw new Error('RT viewport should only have a single display set');
}
const LoadingIndicatorTotalPercent = customizationService.getCustomization(
'ui.loadingIndicatorTotalPercent'
);
const rtDisplaySet = displaySets[0];
const [viewportGrid, viewportGridService] = useViewportGrid();
@@ -1,6 +1,6 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { LoadingIndicatorTotalPercent, ViewportActionArrows } from '@ohif/ui';
import { ViewportActionArrows } from '@ohif/ui';
import { useViewportGrid } from '@ohif/ui-next';
import createSEGToolGroupAndAddTools from '../utils/initSEGToolGroup';
import promptHydrateSEG from '../utils/promptHydrateSEG';
@@ -31,6 +31,10 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) {
viewportActionCornersService,
} = servicesManager.services;
const LoadingIndicatorTotalPercent = customizationService.getCustomization(
'ui.loadingIndicatorTotalPercent'
);
const toolGroupId = `${SEG_TOOLGROUP_BASE_NAME}-${viewportId}`;
// SEG viewport will always have a single display set
+16 -11
View File
@@ -208,8 +208,9 @@ function commandsModule({
*/
setMeasurementLabel: ({ uid }) => {
const labelConfig = customizationService.getCustomization('measurementLabels');
const renderContent = customizationService.getCustomization('ui.labellingComponent');
const measurement = measurementService.getMeasurement(uid);
showLabelAnnotationPopup(measurement, uiDialogService, labelConfig).then(
showLabelAnnotationPopup(measurement, uiDialogService, labelConfig, renderContent).then(
(val: Map<any, any>) => {
measurementService.update(
uid,
@@ -325,16 +326,19 @@ function commandsModule({
renameMeasurement: ({ uid }) => {
const labelConfig = customizationService.getCustomization('measurementLabels');
const renderContent = customizationService.getCustomization('ui.labellingComponent');
const measurement = measurementService.getMeasurement(uid);
showLabelAnnotationPopup(measurement, uiDialogService, labelConfig).then(val => {
measurementService.update(
uid,
{
...val,
},
true
);
});
showLabelAnnotationPopup(measurement, uiDialogService, labelConfig, renderContent).then(
val => {
measurementService.update(
uid,
{
...val,
},
true
);
}
);
},
toggleLockMeasurement: ({ uid }) => {
@@ -376,7 +380,8 @@ function commandsModule({
},
arrowTextCallback: ({ callback, data, uid }) => {
const labelConfig = customizationService.getCustomization('measurementLabels');
callLabelAutocompleteDialog(uiDialogService, callback, {}, labelConfig);
const renderContent = customizationService.getCustomization('ui.labellingComponent');
callLabelAutocompleteDialog(uiDialogService, callback, {}, labelConfig, renderContent);
},
toggleCine: () => {
const { viewports } = viewportGridService.getState();
@@ -1,6 +1,7 @@
import React, { useCallback, useEffect, useRef, useState, ReactElement } from 'react';
import PropTypes from 'prop-types';
import { Button, ProgressLoadingBar } from '@ohif/ui';
import { useSystem } from '@ohif/core';
import { Button } from '@ohif/ui';
import { Icons } from '@ohif/ui-next';
import DicomFileUploader, {
EVENTS,
@@ -40,6 +41,11 @@ function DicomUploadProgress({
dicomFileUploaderArr,
onComplete,
}: DicomUploadProgressProps): ReactElement {
const { servicesManager } = useSystem();
const ProgressLoadingBar =
servicesManager.services.customizationService.getCustomization('ui.progressLoadingBar');
const [totalUploadSize] = useState(
dicomFileUploaderArr.reduce((acc, fileUploader) => acc + fileUploader.getFileSize(), 0)
);
@@ -1,14 +1,16 @@
import React from 'react';
import { useViewportActionCornersContext } from '../contextProviders/ViewportActionCornersProvider';
import { ViewportActionCorners } from '@ohif/ui';
import { useSystem } from '@ohif/core';
export type OHIFViewportActionCornersProps = {
viewportId: string;
};
function OHIFViewportActionCorners({ viewportId }: OHIFViewportActionCornersProps) {
const { servicesManager } = useSystem();
const [viewportActionCornersState] = useViewportActionCornersContext();
const ViewportActionCorners =
servicesManager.services.customizationService.getCustomization('ui.viewportActionCorner');
if (!viewportActionCornersState[viewportId]) {
return null;
}
@@ -1,7 +1,8 @@
import classNames from 'classnames';
import React, { ReactElement, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Button, InputFilterText, LoadingIndicatorProgress } from '@ohif/ui';
import { useSystem } from '@ohif/core';
import { Button, InputFilterText } from '@ohif/ui';
import { Icons } from '@ohif/ui-next';
import { Types } from '@ohif/core';
@@ -16,6 +17,7 @@ function ItemListComponent({
itemList,
onItemClicked,
}: ItemListComponentProps): ReactElement {
const { servicesManager } = useSystem();
const { t } = useTranslation('DataSourceConfiguration');
const [filterValue, setFilterValue] = useState('');
@@ -23,6 +25,10 @@ function ItemListComponent({
setFilterValue('');
}, [itemList]);
const LoadingIndicatorProgress = servicesManager.services.customizationService.getCustomization(
'ui.loadingIndicatorProgress'
);
return (
<div className="flex min-h-[1px] grow flex-col gap-4">
<div className="flex items-center justify-between">
@@ -1,5 +1,4 @@
import * as ContextMenuItemsBuilder from './ContextMenuItemsBuilder';
import ContextMenu from '../../../../platform/ui/src/components/ContextMenu/ContextMenu';
import { CommandsManager } from '@ohif/core';
import { annotation as CsAnnotation } from '@cornerstonejs/tools';
import { Menu, MenuItem, Point, ContextMenuProps } from './types';
@@ -72,6 +71,8 @@ export default class ContextMenuController {
menuId
);
const ContextMenu = this.services.customizationService.getCustomization('ui.contextMenu');
this.services.uiDialogService.dismiss({ id: 'context-menu' });
this.services.uiDialogService.create({
id: 'context-menu',
@@ -1,7 +1,7 @@
import React, { useEffect, useState, useCallback } from 'react';
import PropTypes from 'prop-types';
import { LoadingIndicatorProgress, InvestigationalUseDialog } from '@ohif/ui';
import { InvestigationalUseDialog } from '@ohif/ui';
import { HangingProtocolService, CommandsManager } from '@ohif/core';
import { useAppConfig } from '@state';
import ViewerHeader from './ViewerHeader';
@@ -27,7 +27,7 @@ function ViewerLayout({
}: withAppTypes): React.FunctionComponent {
const [appConfig] = useAppConfig();
const { panelService, hangingProtocolService } = servicesManager.services;
const { panelService, hangingProtocolService, customizationService } = servicesManager.services;
const [showLoadingIndicator, setShowLoadingIndicator] = useState(appConfig.showLoadingIndicator);
const hasPanels = useCallback(
@@ -55,6 +55,10 @@ function ViewerLayout({
setRightPanelClosed
);
const LoadingIndicatorProgress = customizationService.getCustomization(
'ui.loadingIndicatorProgress'
);
/**
* Set body classes (tailwindcss) that don't allow vertical
* or horizontal overflow (no scrolling). Also guarantee window
@@ -0,0 +1,5 @@
import { ContextMenu } from '@ohif/ui';
export default {
'ui.contextMenu': ContextMenu,
};
@@ -0,0 +1,5 @@
import { LabellingFlow } from '@ohif/ui';
export default {
'ui.labellingComponent': LabellingFlow,
};
@@ -0,0 +1,5 @@
import { LoadingIndicatorProgress } from '@ohif/ui';
export default {
'ui.loadingIndicatorProgress': LoadingIndicatorProgress,
};
@@ -0,0 +1,5 @@
import { LoadingIndicatorTotalPercent } from '@ohif/ui';
export default {
'ui.loadingIndicatorTotalPercent': LoadingIndicatorTotalPercent,
};
@@ -0,0 +1,5 @@
import { ProgressLoadingBar } from '@ohif/ui';
export default {
'ui.progressLoadingBar': ProgressLoadingBar,
};
@@ -0,0 +1,5 @@
import { ViewportActionCorners } from '@ohif/ui';
export default {
'ui.viewportActionCorner': ViewportActionCorners,
};
@@ -6,11 +6,17 @@ import customRoutesCustomization from './customizations/customRoutesCustomizatio
import studyBrowserCustomization from './customizations/studyBrowserCustomization';
import overlayItemCustomization from './customizations/overlayItemCustomization';
import contextMenuCustomization from './customizations/contextMenuCustomization';
import contextMenuUICustomization from './customizations/contextMenuUICustomization';
import menuContentCustomization from './customizations/menuContentCustomization';
import getDataSourceConfigurationCustomization from './customizations/dataSourceConfigurationCustomization';
import progressDropdownCustomization from './customizations/progressDropdownCustomization';
import sortingCriteriaCustomization from './customizations/sortingCriteriaCustomization';
import onDropHandlerCustomization from './customizations/onDropHandlerCustomization';
import loadingIndicatorProgressCustomization from './customizations/loadingIndicatorProgressCustomization';
import loadingIndicatorTotalPercentCustomization from './customizations/loadingIndicatorTotalPercentCustomization';
import progressLoadingBarCustomization from './customizations/progressLoadingBarCustomization';
import viewportActionCornersCustomization from './customizations/viewportActionCornersCustomization';
import labellingFlowCustomization from './customizations/labellingFlowCustomization';
/**
*
@@ -48,6 +54,12 @@ export default function getCustomizationModule({ servicesManager, extensionManag
...sortingCriteriaCustomization,
...defaultContextMenuCustomization,
...onDropHandlerCustomization,
...loadingIndicatorProgressCustomization,
...loadingIndicatorTotalPercentCustomization,
...progressLoadingBarCustomization,
...viewportActionCornersCustomization,
...labellingFlowCustomization,
...contextMenuUICustomization,
},
},
];
@@ -89,7 +89,13 @@ export function callInputDialog(
}
}
export function callLabelAutocompleteDialog(uiDialogService, callback, dialogConfig, labelConfig) {
export function callLabelAutocompleteDialog(
uiDialogService,
callback,
dialogConfig,
labelConfig,
renderContent = LabellingFlow
) {
const exclusive = labelConfig ? labelConfig.exclusive : false;
const dropDownItems = labelConfig ? labelConfig.items : [];
@@ -112,7 +118,7 @@ export function callLabelAutocompleteDialog(uiDialogService, callback, dialogCon
centralize: true,
isDraggable: false,
showOverlay: true,
content: LabellingFlow,
content: renderContent,
contentProps: {
labellingDoneCallback: labellingDoneCallback,
measurementData: { label: '' },
@@ -123,7 +129,12 @@ export function callLabelAutocompleteDialog(uiDialogService, callback, dialogCon
});
}
export function showLabelAnnotationPopup(measurement, uiDialogService, labelConfig) {
export function showLabelAnnotationPopup(
measurement,
uiDialogService,
labelConfig,
renderContent = LabellingFlow
) {
const exclusive = labelConfig ? labelConfig.exclusive : false;
const dropDownItems = labelConfig ? labelConfig.items : [];
return new Promise<Map<any, any>>((resolve, reject) => {
@@ -139,7 +150,7 @@ export function showLabelAnnotationPopup(measurement, uiDialogService, labelConf
id: 'select-annotation',
isDraggable: false,
showOverlay: true,
content: LabellingFlow,
content: renderContent,
defaultPosition: {
x: window.innerWidth / 2,
y: window.innerHeight / 2,
@@ -5,11 +5,13 @@ function promptLabelAnnotation({ servicesManager }, ctx, evt) {
const { viewportId, StudyInstanceUID, SeriesInstanceUID, measurementId } = evt;
return new Promise(async function (resolve) {
const labelConfig = customizationService.getCustomization('measurementLabels');
const renderContent = customizationService.getCustomization('ui.labellingComponent');
const measurement = measurementService.getMeasurement(measurementId);
const value = await showLabelAnnotationPopup(
measurement,
servicesManager.services.uiDialogService,
labelConfig
labelConfig,
renderContent
);
measurementService.update(
@@ -1,6 +1,5 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { LoadingIndicatorProgress } from '@ohif/ui';
import { cleanDenaturalizedDataset } from '@ohif/extension-default';
import './DicomMicroscopyViewport.css';
@@ -8,6 +7,7 @@ import ViewportOverlay from './components/ViewportOverlay';
import getDicomWebClient from './utils/dicomWebClient';
import dcmjs from 'dcmjs';
import MicroscopyService from './services/MicroscopyService';
import { CustomizationService } from '@ohif/core';
class DicomMicroscopyViewport extends Component {
state = {
@@ -16,6 +16,7 @@ class DicomMicroscopyViewport extends Component {
};
microscopyService: MicroscopyService;
customizationService: CustomizationService;
viewer: any = null; // dicom-microscopy-viewer instance
managedViewer: any = null; // managed wrapper of microscopy-dicom extension
@@ -25,8 +26,9 @@ class DicomMicroscopyViewport extends Component {
constructor(props: any) {
super(props);
const { microscopyService } = this.props.servicesManager.services;
const { microscopyService, customizationService } = this.props.servicesManager.services;
this.microscopyService = microscopyService;
this.customizationService = customizationService;
}
static propTypes = {
@@ -49,7 +51,6 @@ class DicomMicroscopyViewport extends Component {
resizeRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({ current: PropTypes.any })]),
};
/**
* Get the nearest ROI from the mouse click point
*
@@ -272,6 +273,9 @@ class DicomMicroscopyViewport extends Component {
const style = { width: '100%', height: '100%' };
const displaySet = this.props.displaySets[0];
const firstInstance = displaySet.firstInstance || displaySet.instance;
const LoadingIndicatorProgress = this.customizationService.getCustomization(
'ui.loadingIndicatorProgress'
);
return (
<div