Files
ohif-viewer/platform/ui-next/src/components/SegmentationTable/SegmentationCollapsed.tsx
T
Joe Boccanfuso 82e6a18735 fix(segmentation): List surface representations in the segmentation table for 3D views. (#5700)
This was done by using an array of representation types instead of a single type for a panel.
The first element of the array is the primary type of the panel, and the rest are secondary types
that can also be displayed in the panel.

PR feedback:
- added test to check number of segments in side panel for 3D only view
- fixed jumping to segment in 3D only view
- fixed exception when adding contour segment in 3D only view
2026-01-13 15:57:44 -05:00

182 lines
5.2 KiB
TypeScript

import React from 'react';
import { PanelSection } from '../PanelSection';
import {
useSegmentationTableContext,
SegmentationExpandedProvider,
useSegmentationExpanded,
} from './contexts';
import { useTranslation } from 'react-i18next';
import {
Button,
Icons,
DropdownMenu,
DropdownMenuTrigger,
Tooltip,
TooltipTrigger,
TooltipContent,
Select,
SelectTrigger,
SelectContent,
SelectItem,
SelectValue,
} from '../../components';
// Main header component
const SegmentationCollapsedHeader = ({ children }: { children: React.ReactNode }) => {
return (
<div className="bg-primary-dark flex h-10 w-full items-center space-x-1 rounded-t px-1.5">
{children}
</div>
);
};
// Dropdown menu component - specifically for dropdown menu content
const SegmentationCollapsedDropdownMenu = ({ children }: { children: React.ReactNode }) => {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
>
<Icons.More className="h-6 w-6" />
</Button>
</DropdownMenuTrigger>
{children}
</DropdownMenu>
);
};
// Selector component - for the segmentation selection dropdown
const SegmentationCollapsedSelector = () => {
const { t } = useTranslation('SegmentationPanel.HeaderCollapsed');
const { data, onSegmentationClick, segmentationRepresentationTypes } =
useSegmentationTableContext('SegmentationCollapsedSelector');
const { segmentation } = useSegmentationExpanded('SegmentationCollapsedSelector');
if (!data?.length) {
return null;
}
const segmentations = data
// Only show segmentations of the representation type for this panel. Show all segmentations if no type is specified.
.filter(
seg =>
!segmentationRepresentationTypes ||
segmentationRepresentationTypes.includes(seg.representation?.type)
)
.map(seg => ({
id: seg.segmentation.segmentationId,
label: seg.segmentation.label,
}));
return (
<Select
onValueChange={value => onSegmentationClick(value)}
value={segmentation?.segmentationId}
>
<SelectTrigger className="w-full overflow-hidden">
<SelectValue placeholder={t('Select a segmentation')} />
</SelectTrigger>
<SelectContent>
{segmentations.map(seg => (
<SelectItem
key={seg.id}
value={seg.id}
>
{seg.label}
</SelectItem>
))}
</SelectContent>
</Select>
);
};
// Info component - for displaying the info tooltip
const SegmentationCollapsedInfo = () => {
const { data, activeSegmentationId } = useSegmentationTableContext('SegmentationCollapsedInfo');
const activeSegmentationObj = data.find(
seg => seg.segmentation.segmentationId === activeSegmentationId
);
const info = activeSegmentationObj?.segmentation.cachedStats?.info;
return (
<Tooltip delayDuration={100}>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
>
<Icons.Info className="h-6 w-6" />
</Button>
</TooltipTrigger>
<TooltipContent
side="bottom"
align="end"
>
{info}
</TooltipContent>
</Tooltip>
);
};
// Content component - for the main collapsed view content
const SegmentationCollapsedContent = ({ children }: { children: React.ReactNode }) => {
return <div className="collapsed-content">{children}</div>;
};
// Main compound component
const SegmentationCollapsedRoot: React.FC<{ children?: React.ReactNode }> = ({
children = null,
}) => {
const { mode, data, segmentationRepresentationTypes, selectedSegmentationIdForType } =
useSegmentationTableContext('SegmentationCollapsed');
// Find the segmentations for the representation type for this collapsed view.
const segmentations = data.filter(
segmentation =>
!segmentationRepresentationTypes ||
segmentationRepresentationTypes.includes(segmentation.representation?.type)
);
// Check if we should render.
if (mode !== 'collapsed' || !data || data.length === 0 || segmentations.length === 0) {
return null;
}
// Find the selected segmentation info for the representation type, or default to the first one.
const selectedSegmentationInfo =
segmentations.find(
segmentation => segmentation.segmentation.segmentationId === selectedSegmentationIdForType
) ?? segmentations[0];
return (
<div className="space-y-0">
<PanelSection className="mb-0">
<SegmentationExpandedProvider
segmentation={selectedSegmentationInfo.segmentation}
representation={selectedSegmentationInfo.representation}
isActive={true}
onSegmentationClick={() => {}} // No-op since it's already the active one
>
{children}
</SegmentationExpandedProvider>
</PanelSection>
</div>
);
};
// Add the subcomponents to the main component
const SegmentationCollapsed = Object.assign(SegmentationCollapsedRoot, {
Header: SegmentationCollapsedHeader,
DropdownMenu: SegmentationCollapsedDropdownMenu,
Selector: SegmentationCollapsedSelector,
Info: SegmentationCollapsedInfo,
Content: SegmentationCollapsedContent,
});
// Export the component
export { SegmentationCollapsed };