ui(components): Viewport components updated to ui-next with design updates (#4886)
This commit is contained in:
parent
4e09f85d5d
commit
a8ac297e16
@ -1,6 +1,6 @@
|
|||||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { ViewportActionArrows } from '@ohif/ui';
|
import { ViewportActionArrows } from '@ohif/ui-next';
|
||||||
import { useViewportGrid } from '@ohif/ui-next';
|
import { useViewportGrid } from '@ohif/ui-next';
|
||||||
import { utils } from '@ohif/extension-cornerstone';
|
import { utils } from '@ohif/extension-cornerstone';
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ViewportActionButton } from '@ohif/ui';
|
import { ViewportActionButton } from '@ohif/ui-next';
|
||||||
import { Icons, Tooltip, TooltipTrigger, TooltipContent } from '@ohif/ui-next';
|
import { Icons, Tooltip, TooltipTrigger, TooltipContent } from '@ohif/ui-next';
|
||||||
|
|
||||||
export default function _getStatusComponent({ isHydrated, onStatusClick }) {
|
export default function _getStatusComponent({ isHydrated, onStatusClick }) {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ViewportActionArrows } from '@ohif/ui';
|
import { ViewportActionArrows } from '@ohif/ui-next';
|
||||||
import { useViewportGrid } from '@ohif/ui-next';
|
import { useViewportGrid } from '@ohif/ui-next';
|
||||||
import createSEGToolGroupAndAddTools from '../utils/initSEGToolGroup';
|
import createSEGToolGroupAndAddTools from '../utils/initSEGToolGroup';
|
||||||
import promptHydrateSEG from '../utils/promptHydrateSEG';
|
import promptHydrateSEG from '../utils/promptHydrateSEG';
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ViewportActionButton } from '@ohif/ui';
|
import { ViewportActionButton } from '@ohif/ui-next';
|
||||||
import { Icons, Tooltip, TooltipTrigger, TooltipContent } from '@ohif/ui-next';
|
import { Icons, Tooltip, TooltipTrigger, TooltipContent } from '@ohif/ui-next';
|
||||||
|
|
||||||
export default function _getStatusComponent({ isHydrated, onStatusClick }) {
|
export default function _getStatusComponent({ isHydrated, onStatusClick }) {
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { ExtensionManager, useToolbar } from '@ohif/core';
|
|||||||
|
|
||||||
import { setTrackingUniqueIdentifiersForElement } from '../tools/modules/dicomSRModule';
|
import { setTrackingUniqueIdentifiersForElement } from '../tools/modules/dicomSRModule';
|
||||||
|
|
||||||
import { ViewportActionArrows } from '@ohif/ui';
|
import { ViewportActionArrows } from '@ohif/ui-next';
|
||||||
import createReferencedImageDisplaySet from '../utils/createReferencedImageDisplaySet';
|
import createReferencedImageDisplaySet from '../utils/createReferencedImageDisplaySet';
|
||||||
import { usePositionPresentationStore } from '@ohif/extension-cornerstone';
|
import { usePositionPresentationStore } from '@ohif/extension-cornerstone';
|
||||||
import { useViewportGrid } from '@ohif/ui-next';
|
import { useViewportGrid } from '@ohif/ui-next';
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { SOPClassHandlerId, SOPClassHandlerId3D } from './id';
|
import { SOPClassHandlerId, SOPClassHandlerId3D } from './id';
|
||||||
import { ViewportActionButton } from '@ohif/ui';
|
import { ViewportActionButton } from '@ohif/ui-next';
|
||||||
import i18n from '@ohif/i18n';
|
import i18n from '@ohif/i18n';
|
||||||
|
|
||||||
export default function onModeEnter({ servicesManager }) {
|
export default function onModeEnter({ servicesManager }) {
|
||||||
|
|||||||
@ -3,7 +3,8 @@ import { useResizeDetector } from 'react-resize-detector';
|
|||||||
import * as cs3DTools from '@cornerstonejs/tools';
|
import * as cs3DTools from '@cornerstonejs/tools';
|
||||||
import { Enums, eventTarget, getEnabledElement } from '@cornerstonejs/core';
|
import { Enums, eventTarget, getEnabledElement } from '@cornerstonejs/core';
|
||||||
import { MeasurementService } from '@ohif/core';
|
import { MeasurementService } from '@ohif/core';
|
||||||
import { AllInOneMenu, useViewportDialog } from '@ohif/ui';
|
import { AllInOneMenu } from '@ohif/ui-next';
|
||||||
|
import { useViewportDialog } from '@ohif/ui';
|
||||||
import type { Types as csTypes } from '@cornerstonejs/core';
|
import type { Types as csTypes } from '@cornerstonejs/core';
|
||||||
|
|
||||||
import { setEnabledElement } from '../state';
|
import { setEnabledElement } from '../state';
|
||||||
@ -99,7 +100,7 @@ const OHIFCornerstoneViewport = React.memo(
|
|||||||
const [viewportDialogState] = useViewportDialog();
|
const [viewportDialogState] = useViewportDialog();
|
||||||
// useCallback for scroll bar height calculation
|
// useCallback for scroll bar height calculation
|
||||||
const setImageScrollBarHeight = useCallback(() => {
|
const setImageScrollBarHeight = useCallback(() => {
|
||||||
const scrollbarHeight = `${elementRef.current.clientHeight - 40}px`;
|
const scrollbarHeight = `${elementRef.current.clientHeight - 10}px`;
|
||||||
setScrollbarHeight(scrollbarHeight);
|
setScrollbarHeight(scrollbarHeight);
|
||||||
}, [elementRef]);
|
}, [elementRef]);
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { vec3 } from 'gl-matrix';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { metaData, Enums, utilities } from '@cornerstonejs/core';
|
import { metaData, Enums, utilities } from '@cornerstonejs/core';
|
||||||
import type { ImageSliceData } from '@cornerstonejs/core/types';
|
import type { ImageSliceData } from '@cornerstonejs/core/types';
|
||||||
import { ViewportOverlay } from '@ohif/ui';
|
import { ViewportOverlay } from '@ohif/ui-next';
|
||||||
import type { InstanceMetadata } from '@ohif/core/src/types';
|
import type { InstanceMetadata } from '@ohif/core/src/types';
|
||||||
import { formatDICOMDate, formatDICOMTime, formatNumberPrecision } from './utils';
|
import { formatDICOMDate, formatDICOMTime, formatNumberPrecision } from './utils';
|
||||||
import { utils } from '@ohif/core';
|
import { utils } from '@ohif/core';
|
||||||
@ -368,7 +368,7 @@ function OverlayItem(props) {
|
|||||||
title={title}
|
title={title}
|
||||||
>
|
>
|
||||||
{label ? <span className="mr-1 shrink-0">{label}</span> : null}
|
{label ? <span className="mr-1 shrink-0">{label}</span> : null}
|
||||||
<span className="ml-1 mr-2 shrink-0">{value}</span>
|
<span className="ml-0 mr-2 shrink-0">{value}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -387,10 +387,10 @@ function VOIOverlayItem({ voi, customization }: OverlayItemProps) {
|
|||||||
className="overlay-item flex flex-row"
|
className="overlay-item flex flex-row"
|
||||||
style={{ color: customization?.color }}
|
style={{ color: customization?.color }}
|
||||||
>
|
>
|
||||||
<span className="mr-1 shrink-0">W:</span>
|
<span className="mr-0.5 shrink-0 opacity-[0.67]">W:</span>
|
||||||
<span className="ml-1 mr-2 shrink-0">{windowWidth.toFixed(0)}</span>
|
<span className="mr-2.5 shrink-0">{windowWidth.toFixed(0)}</span>
|
||||||
<span className="mr-1 shrink-0">L:</span>
|
<span className="mr-0.5 shrink-0 opacity-[0.67]">L:</span>
|
||||||
<span className="ml-1 shrink-0">{windowCenter.toFixed(0)}</span>
|
<span className="shrink-0">{windowCenter.toFixed(0)}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -404,7 +404,7 @@ function ZoomOverlayItem({ scale, customization }: OverlayItemProps) {
|
|||||||
className="overlay-item flex flex-row"
|
className="overlay-item flex flex-row"
|
||||||
style={{ color: (customization && customization.color) || undefined }}
|
style={{ color: (customization && customization.color) || undefined }}
|
||||||
>
|
>
|
||||||
<span className="mr-1 shrink-0">Zoom:</span>
|
<span className="mr-0.5 shrink-0 opacity-[0.67]">Zoom:</span>
|
||||||
<span>{scale.toFixed(2)}x</span>
|
<span>{scale.toFixed(2)}x</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -428,7 +428,7 @@ function InstanceNumberOverlayItem({
|
|||||||
<span>
|
<span>
|
||||||
{instanceNumber !== undefined && instanceNumber !== null ? (
|
{instanceNumber !== undefined && instanceNumber !== null ? (
|
||||||
<>
|
<>
|
||||||
<span className="mr-1 shrink-0">I:</span>
|
<span className="mr-0.5 shrink-0 opacity-[0.67]">I:</span>
|
||||||
<span>{`${instanceNumber} (${imageIndex + 1}/${numberOfSlices})`}</span>
|
<span>{`${instanceNumber} (${imageIndex + 1}/${numberOfSlices})`}</span>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Enums, VolumeViewport3D, utilities as csUtils } from '@cornerstonejs/core';
|
import { Enums, VolumeViewport3D, utilities as csUtils } from '@cornerstonejs/core';
|
||||||
import { ImageScrollbar } from '@ohif/ui';
|
import { ImageScrollbar } from '@ohif/ui-next';
|
||||||
|
|
||||||
function CornerstoneImageScrollbar({
|
function CornerstoneImageScrollbar({
|
||||||
viewportData,
|
viewportData,
|
||||||
|
|||||||
@ -3,19 +3,18 @@
|
|||||||
--marker-height: 100px;
|
--marker-height: 100px;
|
||||||
--scrollbar-width: 20px;
|
--scrollbar-width: 20px;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
font-size: 15px;
|
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
}
|
}
|
||||||
.ViewportOrientationMarkers .orientation-marker {
|
.ViewportOrientationMarkers .orientation-marker {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
.ViewportOrientationMarkers .top-mid {
|
.ViewportOrientationMarkers .top-mid {
|
||||||
top: 0.6rem;
|
top: 0.38rem;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
}
|
}
|
||||||
.ViewportOrientationMarkers .left-mid {
|
.ViewportOrientationMarkers .left-mid {
|
||||||
top: 47%;
|
top: 47%;
|
||||||
left: 5px;
|
left: 0.38rem;
|
||||||
}
|
}
|
||||||
.ViewportOrientationMarkers .right-mid {
|
.ViewportOrientationMarkers .right-mid {
|
||||||
top: 47%;
|
top: 47%;
|
||||||
|
|||||||
@ -119,8 +119,8 @@ function ViewportOrientationMarkers({
|
|||||||
className={classNames(
|
className={classNames(
|
||||||
'overlay-text',
|
'overlay-text',
|
||||||
`${m}-mid orientation-marker`,
|
`${m}-mid orientation-marker`,
|
||||||
'text-aqua-pale',
|
'text-highlight/65',
|
||||||
'text-[13px]',
|
'text-base',
|
||||||
'leading-5'
|
'leading-5'
|
||||||
)}
|
)}
|
||||||
key={`${m}-mid orientation-marker`}
|
key={`${m}-mid orientation-marker`}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, { ReactElement, useCallback, useEffect, useState } from 'react';
|
import React, { ReactElement, useCallback, useEffect, useState } from 'react';
|
||||||
import { SwitchButton } from '@ohif/ui';
|
import { Switch } from '@ohif/ui-next';
|
||||||
import { StackViewport, VolumeViewport } from '@cornerstonejs/core';
|
import { StackViewport, VolumeViewport } from '@cornerstonejs/core';
|
||||||
import { ColorbarProps } from '../../types/Colorbar';
|
import { ColorbarProps } from '../../types/Colorbar';
|
||||||
import { utilities } from '@cornerstonejs/core';
|
import { utilities } from '@cornerstonejs/core';
|
||||||
@ -22,7 +22,7 @@ export function setViewportColorbar(
|
|||||||
colorbarOptions.ticks = {
|
colorbarOptions.ticks = {
|
||||||
position: 'left',
|
position: 'left',
|
||||||
style: {
|
style: {
|
||||||
font: '12px Arial',
|
font: '13px Inter',
|
||||||
color: '#000000',
|
color: '#000000',
|
||||||
maxNumTicks: 8,
|
maxNumTicks: 8,
|
||||||
tickSize: 5,
|
tickSize: 5,
|
||||||
@ -101,12 +101,18 @@ export function Colorbar({
|
|||||||
}, [viewportId]);
|
}, [viewportId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="all-in-one-menu-item flex w-full justify-center">
|
<div
|
||||||
<div className="mr-2 w-[28px]"></div>
|
className="hover:bg-accent flex h-8 w-full flex-shrink-0 cursor-pointer items-center px-2 text-base hover:rounded"
|
||||||
<SwitchButton
|
onClick={() => {
|
||||||
label="Display Color bar"
|
onSetColorbar();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex w-7 flex-shrink-0 items-center justify-center"></div>
|
||||||
|
<span className="flex-grow">Display Color bar</span>
|
||||||
|
<Switch
|
||||||
|
className="ml-2 flex-shrink-0"
|
||||||
checked={showColorbar}
|
checked={showColorbar}
|
||||||
onChange={() => {
|
onCheckedChange={() => {
|
||||||
onSetColorbar();
|
onSetColorbar();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import React, { ReactElement, useCallback, useEffect, useRef, useState, useMemo } from 'react';
|
import React, { ReactElement, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { AllInOneMenu, ButtonGroup, SwitchButton } from '@ohif/ui';
|
import { AllInOneMenu, Switch, Tabs, TabsList, TabsTrigger } from '@ohif/ui-next';
|
||||||
|
|
||||||
import { StackViewport, Types } from '@cornerstonejs/core';
|
import { StackViewport, Types } from '@cornerstonejs/core';
|
||||||
import { ColormapProps } from '../../types/Colormap';
|
import { ColormapProps } from '../../types/Colormap';
|
||||||
|
|
||||||
@ -26,6 +27,7 @@ export function Colormap({
|
|||||||
|
|
||||||
const onSetColorLUT = useCallback(
|
const onSetColorLUT = useCallback(
|
||||||
props => {
|
props => {
|
||||||
|
debugger;
|
||||||
// TODO: Better way to check if it's a fusion
|
// TODO: Better way to check if it's a fusion
|
||||||
const oneOpacityColormaps = ['Grayscale', 'X Ray'];
|
const oneOpacityColormaps = ['Grayscale', 'X Ray'];
|
||||||
const opacity =
|
const opacity =
|
||||||
@ -64,20 +66,9 @@ export function Colormap({
|
|||||||
return colormap;
|
return colormap;
|
||||||
};
|
};
|
||||||
|
|
||||||
const buttons = useMemo(() => {
|
const activeIndex = displaySets.findIndex(
|
||||||
return displaySets.map((displaySet, index) => ({
|
ds => ds.displaySetInstanceUID === activeDisplaySetRef.current.displaySetInstanceUID
|
||||||
children: displaySet.Modality,
|
);
|
||||||
key: index,
|
|
||||||
style: {
|
|
||||||
minWidth: `calc(100% / ${displaySets.length})`,
|
|
||||||
fontSize: '0.8rem',
|
|
||||||
textAlign: 'center',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
}, [displaySets]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setActiveDisplaySet(displaySets[displaySets.length - 1]);
|
setActiveDisplaySet(displaySets[displaySets.length - 1]);
|
||||||
@ -85,46 +76,52 @@ export function Colormap({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{buttons.length > 1 && (
|
{displaySets.length > 1 && (
|
||||||
<div className="all-in-one-menu-item flex w-full justify-center">
|
<div className="flex h-8 w-full flex-shrink-0 items-center justify-center px-2 text-base">
|
||||||
<ButtonGroup
|
<Tabs
|
||||||
onActiveIndexChange={index => {
|
value={String(activeIndex)}
|
||||||
|
onValueChange={val => {
|
||||||
|
const index = parseInt(val, 10);
|
||||||
setActiveDisplaySet(displaySets[index]);
|
setActiveDisplaySet(displaySets[index]);
|
||||||
setPrePreviewColormap(null);
|
setPrePreviewColormap(null);
|
||||||
}}
|
}}
|
||||||
activeIndex={
|
|
||||||
displaySets.findIndex(
|
|
||||||
ds => ds.displaySetInstanceUID === activeDisplaySetRef.current.displaySetInstanceUID
|
|
||||||
) || 1
|
|
||||||
}
|
|
||||||
className="w-[70%] text-[10px]"
|
|
||||||
>
|
>
|
||||||
{buttons.map(({ children, key, style }) => (
|
<TabsList>
|
||||||
<div
|
{displaySets.map((ds, i) => (
|
||||||
key={key}
|
<TabsTrigger
|
||||||
style={style}
|
key={i}
|
||||||
>
|
value={String(i)}
|
||||||
{children}
|
>
|
||||||
</div>
|
{ds.Modality}
|
||||||
))}
|
</TabsTrigger>
|
||||||
</ButtonGroup>
|
))}
|
||||||
|
</TabsList>
|
||||||
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="all-in-one-menu-item flex w-full justify-center">
|
<div
|
||||||
<SwitchButton
|
className="hover:bg-accent flex h-8 w-full flex-shrink-0 cursor-pointer items-center px-2 text-base hover:rounded"
|
||||||
label="Preview in viewport"
|
onClick={() => setShowPreview(!showPreview)}
|
||||||
|
>
|
||||||
|
<span className="flex-shrink-0">Preview in viewport</span>
|
||||||
|
<Switch
|
||||||
|
className="ml-auto flex-shrink-0"
|
||||||
checked={showPreview}
|
checked={showPreview}
|
||||||
onChange={checked => {
|
onCheckedChange={checked => {
|
||||||
setShowPreview(checked);
|
setShowPreview(checked);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<AllInOneMenu.DividerItem />
|
<AllInOneMenu.DividerItem />
|
||||||
<AllInOneMenu.ItemPanel>
|
<AllInOneMenu.ItemPanel
|
||||||
|
maxHeight="calc(100vh - 250px)"
|
||||||
|
className="min-h-[200px] flex-grow"
|
||||||
|
>
|
||||||
{colormaps.map((colormap, index) => (
|
{colormaps.map((colormap, index) => (
|
||||||
<AllInOneMenu.Item
|
<AllInOneMenu.Item
|
||||||
key={index}
|
key={index}
|
||||||
label={colormap.description}
|
label={colormap.description}
|
||||||
|
useIconSpace={false}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onSetColorLUT({
|
onSetColorLUT({
|
||||||
viewportId,
|
viewportId,
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import React, { ReactElement, useState, useEffect, useCallback } from 'react';
|
import React, { ReactElement, useState, useEffect, useCallback } from 'react';
|
||||||
import { VolumeLightingProps } from '../../types/ViewportPresets';
|
import { VolumeLightingProps } from '../../types/ViewportPresets';
|
||||||
|
import { Numeric } from '@ohif/ui-next';
|
||||||
|
|
||||||
export function VolumeLighting({
|
export function VolumeLighting({
|
||||||
servicesManager,
|
servicesManager,
|
||||||
@ -8,135 +9,75 @@ export function VolumeLighting({
|
|||||||
hasShade,
|
hasShade,
|
||||||
}: VolumeLightingProps): ReactElement {
|
}: VolumeLightingProps): ReactElement {
|
||||||
const { cornerstoneViewportService } = servicesManager.services;
|
const { cornerstoneViewportService } = servicesManager.services;
|
||||||
const [ambient, setAmbient] = useState(null);
|
const [lightingValues, setLightingValues] = useState({
|
||||||
const [diffuse, setDiffuse] = useState(null);
|
ambient: null,
|
||||||
const [specular, setSpecular] = useState(null);
|
diffuse: null,
|
||||||
|
specular: null,
|
||||||
|
});
|
||||||
|
|
||||||
const onAmbientChange = useCallback(() => {
|
// Single callback to handle all lighting property changes
|
||||||
commandsManager.runCommand('setVolumeLighting', { viewportId, options: { ambient } });
|
const onLightingChange = useCallback(
|
||||||
}, [ambient, commandsManager, viewportId]);
|
(property, value) => {
|
||||||
|
commandsManager.runCommand('setVolumeLighting', {
|
||||||
const onDiffuseChange = useCallback(() => {
|
viewportId,
|
||||||
commandsManager.runCommand('setVolumeLighting', { viewportId, options: { diffuse } });
|
options: { [property]: value },
|
||||||
}, [diffuse, commandsManager, viewportId]);
|
});
|
||||||
|
setLightingValues(prev => ({
|
||||||
const onSpecularChange = useCallback(() => {
|
...prev,
|
||||||
commandsManager.runCommand('setVolumeLighting', { viewportId, options: { specular } });
|
[property]: value,
|
||||||
}, [specular, commandsManager, viewportId]);
|
}));
|
||||||
|
},
|
||||||
const calculateBackground = value => {
|
[commandsManager, viewportId]
|
||||||
const percentage = ((value - 0) / (1 - 0)) * 100;
|
);
|
||||||
return `linear-gradient(to right, #5acce6 0%, #5acce6 ${percentage}%, #3a3f99 ${percentage}%, #3a3f99 100%)`;
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||||
const { actor } = viewport.getActors()[0];
|
const { actor } = viewport.getActors()[0];
|
||||||
const ambient = actor.getProperty().getAmbient();
|
const property = actor.getProperty();
|
||||||
const diffuse = actor.getProperty().getDiffuse();
|
|
||||||
const specular = actor.getProperty().getSpecular();
|
|
||||||
setAmbient(ambient);
|
|
||||||
setDiffuse(diffuse);
|
|
||||||
setSpecular(specular);
|
|
||||||
}, [viewportId, cornerstoneViewportService]);
|
|
||||||
const disableOption = hasShade ? '' : 'ohif-disabled !opacity-40';
|
|
||||||
const disableSlider = !hasShade;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div
|
|
||||||
className={`all-in-one-menu-item flex w-full flex-row !items-center justify-between gap-[10px] ${disableOption}`}
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
className="block text-white"
|
|
||||||
htmlFor="ambient"
|
|
||||||
>
|
|
||||||
Ambient
|
|
||||||
</label>
|
|
||||||
{ambient !== null && (
|
|
||||||
<input
|
|
||||||
className="bg-inputfield-main h-2 w-[120px] cursor-pointer appearance-none rounded-lg"
|
|
||||||
value={ambient}
|
|
||||||
onChange={e => {
|
|
||||||
setAmbient(e.target.value);
|
|
||||||
onAmbientChange();
|
|
||||||
}}
|
|
||||||
id="ambient"
|
|
||||||
disabled={disableSlider}
|
|
||||||
max={1}
|
|
||||||
min={0}
|
|
||||||
type="range"
|
|
||||||
step={0.1}
|
|
||||||
style={{
|
|
||||||
background: calculateBackground(ambient),
|
|
||||||
'--thumb-inner-color': '#5acce6',
|
|
||||||
'--thumb-outer-color': '#090c29',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`all-in-one-menu-item flex w-full flex-row !items-center justify-between gap-[10px] ${disableOption}`}
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
className="block text-white"
|
|
||||||
htmlFor="diffuse"
|
|
||||||
>
|
|
||||||
Diffuse
|
|
||||||
</label>
|
|
||||||
{diffuse !== null && (
|
|
||||||
<input
|
|
||||||
className="bg-inputfield-main h-2 w-[120px] cursor-pointer appearance-none rounded-lg"
|
|
||||||
value={diffuse}
|
|
||||||
onChange={e => {
|
|
||||||
setDiffuse(e.target.value);
|
|
||||||
onDiffuseChange();
|
|
||||||
}}
|
|
||||||
disabled={disableSlider}
|
|
||||||
id="diffuse"
|
|
||||||
max={1}
|
|
||||||
min={0}
|
|
||||||
type="range"
|
|
||||||
step={0.1}
|
|
||||||
style={{
|
|
||||||
background: calculateBackground(diffuse),
|
|
||||||
'--thumb-inner-color': '#5acce6',
|
|
||||||
'--thumb-outer-color': '#090c29',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
const values = {
|
||||||
className={`all-in-one-menu-item flex w-full flex-row !items-center justify-between gap-[10px] ${disableOption}`}
|
ambient: property.getAmbient(),
|
||||||
>
|
diffuse: property.getDiffuse(),
|
||||||
<label
|
specular: property.getSpecular(),
|
||||||
className="block text-white"
|
};
|
||||||
htmlFor="specular"
|
|
||||||
>
|
setLightingValues(values);
|
||||||
Specular
|
}, [viewportId, cornerstoneViewportService]);
|
||||||
</label>
|
|
||||||
{specular !== null && (
|
const disableOption = hasShade ? '' : 'ohif-disabled !opacity-40';
|
||||||
<input
|
|
||||||
className="bg-inputfield-main h-2 w-[120px] cursor-pointer appearance-none rounded-lg"
|
// Configuration for our lighting properties
|
||||||
value={specular}
|
const lightingProperties = [
|
||||||
disabled={disableSlider}
|
{ key: 'ambient', label: 'Ambient' },
|
||||||
onChange={e => {
|
{ key: 'diffuse', label: 'Diffuse' },
|
||||||
setSpecular(e.target.value);
|
{ key: 'specular', label: 'Specular' },
|
||||||
onSpecularChange();
|
];
|
||||||
}}
|
|
||||||
id="specular"
|
return (
|
||||||
max={1}
|
<div className="my-1 mt-2 flex flex-col space-y-2">
|
||||||
min={0}
|
{lightingProperties.map(
|
||||||
type="range"
|
({ key, label }) =>
|
||||||
step={0.1}
|
lightingValues[key] !== null && (
|
||||||
style={{
|
<div
|
||||||
background: calculateBackground(specular),
|
key={key}
|
||||||
'--thumb-inner-color': '#5acce6',
|
className={`w-full pl-2 pr-1 ${disableOption}`}
|
||||||
'--thumb-outer-color': '#090c29',
|
>
|
||||||
}}
|
<Numeric.Container
|
||||||
/>
|
mode="singleRange"
|
||||||
)}
|
min={0}
|
||||||
</div>
|
max={1}
|
||||||
</>
|
step={0.1}
|
||||||
|
value={lightingValues[key]}
|
||||||
|
onChange={value => onLightingChange(key, value)}
|
||||||
|
>
|
||||||
|
<div className="flex flex-row items-center">
|
||||||
|
<Numeric.Label className="w-16">{label}</Numeric.Label>
|
||||||
|
<Numeric.SingleRange sliderClassName="mx-2 flex-grow" />
|
||||||
|
</div>
|
||||||
|
</Numeric.Container>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, { ReactElement, useState } from 'react';
|
import React, { ReactElement, useState } from 'react';
|
||||||
import { AllInOneMenu } from '@ohif/ui';
|
import { AllInOneMenu } from '@ohif/ui-next';
|
||||||
import { VolumeRenderingOptionsProps } from '../../types/ViewportPresets';
|
import { VolumeRenderingOptionsProps } from '../../types/ViewportPresets';
|
||||||
import { VolumeRenderingQuality } from './VolumeRenderingQuality';
|
import { VolumeRenderingQuality } from './VolumeRenderingQuality';
|
||||||
import { VolumeShift } from './VolumeShift';
|
import { VolumeShift } from './VolumeShift';
|
||||||
@ -26,11 +26,11 @@ export function VolumeRenderingOptions({
|
|||||||
commandsManager={commandsManager}
|
commandsManager={commandsManager}
|
||||||
servicesManager={servicesManager}
|
servicesManager={servicesManager}
|
||||||
/>
|
/>
|
||||||
<div className="all-in-one-menu-item mt-2 flex !h-[20px] w-full justify-start">
|
<div className="mt-2 flex h-8 !h-[20px] w-full flex-shrink-0 items-center justify-start px-2 text-base">
|
||||||
<div className="text-aqua-pale text-[13px]">LIGHTING</div>
|
<div className="text-muted-foreground text-sm">Lighting</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-primary-dark mt-1 mb-1 h-[2px] w-full"></div>
|
<div className="bg-background mt-1 mb-1 h-px w-full"></div>
|
||||||
<div className="all-in-one-menu-item flex w-full justify-center">
|
<div className="hover:bg-accent flex h-8 w-full flex-shrink-0 items-center px-2 text-base hover:rounded">
|
||||||
<VolumeShade
|
<VolumeShade
|
||||||
commandsManager={commandsManager}
|
commandsManager={commandsManager}
|
||||||
servicesManager={servicesManager}
|
servicesManager={servicesManager}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { AllInOneMenu } from '@ohif/ui';
|
import { AllInOneMenu } from '@ohif/ui-next';
|
||||||
import { Icons } from '@ohif/ui-next';
|
import { Icons } from '@ohif/ui-next';
|
||||||
import React, { ReactElement } from 'react';
|
import React, { ReactElement } from 'react';
|
||||||
import { VolumeRenderingPresetsProps } from '../../types/ViewportPresets';
|
import { VolumeRenderingPresetsProps } from '../../types/ViewportPresets';
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import React, { ReactElement, useCallback, useState, useEffect } from 'react';
|
import React, { ReactElement, useCallback, useState, useEffect } from 'react';
|
||||||
import { VolumeRenderingQualityProps } from '../../types/ViewportPresets';
|
import { VolumeRenderingQualityProps } from '../../types/ViewportPresets';
|
||||||
|
import { Numeric } from '@ohif/ui-next';
|
||||||
|
|
||||||
export function VolumeRenderingQuality({
|
export function VolumeRenderingQuality({
|
||||||
volumeRenderingQualityRange,
|
volumeRenderingQualityRange,
|
||||||
@ -22,11 +23,6 @@ export function VolumeRenderingQuality({
|
|||||||
[commandsManager, viewportId]
|
[commandsManager, viewportId]
|
||||||
);
|
);
|
||||||
|
|
||||||
const calculateBackground = value => {
|
|
||||||
const percentage = ((value - 0) / (1 - 0)) * 100;
|
|
||||||
return `linear-gradient(to right, #5acce6 0%, #5acce6 ${percentage}%, #3a3f99 ${percentage}%, #3a3f99 100%)`;
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||||
const { actor } = viewport.getActors()[0];
|
const { actor } = viewport.getActors()[0];
|
||||||
@ -41,33 +37,26 @@ export function VolumeRenderingQuality({
|
|||||||
setQuality(Math.sqrt(averageSpacing / (sampleDistance * 0.5)));
|
setQuality(Math.sqrt(averageSpacing / (sampleDistance * 0.5)));
|
||||||
}
|
}
|
||||||
}, [cornerstoneViewportService, viewportId]);
|
}, [cornerstoneViewportService, viewportId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="my-1 mt-2 flex flex-col space-y-2">
|
||||||
<div className="all-in-one-menu-item flex w-full flex-row !items-center justify-between gap-[10px]">
|
{quality !== null && (
|
||||||
<label
|
<div className="w-full pl-2 pr-1">
|
||||||
className="block text-white"
|
<Numeric.Container
|
||||||
htmlFor="volume"
|
mode="singleRange"
|
||||||
>
|
|
||||||
Quality
|
|
||||||
</label>
|
|
||||||
{quality !== null && (
|
|
||||||
<input
|
|
||||||
className="bg-inputfield-main h-2 w-[120px] cursor-pointer appearance-none rounded-lg"
|
|
||||||
value={quality}
|
|
||||||
id="volume"
|
|
||||||
max={max}
|
|
||||||
min={min}
|
min={min}
|
||||||
type="range"
|
max={max}
|
||||||
step={step}
|
step={step}
|
||||||
onChange={e => onChange(parseInt(e.target.value, 10))}
|
value={quality}
|
||||||
style={{
|
onChange={onChange}
|
||||||
background: calculateBackground((quality - min) / (max - min)),
|
>
|
||||||
'--thumb-inner-color': '#5acce6',
|
<div className="flex flex-row items-center">
|
||||||
'--thumb-outer-color': '#090c29',
|
<Numeric.Label className="w-16">Quality</Numeric.Label>
|
||||||
}}
|
<Numeric.SingleRange sliderClassName="mx-2 flex-grow" />
|
||||||
/>
|
</div>
|
||||||
)}
|
</Numeric.Container>
|
||||||
</div>
|
</div>
|
||||||
</>
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, { ReactElement, useCallback, useEffect, useState } from 'react';
|
import React, { ReactElement, useCallback, useEffect, useState } from 'react';
|
||||||
import { SwitchButton } from '@ohif/ui';
|
import { Switch } from '@ohif/ui-next';
|
||||||
import { VolumeShadeProps } from '../../types/ViewportPresets';
|
import { VolumeShadeProps } from '../../types/ViewportPresets';
|
||||||
|
|
||||||
export function VolumeShade({
|
export function VolumeShade({
|
||||||
@ -28,15 +28,18 @@ export function VolumeShade({
|
|||||||
}, [viewportId, cornerstoneViewportService]);
|
}, [viewportId, cornerstoneViewportService]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SwitchButton
|
<>
|
||||||
key={key}
|
<span className="flex-grow">Shade</span>
|
||||||
label="Shade"
|
<Switch
|
||||||
checked={shade}
|
className="ml-2 flex-shrink-0"
|
||||||
onChange={() => {
|
key={key}
|
||||||
setShade(!shade);
|
checked={shade}
|
||||||
onClickShade(!shade);
|
onCheckedChange={() => {
|
||||||
onShadeChange(!shade);
|
setShade(!shade);
|
||||||
}}
|
onClickShade(!shade);
|
||||||
/>
|
onShadeChange(!shade);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import React, { ReactElement, useCallback, useEffect, useState, useRef } from 'react';
|
import React, { ReactElement, useCallback, useEffect, useState, useRef } from 'react';
|
||||||
import { VolumeShiftProps } from '../../types/ViewportPresets';
|
import { VolumeShiftProps } from '../../types/ViewportPresets';
|
||||||
|
import { Numeric } from '@ohif/ui-next';
|
||||||
|
|
||||||
export function VolumeShift({
|
export function VolumeShift({
|
||||||
viewportId,
|
viewportId,
|
||||||
@ -46,48 +47,32 @@ export function VolumeShift({
|
|||||||
viewportId,
|
viewportId,
|
||||||
shift: shiftDifference,
|
shift: shiftDifference,
|
||||||
});
|
});
|
||||||
|
setShift(newShift);
|
||||||
},
|
},
|
||||||
[commandsManager, viewportId, viewport]
|
[commandsManager, viewportId, viewport]
|
||||||
);
|
);
|
||||||
|
|
||||||
const calculateBackground = value => {
|
|
||||||
const percentage = ((value - 0) / (1 - 0)) * 100;
|
|
||||||
return `linear-gradient(to right, #5acce6 0%, #5acce6 ${percentage}%, #3a3f99 ${percentage}%, #3a3f99 100%)`;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="my-1 mt-2 flex flex-col space-y-2">
|
||||||
<div className="all-in-one-menu-item flex w-full flex-row !items-center justify-between gap-[10px]">
|
{step !== null && minShift !== null && maxShift !== null && (
|
||||||
<label
|
<div className="w-full pl-2 pr-1">
|
||||||
className="block text-white"
|
<Numeric.Container
|
||||||
htmlFor="shift"
|
mode="singleRange"
|
||||||
>
|
min={minShift}
|
||||||
Shift
|
max={maxShift}
|
||||||
</label>
|
step={step}
|
||||||
{step !== null && (
|
|
||||||
<input
|
|
||||||
className="bg-inputfield-main h-2 w-[120px] cursor-pointer appearance-none rounded-lg"
|
|
||||||
value={shift}
|
value={shift}
|
||||||
onChange={e => {
|
onChange={onChangeRange}
|
||||||
const shiftValue = parseInt(e.target.value, 10);
|
|
||||||
setShift(shiftValue);
|
|
||||||
onChangeRange(shiftValue);
|
|
||||||
}}
|
|
||||||
id="shift"
|
|
||||||
onMouseDown={() => setIsBlocking(true)}
|
onMouseDown={() => setIsBlocking(true)}
|
||||||
onMouseUp={() => setIsBlocking(false)}
|
onMouseUp={() => setIsBlocking(false)}
|
||||||
max={maxShift}
|
>
|
||||||
min={minShift}
|
<div className="flex flex-row items-center">
|
||||||
type="range"
|
<Numeric.Label className="w-16">Shift</Numeric.Label>
|
||||||
step={step}
|
<Numeric.SingleRange sliderClassName="mx-2 flex-grow" />
|
||||||
style={{
|
</div>
|
||||||
background: calculateBackground((shift - minShift) / (maxShift - minShift)),
|
</Numeric.Container>
|
||||||
'--thumb-inner-color': '#5acce6',
|
</div>
|
||||||
'--thumb-outer-color': '#090c29',
|
)}
|
||||||
}}
|
</div>
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, { ReactElement, useCallback } from 'react';
|
import React, { ReactElement, useCallback } from 'react';
|
||||||
import { AllInOneMenu } from '@ohif/ui';
|
import { AllInOneMenu } from '@ohif/ui-next';
|
||||||
import { WindowLevelPreset } from '../../types/WindowLevel';
|
import { WindowLevelPreset } from '../../types/WindowLevel';
|
||||||
import { CommandsManager } from '@ohif/core';
|
import { CommandsManager } from '@ohif/core';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -45,6 +45,7 @@ export function WindowLevel({
|
|||||||
key={`${modality}-${index}`}
|
key={`${modality}-${index}`}
|
||||||
label={preset.description}
|
label={preset.description}
|
||||||
secondaryLabel={`${preset.window} / ${preset.level}`}
|
secondaryLabel={`${preset.window} / ${preset.level}`}
|
||||||
|
useIconSpace={false}
|
||||||
onClick={() => onSetWindowLevel(preset)}
|
onClick={() => onSetWindowLevel(preset)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import React, { ReactElement, useCallback, useEffect, useState } from 'react';
|
import React, { ReactElement, useCallback, useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { AllInOneMenu } from '@ohif/ui';
|
import { AllInOneMenu } from '@ohif/ui-next';
|
||||||
import { useViewportGrid } from '@ohif/ui-next';
|
import { useViewportGrid } from '@ohif/ui-next';
|
||||||
import { Colormap } from './Colormap';
|
import { Colormap } from './Colormap';
|
||||||
import { Colorbar } from './Colorbar';
|
import { Colorbar } from './Colorbar';
|
||||||
@ -119,8 +119,8 @@ export function WindowLevelActionMenu({
|
|||||||
iconClassName={classNames(
|
iconClassName={classNames(
|
||||||
// Visible on hover and for the active viewport
|
// Visible on hover and for the active viewport
|
||||||
activeViewportId === viewportId ? 'visible' : 'invisible group-hover/pane:visible',
|
activeViewportId === viewportId ? 'visible' : 'invisible group-hover/pane:visible',
|
||||||
'flex shrink-0 cursor-pointer rounded active:text-white text-primary-light',
|
'flex shrink-0 cursor-pointer rounded active:text-foreground text-highlight',
|
||||||
isLight ? ' hover:bg-secondary-dark' : 'hover:bg-secondary-light/60'
|
isLight ? ' hover:bg-primary/30' : 'hover:bg-primary/30'
|
||||||
)}
|
)}
|
||||||
menuStyle={{ maxHeight: vpHeight - 32, minWidth: 218 }}
|
menuStyle={{ maxHeight: vpHeight - 32, minWidth: 218 }}
|
||||||
onVisibilityChange={() => {
|
onVisibilityChange={() => {
|
||||||
@ -144,8 +144,10 @@ export function WindowLevelActionMenu({
|
|||||||
key="colorLUTPresets"
|
key="colorLUTPresets"
|
||||||
itemLabel="Color LUT"
|
itemLabel="Color LUT"
|
||||||
itemIcon="icon-color-lut"
|
itemIcon="icon-color-lut"
|
||||||
|
className="flex h-[calc(100%-32px)] flex-col"
|
||||||
>
|
>
|
||||||
<Colormap
|
<Colormap
|
||||||
|
className="flex h-full w-full flex-col"
|
||||||
colormaps={colormaps}
|
colormaps={colormaps}
|
||||||
viewportId={viewportId}
|
viewportId={viewportId}
|
||||||
displaySets={displaySets.filter(ds => !nonWLModalities.includes(ds.Modality))}
|
displaySets={displaySets.filter(ds => !nonWLModalities.includes(ds.Modality))}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import React, {
|
|||||||
} from 'react';
|
} from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { Types, ViewportActionCornersLocations } from '@ohif/ui';
|
import { Types, ViewportActionCornersLocations } from '@ohif/ui-next';
|
||||||
import ViewportActionCornersService, {
|
import ViewportActionCornersService, {
|
||||||
ActionComponentInfo,
|
ActionComponentInfo,
|
||||||
} from '../services/ViewportActionCornersService/ViewportActionCornersService';
|
} from '../services/ViewportActionCornersService/ViewportActionCornersService';
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { PubSubService } from '@ohif/core';
|
import { PubSubService } from '@ohif/core';
|
||||||
import { ViewportActionCornersLocations } from '@ohif/ui';
|
import { ViewportActionCornersLocations } from '@ohif/ui-next';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
export type ActionComponentInfo = {
|
export type ActionComponentInfo = {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { ViewportActionCorners } from '@ohif/ui';
|
import { ViewportActionCorners } from '@ohif/ui-next';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
'ui.viewportActionCorner': ViewportActionCorners,
|
'ui.viewportActionCorner': ViewportActionCorners,
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import getContextModule from './getContextModule';
|
|||||||
import getPanelModule from './getPanelModule';
|
import getPanelModule from './getPanelModule';
|
||||||
import getViewportModule from './getViewportModule';
|
import getViewportModule from './getViewportModule';
|
||||||
import { id } from './id.js';
|
import { id } from './id.js';
|
||||||
import { ViewportActionButton } from '@ohif/ui';
|
import { ViewportActionButton } from '@ohif/ui-next';
|
||||||
import i18n from '@ohif/i18n';
|
import i18n from '@ohif/i18n';
|
||||||
import { measurementTrackingMode } from './contexts/TrackedMeasurementsContext/promptBeginTracking';
|
import { measurementTrackingMode } from './contexts/TrackedMeasurementsContext/promptBeginTracking';
|
||||||
import getCustomizationModule from './getCustomizationModule';
|
import getCustomizationModule from './getCustomizationModule';
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import React, { useState, useEffect, useCallback } from 'react';
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { ViewportActionArrows } from '@ohif/ui';
|
import { ViewportActionArrows } from '@ohif/ui-next';
|
||||||
import { useViewportGrid, Icons, Tooltip, TooltipTrigger, TooltipContent } from '@ohif/ui-next';
|
import { useViewportGrid, Icons, Tooltip, TooltipTrigger, TooltipContent } from '@ohif/ui-next';
|
||||||
|
|
||||||
import { annotation } from '@cornerstonejs/tools';
|
import { annotation } from '@cornerstonejs/tools';
|
||||||
@ -325,7 +325,7 @@ function _getStatusComponent(isTracked, t) {
|
|||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<span>
|
<span>
|
||||||
<Icons.StatusTracking className="text-muted-foreground" />
|
<Icons.StatusTracking className="text-muted-foreground mt-0.5 ml-0.5" />
|
||||||
</span>
|
</span>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent
|
<TooltipContent
|
||||||
|
|||||||
@ -376,11 +376,11 @@ function ViewerViewportGrid(props: withAppTypes) {
|
|||||||
const tolerance = 0.01;
|
const tolerance = 0.01;
|
||||||
|
|
||||||
if (x + width < 1 - tolerance) {
|
if (x + width < 1 - tolerance) {
|
||||||
style.borderRight = '1px solid #3a3f99';
|
style.borderRight = '1px solid hsl(var(--input))';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (y + height < 1 - tolerance) {
|
if (y + height < 1 - tolerance) {
|
||||||
style.borderBottom = '1px solid #3a3f99';
|
style.borderBottom = '1px solid hsl(var(--input))';
|
||||||
}
|
}
|
||||||
|
|
||||||
return style;
|
return style;
|
||||||
@ -446,7 +446,7 @@ function ViewerViewportGrid(props: withAppTypes) {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={resizeRef}
|
ref={resizeRef}
|
||||||
className="border-secondary-light h-full w-full border"
|
className="border-input h-[calc(100%-0.25rem)] w-full border"
|
||||||
>
|
>
|
||||||
<ViewportGrid
|
<ViewportGrid
|
||||||
numRows={numRows}
|
numRows={numRows}
|
||||||
|
|||||||
@ -56,7 +56,6 @@ declare global {
|
|||||||
export type UIViewportDialogService = UIViewportDialogServiceType;
|
export type UIViewportDialogService = UIViewportDialogServiceType;
|
||||||
export type PanelService = PanelServiceType;
|
export type PanelService = PanelServiceType;
|
||||||
export type StudyPrefetcherService = StudyPrefetcherServiceType;
|
export type StudyPrefetcherService = StudyPrefetcherServiceType;
|
||||||
export type MultiMonitorService;
|
|
||||||
|
|
||||||
export interface Managers {
|
export interface Managers {
|
||||||
servicesManager?: ServicesManager;
|
servicesManager?: ServicesManager;
|
||||||
|
|||||||
@ -26,7 +26,7 @@
|
|||||||
--destructive: 0 62.8% 30.6%;
|
--destructive: 0 62.8% 30.6%;
|
||||||
--destructive-foreground: 0 0% 98%;
|
--destructive-foreground: 0 0% 98%;
|
||||||
--border: 0 0% 14.9%;
|
--border: 0 0% 14.9%;
|
||||||
--input: 236 45% 21%;
|
--input: 236 52% 30%;
|
||||||
--ring: 214 98% 60%;
|
--ring: 214 98% 60%;
|
||||||
--chart-1: 220 70% 50%;
|
--chart-1: 220 70% 50%;
|
||||||
--chart-2: 160 60% 45%;
|
--chart-2: 160 60% 45%;
|
||||||
@ -57,7 +57,7 @@
|
|||||||
--destructive: 0 62.8% 30.6%;
|
--destructive: 0 62.8% 30.6%;
|
||||||
--destructive-foreground: 0 0% 98%;
|
--destructive-foreground: 0 0% 98%;
|
||||||
--border: 0 0% 14.9%;
|
--border: 0 0% 14.9%;
|
||||||
--input: 236 45% 21%;
|
--input: 236 52% 30%;
|
||||||
--ring: 214 98% 60%;
|
--ring: 214 98% 60%;
|
||||||
--chart-1: 220 70% 50%;
|
--chart-1: 220 70% 50%;
|
||||||
--chart-2: 160 60% 45%;
|
--chart-2: 160 60% 45%;
|
||||||
|
|||||||
29
platform/ui-next/src/components/AllInOneMenu/BackItem.tsx
Normal file
29
platform/ui-next/src/components/AllInOneMenu/BackItem.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { Icons } from '@ohif/ui-next';
|
||||||
|
import DividerItem from './DividerItem';
|
||||||
|
|
||||||
|
type BackItemProps = {
|
||||||
|
backLabel?: string;
|
||||||
|
onBackClick: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const BackItem = ({ backLabel, onBackClick }: BackItemProps) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className="hover:bg-accent flex h-8 w-full flex-shrink-0 cursor-pointer items-center pl-1 pr-2 text-base hover:rounded"
|
||||||
|
onClick={onBackClick}
|
||||||
|
>
|
||||||
|
<Icons.ByName
|
||||||
|
name="content-prev"
|
||||||
|
className="ml-2 mr-2"
|
||||||
|
/>
|
||||||
|
<span>{backLabel || 'Back to Display Options'}</span>
|
||||||
|
</div>
|
||||||
|
<DividerItem></DividerItem>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BackItem;
|
||||||
11
platform/ui-next/src/components/AllInOneMenu/DividerItem.tsx
Normal file
11
platform/ui-next/src/components/AllInOneMenu/DividerItem.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const DividerItem = () => {
|
||||||
|
return (
|
||||||
|
<div className="flex h-3.5 shrink-0 items-center px-2">
|
||||||
|
<div className="bg-primary-dark h-[2px] w-full"></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DividerItem;
|
||||||
15
platform/ui-next/src/components/AllInOneMenu/HeaderItem.tsx
Normal file
15
platform/ui-next/src/components/AllInOneMenu/HeaderItem.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import React, { ReactNode } from 'react';
|
||||||
|
|
||||||
|
type HeaderItemProps = {
|
||||||
|
children: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
const HeaderItem = ({ children }: HeaderItemProps) => {
|
||||||
|
return (
|
||||||
|
<div className="text-muted-foreground mx-2 flex h-6 shrink-0 items-center text-sm">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HeaderItem;
|
||||||
83
platform/ui-next/src/components/AllInOneMenu/IconMenu.tsx
Normal file
83
platform/ui-next/src/components/AllInOneMenu/IconMenu.tsx
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import React, { useCallback, useState } from 'react';
|
||||||
|
import OutsideClickHandler from 'react-outside-click-handler';
|
||||||
|
import { MenuProps } from './Menu';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { AllInOneMenu } from '..';
|
||||||
|
import { Icons } from '@ohif/ui-next';
|
||||||
|
|
||||||
|
export interface IconMenuProps extends MenuProps {
|
||||||
|
icon: string;
|
||||||
|
iconClassName?: string;
|
||||||
|
horizontalDirection?: AllInOneMenu.HorizontalDirection;
|
||||||
|
verticalDirection?: AllInOneMenu.VerticalDirection;
|
||||||
|
menuKey?: number | string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An IconMenu allows for a div wrapped icon to be clicked to show and hide
|
||||||
|
* an AllInOneMenu.Menu. Based on the direction(s) specified, the menu is
|
||||||
|
* positioned relative to the icon.
|
||||||
|
*
|
||||||
|
* HorizontalDirection.LeftToRight - the left edges of the icon and menu are aligned
|
||||||
|
* HorizontalDirection.RightRoLeft - the right edges of the icon and menu are aligned
|
||||||
|
* VerticalDirection.TopToBottom - the top edge of the menu appears directly below the bottom edge of the icon
|
||||||
|
* VerticalDirection.BottomToTop - the bottom edge of the menu appears directly above the top edge of the icon
|
||||||
|
*
|
||||||
|
* For example, if an IconMenu were situated in the bottom-left corner of a container,
|
||||||
|
* it would be best to use BottomToTop and LeftToRight directions for it.
|
||||||
|
*/
|
||||||
|
export default function IconMenu({
|
||||||
|
icon,
|
||||||
|
iconClassName,
|
||||||
|
horizontalDirection,
|
||||||
|
verticalDirection,
|
||||||
|
children,
|
||||||
|
backLabel,
|
||||||
|
menuClassName,
|
||||||
|
menuStyle,
|
||||||
|
onVisibilityChange,
|
||||||
|
menuKey,
|
||||||
|
}: IconMenuProps) {
|
||||||
|
const [isMenuVisible, setIsMenuVisible] = useState(false);
|
||||||
|
|
||||||
|
const toggleMenuVisibility = useCallback(() => setIsMenuVisible(isVisible => !isVisible), []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<OutsideClickHandler
|
||||||
|
onOutsideClick={toggleMenuVisibility}
|
||||||
|
disabled={!isMenuVisible}
|
||||||
|
>
|
||||||
|
<div className="relative">
|
||||||
|
<div
|
||||||
|
className={iconClassName}
|
||||||
|
onClick={toggleMenuVisibility}
|
||||||
|
>
|
||||||
|
<Icons.ByName name={icon} />
|
||||||
|
</div>
|
||||||
|
<AllInOneMenu.Menu
|
||||||
|
key={menuKey}
|
||||||
|
isVisible={isMenuVisible}
|
||||||
|
backLabel={backLabel}
|
||||||
|
menuClassName={classNames(
|
||||||
|
menuClassName,
|
||||||
|
'absolute',
|
||||||
|
verticalDirection === AllInOneMenu.VerticalDirection.TopToBottom
|
||||||
|
? 'top-[100%]'
|
||||||
|
: 'bottom-[100%]',
|
||||||
|
horizontalDirection === AllInOneMenu.HorizontalDirection.LeftToRight
|
||||||
|
? 'left-0'
|
||||||
|
: 'right-0'
|
||||||
|
)}
|
||||||
|
menuStyle={menuStyle}
|
||||||
|
onVisibilityChange={isVis => {
|
||||||
|
setIsMenuVisible(isVis);
|
||||||
|
onVisibilityChange?.(isVis);
|
||||||
|
}}
|
||||||
|
horizontalDirection={horizontalDirection}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</AllInOneMenu.Menu>
|
||||||
|
</div>
|
||||||
|
</OutsideClickHandler>
|
||||||
|
);
|
||||||
|
}
|
||||||
51
platform/ui-next/src/components/AllInOneMenu/Item.tsx
Normal file
51
platform/ui-next/src/components/AllInOneMenu/Item.tsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import React, { ReactNode, useCallback, useContext } from 'react';
|
||||||
|
import { MenuContext } from './Menu';
|
||||||
|
|
||||||
|
type ItemProps = {
|
||||||
|
label: string;
|
||||||
|
secondaryLabel?: string;
|
||||||
|
icon?: ReactNode;
|
||||||
|
rightIcon?: ReactNode;
|
||||||
|
onClick?: () => void;
|
||||||
|
onMouseEnter?: () => void;
|
||||||
|
onMouseLeave?: () => void;
|
||||||
|
useIconSpace?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Item = ({
|
||||||
|
label,
|
||||||
|
secondaryLabel,
|
||||||
|
icon,
|
||||||
|
rightIcon,
|
||||||
|
onClick,
|
||||||
|
onMouseEnter,
|
||||||
|
onMouseLeave,
|
||||||
|
useIconSpace = false,
|
||||||
|
}: ItemProps) => {
|
||||||
|
const { hideMenu } = useContext(MenuContext);
|
||||||
|
|
||||||
|
const onClickHandler = useCallback(() => {
|
||||||
|
hideMenu();
|
||||||
|
onClick?.();
|
||||||
|
}, [hideMenu, onClick]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="hover:bg-accent flex h-8 w-full flex-shrink-0 cursor-pointer items-center px-2 text-base leading-[18px] hover:rounded"
|
||||||
|
onClick={onClickHandler}
|
||||||
|
onMouseEnter={onMouseEnter}
|
||||||
|
onMouseLeave={onMouseLeave}
|
||||||
|
>
|
||||||
|
{(icon || useIconSpace) && (
|
||||||
|
<div className="flex w-7 flex-shrink-0 items-center justify-center">{icon}</div>
|
||||||
|
)}
|
||||||
|
<span className="flex-grow">{label}</span>
|
||||||
|
{secondaryLabel && (
|
||||||
|
<span className="text-muted-foreground ml-2 flex-shrink-0">{secondaryLabel}</span>
|
||||||
|
)}
|
||||||
|
{rightIcon && <div className="ml-2 flex-shrink-0">{rightIcon}</div>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Item;
|
||||||
38
platform/ui-next/src/components/AllInOneMenu/ItemPanel.tsx
Normal file
38
platform/ui-next/src/components/AllInOneMenu/ItemPanel.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import React, { ReactNode, useContext, useEffect } from 'react';
|
||||||
|
import { MenuContext } from './Menu';
|
||||||
|
import { ScrollArea } from '@ohif/ui-next';
|
||||||
|
|
||||||
|
type ItemPanelProps = {
|
||||||
|
label?: string;
|
||||||
|
index?: number;
|
||||||
|
children: ReactNode;
|
||||||
|
maxHeight?: string;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ItemPanel = ({
|
||||||
|
label,
|
||||||
|
index = 0,
|
||||||
|
children,
|
||||||
|
maxHeight = '250px',
|
||||||
|
className,
|
||||||
|
}: ItemPanelProps) => {
|
||||||
|
const { addItemPanel, activePanelIndex } = useContext(MenuContext);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
addItemPanel(index, label);
|
||||||
|
}, [addItemPanel, index, label]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
activePanelIndex === index && (
|
||||||
|
<ScrollArea
|
||||||
|
style={{ scrollbarGutter: 'auto', maxHeight, height: '100%' }}
|
||||||
|
className={`flex flex-col ${className || ''}`}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ScrollArea>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ItemPanel;
|
||||||
187
platform/ui-next/src/components/AllInOneMenu/Menu.tsx
Normal file
187
platform/ui-next/src/components/AllInOneMenu/Menu.tsx
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
import React, { createContext, ReactNode, useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import DividerItem from './DividerItem';
|
||||||
|
import PanelSelector from './PanelSelector';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import BackItem from './BackItem';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The vertical direction that the menu will be opened/used with.
|
||||||
|
*
|
||||||
|
* A TopToBottom menu would be used for cases where the menu is opened "near"
|
||||||
|
* the top edge of its container. Likewise a BottomToTop menu would be used
|
||||||
|
* for cases where the menu is opened "near" the bottom edge of its container.
|
||||||
|
*
|
||||||
|
* See IconMenu for more information.
|
||||||
|
*/
|
||||||
|
export enum VerticalDirection {
|
||||||
|
TopToBottom,
|
||||||
|
BottomToTop,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The horizontal direction that the menu is opened/used with.
|
||||||
|
* This direction dictates the general direction sub-menus and
|
||||||
|
* back-to-menus are opened with. For example, a RightToLeft menu
|
||||||
|
* will have sub-menu items indicated with a left pointing chevron
|
||||||
|
* and aligned with the left edge of the menu. Similarly back-to items of a
|
||||||
|
* RightToLeft menu are indicated with a right pointing chevron and
|
||||||
|
* aligned with the right edge of the menu.
|
||||||
|
*
|
||||||
|
* It is also worth noting that a LeftToRight menu would be used for
|
||||||
|
* cases where a menu is opened "near" the left edge of its container.
|
||||||
|
* Likewise, a RightToLeft menu would be used for cases where a menu is opened
|
||||||
|
* "near" the right edge of its container.
|
||||||
|
*
|
||||||
|
* See IconMenu for more information.
|
||||||
|
*/
|
||||||
|
export enum HorizontalDirection {
|
||||||
|
LeftToRight,
|
||||||
|
RightToLeft,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MenuProps {
|
||||||
|
menuStyle?: unknown;
|
||||||
|
menuClassName?: string;
|
||||||
|
isVisible?: boolean;
|
||||||
|
preventHideMenu?: boolean;
|
||||||
|
backLabel?: string;
|
||||||
|
headerComponent?: ReactNode;
|
||||||
|
showHeaderDivider?: boolean;
|
||||||
|
activePanelIndex?: number;
|
||||||
|
onVisibilityChange?: (isVisible: boolean) => void;
|
||||||
|
horizontalDirection?: HorizontalDirection;
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
type MenuContextProps = {
|
||||||
|
showSubMenu: (subMenuProps: MenuProps) => void;
|
||||||
|
hideMenu: () => void;
|
||||||
|
addItemPanel: (index: number, label: string) => void;
|
||||||
|
horizontalDirection: HorizontalDirection;
|
||||||
|
activePanelIndex: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type MenuPathState = {
|
||||||
|
props: MenuProps;
|
||||||
|
activePanelIndex: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MenuContext = createContext<MenuContextProps>(null);
|
||||||
|
|
||||||
|
const Menu = (props: MenuProps) => {
|
||||||
|
const {
|
||||||
|
isVisible,
|
||||||
|
onVisibilityChange,
|
||||||
|
activePanelIndex,
|
||||||
|
preventHideMenu,
|
||||||
|
menuClassName,
|
||||||
|
menuStyle,
|
||||||
|
horizontalDirection = HorizontalDirection.LeftToRight,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const [isMenuVisible, setIsMenuVisible] = useState(isVisible);
|
||||||
|
|
||||||
|
// The menuPath is an array consisting of this top Menu and every SubMenu
|
||||||
|
// that has been traversed/opened by the user with the last item in the array
|
||||||
|
// being the current (sub)menu that is currently visible. This allows for the previously
|
||||||
|
// viewed menus to be returned to via the Back button at the top of the menu.
|
||||||
|
const [menuPath, setMenuPath] = useState<Array<MenuPathState>>([
|
||||||
|
{ props, activePanelIndex: activePanelIndex || 0 },
|
||||||
|
]);
|
||||||
|
const [itemPanelLabels, setItemPanelLabels] = useState<Array<string>>([]);
|
||||||
|
|
||||||
|
const hideMenu = useCallback(() => {
|
||||||
|
if (preventHideMenu) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setMenuPath(path => [path[0]]);
|
||||||
|
setItemPanelLabels([]);
|
||||||
|
setIsMenuVisible(false);
|
||||||
|
onVisibilityChange?.(false);
|
||||||
|
}, [preventHideMenu, onVisibilityChange]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isVisible) {
|
||||||
|
setIsMenuVisible(isVisible);
|
||||||
|
onVisibilityChange?.(isVisible);
|
||||||
|
} else {
|
||||||
|
hideMenu();
|
||||||
|
}
|
||||||
|
}, [hideMenu, isVisible, onVisibilityChange]);
|
||||||
|
|
||||||
|
const showSubMenu = useCallback((subMenuProps: MenuProps) => {
|
||||||
|
setMenuPath(path => {
|
||||||
|
return [
|
||||||
|
...path,
|
||||||
|
{ props: subMenuProps, activePanelIndex: subMenuProps.activePanelIndex || 0 },
|
||||||
|
];
|
||||||
|
});
|
||||||
|
setItemPanelLabels([]);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const addItemPanel = useCallback((index, label) => {
|
||||||
|
setItemPanelLabels(labels => {
|
||||||
|
return [...labels.slice(0, index), label, ...labels.slice(index + 1, labels.length)];
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onActivePanelIndexChange = useCallback(index => {
|
||||||
|
setMenuPath(path => {
|
||||||
|
return [
|
||||||
|
...path.slice(0, path.length - 1),
|
||||||
|
{ ...path[path.length - 1], activePanelIndex: index },
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onBackClick = useCallback(() => {
|
||||||
|
setMenuPath(path => [...path.slice(0, path.length - 1)]);
|
||||||
|
setItemPanelLabels([]);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const { props: currentMenuProps, activePanelIndex: currentMenuActivePanelIndex } =
|
||||||
|
menuPath[menuPath.length - 1];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MenuContext.Provider
|
||||||
|
value={{
|
||||||
|
showSubMenu,
|
||||||
|
hideMenu,
|
||||||
|
addItemPanel,
|
||||||
|
activePanelIndex: currentMenuActivePanelIndex,
|
||||||
|
horizontalDirection,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isMenuVisible && (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'bg-popover/90 text-foreground flex select-none flex-col rounded px-1 py-1.5',
|
||||||
|
menuClassName
|
||||||
|
)}
|
||||||
|
style={menuStyle}
|
||||||
|
>
|
||||||
|
{menuPath.length > 1 && (
|
||||||
|
<BackItem
|
||||||
|
backLabel={menuPath[menuPath.length - 2].props.backLabel}
|
||||||
|
onBackClick={onBackClick}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{itemPanelLabels.length > 1 && (
|
||||||
|
<PanelSelector
|
||||||
|
panelLabels={itemPanelLabels}
|
||||||
|
activeIndex={currentMenuActivePanelIndex}
|
||||||
|
onActiveIndexChange={onActivePanelIndexChange}
|
||||||
|
></PanelSelector>
|
||||||
|
)}
|
||||||
|
{currentMenuProps.headerComponent}
|
||||||
|
{currentMenuProps.showHeaderDivider && <DividerItem />}
|
||||||
|
{currentMenuProps.children}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</MenuContext.Provider>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Menu;
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
import React, { ReactNode } from 'react';
|
||||||
|
import { Tabs, TabsList, TabsTrigger } from '../Tabs';
|
||||||
|
|
||||||
|
type PanelSelectorProps = {
|
||||||
|
panelLabels: Array<ReactNode>;
|
||||||
|
onActiveIndexChange: (index: number) => void;
|
||||||
|
activeIndex: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const PanelSelector = ({ panelLabels, onActiveIndexChange, activeIndex }: PanelSelectorProps) => {
|
||||||
|
return (
|
||||||
|
<div className="mx-2 my-1 flex justify-center">
|
||||||
|
<Tabs
|
||||||
|
value={String(activeIndex)}
|
||||||
|
onValueChange={val => onActiveIndexChange(parseInt(val, 10))}
|
||||||
|
>
|
||||||
|
<TabsList>
|
||||||
|
{panelLabels.map((panelLabel, index) => (
|
||||||
|
<TabsTrigger
|
||||||
|
key={index}
|
||||||
|
value={String(index)}
|
||||||
|
>
|
||||||
|
{panelLabel}
|
||||||
|
</TabsTrigger>
|
||||||
|
))}
|
||||||
|
</TabsList>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PanelSelector;
|
||||||
36
platform/ui-next/src/components/AllInOneMenu/SubMenu.tsx
Normal file
36
platform/ui-next/src/components/AllInOneMenu/SubMenu.tsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import React, { useCallback, useContext } from 'react';
|
||||||
|
import { MenuContext, MenuProps } from './Menu';
|
||||||
|
import { Icons } from '@ohif/ui-next';
|
||||||
|
export interface SubMenuProps extends MenuProps {
|
||||||
|
itemLabel: string;
|
||||||
|
onClick?: () => void;
|
||||||
|
itemIcon?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SubMenu = (props: SubMenuProps) => {
|
||||||
|
const { showSubMenu } = useContext(MenuContext);
|
||||||
|
|
||||||
|
const onClickHandler = useCallback(() => {
|
||||||
|
showSubMenu(props);
|
||||||
|
props.onClick?.();
|
||||||
|
}, [showSubMenu, props]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="hover:bg-accent flex h-8 w-full cursor-pointer items-center px-2 text-base hover:rounded"
|
||||||
|
onClick={onClickHandler}
|
||||||
|
>
|
||||||
|
{props.itemIcon && (
|
||||||
|
<div className="flex w-7 flex-shrink-0 items-center justify-center">
|
||||||
|
<Icons.ByName name={props.itemIcon}></Icons.ByName>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<span className="flex-grow">{props.itemLabel}</span>
|
||||||
|
<div className="ml-2 flex-shrink-0">
|
||||||
|
<Icons.ByName name="content-next"></Icons.ByName>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SubMenu;
|
||||||
42
platform/ui-next/src/components/AllInOneMenu/index.ts
Normal file
42
platform/ui-next/src/components/AllInOneMenu/index.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import Menu, { VerticalDirection, HorizontalDirection } from './Menu';
|
||||||
|
import SubMenu from './SubMenu';
|
||||||
|
import BackItem from './BackItem';
|
||||||
|
import HeaderItem from './HeaderItem';
|
||||||
|
import DividerItem from './DividerItem';
|
||||||
|
import IconMenu from './IconMenu';
|
||||||
|
import ItemPanel from './ItemPanel';
|
||||||
|
import PanelSelector from './PanelSelector';
|
||||||
|
import Item from './Item';
|
||||||
|
|
||||||
|
// Export enums
|
||||||
|
export { VerticalDirection, HorizontalDirection };
|
||||||
|
|
||||||
|
// Export components
|
||||||
|
export {
|
||||||
|
Menu,
|
||||||
|
SubMenu,
|
||||||
|
BackItem,
|
||||||
|
HeaderItem,
|
||||||
|
DividerItem,
|
||||||
|
IconMenu,
|
||||||
|
ItemPanel,
|
||||||
|
PanelSelector,
|
||||||
|
Item,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Namespace for components and enums
|
||||||
|
const AllInOneMenu = {
|
||||||
|
Menu,
|
||||||
|
SubMenu,
|
||||||
|
BackItem,
|
||||||
|
HeaderItem,
|
||||||
|
DividerItem,
|
||||||
|
IconMenu,
|
||||||
|
ItemPanel,
|
||||||
|
PanelSelector,
|
||||||
|
Item,
|
||||||
|
VerticalDirection,
|
||||||
|
HorizontalDirection,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AllInOneMenu;
|
||||||
@ -46,7 +46,7 @@ const DropdownMenuSubContent = React.forwardRef<
|
|||||||
<DropdownMenuPrimitive.SubContent
|
<DropdownMenuPrimitive.SubContent
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 border-input z-50 min-w-[8rem] overflow-hidden rounded border p-1 shadow-lg',
|
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 border-input/50 z-50 min-w-[8rem] overflow-hidden rounded border p-1 shadow-lg',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@ -68,7 +68,7 @@ const DropdownMenuContent = React.forwardRef<
|
|||||||
props.onKeyDown?.(e);
|
props.onKeyDown?.(e);
|
||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
'bg-popover text-popover-foreground border-input z-50 min-w-[8rem] overflow-hidden rounded border p-1 shadow-md',
|
'bg-popover text-popover-foreground border-input/50 z-50 min-w-[8rem] overflow-hidden rounded border p-1 shadow-md',
|
||||||
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import { Icons } from '../Icons';
|
|||||||
interface ScrollAreaProps extends React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root> {
|
interface ScrollAreaProps extends React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root> {
|
||||||
/** Flag to show/hide scroll indicator arrows at top and bottom */
|
/** Flag to show/hide scroll indicator arrows at top and bottom */
|
||||||
showArrows?: boolean;
|
showArrows?: boolean;
|
||||||
|
type?: 'auto' | 'always' | 'scroll';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -56,6 +57,7 @@ const ScrollArea = React.forwardRef<
|
|||||||
<ScrollAreaPrimitive.Root
|
<ScrollAreaPrimitive.Root
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn('relative h-full overflow-hidden', className)}
|
className={cn('relative h-full overflow-hidden', className)}
|
||||||
|
type={props.type || 'auto'}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ScrollAreaPrimitive.Viewport
|
<ScrollAreaPrimitive.Viewport
|
||||||
|
|||||||
@ -0,0 +1,85 @@
|
|||||||
|
/* Scrollbar container */
|
||||||
|
.scrollbarContainer {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
padding: 5px 5px 0 5px;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollbarInner {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
width: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Apply these styles to any range input inside our component */
|
||||||
|
.scrollbarInput {
|
||||||
|
position: absolute;
|
||||||
|
left: 12px;
|
||||||
|
top: 0;
|
||||||
|
height: 12px;
|
||||||
|
transform: rotate(90deg);
|
||||||
|
transform-origin: top left;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
background-color: transparent;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Thumb styles for WebKit browsers - more specific selector */
|
||||||
|
input[type='range'].scrollbarInput::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none !important;
|
||||||
|
background-color: hsl(var(--input));
|
||||||
|
border: none;
|
||||||
|
border-radius: 57px;
|
||||||
|
cursor: -webkit-grab;
|
||||||
|
height: 8px;
|
||||||
|
margin-top: -4px;
|
||||||
|
width: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='range'].scrollbarInput::-webkit-slider-thumb:active,
|
||||||
|
input[type='range'].scrollbarInput::-webkit-slider-thumb:hover {
|
||||||
|
background-color: hsl(var(--primary));
|
||||||
|
cursor: -webkit-grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Thumb styles for Firefox - more specific selector */
|
||||||
|
input[type='range'].scrollbarInput::-moz-range-thumb {
|
||||||
|
background-color: hsl(var(--input));
|
||||||
|
border: none;
|
||||||
|
border-radius: 57px;
|
||||||
|
cursor: -moz-grab;
|
||||||
|
height: 12px;
|
||||||
|
width: 39px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='range'].scrollbarInput::-moz-range-thumb:active,
|
||||||
|
input[type='range'].scrollbarInput::-moz-range-thumb:hover {
|
||||||
|
background-color: hsl(var(--primary));
|
||||||
|
cursor: -moz-grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Track styles for WebKit browsers - more specific selector */
|
||||||
|
input[type='range'].scrollbarInput::-webkit-slider-runnable-track {
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
height: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Track styles for Firefox - more specific selector */
|
||||||
|
input[type='range'].scrollbarInput::-moz-range-track {
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
height: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure focus doesn't show an outline */
|
||||||
|
.scrollbarInput:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
72
platform/ui-next/src/components/Viewport/ImageScrollbar.tsx
Normal file
72
platform/ui-next/src/components/Viewport/ImageScrollbar.tsx
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import React, { useCallback } from 'react';
|
||||||
|
import { cn } from '../../lib/utils';
|
||||||
|
import styles from './ImageScrollbar.module.css';
|
||||||
|
|
||||||
|
export interface ImageScrollbarProps {
|
||||||
|
value: number;
|
||||||
|
max: number;
|
||||||
|
height: string;
|
||||||
|
onChange: (value: number) => void;
|
||||||
|
onContextMenu?: (e: React.MouseEvent) => void;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ImageScrollbar: React.FC<ImageScrollbarProps> = ({
|
||||||
|
value,
|
||||||
|
max,
|
||||||
|
height,
|
||||||
|
onChange,
|
||||||
|
onContextMenu = e => e.preventDefault(),
|
||||||
|
className = '',
|
||||||
|
}) => {
|
||||||
|
if (max === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const style = {
|
||||||
|
width: height, // This is intentional for the rotation
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange = useCallback(
|
||||||
|
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const intValue = parseInt(event.target.value, 10);
|
||||||
|
onChange(intValue);
|
||||||
|
},
|
||||||
|
[onChange]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleKeyDown = useCallback((event: React.KeyboardEvent) => {
|
||||||
|
// We don't allow direct keyboard navigation (arrow keys)
|
||||||
|
const keys = {
|
||||||
|
DOWN: 40,
|
||||||
|
UP: 38,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (event.which === keys.DOWN || event.which === keys.UP) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(styles.scrollbarContainer, className)}
|
||||||
|
onContextMenu={onContextMenu}
|
||||||
|
>
|
||||||
|
<div className={styles.scrollbarInner}>
|
||||||
|
<input
|
||||||
|
className={cn(styles.scrollbarInput, 'mousetrap')}
|
||||||
|
style={style}
|
||||||
|
type="range"
|
||||||
|
min="0"
|
||||||
|
max={max}
|
||||||
|
step="1"
|
||||||
|
value={value}
|
||||||
|
onChange={handleChange}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
aria-label="Image navigation scrollbar"
|
||||||
|
data-testid="image-scrollbar-input"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -5,7 +5,7 @@ import PropTypes from 'prop-types';
|
|||||||
import { Icons } from '@ohif/ui-next';
|
import { Icons } from '@ohif/ui-next';
|
||||||
|
|
||||||
const arrowClasses =
|
const arrowClasses =
|
||||||
'cursor-pointer flex items-center justify-center shrink-0 text-primary-light active:text-white hover:bg-secondary-light/60 rounded';
|
'cursor-pointer flex items-center justify-center shrink-0 text-highlight active:text-foreground hover:bg-primary/30 rounded';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A small set of left/right arrow icons for stepping through slices or series.
|
* A small set of left/right arrow icons for stepping through slices or series.
|
||||||
|
|||||||
@ -13,7 +13,7 @@ export enum ViewportActionCornersLocations {
|
|||||||
bottomRight,
|
bottomRight,
|
||||||
}
|
}
|
||||||
|
|
||||||
const commonClasses = 'pointer-events-auto flex items-center gap-1';
|
const commonClasses = 'pointer-events-auto flex items-center';
|
||||||
const locationClasses = {
|
const locationClasses = {
|
||||||
[ViewportActionCornersLocations.topLeft]: classNames(
|
[ViewportActionCornersLocations.topLeft]: classNames(
|
||||||
commonClasses,
|
commonClasses,
|
||||||
@ -21,7 +21,7 @@ const locationClasses = {
|
|||||||
),
|
),
|
||||||
[ViewportActionCornersLocations.topRight]: classNames(
|
[ViewportActionCornersLocations.topRight]: classNames(
|
||||||
commonClasses,
|
commonClasses,
|
||||||
'absolute top-[4px] right-[4px] right-viewport-scrollbar'
|
'absolute top-[4px] right-[16px] right-viewport-scrollbar'
|
||||||
),
|
),
|
||||||
[ViewportActionCornersLocations.bottomLeft]: classNames(
|
[ViewportActionCornersLocations.bottomLeft]: classNames(
|
||||||
commonClasses,
|
commonClasses,
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.ViewportOverlay {
|
.ViewportOverlay {
|
||||||
color: #9ccef9;
|
color: hsl(var(--highlight));
|
||||||
}
|
}
|
||||||
.ViewportOverlay .overlay-element {
|
.ViewportOverlay .overlay-element {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -11,12 +11,12 @@
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
.overlay-top {
|
.overlay-top {
|
||||||
top: 28px;
|
top: 2.15rem;
|
||||||
}
|
}
|
||||||
.overlay-bottom {
|
.overlay-bottom {
|
||||||
bottom: 3px;
|
bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay-text {
|
.overlay-text {
|
||||||
text-shadow: 0.8px 0.8px 0.5px rgba(0, 0, 0, 0.75);
|
text-shadow: 0.8px 0.8px 0.5px rgba(0, 0, 0, 0.65);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,17 +17,11 @@ const classes = {
|
|||||||
bottomLeft: 'overlay-bottom left-viewport',
|
bottomLeft: 'overlay-bottom left-viewport',
|
||||||
};
|
};
|
||||||
|
|
||||||
function ViewportOverlay({
|
function ViewportOverlay({ topLeft, topRight, bottomRight, bottomLeft, color = 'text-highlight' }) {
|
||||||
topLeft,
|
|
||||||
topRight,
|
|
||||||
bottomRight,
|
|
||||||
bottomLeft,
|
|
||||||
color = 'text-primary-light',
|
|
||||||
}) {
|
|
||||||
const overlay = 'absolute pointer-events-none viewport-overlay';
|
const overlay = 'absolute pointer-events-none viewport-overlay';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames(color, 'overlay-text text-[13px] leading-5')}>
|
<div className={classNames(color, 'overlay-text text-base leading-5')}>
|
||||||
<div
|
<div
|
||||||
data-cy="viewport-overlay-top-left"
|
data-cy="viewport-overlay-top-left"
|
||||||
className={classNames(overlay, classes.topLeft)}
|
className={classNames(overlay, classes.topLeft)}
|
||||||
@ -37,14 +31,14 @@ function ViewportOverlay({
|
|||||||
<div
|
<div
|
||||||
data-cy="viewport-overlay-top-right"
|
data-cy="viewport-overlay-top-right"
|
||||||
className={classNames(overlay, classes.topRight)}
|
className={classNames(overlay, classes.topRight)}
|
||||||
style={{ transform: 'translateX(-8px)' }}
|
style={{ transform: 'translateX(9px)' }}
|
||||||
>
|
>
|
||||||
{topRight}
|
{topRight}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
data-cy="viewport-overlay-bottom-right"
|
data-cy="viewport-overlay-bottom-right"
|
||||||
className={classNames(overlay, classes.bottomRight)}
|
className={classNames(overlay, classes.bottomRight)}
|
||||||
style={{ transform: 'translateX(-8px)' }}
|
style={{ transform: 'translateX(6px)' }}
|
||||||
>
|
>
|
||||||
{bottomRight}
|
{bottomRight}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -68,8 +68,8 @@ function ViewportPane({
|
|||||||
{/* Border overlay */}
|
{/* Border overlay */}
|
||||||
<div
|
<div
|
||||||
className={classNames('pointer-events-none absolute inset-0', {
|
className={classNames('pointer-events-none absolute inset-0', {
|
||||||
'border-primary-light border': isActive,
|
'border-highlight rounded-md border': isActive,
|
||||||
'group-hover:border-primary-light/70 border border-transparent': !isActive,
|
'group-hover:border-highlight/50 rounded-md border border-transparent': !isActive,
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -6,3 +6,4 @@ export { ViewportPane } from './ViewportPane';
|
|||||||
export { ViewportActionCorners, ViewportActionCornersLocations } from './ViewportActionCorners';
|
export { ViewportActionCorners, ViewportActionCornersLocations } from './ViewportActionCorners';
|
||||||
export { ViewportOverlay } from './ViewportOverlay';
|
export { ViewportOverlay } from './ViewportOverlay';
|
||||||
export { ViewportGrid } from './ViewportGrid';
|
export { ViewportGrid } from './ViewportGrid';
|
||||||
|
export { ImageScrollbar } from './ImageScrollbar';
|
||||||
@ -81,8 +81,6 @@ import {
|
|||||||
} from './DropdownMenu';
|
} from './DropdownMenu';
|
||||||
import { Onboarding } from './Onboarding';
|
import { Onboarding } from './Onboarding';
|
||||||
import { DoubleSlider } from './DoubleSlider';
|
import { DoubleSlider } from './DoubleSlider';
|
||||||
export { DataRow } from './DataRow';
|
|
||||||
export { MeasurementTable } from './MeasurementTable';
|
|
||||||
import {
|
import {
|
||||||
SegmentationTable,
|
SegmentationTable,
|
||||||
useSegmentationTableContext,
|
useSegmentationTableContext,
|
||||||
@ -92,7 +90,6 @@ import {
|
|||||||
import { Toaster, toast } from './Sonner';
|
import { Toaster, toast } from './Sonner';
|
||||||
import { StudySummary } from './StudySummary';
|
import { StudySummary } from './StudySummary';
|
||||||
import { ErrorBoundary } from './Errorboundary';
|
import { ErrorBoundary } from './Errorboundary';
|
||||||
export * from './ColorCircle';
|
|
||||||
import { Header } from './Header';
|
import { Header } from './Header';
|
||||||
import { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } from './Card';
|
import { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } from './Card';
|
||||||
import {
|
import {
|
||||||
@ -105,6 +102,7 @@ import {
|
|||||||
ViewportActionCornersLocations,
|
ViewportActionCornersLocations,
|
||||||
ViewportOverlay,
|
ViewportOverlay,
|
||||||
ViewportGrid,
|
ViewportGrid,
|
||||||
|
ImageScrollbar,
|
||||||
} from './Viewport';
|
} from './Viewport';
|
||||||
import {
|
import {
|
||||||
ToolButton,
|
ToolButton,
|
||||||
@ -115,6 +113,11 @@ import {
|
|||||||
ToolButtonListDivider,
|
ToolButtonListDivider,
|
||||||
} from './ToolButton';
|
} from './ToolButton';
|
||||||
import { ToolSettings } from './OHIFToolSettings';
|
import { ToolSettings } from './OHIFToolSettings';
|
||||||
|
export { DataRow } from './DataRow';
|
||||||
|
export { MeasurementTable } from './MeasurementTable';
|
||||||
|
export * from './ColorCircle';
|
||||||
|
export { default as AllInOneMenu } from './AllInOneMenu';
|
||||||
|
export * from './AllInOneMenu';
|
||||||
|
|
||||||
// Segmentation Context Exports
|
// Segmentation Context Exports
|
||||||
export { useSegmentationTableContext, useSegmentationExpanded, useSegmentStatistics };
|
export { useSegmentationTableContext, useSegmentationExpanded, useSegmentStatistics };
|
||||||
@ -232,6 +235,7 @@ export {
|
|||||||
ViewportActionCornersLocations,
|
ViewportActionCornersLocations,
|
||||||
ViewportOverlay,
|
ViewportOverlay,
|
||||||
ViewportGrid,
|
ViewportGrid,
|
||||||
|
ImageScrollbar,
|
||||||
Clipboard,
|
Clipboard,
|
||||||
ToolButton,
|
ToolButton,
|
||||||
ToolButtonList,
|
ToolButtonList,
|
||||||
|
|||||||
@ -26,7 +26,7 @@
|
|||||||
--destructive: 0 62.8% 30.6%;
|
--destructive: 0 62.8% 30.6%;
|
||||||
--destructive-foreground: 0 0% 98%;
|
--destructive-foreground: 0 0% 98%;
|
||||||
--border: 0 0% 14.9%;
|
--border: 0 0% 14.9%;
|
||||||
--input: 236 45% 21%;
|
--input: 236 52% 30%;
|
||||||
--ring: 214 98% 60%;
|
--ring: 214 98% 60%;
|
||||||
--chart-1: 220 70% 50%;
|
--chart-1: 220 70% 50%;
|
||||||
--chart-2: 160 60% 45%;
|
--chart-2: 160 60% 45%;
|
||||||
@ -69,7 +69,7 @@
|
|||||||
--destructive: 0 62.8% 30.6%;
|
--destructive: 0 62.8% 30.6%;
|
||||||
--destructive-foreground: 0 0% 98%;
|
--destructive-foreground: 0 0% 98%;
|
||||||
--border: 0 0% 14.9%;
|
--border: 0 0% 14.9%;
|
||||||
--input: 236 45% 21%;
|
--input: 236 52% 30%;
|
||||||
--ring: 214 98% 60%;
|
--ring: 214 98% 60%;
|
||||||
--chart-1: 220 70% 50%;
|
--chart-1: 220 70% 50%;
|
||||||
--chart-2: 160 60% 45%;
|
--chart-2: 160 60% 45%;
|
||||||
|
|||||||
@ -2,8 +2,8 @@ import React from 'react';
|
|||||||
|
|
||||||
const DividerItem = () => {
|
const DividerItem = () => {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-3.5 shrink-0 items-center px-2">
|
<div className="flex h-3.5 shrink-0 items-center px-0">
|
||||||
<div className="bg-primary-dark h-[2px] w-full"></div>
|
<div className="bg-background h-px w-full"></div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -11,6 +11,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Truncate 2 lines utility */
|
/* Truncate 2 lines utility */
|
||||||
.truncate-2-lines {
|
.truncate-2-lines {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -49,7 +51,7 @@
|
|||||||
--destructive: 0 62.8% 30.6%;
|
--destructive: 0 62.8% 30.6%;
|
||||||
--destructive-foreground: 0 0% 98%;
|
--destructive-foreground: 0 0% 98%;
|
||||||
--border: 0 0% 14.9%;
|
--border: 0 0% 14.9%;
|
||||||
--input: 236 45% 21%;
|
--input: 236 52% 30%;
|
||||||
--ring: 214 98% 60%;
|
--ring: 214 98% 60%;
|
||||||
--chart-1: 220 70% 50%;
|
--chart-1: 220 70% 50%;
|
||||||
--chart-2: 160 60% 45%;
|
--chart-2: 160 60% 45%;
|
||||||
@ -92,7 +94,7 @@
|
|||||||
--destructive: 0 62.8% 30.6%;
|
--destructive: 0 62.8% 30.6%;
|
||||||
--destructive-foreground: 0 0% 98%;
|
--destructive-foreground: 0 0% 98%;
|
||||||
--border: 0 0% 14.9%;
|
--border: 0 0% 14.9%;
|
||||||
--input: 236 45% 21%;
|
--input: 236 52% 30%;
|
||||||
--ring: 214 98% 60%;
|
--ring: 214 98% 60%;
|
||||||
--chart-1: 220 70% 50%;
|
--chart-1: 220 70% 50%;
|
||||||
--chart-2: 160 60% 45%;
|
--chart-2: 160 60% 45%;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user