feat(studies-panel): New OHIF study panel - under experimental flag (#4254)
This commit is contained in:
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}
|
||||
|
||||
+75
-17
@@ -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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+72
@@ -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 };
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import type { actionIcon } from '../PanelStudyBrowserTracking/types/actionsIcon';
|
||||
|
||||
const defaultActionIcons = [
|
||||
{
|
||||
id: 'settings',
|
||||
iconName: 'Settings',
|
||||
value: false,
|
||||
},
|
||||
] as actionIcon[];
|
||||
|
||||
export { defaultActionIcons };
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import { defaultActionIcons } from './actionIcons';
|
||||
import { defaultViewPresets } from './viewPresets';
|
||||
|
||||
export { defaultActionIcons, defaultViewPresets };
|
||||
+16
@@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
+7
@@ -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 };
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
type viewPreset = {
|
||||
id: string;
|
||||
iconName: string;
|
||||
selected: boolean;
|
||||
};
|
||||
|
||||
export type { viewPreset };
|
||||
Reference in new issue
Block a user