feat(studies-panel): New OHIF study panel - under experimental flag (#4254)

This commit is contained in:
Ibrahim authored and GitHub committed 2024-09-11 14:57:19 -04:00
1 parent 4432b6b420
commit 7a96406a11
80 files changed
+4119 -253

No files matched your search

@@ -1,11 +1,13 @@
import { Types } from '@ohif/core';
import { PanelMeasurementTableTracking, PanelStudyBrowserTracking } from './panels';
import i18n from 'i18next';
import React from 'react';
// TODO:
// - No loading UI exists yet
// - cancel promises when component is destroyed
// - show errors in UI for thumbnails if promise fails
function getPanelModule({ commandsManager, extensionManager, servicesManager }): Types.Panel[] {
return [
{
@@ -13,11 +15,14 @@ function getPanelModule({ commandsManager, extensionManager, servicesManager }):
iconName: 'tab-studies',
iconLabel: 'Studies',
label: i18n.t('SidePanel:Studies'),
component: PanelStudyBrowserTracking.bind(null, {
commandsManager,
extensionManager,
servicesManager,
}),
component: props => (
<PanelStudyBrowserTracking
{...props}
commandsManager={commandsManager}
extensionManager={extensionManager}
servicesManager={servicesManager}
/>
),
},
{
@@ -25,11 +30,14 @@ function getPanelModule({ commandsManager, extensionManager, servicesManager }):
iconName: 'tab-linear',
iconLabel: 'Measure',
label: i18n.t('SidePanel:Measurements'),
component: PanelMeasurementTableTracking.bind(null, {
commandsManager,
extensionManager,
servicesManager,
}),
component: props => (
<PanelMeasurementTableTracking
{...props}
commandsManager={commandsManager}
extensionManager={extensionManager}
servicesManager={servicesManager}
/>
),
},
];
}
@@ -7,6 +7,7 @@ import { useAppConfig } from '@state';
import { useTrackedMeasurements } from '../../getContextModule';
import debounce from 'lodash.debounce';
import { useTranslation } from 'react-i18next';
import { Separator } from '@ohif/ui-next';
const { downloadCSVReport } = utils;
const { formatDate } = utils;
@@ -18,7 +19,13 @@ const DISPLAY_STUDY_SUMMARY_INITIAL_VALUE = {
description: '', // 'CHEST/ABD/PELVIS W CONTRAST',
};
function PanelMeasurementTableTracking({ servicesManager, extensionManager }: withAppTypes) {
function PanelMeasurementTableTracking({
servicesManager,
extensionManager,
renderHeader,
getCloseIcon,
tab,
}: withAppTypes) {
const [viewportGrid] = useViewportGrid();
const { t } = useTranslation('MeasurementTable');
const [measurementChangeTimestamp, setMeasurementsUpdated] = useState(Date.now().toString());
@@ -176,6 +183,24 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }: wi
return (
<>
{renderHeader && (
<>
<div className="bg-primary-dark flex select-none rounded-t pt-1.5 pb-[2px]">
<div className="flex h-[24px] w-full cursor-pointer select-none justify-center self-center text-[14px]">
<div className="text-primary-active flex grow cursor-pointer select-none justify-center self-center text-[13px]">
<span>{tab.label}</span>
</div>
</div>
{getCloseIcon()}
</div>
<Separator
orientation="horizontal"
className="bg-black"
thickness="2px"
/>
</>
)}
<div
className="invisible-scrollbar overflow-y-auto overflow-x-hidden"
ref={measurementsPanelRef}
@@ -3,8 +3,15 @@ import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import PropTypes from 'prop-types';
import { utils } from '@ohif/core';
import { StudyBrowser, useImageViewer, useViewportGrid, Dialog, ButtonEnums } from '@ohif/ui';
import { useImageViewer, useViewportGrid, Dialog, ButtonEnums } from '@ohif/ui';
import { StudyBrowser as NewStudyBrowser } from '@ohif/ui-next';
import { StudyBrowser as OldStudyBrowser } from '@ohif/ui';
import { useAppConfig } from '@state';
import { useTrackedMeasurements } from '../../getContextModule';
import { Separator } from '@ohif/ui-next';
import { PanelStudyBrowserTrackingHeader } from './PanelStudyBrowserTrackingHeader';
import { defaultActionIcons, defaultViewPresets } from './constants';
const { formatDate, createStudyBrowserTabs } = utils;
@@ -18,6 +25,9 @@ function PanelStudyBrowserTracking({
getStudiesForPatientByMRN,
requestDisplaySetCreationForStudy,
dataSource,
renderHeader,
getCloseIcon,
tab,
}: withAppTypes) {
const {
displaySetService,
@@ -26,10 +36,12 @@ function PanelStudyBrowserTracking({
uiNotificationService,
measurementService,
studyPrefetcherService,
customizationService,
} = servicesManager.services;
const navigate = useNavigate();
const { t } = useTranslation('Common');
const [appConfig] = useAppConfig();
// Normally you nest the components so the tree isn't so deep, and the data
// doesn't have to have such an intense shape. This works well enough for now.
@@ -49,6 +61,29 @@ function PanelStudyBrowserTracking({
const [thumbnailImageSrcMap, setThumbnailImageSrcMap] = useState({});
const [jumpToDisplaySet, setJumpToDisplaySet] = useState(null);
const [viewPresets, setViewPresets] = useState(
customizationService.getCustomization('studyBrowser.viewPresets')?.value || defaultViewPresets
);
const [actionIcons, setActionIcons] = useState(defaultActionIcons);
const updateActionIconValue = actionIcon => {
actionIcon.value = !actionIcon.value;
const newActionIcons = [...actionIcons];
setActionIcons(newActionIcons);
};
const updateViewPresetValue = viewPreset => {
if (!viewPreset) {
return;
}
const newViewPresets = viewPresets.map(preset => {
preset.selected = preset.id === viewPreset.id;
return preset;
});
setViewPresets(newViewPresets);
};
const onDoubleClickThumbnailHandler = displaySetInstanceUID => {
let updatedViewports = [];
const viewportId = activeViewportId;
@@ -431,23 +466,46 @@ function PanelStudyBrowserTracking({
});
};
const StudyBrowser = appConfig.useExperimentalUI ? NewStudyBrowser : OldStudyBrowser;
return (
<StudyBrowser
tabs={tabs}
servicesManager={servicesManager}
activeTabName={activeTabName}
expandedStudyInstanceUIDs={expandedStudyInstanceUIDs}
onClickStudy={_handleStudyClick}
onClickTab={clickedTabName => {
setActiveTabName(clickedTabName);
}}
onClickUntrack={displaySetInstanceUID => {
onClickUntrack(displaySetInstanceUID);
}}
onClickThumbnail={() => {}}
onDoubleClickThumbnail={onDoubleClickThumbnailHandler}
activeDisplaySetInstanceUIDs={activeViewportDisplaySetInstanceUIDs}
/>
<>
{renderHeader && (
<>
<PanelStudyBrowserTrackingHeader
tab={tab}
getCloseIcon={getCloseIcon}
viewPresets={viewPresets}
updateViewPresetValue={updateViewPresetValue}
actionIcons={actionIcons}
updateActionIconValue={updateActionIconValue}
/>
<Separator
orientation="horizontal"
className="bg-black"
thickness="2px"
/>
</>
)}
<StudyBrowser
tabs={tabs}
servicesManager={servicesManager}
activeTabName={activeTabName}
expandedStudyInstanceUIDs={expandedStudyInstanceUIDs}
onClickStudy={_handleStudyClick}
onClickTab={clickedTabName => {
setActiveTabName(clickedTabName);
}}
onClickUntrack={displaySetInstanceUID => {
onClickUntrack(displaySetInstanceUID);
}}
onClickThumbnail={() => {}}
onDoubleClickThumbnail={onDoubleClickThumbnailHandler}
activeDisplaySetInstanceUIDs={activeViewportDisplaySetInstanceUIDs}
showSettings={actionIcons.find(icon => icon.id === 'settings').value}
viewPresets={viewPresets}
/>
</>
);
}
@@ -0,0 +1,72 @@
import React from 'react';
import { ToggleGroup, ToggleGroupItem } from '@ohif/ui-next';
import { Icons } from '@ohif/ui-next';
import { actionIcon, viewPreset } from './types';
function PanelStudyBrowserTrackingHeader({
tab,
getCloseIcon,
viewPresets,
updateViewPresetValue,
actionIcons,
updateActionIconValue,
}: {
tab: any;
getCloseIcon: () => JSX.Element;
viewPresets: viewPreset[];
updateViewPresetValue: (viewPreset: viewPreset) => void;
actionIcons: actionIcon[];
updateActionIconValue: (actionIcon: actionIcon) => void;
}) {
return (
<>
<div className="bg-muted flex h-[40px] select-none rounded-t p-2">
<div className={'flex h-[24px] w-full select-none justify-center self-center text-[14px]'}>
<div className="flex w-full items-center justify-between">
<div className="flex h-full items-center justify-center">
<ToggleGroup
type="single"
value={viewPresets.filter(preset => preset.selected)[0].id}
onValueChange={value => {
const selectedViewPreset = viewPresets.find(preset => preset.id === value);
updateViewPresetValue(selectedViewPreset);
}}
>
{viewPresets.map((viewPreset: viewPreset, index) => (
<ToggleGroupItem
key={index}
aria-label={viewPreset.id}
value={viewPreset.id}
className="text-actions-primary"
>
{React.createElement(Icons[viewPreset.iconName] || Icons.MissingIcon)}
</ToggleGroupItem>
))}
</ToggleGroup>
</div>
<div className="text-muted-foreground flex items-center justify-center">
{' '}
<span>{tab.label}</span>{' '}
</div>
<div className="mr-[30px] flex items-center justify-center">
<div className="flex items-center space-x-1">
{actionIcons.map((icon: actionIcon, index) =>
React.createElement(Icons[icon.iconName] || Icons.MissingIcon, {
key: index,
onClick: () => updateActionIconValue(icon),
className: `cursor-pointer`,
})
)}
</div>
</div>
</div>
</div>
{getCloseIcon()}
</div>
</>
);
}
export { PanelStudyBrowserTrackingHeader };
@@ -0,0 +1,11 @@
import type { actionIcon } from '../PanelStudyBrowserTracking/types/actionsIcon';
const defaultActionIcons = [
{
id: 'settings',
iconName: 'Settings',
value: false,
},
] as actionIcon[];
export { defaultActionIcons };
@@ -0,0 +1,4 @@
import { defaultActionIcons } from './actionIcons';
import { defaultViewPresets } from './viewPresets';
export { defaultActionIcons, defaultViewPresets };
@@ -0,0 +1,16 @@
import type { viewPreset } from '../PanelStudyBrowserTracking/types/viewPreset';
const defaultViewPresets = [
{
id: 'list',
iconName: 'ListView',
selected: false,
},
{
id: 'thumbnails',
iconName: 'ThumbnailView',
selected: true,
},
] as viewPreset[];
export { defaultViewPresets };
@@ -1,4 +1,4 @@
import React, { useCallback } from 'react';
import React, { useCallback, useEffect } from 'react';
import PropTypes from 'prop-types';
//
import PanelStudyBrowserTracking from './PanelStudyBrowserTracking';
@@ -25,6 +25,9 @@ function WrappedPanelStudyBrowserTracking({
commandsManager,
extensionManager,
servicesManager,
renderHeader,
getCloseIcon,
tab,
}: withAppTypes) {
const dataSource = extensionManager.getActiveDataSource()[0];
@@ -46,6 +49,9 @@ function WrappedPanelStudyBrowserTracking({
getImageSrc={_getImageSrcFromImageId}
getStudiesForPatientByMRN={_getStudiesForPatientByMRN}
requestDisplaySetCreationForStudy={_requestDisplaySetCreationForStudy}
renderHeader={renderHeader}
getCloseIcon={getCloseIcon}
tab={tab}
/>
);
}
@@ -0,0 +1,7 @@
type actionIcon = {
id: string;
iconName: string;
value: boolean;
};
export type { actionIcon };
@@ -0,0 +1,4 @@
import type { actionIcon } from './actionIcon';
import type { viewPreset } from './viewPreset';
export type { actionIcon, viewPreset };
@@ -0,0 +1,7 @@
type viewPreset = {
id: string;
iconName: string;
selected: boolean;
};
export type { viewPreset };