feat(panels): responsive thumbnails based on panel size (#4723)
This commit is contained in:
1 parent
9d07705a1f
commit
d9abc3da8d
15 files changed
+282
-176
No files matched your search
@@ -63,7 +63,7 @@ const StudyBrowser = ({
|
||||
className="ohif-scrollbar invisible-scrollbar bg-bkg-low flex flex-1 flex-col gap-[4px] overflow-auto"
|
||||
data-cy={'studyBrowser-panel'}
|
||||
>
|
||||
<div>
|
||||
<div className="flex flex-col gap-[4px]">
|
||||
{showSettings && (
|
||||
<div className="w-100 bg-bkg-low flex h-[48px] items-center justify-center gap-[10px] px-[8px] py-[10px]">
|
||||
<>
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
} from '../DropdownMenu/DropdownMenu';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '../Tooltip';
|
||||
|
||||
export function StudyBrowserSort({ servicesManager }: withAppTypes) {
|
||||
// Todo: this should not be here, no servicesManager should be in ui-next, only
|
||||
@@ -50,11 +51,16 @@ export function StudyBrowserSort({ servicesManager }: withAppTypes) {
|
||||
}, [displaySetService, selectedSort, sortDirection]);
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="flex w-[50%] items-center gap-1">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger className="border-inputfield-main focus:border-inputfield-main flex h-[26px] w-[125px] items-center justify-start rounded border bg-black p-2 text-base text-white">
|
||||
{selectedSort.label}
|
||||
</DropdownMenuTrigger>
|
||||
<Tooltip>
|
||||
<TooltipTrigger className="w-full overflow-hidden">
|
||||
<DropdownMenuTrigger className="border-inputfield-main focus:border-inputfield-main flex h-[26px] w-full items-center justify-start overflow-hidden whitespace-nowrap rounded border bg-black p-2 text-base text-white">
|
||||
{selectedSort.label}
|
||||
</DropdownMenuTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{selectedSort.label}</TooltipContent>
|
||||
</Tooltip>
|
||||
<DropdownMenuContent className="bg-black">
|
||||
{sortFunctions.map(sort => (
|
||||
<DropdownMenuItem
|
||||
@@ -67,16 +73,21 @@ export function StudyBrowserSort({ servicesManager }: withAppTypes) {
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<button
|
||||
onClick={toggleSortDirection}
|
||||
className="flex h-[26px] items-center justify-center bg-black"
|
||||
>
|
||||
{sortDirection === 'ascending' ? (
|
||||
<Icons.SortingAscending className="text-primary-main w-2" />
|
||||
) : (
|
||||
<Icons.SortingDescending className="text-primary-main w-2" />
|
||||
)}
|
||||
</button>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<button
|
||||
onClick={toggleSortDirection}
|
||||
className="flex h-[26px] items-center justify-center bg-black"
|
||||
>
|
||||
{sortDirection === 'ascending' ? (
|
||||
<Icons.SortingAscending className="text-primary-main w-2" />
|
||||
) : (
|
||||
<Icons.SortingDescending className="text-primary-main w-2" />
|
||||
)}
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Sort direction</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
} from '../DropdownMenu/DropdownMenu';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '../Tooltip';
|
||||
|
||||
export function StudyBrowserViewOptions({ tabs, onSelectTab, activeTabName }: withAppTypes) {
|
||||
const handleTabChange = (tabName: string) => {
|
||||
@@ -15,9 +16,14 @@ export function StudyBrowserViewOptions({ tabs, onSelectTab, activeTabName }: wi
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger className="border-inputfield-main focus:border-inputfield-main flex h-[26px] w-[125px] items-center justify-start rounded border bg-black p-2 text-base text-white">
|
||||
{activeTab?.label}
|
||||
</DropdownMenuTrigger>
|
||||
<Tooltip>
|
||||
<TooltipTrigger className="w-full w-[50%] overflow-hidden">
|
||||
<DropdownMenuTrigger className="border-inputfield-main focus:border-inputfield-main flex h-[26px] w-full items-center justify-start rounded border bg-black p-2 text-base text-white">
|
||||
{activeTab?.label}
|
||||
</DropdownMenuTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{activeTab?.label}</TooltipContent>
|
||||
</Tooltip>
|
||||
<DropdownMenuContent className="bg-black">
|
||||
{tabs.map(tab => {
|
||||
const { name, label, studies } = tab;
|
||||
|
||||
@@ -4,6 +4,7 @@ import classnames from 'classnames';
|
||||
import { ThumbnailList } from '../ThumbnailList';
|
||||
|
||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '../Accordion';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '../Tooltip';
|
||||
|
||||
const StudyItem = ({
|
||||
date,
|
||||
@@ -34,16 +35,28 @@ const StudyItem = ({
|
||||
defaultValue={isActive ? 'study-item' : undefined}
|
||||
>
|
||||
<AccordionItem value="study-item">
|
||||
<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">
|
||||
<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>
|
||||
<AccordionTrigger className={classnames('hover:bg-accent bg-popover group w-full rounded')}>
|
||||
<div className="flex h-[40px] w-full flex-row overflow-hidden">
|
||||
<div className="flex w-full flex-row items-center justify-between">
|
||||
<div className="flex min-w-0 flex-col items-start text-[13px]">
|
||||
<Tooltip>
|
||||
<TooltipContent>{date}</TooltipContent>
|
||||
<TooltipTrigger className="w-full">
|
||||
<div className="h-[18px] w-full max-w-[160px] overflow-hidden truncate whitespace-nowrap text-left text-white">
|
||||
{date}
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipContent>{description}</TooltipContent>
|
||||
<TooltipTrigger className="w-full">
|
||||
<div className="text-muted-foreground h-[18px] w-full overflow-hidden truncate whitespace-nowrap text-left">
|
||||
{description}
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="text-muted-foreground ml-auto flex flex-col items-end text-[12px]">
|
||||
<div className="text-muted-foreground flex flex-col items-end pl-[10px] text-[12px]">
|
||||
<div className="max-w-[150px] overflow-hidden text-ellipsis">{modalities}</div>
|
||||
<div>{numInstances}</div>
|
||||
</div>
|
||||
|
||||
@@ -20,17 +20,17 @@ const Thumbnail = ({
|
||||
loadingProgress,
|
||||
countIcon,
|
||||
messages,
|
||||
dragData = {},
|
||||
isActive,
|
||||
onClick,
|
||||
onDoubleClick,
|
||||
viewPreset = 'thumbnails',
|
||||
thumbnailType,
|
||||
modality,
|
||||
viewPreset = 'thumbnails',
|
||||
isHydratedForDerivedDisplaySet = false,
|
||||
isTracked = false,
|
||||
canReject = false,
|
||||
dragData = {},
|
||||
onReject = () => {},
|
||||
thumbnailType = 'thumbnail',
|
||||
onClickUntrack = () => {},
|
||||
ThumbnailMenuItems = () => {},
|
||||
}: withAppTypes): React.ReactNode => {
|
||||
@@ -135,10 +135,15 @@ const Thumbnail = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 flex h-[52px] w-[128px] flex-col">
|
||||
<div className="min-h-[18px] w-[128px] overflow-hidden text-ellipsis pb-0.5 pl-1 text-[12px] font-normal leading-4 text-white">
|
||||
{description}
|
||||
</div>
|
||||
<div className="flex h-[52px] w-[128px] flex-col justify-end">
|
||||
<Tooltip>
|
||||
<TooltipContent>{description}</TooltipContent>
|
||||
<TooltipTrigger>
|
||||
<div className="min-h-[18px] w-[128px] overflow-hidden text-ellipsis whitespace-nowrap pb-0.5 pl-1 text-left text-[12px] font-normal leading-4 text-white">
|
||||
{description}
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
</Tooltip>
|
||||
<div className="flex h-[12px] items-center gap-[7px] overflow-hidden">
|
||||
<div className="text-muted-foreground pl-1 text-[11px]"> S:{seriesNumber}</div>
|
||||
<div className="text-muted-foreground text-[11px]">
|
||||
@@ -165,21 +170,25 @@ const Thumbnail = ({
|
||||
isActive && 'bg-popover'
|
||||
)}
|
||||
>
|
||||
<div className="relative flex h-[32px] items-center gap-[8px]">
|
||||
<div className="relative flex h-[32px] w-full items-center gap-[8px] overflow-hidden">
|
||||
<div
|
||||
className={classnames(
|
||||
'h-[32px] w-[4px] rounded-[2px]',
|
||||
'h-[32px] w-[4px] min-w-[4px] rounded-[2px]',
|
||||
isActive || isHydratedForDerivedDisplaySet ? 'bg-highlight' : 'bg-primary/65',
|
||||
loadingProgress && loadingProgress < 1 && 'bg-primary/25'
|
||||
)}
|
||||
></div>
|
||||
<div className="flex h-full flex-col">
|
||||
<div className="flex h-full w-[calc(100%-12px)] flex-col">
|
||||
<div className="flex items-center gap-[7px]">
|
||||
<div className="text-[13px] font-semibold text-white">{modality}</div>
|
||||
|
||||
<div className="max-w-[160px] overflow-hidden overflow-ellipsis whitespace-nowrap text-[13px] font-normal text-white">
|
||||
{description}
|
||||
</div>
|
||||
<Tooltip>
|
||||
<TooltipContent>{description}</TooltipContent>
|
||||
<TooltipTrigger className="w-full overflow-hidden">
|
||||
<div className="max-w-[160px] overflow-hidden overflow-ellipsis whitespace-nowrap text-left text-[13px] font-normal text-white">
|
||||
{description}
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div className="flex h-[12px] items-center gap-[7px] overflow-hidden">
|
||||
@@ -246,7 +255,7 @@ const Thumbnail = ({
|
||||
className,
|
||||
'bg-muted hover:bg-primary/30 group flex cursor-pointer select-none flex-col rounded outline-none',
|
||||
viewPreset === 'thumbnails' && 'h-[170px] w-[135px]',
|
||||
viewPreset === 'list' && 'col-span-2 h-[40px] w-[275px]'
|
||||
viewPreset === 'list' && 'h-[40px] w-full'
|
||||
)}
|
||||
id={`thumbnail-${displaySetInstanceUID}`}
|
||||
data-cy={
|
||||
|
||||
@@ -11,68 +11,73 @@ const ThumbnailList = ({
|
||||
activeDisplaySetInstanceUIDs = [],
|
||||
viewPreset,
|
||||
ThumbnailMenuItems,
|
||||
}: withAppTypes) => {
|
||||
}) => {
|
||||
// Filter thumbnails into list items and thumbnail items
|
||||
const listItems = thumbnails?.filter(
|
||||
({ componentType }) => componentType === 'thumbnailNoImage' || viewPreset === 'list'
|
||||
);
|
||||
|
||||
const thumbnailItems = thumbnails?.filter(
|
||||
({ componentType }) => componentType !== 'thumbnailNoImage' && viewPreset === 'thumbnails'
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="min-h-[350px]"
|
||||
style={{
|
||||
'--radix-accordion-content-height': '350px',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
id="ohif-thumbnail-list"
|
||||
className={`ohif-scrollbar bg-bkg-low grid place-items-center overflow-y-hidden pt-[4px] pr-[2.5px] pl-[2.5px] ${viewPreset === 'thumbnails' ? 'grid-cols-2 gap-[4px] pb-[12px]' : 'grid-cols-1 gap-[2px]'}`}
|
||||
>
|
||||
{thumbnails.map(
|
||||
({
|
||||
displaySetInstanceUID,
|
||||
description,
|
||||
dragData,
|
||||
seriesNumber,
|
||||
numInstances,
|
||||
loadingProgress,
|
||||
modality,
|
||||
componentType,
|
||||
countIcon,
|
||||
canReject,
|
||||
onReject,
|
||||
isTracked,
|
||||
imageSrc,
|
||||
messages,
|
||||
imageAltText,
|
||||
isHydratedForDerivedDisplaySet,
|
||||
}) => {
|
||||
<div className="flex flex-col gap-[4px] pt-[4px] pr-[2.5px] pl-[5px] pb-[4px]">
|
||||
{/* Thumbnail Items */}
|
||||
{thumbnailItems.length > 0 && (
|
||||
<div
|
||||
id="ohif-thumbnail-list"
|
||||
className="ohif-scrollbar bg-bkg-low grid grid-cols-[repeat(auto-fit,_minmax(0,135px))] place-items-start gap-[4px] overflow-y-hidden"
|
||||
>
|
||||
{thumbnailItems.map(item => {
|
||||
const { displaySetInstanceUID, componentType, numInstances, ...rest } = item;
|
||||
|
||||
const isActive = activeDisplaySetInstanceUIDs.includes(displaySetInstanceUID);
|
||||
return (
|
||||
<Thumbnail
|
||||
key={displaySetInstanceUID}
|
||||
{...rest}
|
||||
displaySetInstanceUID={displaySetInstanceUID}
|
||||
dragData={dragData}
|
||||
description={description}
|
||||
seriesNumber={seriesNumber}
|
||||
numInstances={numInstances || 1}
|
||||
countIcon={countIcon}
|
||||
imageSrc={imageSrc}
|
||||
imageAltText={imageAltText}
|
||||
messages={messages}
|
||||
isActive={isActive}
|
||||
canReject={canReject}
|
||||
onReject={onReject}
|
||||
modality={modality}
|
||||
viewPreset={componentType === 'thumbnailNoImage' ? 'list' : viewPreset}
|
||||
thumbnailType={componentType}
|
||||
onClick={() => onThumbnailClick(displaySetInstanceUID)}
|
||||
onDoubleClick={() => onThumbnailDoubleClick(displaySetInstanceUID)}
|
||||
isTracked={isTracked}
|
||||
loadingProgress={loadingProgress}
|
||||
onClickUntrack={() => onClickUntrack(displaySetInstanceUID)}
|
||||
isHydratedForDerivedDisplaySet={isHydratedForDerivedDisplaySet}
|
||||
viewPreset="thumbnails"
|
||||
onClick={onThumbnailClick.bind(null, displaySetInstanceUID)}
|
||||
onDoubleClick={onThumbnailDoubleClick.bind(null, displaySetInstanceUID)}
|
||||
onClickUntrack={onClickUntrack.bind(null, displaySetInstanceUID)}
|
||||
ThumbnailMenuItems={ThumbnailMenuItems}
|
||||
/>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{/* List Items */}
|
||||
{listItems.length > 0 && (
|
||||
<div
|
||||
id="ohif-thumbnail-list"
|
||||
className="ohif-scrollbar bg-bkg-low grid grid-cols-[repeat(auto-fit,_minmax(0,275px))] place-items-start gap-[2px] overflow-y-hidden"
|
||||
>
|
||||
{listItems.map(item => {
|
||||
const { displaySetInstanceUID, componentType, numInstances, ...rest } = item;
|
||||
const isActive = activeDisplaySetInstanceUIDs.includes(displaySetInstanceUID);
|
||||
return (
|
||||
<Thumbnail
|
||||
key={displaySetInstanceUID}
|
||||
{...rest}
|
||||
displaySetInstanceUID={displaySetInstanceUID}
|
||||
numInstances={numInstances || 1}
|
||||
isActive={isActive}
|
||||
thumbnailType={componentType}
|
||||
viewPreset="list"
|
||||
onClick={onThumbnailClick.bind(null, displaySetInstanceUID)}
|
||||
onDoubleClick={onThumbnailDoubleClick.bind(null, displaySetInstanceUID)}
|
||||
onClickUntrack={onClickUntrack.bind(null, displaySetInstanceUID)}
|
||||
ThumbnailMenuItems={ThumbnailMenuItems}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -107,6 +112,7 @@ ThumbnailList.propTypes = {
|
||||
onThumbnailDoubleClick: PropTypes.func.isRequired,
|
||||
onClickUntrack: PropTypes.func.isRequired,
|
||||
viewPreset: PropTypes.string,
|
||||
ThumbnailMenuItems: PropTypes.any,
|
||||
};
|
||||
|
||||
export { ThumbnailList };
|
||||
Reference in new issue
Block a user