feat(segmentation): Enhanced segmentation panel design for TMTV (#3988)
Co-authored-by: Alireza <ar.sedghi@gmail.com>
This commit is contained in:
1 parent
e8e209291c
commit
9f3235ff09
51 files changed
+2001
-572
No files matched your search
@@ -2,15 +2,14 @@ import React from 'react';
|
||||
import Icon from '../Icon';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
function AddSegmentRow({ onClick }) {
|
||||
function AddSegmentRow({ onClick, onToggleSegmentationVisibility = null, segmentation = null }) {
|
||||
const { t } = useTranslation('SegmentationTable');
|
||||
return (
|
||||
<div
|
||||
className="flex hover:cursor-pointer"
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className="h-[28px] w-[28px]"></div>
|
||||
<div className="group ml-2.5 mt-1">
|
||||
<div className="flex justify-between bg-black pl-[34px] hover:cursor-pointer">
|
||||
<div
|
||||
className="group pt-[5px] pb-[5px]"
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className="text-primary-active group-hover:bg-secondary-dark flex items-center rounded-[4px] pr-2">
|
||||
<div className="grid h-[28px] w-[28px] place-items-center">
|
||||
<Icon name="icon-add" />
|
||||
@@ -18,6 +17,26 @@ function AddSegmentRow({ onClick }) {
|
||||
<span className="text-[13px]">{t('Add segment')}</span>
|
||||
</div>
|
||||
</div>
|
||||
{segmentation && (
|
||||
<div className="flex items-center">
|
||||
<div
|
||||
className="hover:bg-secondary-dark ml-3 mr-1 grid h-[28px] w-[28px] cursor-pointer place-items-center rounded-[4px]"
|
||||
onClick={() => onToggleSegmentationVisibility(segmentation.id)}
|
||||
>
|
||||
{segmentation.isVisible ? (
|
||||
<Icon
|
||||
name="row-shown"
|
||||
className="text-primary-active"
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
name="row-hidden"
|
||||
className="text-primary-active"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Select, Icon, Dropdown } from '../../components';
|
||||
import { Select, Icon, Dropdown, Tooltip } from '../../components';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -31,7 +31,7 @@ function SegmentationDropDownRow({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="group mx-0.5 mt-[8px] flex items-center">
|
||||
<div className="group mx-0.5 mt-[8px] flex items-center pb-[10px]">
|
||||
<div
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
@@ -97,7 +97,7 @@ function SegmentationDropDownRow({
|
||||
],
|
||||
]}
|
||||
>
|
||||
<div className="hover:bg-secondary-dark mx-1 grid h-[28px] w-[28px] cursor-pointer place-items-center rounded-[4px]">
|
||||
<div className="hover:bg-secondary-dark grid h-[28px] w-[28px] cursor-pointer place-items-center rounded-[4px]">
|
||||
<Icon name="icon-more-menu"></Icon>
|
||||
</div>
|
||||
</Dropdown>
|
||||
@@ -122,8 +122,22 @@ function SegmentationDropDownRow({
|
||||
/>
|
||||
)}
|
||||
<div className="flex items-center">
|
||||
<Tooltip
|
||||
position="bottom-right"
|
||||
content={
|
||||
<div className="flex flex-col">
|
||||
<div className="text-[13px] text-white">Series:</div>
|
||||
<div className="text-aqua-pale text-[13px]">{activeSegmentation.description}</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
name="info-action"
|
||||
className="text-primary-active"
|
||||
/>
|
||||
</Tooltip>
|
||||
<div
|
||||
className="hover:bg-secondary-dark ml-3 mr-1 grid h-[28px] w-[28px] cursor-pointer place-items-center rounded-[4px]"
|
||||
className="hover:bg-secondary-dark mr-1 grid h-[28px] w-[28px] cursor-pointer place-items-center rounded-[4px]"
|
||||
onClick={() => onToggleSegmentationVisibility(activeSegmentation.id)}
|
||||
>
|
||||
{activeSegmentation.isVisible ? (
|
||||
|
||||
@@ -20,6 +20,7 @@ const SegmentItem = ({
|
||||
onColor,
|
||||
onToggleVisibility,
|
||||
onToggleLocked,
|
||||
displayText,
|
||||
}) => {
|
||||
const [isNumberBoxHovering, setIsNumberBoxHovering] = useState(false);
|
||||
|
||||
@@ -27,7 +28,9 @@ const SegmentItem = ({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classnames('text-aqua-pale group/row flex min-h-[28px] bg-black')}
|
||||
className={classnames('text-aqua-pale group/row bg-primary-dark flex min-h-[28px] flex-col', {
|
||||
'bg-primary-light border-primary-light rounded-l-[4px] border text-black': isActive,
|
||||
})}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
onClick(segmentationId, segmentIndex);
|
||||
@@ -35,114 +38,140 @@ const SegmentItem = ({
|
||||
tabIndex={0}
|
||||
data-cy={'segment-item'}
|
||||
>
|
||||
<div
|
||||
className={classnames('bg-primary-dark group/number grid w-[32px] place-items-center', {
|
||||
'!bg-primary-light border-primary-light rounded-l-[4px] border text-black': isActive,
|
||||
'border-primary-dark border': !isActive,
|
||||
})}
|
||||
onMouseEnter={() => setIsNumberBoxHovering(true)}
|
||||
onMouseLeave={() => setIsNumberBoxHovering(false)}
|
||||
>
|
||||
{isNumberBoxHovering && showDelete ? (
|
||||
<Icon
|
||||
name="close"
|
||||
className={classnames('h-[8px] w-[8px]', {
|
||||
'hover:cursor-pointer hover:opacity-60': !disableEditing,
|
||||
})}
|
||||
onClick={e => {
|
||||
if (disableEditing) {
|
||||
return;
|
||||
}
|
||||
e.stopPropagation();
|
||||
onDelete(segmentationId, segmentIndex);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div>{segmentIndex}</div>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={classnames('relative flex w-full', {
|
||||
'border-primary-light bg-primary-dark rounded-r-[4px] border border-l-0': isActive,
|
||||
'border border-l-0 border-transparent': !isActive,
|
||||
})}
|
||||
>
|
||||
<div className="group-hover/row:bg-primary-dark flex h-full w-full flex-grow items-center">
|
||||
<div className="pl-2 pr-1.5">
|
||||
<div
|
||||
className={classnames('h-[8px] w-[8px] grow-0 rounded-full', {
|
||||
<div className="flex min-h-[28px]">
|
||||
<div
|
||||
className={classnames('group/number grid w-[28px] place-items-center', {
|
||||
'bg-primary-light border-primary-light rounded-l-[4px] border text-black': isActive,
|
||||
'bg-primary-dark border-primary-dark border': !isActive,
|
||||
})}
|
||||
onMouseEnter={() => setIsNumberBoxHovering(true)}
|
||||
onMouseLeave={() => setIsNumberBoxHovering(false)}
|
||||
>
|
||||
{isNumberBoxHovering && showDelete ? (
|
||||
<Icon
|
||||
name="close"
|
||||
className={classnames('h-[8px] w-[8px]', {
|
||||
'hover:cursor-pointer hover:opacity-60': !disableEditing,
|
||||
})}
|
||||
style={{ backgroundColor: cssColor }}
|
||||
onClick={e => {
|
||||
if (disableEditing) {
|
||||
return;
|
||||
}
|
||||
e.stopPropagation();
|
||||
onColor(segmentationId, segmentIndex);
|
||||
onDelete(segmentationId, segmentIndex);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center py-1 hover:cursor-pointer">{label}</div>
|
||||
) : (
|
||||
<div>{segmentIndex}</div>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={classnames(
|
||||
'absolute right-3 top-0 flex flex-row-reverse rounded-lg pt-[3px]',
|
||||
{}
|
||||
)}
|
||||
className=" h-[30px] bg-black"
|
||||
style={{ width: '3px' }}
|
||||
></div>
|
||||
<div
|
||||
className={classnames('text-aqua-pale relative flex w-full', {
|
||||
'border border-l-0 border-transparent': !isActive,
|
||||
})}
|
||||
style={{
|
||||
width: 'calc(100% - 40px)',
|
||||
}}
|
||||
>
|
||||
<div className="group-hover/row:hidden">
|
||||
{!isVisible && (
|
||||
<Icon
|
||||
name="row-hidden"
|
||||
className="h-5 w-5 text-[#3d5871]"
|
||||
<div className="bg-primary-dark flex h-full flex-grow items-center">
|
||||
<div className="pl-2 pr-1.5">
|
||||
<div
|
||||
className={classnames('h-[8px] w-[8px] grow-0 rounded-full', {
|
||||
'hover:cursor-pointer hover:opacity-60': !disableEditing,
|
||||
})}
|
||||
style={{ backgroundColor: cssColor }}
|
||||
onClick={e => {
|
||||
if (disableEditing) {
|
||||
return;
|
||||
}
|
||||
e.stopPropagation();
|
||||
onToggleVisibility(segmentationId, segmentIndex);
|
||||
onColor(segmentationId, segmentIndex);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center py-1 hover:cursor-pointer">{label}</div>
|
||||
</div>
|
||||
|
||||
{/* Icon for 'row-lock' that shows when NOT hovering and 'isLocked' is true */}
|
||||
<div className="group-hover/row:hidden">
|
||||
{isLocked && (
|
||||
<div className="flex">
|
||||
<div
|
||||
className={classnames(
|
||||
'absolute right-3 top-0 flex flex-row-reverse rounded-lg pt-[3px]',
|
||||
{}
|
||||
)}
|
||||
>
|
||||
<div className="group-hover/row:hidden">
|
||||
{!isVisible && (
|
||||
<Icon
|
||||
name="row-lock"
|
||||
name="row-hidden"
|
||||
className="h-5 w-5 text-[#3d5871]"
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
onToggleLocked(segmentationId, segmentIndex);
|
||||
onToggleVisibility(segmentationId, segmentIndex);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* This icon is visible when 'isVisible' is true */}
|
||||
{isVisible && (
|
||||
{/* Icon for 'row-lock' that shows when NOT hovering and 'isLocked' is true */}
|
||||
<div className="group-hover/row:hidden">
|
||||
{isLocked && (
|
||||
<div className="flex">
|
||||
<Icon
|
||||
name="row-hidden"
|
||||
className="h-5 w-5 opacity-0"
|
||||
name="row-lock"
|
||||
className="h-5 w-5 text-[#3d5871]"
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
onToggleLocked(segmentationId, segmentIndex);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Icons that show only when hovering */}
|
||||
<div className="hidden group-hover/row:flex">
|
||||
<HoveringIcons
|
||||
disableEditing={disableEditing}
|
||||
onEdit={onEdit}
|
||||
isLocked={isLocked}
|
||||
isVisible={isVisible}
|
||||
onToggleLocked={onToggleLocked}
|
||||
onToggleVisibility={onToggleVisibility}
|
||||
segmentationId={segmentationId}
|
||||
segmentIndex={segmentIndex}
|
||||
/>
|
||||
{/* This icon is visible when 'isVisible' is true */}
|
||||
{isVisible && (
|
||||
<Icon
|
||||
name="row-hidden"
|
||||
className="h-5 w-5 opacity-0"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Icons that show only when hovering */}
|
||||
<div className="hidden group-hover/row:flex">
|
||||
<HoveringIcons
|
||||
disableEditing={disableEditing}
|
||||
onEdit={onEdit}
|
||||
isLocked={isLocked}
|
||||
isVisible={isVisible}
|
||||
onToggleLocked={onToggleLocked}
|
||||
onToggleVisibility={onToggleVisibility}
|
||||
segmentationId={segmentationId}
|
||||
segmentIndex={segmentIndex}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{Array.isArray(displayText) ? (
|
||||
<div className="flex flex-col py-[5px] pl-[43px]">
|
||||
{displayText.map(text => (
|
||||
<div
|
||||
key={text}
|
||||
className="text-aqua-pale flex h-full items-center bg-black text-[11px]"
|
||||
>
|
||||
{text}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
displayText && (
|
||||
<div className="text-aqua-pale flex h-full items-center bg-black px-2 py-[5px] pl-[45px] text-[11px]">
|
||||
{displayText}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -205,6 +234,7 @@ SegmentItem.propTypes = {
|
||||
onDelete: PropTypes.func.isRequired,
|
||||
onToggleVisibility: PropTypes.func.isRequired,
|
||||
onToggleLocked: PropTypes.func,
|
||||
displayText: PropTypes.string,
|
||||
};
|
||||
|
||||
SegmentItem.defaultProps = {
|
||||
|
||||
@@ -100,7 +100,7 @@ const SegmentationGroupTable = ({
|
||||
)}
|
||||
<div className="bg-primary-dark">
|
||||
{segmentations?.length === 0 ? (
|
||||
<div className="select-none rounded-[4px]">
|
||||
<div className="select-none bg-black pt-[5px] pb-[5px]">
|
||||
{showAddSegmentation && !disableEditing && (
|
||||
<NoSegmentationRow onSegmentationAdd={onSegmentationAdd} />
|
||||
)}
|
||||
@@ -127,7 +127,7 @@ const SegmentationGroupTable = ({
|
||||
)}
|
||||
</div>
|
||||
{activeSegmentation && (
|
||||
<div className="ohif-scrollbar mt-1.5 flex min-h-0 flex-col overflow-y-hidden">
|
||||
<div className="ohif-scrollbar flex h-fit max-h-[500px] min-h-0 flex-col overflow-auto bg-black">
|
||||
{activeSegmentation?.segments?.map(segment => {
|
||||
if (!segment) {
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { PanelSection } from '../../components';
|
||||
import SegmentationConfig from './SegmentationConfig';
|
||||
import NoSegmentationRow from './NoSegmentationRow';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import SegmentationItem from './SegmentationItem';
|
||||
|
||||
const SegmentationGroupTableExpanded = ({
|
||||
segmentations,
|
||||
// segmentation initial config
|
||||
segmentationConfig,
|
||||
// UI show/hide
|
||||
disableEditing,
|
||||
showAddSegmentation,
|
||||
showAddSegment,
|
||||
showDeleteSegment,
|
||||
// segmentation/segment handlers
|
||||
onSegmentationAdd,
|
||||
onSegmentationEdit,
|
||||
onSegmentationClick,
|
||||
onSegmentationDelete,
|
||||
onSegmentationDownload,
|
||||
onSegmentationDownloadRTSS,
|
||||
storeSegmentation,
|
||||
// segment handlers
|
||||
onSegmentClick,
|
||||
onSegmentAdd,
|
||||
onSegmentDelete,
|
||||
onSegmentEdit,
|
||||
onToggleSegmentationVisibility,
|
||||
onToggleSegmentVisibility,
|
||||
onToggleSegmentLock,
|
||||
onSegmentColorClick,
|
||||
// segmentation config handlers
|
||||
setFillAlpha,
|
||||
setFillAlphaInactive,
|
||||
setOutlineWidthActive,
|
||||
setOutlineOpacityActive,
|
||||
setRenderFill,
|
||||
setRenderInactiveSegmentations,
|
||||
setRenderOutline,
|
||||
}) => {
|
||||
const [isConfigOpen, setIsConfigOpen] = useState(false);
|
||||
const [activeSegmentationId, setActiveSegmentationId] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
// find the first active segmentation to set
|
||||
let activeSegmentationIdToSet = segmentations?.find(segmentation => segmentation.isActive)?.id;
|
||||
|
||||
// If there is no active segmentation, set the first one to be active
|
||||
if (!activeSegmentationIdToSet && segmentations?.length > 0) {
|
||||
activeSegmentationIdToSet = segmentations[0].id;
|
||||
}
|
||||
|
||||
// If there is no segmentation, set the active segmentation to null
|
||||
if (segmentations?.length === 0) {
|
||||
activeSegmentationIdToSet = null;
|
||||
}
|
||||
|
||||
setActiveSegmentationId(activeSegmentationIdToSet);
|
||||
}, [segmentations]);
|
||||
|
||||
const activeSegmentation = segmentations?.find(
|
||||
segmentation => segmentation.id === activeSegmentationId
|
||||
);
|
||||
const { t } = useTranslation('SegmentationTable');
|
||||
|
||||
return (
|
||||
<div className="flex min-h-0 flex-col bg-black text-[13px] font-[300]">
|
||||
<PanelSection
|
||||
title={t('Segmentation')}
|
||||
actionIcons={
|
||||
activeSegmentation && [
|
||||
{
|
||||
name: 'settings-bars',
|
||||
onClick: () => setIsConfigOpen(isOpen => !isOpen),
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
{isConfigOpen && (
|
||||
<SegmentationConfig
|
||||
setFillAlpha={setFillAlpha}
|
||||
setFillAlphaInactive={setFillAlphaInactive}
|
||||
setOutlineWidthActive={setOutlineWidthActive}
|
||||
setOutlineOpacityActive={setOutlineOpacityActive}
|
||||
setRenderFill={setRenderFill}
|
||||
setRenderInactiveSegmentations={setRenderInactiveSegmentations}
|
||||
setRenderOutline={setRenderOutline}
|
||||
segmentationConfig={segmentationConfig}
|
||||
/>
|
||||
)}
|
||||
<div className="bg-primary-dark">
|
||||
<div className="select-none bg-black pt-[5px] pb-[5px]">
|
||||
{showAddSegmentation && !disableEditing && (
|
||||
<NoSegmentationRow onSegmentationAdd={onSegmentationAdd} />
|
||||
)}
|
||||
</div>
|
||||
{segmentations?.length > 0 && (
|
||||
<div className="ohif-scrollbar flex max-h-[500px] select-none flex-col gap-[5px] overflow-auto bg-black">
|
||||
{segmentations?.map(segmentation => {
|
||||
return (
|
||||
<div key={segmentation.id}>
|
||||
<SegmentationItem
|
||||
key={segmentation.id}
|
||||
segmentation={segmentation}
|
||||
disableEditing={disableEditing}
|
||||
onToggleSegmentationVisibility={onToggleSegmentationVisibility}
|
||||
onSegmentationEdit={onSegmentationEdit}
|
||||
onSegmentationDelete={onSegmentationDelete}
|
||||
onSegmentationDownload={onSegmentationDownload}
|
||||
onSegmentationDownloadRTSS={onSegmentationDownloadRTSS}
|
||||
storeSegmentation={storeSegmentation}
|
||||
showAddSegment={showAddSegment}
|
||||
onSegmentAdd={onSegmentAdd}
|
||||
onSegmentClick={onSegmentClick}
|
||||
onSegmentDelete={onSegmentDelete}
|
||||
onSegmentEdit={onSegmentEdit}
|
||||
showDeleteSegment={showDeleteSegment}
|
||||
onSegmentColorClick={onSegmentColorClick}
|
||||
onToggleSegmentVisibility={onToggleSegmentVisibility}
|
||||
onToggleSegmentLock={onToggleSegmentLock}
|
||||
activeSegmentationId={activeSegmentationId}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</PanelSection>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
SegmentationGroupTableExpanded.propTypes = {
|
||||
segmentations: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
isActive: PropTypes.bool.isRequired,
|
||||
segments: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
segmentIndex: PropTypes.number.isRequired,
|
||||
color: PropTypes.array.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
isVisible: PropTypes.bool.isRequired,
|
||||
isLocked: PropTypes.bool.isRequired,
|
||||
})
|
||||
),
|
||||
})
|
||||
),
|
||||
segmentationConfig: PropTypes.object.isRequired,
|
||||
disableEditing: PropTypes.bool,
|
||||
showAddSegmentation: PropTypes.bool,
|
||||
showAddSegment: PropTypes.bool,
|
||||
showDeleteSegment: PropTypes.bool,
|
||||
onSegmentationAdd: PropTypes.func.isRequired,
|
||||
onSegmentationEdit: PropTypes.func.isRequired,
|
||||
onSegmentationClick: PropTypes.func.isRequired,
|
||||
onSegmentationDelete: PropTypes.func.isRequired,
|
||||
onSegmentationDownload: PropTypes.func.isRequired,
|
||||
onSegmentationDownloadRTSS: PropTypes.func,
|
||||
storeSegmentation: PropTypes.func.isRequired,
|
||||
onSegmentClick: PropTypes.func.isRequired,
|
||||
onSegmentAdd: PropTypes.func.isRequired,
|
||||
onSegmentDelete: PropTypes.func.isRequired,
|
||||
onSegmentEdit: PropTypes.func.isRequired,
|
||||
onToggleSegmentationVisibility: PropTypes.func.isRequired,
|
||||
onToggleSegmentVisibility: PropTypes.func.isRequired,
|
||||
onToggleSegmentLock: PropTypes.func.isRequired,
|
||||
onSegmentColorClick: PropTypes.func.isRequired,
|
||||
setFillAlpha: PropTypes.func.isRequired,
|
||||
setFillAlphaInactive: PropTypes.func.isRequired,
|
||||
setOutlineWidthActive: PropTypes.func.isRequired,
|
||||
setOutlineOpacityActive: PropTypes.func.isRequired,
|
||||
setRenderFill: PropTypes.func.isRequired,
|
||||
setRenderInactiveSegmentations: PropTypes.func.isRequired,
|
||||
setRenderOutline: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
SegmentationGroupTableExpanded.defaultProps = {
|
||||
segmentations: [],
|
||||
disableEditing: false,
|
||||
showAddSegmentation: true,
|
||||
showAddSegment: true,
|
||||
showDeleteSegment: true,
|
||||
onSegmentationAdd: () => {},
|
||||
onSegmentationEdit: () => {},
|
||||
onSegmentationClick: () => {},
|
||||
onSegmentationDelete: () => {},
|
||||
onSegmentationDownload: () => {},
|
||||
onSemgnetationDownloadRTSS: () => {},
|
||||
storeSegmentation: () => {},
|
||||
onSegmentClick: () => {},
|
||||
onSegmentAdd: () => {},
|
||||
onSegmentDelete: () => {},
|
||||
onSegmentEdit: () => {},
|
||||
onToggleSegmentationVisibility: () => {},
|
||||
onToggleSegmentVisibility: () => {},
|
||||
onToggleSegmentLock: () => {},
|
||||
onSegmentColorClick: () => {},
|
||||
setFillAlpha: () => {},
|
||||
setFillAlphaInactive: () => {},
|
||||
setOutlineWidthActive: () => {},
|
||||
setOutlineOpacityActive: () => {},
|
||||
setRenderFill: () => {},
|
||||
setRenderInactiveSegmentations: () => {},
|
||||
setRenderOutline: () => {},
|
||||
};
|
||||
export default SegmentationGroupTableExpanded;
|
||||
@@ -0,0 +1,211 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Icon, Dropdown } from '../../components';
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import AddSegmentRow from './AddSegmentRow';
|
||||
import SegmentationGroupSegment from './SegmentationGroupSegment';
|
||||
import { Tooltip } from '../../components';
|
||||
|
||||
function SegmentationItem({
|
||||
segmentation,
|
||||
disableEditing,
|
||||
onSegmentationEdit,
|
||||
onSegmentationDownload,
|
||||
onSegmentationDownloadRTSS,
|
||||
storeSegmentation,
|
||||
onSegmentationDelete,
|
||||
showAddSegment,
|
||||
onToggleSegmentationVisibility,
|
||||
onSegmentAdd,
|
||||
onSegmentClick,
|
||||
onSegmentDelete,
|
||||
onSegmentEdit,
|
||||
showDeleteSegment,
|
||||
onSegmentColorClick,
|
||||
onToggleSegmentVisibility,
|
||||
onToggleSegmentLock,
|
||||
activeSegmentationId,
|
||||
}) {
|
||||
const { t } = useTranslation('SegmentationTable');
|
||||
|
||||
const [areChildrenVisible, setChildrenVisible] = useState(true);
|
||||
|
||||
const handleHeaderClick = () => {
|
||||
setChildrenVisible(!areChildrenVisible);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="bg-secondary-dark group relative flex items-center justify-start gap-1">
|
||||
<div
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
className="flex"
|
||||
>
|
||||
<Dropdown
|
||||
id="segmentation-dropdown"
|
||||
showDropdownIcon={false}
|
||||
alignment="left"
|
||||
itemsClassName="text-primary-active"
|
||||
showBorders={false}
|
||||
maxCharactersPerLine={30}
|
||||
list={[
|
||||
...(!disableEditing
|
||||
? [
|
||||
{
|
||||
title: t('Rename'),
|
||||
onClick: () => {
|
||||
onSegmentationEdit(segmentation.id);
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
title: t('Delete'),
|
||||
onClick: () => {
|
||||
onSegmentationDelete(segmentation.id);
|
||||
},
|
||||
},
|
||||
...(!disableEditing
|
||||
? [
|
||||
{
|
||||
title: t('Export DICOM SEG'),
|
||||
onClick: () => {
|
||||
storeSegmentation(segmentation.id);
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...[
|
||||
{
|
||||
title: t('Download DICOM SEG'),
|
||||
onClick: () => {
|
||||
onSegmentationDownload(segmentation.id);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Download DICOM RTSTRUCT'),
|
||||
onClick: () => {
|
||||
onSegmentationDownloadRTSS(segmentation.id);
|
||||
},
|
||||
},
|
||||
],
|
||||
]}
|
||||
>
|
||||
<div className="hover:bg-secondary-dark grid h-[28px] w-[28px] cursor-pointer place-items-center rounded-[4px]">
|
||||
<Icon name="icon-more-menu"></Icon>
|
||||
</div>
|
||||
</Dropdown>
|
||||
<div
|
||||
className=" h-[28px] bg-black"
|
||||
style={{ width: '3px' }}
|
||||
></div>
|
||||
</div>
|
||||
<div
|
||||
className="flex h-full w-full cursor-pointer items-center justify-between pr-[8px]"
|
||||
onClick={handleHeaderClick}
|
||||
>
|
||||
<div className="font-inter text-aqua-pale text-[13px]">{segmentation.label}</div>
|
||||
<div className="flex h-[28px] items-center justify-center gap-2">
|
||||
<Tooltip
|
||||
position="bottom-right"
|
||||
content={
|
||||
<div className="flex flex-col">
|
||||
<div className="text-[13px] text-white">Series:</div>
|
||||
<div className="text-aqua-pale text-[13px]">{segmentation.description}</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
name="info-action"
|
||||
className="text-primary-active"
|
||||
/>
|
||||
</Tooltip>
|
||||
<div className={areChildrenVisible ? '' : 'mr-[4px]'}>
|
||||
<Icon name={areChildrenVisible ? 'chevron-down-new' : 'chevron-left-new'} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{areChildrenVisible && (
|
||||
<>
|
||||
{!disableEditing && showAddSegment && (
|
||||
<AddSegmentRow
|
||||
onClick={() => onSegmentAdd(segmentation.id)}
|
||||
onToggleSegmentationVisibility={onToggleSegmentationVisibility}
|
||||
segmentation={segmentation}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={classNames('ohif-scrollbar flex min-h-0 flex-col overflow-y-hidden', {
|
||||
'mt-1': disableEditing || !showAddSegment,
|
||||
})}
|
||||
>
|
||||
{segmentation?.segments?.map(segment => {
|
||||
if (!segment) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { segmentIndex, color, label, isVisible, isLocked, displayText } = segment;
|
||||
return (
|
||||
<div key={segmentIndex}>
|
||||
<SegmentationGroupSegment
|
||||
segmentationId={segmentation.id}
|
||||
segmentIndex={segmentIndex}
|
||||
label={label}
|
||||
color={color}
|
||||
isActive={
|
||||
segmentation.activeSegmentIndex === segmentIndex &&
|
||||
segmentation.id === activeSegmentationId
|
||||
}
|
||||
disableEditing={disableEditing}
|
||||
isLocked={isLocked}
|
||||
isVisible={isVisible}
|
||||
onClick={onSegmentClick}
|
||||
onEdit={onSegmentEdit}
|
||||
onDelete={onSegmentDelete}
|
||||
showDelete={showDeleteSegment}
|
||||
onColor={onSegmentColorClick}
|
||||
onToggleVisibility={onToggleSegmentVisibility}
|
||||
onToggleLocked={onToggleSegmentLock}
|
||||
displayText={displayText}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
SegmentationItem.propTypes = {
|
||||
segmentation: PropTypes.object,
|
||||
disableEditing: PropTypes.bool,
|
||||
onToggleSegmentationVisibility: PropTypes.func,
|
||||
onSegmentationEdit: PropTypes.func,
|
||||
onSegmentationDownload: PropTypes.func,
|
||||
onSegmentationDownloadRTSS: PropTypes.func,
|
||||
storeSegmentation: PropTypes.func,
|
||||
onSegmentationDelete: PropTypes.func,
|
||||
showAddSegment: PropTypes.bool,
|
||||
onSegmentAdd: PropTypes.func,
|
||||
onSegmentClick: PropTypes.func,
|
||||
onSegmentDelete: PropTypes.func,
|
||||
onSegmentEdit: PropTypes.func,
|
||||
showDeleteSegment: PropTypes.bool,
|
||||
onSegmentColorClick: PropTypes.func,
|
||||
onToggleSegmentVisibility: PropTypes.func,
|
||||
onToggleSegmentLock: PropTypes.func,
|
||||
activeSegmentationId: PropTypes.string,
|
||||
};
|
||||
|
||||
SegmentationItem.defaultProps = {
|
||||
segmentation: null,
|
||||
disableEditing: false,
|
||||
};
|
||||
|
||||
export default SegmentationItem;
|
||||
@@ -1,3 +1,4 @@
|
||||
import SegmentationGroupTable from './SegmentationGroupTable';
|
||||
import SegmentationGroupTableExpanded from './SegmentationGroupTableExpanded';
|
||||
|
||||
export default SegmentationGroupTable;
|
||||
export { SegmentationGroupTable, SegmentationGroupTableExpanded };
|
||||
Reference in new issue
Block a user