feat(multimonitor): Add simple multi-monitor support to open another study(#4178)

This commit is contained in:
Bill Wallace authored and GitHub committed 2025-01-13 10:49:08 -05:00
1 parent a4a6de07f4
commit 07c628e689
49 files changed
+1476 -367

No files matched your search

@@ -5,17 +5,6 @@ import { StudyItem } from '../StudyItem';
import { StudyBrowserSort } from '../StudyBrowserSort';
import { StudyBrowserViewOptions } from '../StudyBrowserViewOptions';
const getTrackedSeries = displaySets => {
let trackedSeries = 0;
displaySets.forEach(displaySet => {
if (displaySet.isTracked) {
trackedSeries++;
}
});
return trackedSeries;
};
const noop = () => {};
const StudyBrowser = ({
@@ -31,7 +20,8 @@ const StudyBrowser = ({
servicesManager,
showSettings,
viewPresets,
onThumbnailContextMenu,
ThumbnailMenuItems,
StudyMenuItems,
}: withAppTypes) => {
const getTabContent = () => {
const tabData = tabs.find(tab => tab.name === activeTabName);
@@ -50,18 +40,17 @@ const StudyBrowser = ({
isExpanded={isExpanded}
displaySets={displaySets}
modalities={modalities}
trackedSeries={getTrackedSeries(displaySets)}
isActive={isExpanded}
onClick={() => {
onClickStudy(studyInstanceUid);
}}
onClick={() => onClickStudy(studyInstanceUid)}
onClickThumbnail={onClickThumbnail}
onDoubleClickThumbnail={onDoubleClickThumbnail}
onClickUntrack={onClickUntrack}
activeDisplaySetInstanceUIDs={activeDisplaySetInstanceUIDs}
data-cy="thumbnail-list"
viewPreset={viewPreset}
onThumbnailContextMenu={onThumbnailContextMenu}
ThumbnailMenuItems={ThumbnailMenuItems}
StudyMenuItems={StudyMenuItems}
StudyInstanceUID={studyInstanceUid}
/>
</React.Fragment>
);
@@ -142,6 +131,7 @@ StudyBrowser.propTypes = {
).isRequired,
})
),
StudyMenuItems: PropTypes.func,
};
export { StudyBrowser };
@@ -8,6 +8,8 @@ import {
} from '../DropdownMenu/DropdownMenu';
export function StudyBrowserSort({ servicesManager }: withAppTypes) {
// Todo: this should not be here, no servicesManager should be in ui-next, only
// customization service
const { customizationService, displaySetService } = servicesManager.services;
const { values: sortFunctions } = customizationService.get('studyBrowser.sortFunctions');
@@ -19,7 +19,9 @@ const StudyItem = ({
onDoubleClickThumbnail,
onClickUntrack,
viewPreset = 'thumbnails',
onThumbnailContextMenu,
ThumbnailMenuItems,
StudyMenuItems,
StudyInstanceUID,
}: withAppTypes) => {
return (
<Accordion
@@ -32,19 +34,24 @@ const StudyItem = ({
defaultValue={isActive ? 'study-item' : undefined}
>
<AccordionItem value="study-item">
<AccordionTrigger className={classnames('hover:bg-accent bg-popover rounded')}>
<AccordionTrigger className={classnames('hover:bg-accent bg-popover group rounded')}>
<div className="flex h-[40px] flex-1 flex-row">
<div className="flex w-full flex-row items-center justify-between">
<div className="flex w-full flex-row items-center">
<div className="flex flex-col items-start text-[13px]">
<div className="text-white">{date}</div>
<div className="text-muted-foreground h-[18px] max-w-[160px] overflow-hidden truncate whitespace-nowrap">
{description}
</div>
</div>
<div className="text-muted-foreground mr-2 flex flex-col items-end text-[12px]">
<div className="text-muted-foreground ml-auto flex flex-col items-end text-[12px]">
<div className="max-w-[150px] overflow-hidden text-ellipsis">{modalities}</div>
<div>{numInstances}</div>
</div>
{StudyMenuItems && (
<div className="ml-2 flex items-center">
<StudyMenuItems StudyInstanceUID={StudyInstanceUID} />
</div>
)}
</div>
</div>
</AccordionTrigger>
@@ -61,7 +68,7 @@ const StudyItem = ({
onThumbnailDoubleClick={onDoubleClickThumbnail}
onClickUntrack={onClickUntrack}
viewPreset={viewPreset}
onThumbnailContextMenu={onThumbnailContextMenu}
ThumbnailMenuItems={ThumbnailMenuItems}
/>
)}
</AccordionContent>
@@ -75,7 +82,6 @@ StudyItem.propTypes = {
description: PropTypes.string,
modalities: PropTypes.string.isRequired,
numInstances: PropTypes.number.isRequired,
trackedSeries: PropTypes.number,
isActive: PropTypes.bool,
onClick: PropTypes.func.isRequired,
isExpanded: PropTypes.bool,
@@ -85,6 +91,8 @@ StudyItem.propTypes = {
onDoubleClickThumbnail: PropTypes.func,
onClickUntrack: PropTypes.func,
viewPreset: PropTypes.string,
StudyMenuItems: PropTypes.func,
StudyInstanceUID: PropTypes.string,
};
export { StudyItem };
@@ -5,13 +5,6 @@ import { useDrag } from 'react-dnd';
import { Icons } from '../Icons';
import { DisplaySetMessageListTooltip } from '../DisplaySetMessageListTooltip';
import { TooltipTrigger, TooltipContent, Tooltip } from '../Tooltip';
import { Button } from '../Button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '../DropdownMenu';
/**
* Display a thumbnail for a display set.
@@ -34,12 +27,12 @@ const Thumbnail = ({
viewPreset = 'thumbnails',
modality,
isHydratedForDerivedDisplaySet = false,
isTracked = false,
canReject = false,
onReject = () => {},
isTracked = false,
thumbnailType = 'thumbnail',
onClickUntrack = () => {},
onThumbnailContextMenu,
ThumbnailMenuItems = () => {},
}: withAppTypes): React.ReactNode => {
// TODO: We should wrap our thumbnail to create a "DraggableThumbnail", as
// this will still allow for "drag", even if there is no drop target for the
@@ -134,44 +127,11 @@ const Thumbnail = ({
</div>
{/* bottom right */}
<div className="absolute bottom-0 right-0 flex items-center gap-[4px] p-[4px]">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="hidden group-hover:inline-flex data-[state=open]:inline-flex"
>
<Icons.More />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
hideWhenDetached
align="start"
>
<DropdownMenuItem
onSelect={() => {
onThumbnailContextMenu('openDICOMTagViewer', {
displaySetInstanceUID,
});
}}
className="gap-[6px]"
>
<Icons.DicomTagBrowser />
Tag Browser
</DropdownMenuItem>
{canReject && (
<DropdownMenuItem
onSelect={() => {
onReject();
}}
className="gap-[6px]"
>
<Icons.Trash className="h-5 w-5 text-red-500" />
Delete Report
</DropdownMenuItem>
)}
</DropdownMenuContent>
</DropdownMenu>
<ThumbnailMenuItems
displaySetInstanceUID={displaySetInstanceUID}
canReject={canReject}
onReject={onReject}
/>
</div>
</div>
</div>
@@ -243,7 +203,6 @@ const Thumbnail = ({
messages={messages}
id={`display-set-tooltip-${displaySetInstanceUID}`}
/>
{isTracked && (
<Tooltip>
<TooltipTrigger>
@@ -271,41 +230,11 @@ const Thumbnail = ({
</TooltipContent>
</Tooltip>
)}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="hidden group-hover:inline-flex data-[state=open]:inline-flex"
>
<Icons.More />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent hideWhenDetached>
<DropdownMenuItem
onSelect={() => {
onThumbnailContextMenu('openDICOMTagViewer', {
displaySetInstanceUID,
});
}}
className="gap-[6px]"
>
<Icons.DicomTagBrowser />
Tag Browser
</DropdownMenuItem>
{canReject && (
<DropdownMenuItem
onSelect={() => {
onReject();
}}
className="gap-[6px]"
>
<Icons.Trash className="h-5 w-5 text-red-500" />
Delete Report
</DropdownMenuItem>
)}
</DropdownMenuContent>
</DropdownMenu>
<ThumbnailMenuItems
displaySetInstanceUID={displaySetInstanceUID}
canReject={canReject}
onReject={onReject}
/>
</div>
</div>
);
@@ -369,8 +298,6 @@ Thumbnail.propTypes = {
viewPreset: PropTypes.string,
modality: PropTypes.string,
isHydratedForDerivedDisplaySet: PropTypes.bool,
canReject: PropTypes.bool,
onReject: PropTypes.func,
isTracked: PropTypes.bool,
onClickUntrack: PropTypes.func,
countIcon: PropTypes.string,
@@ -10,7 +10,7 @@ const ThumbnailList = ({
onClickUntrack,
activeDisplaySetInstanceUIDs = [],
viewPreset,
onThumbnailContextMenu,
ThumbnailMenuItems,
}: withAppTypes) => {
return (
<div
@@ -34,9 +34,9 @@ const ThumbnailList = ({
modality,
componentType,
countIcon,
isTracked,
canReject,
onReject,
isTracked,
imageSrc,
messages,
imageAltText,
@@ -56,6 +56,8 @@ const ThumbnailList = ({
imageAltText={imageAltText}
messages={messages}
isActive={isActive}
canReject={canReject}
onReject={onReject}
modality={modality}
viewPreset={componentType === 'thumbnailNoImage' ? 'list' : viewPreset}
thumbnailType={componentType}
@@ -65,9 +67,7 @@ const ThumbnailList = ({
loadingProgress={loadingProgress}
onClickUntrack={() => onClickUntrack(displaySetInstanceUID)}
isHydratedForDerivedDisplaySet={isHydratedForDerivedDisplaySet}
canReject={canReject}
onReject={onReject}
onThumbnailContextMenu={onThumbnailContextMenu}
ThumbnailMenuItems={ThumbnailMenuItems}
/>
);
}