fix(viewport): Reset viewport state and fix CINE looping, thumbnail resolution, and dynamic tool settings (#4037)

This commit is contained in:
Alireza authored and GitHub committed 2024-04-16 11:04:48 -04:00
1 parent a6c6fff43a
commit f99a0bfb31
80 files changed
+1129 -801

No files matched your search

@@ -1,11 +1,19 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import Icon from '../Icon/Icon';
function LayoutPreset({ onSelection, title, icon, commandOptions, classNames }) {
function LayoutPreset({
onSelection,
title,
icon,
commandOptions,
classNames: classNameProps,
disabled,
}) {
return (
<div
className={classNames}
className={classNames(classNameProps, disabled && 'ohif-disabled')}
onClick={() => {
onSelection(commandOptions);
}}
@@ -29,6 +37,7 @@ LayoutPreset.propTypes = {
icon: PropTypes.string.isRequired,
commandOptions: PropTypes.object.isRequired,
classNames: PropTypes.string,
disabled: PropTypes.bool,
};
export default LayoutPreset;
@@ -7,7 +7,7 @@ function AddSegmentRow({ onClick, onToggleSegmentationVisibility = null, segment
return (
<div className="flex justify-between bg-black pl-[34px] hover:cursor-pointer">
<div
className="group pt-[5px] pb-[5px]"
className="group py-[5px] pb-[5px]"
onClick={onClick}
>
<div className="text-primary-active group-hover:bg-secondary-dark flex items-center rounded-[4px] pr-2">
@@ -2,11 +2,11 @@ import React from 'react';
import Icon from '../Icon';
import { useTranslation } from 'react-i18next';
function NoSegmentationRow({ onSegmentationAdd }) {
function NoSegmentationRow({ onSegmentationAdd, addSegmentationClassName }) {
const { t } = useTranslation('SegmentationTable');
return (
<div
className="group"
className={`group ${addSegmentationClassName}`}
onClick={onSegmentationAdd}
>
<div className="text-primary-active group-hover:bg-secondary-dark flex items-center rounded-[4px] group-hover:cursor-pointer">
@@ -15,6 +15,7 @@ function SegmentationDropDownRow({
storeSegmentation,
onSegmentationDelete,
onSegmentationAdd,
addSegmentationClassName,
}) {
const handleChange = option => {
onActiveSegmentationChange(option.value); // Notify the parent
@@ -41,7 +42,7 @@ function SegmentationDropDownRow({
id="segmentation-dropdown"
showDropdownIcon={false}
alignment="left"
itemsClassName="text-primary-active"
itemsClassName={`text-primary-active ${addSegmentationClassName}`}
showBorders={false}
maxCharactersPerLine={30}
list={[
@@ -42,6 +42,7 @@ const SegmentationGroupTable = ({
setRenderFill,
setRenderInactiveSegmentations,
setRenderOutline,
addSegmentationClassName,
}) => {
const [isConfigOpen, setIsConfigOpen] = useState(false);
const [activeSegmentationId, setActiveSegmentationId] = useState(null);
@@ -100,9 +101,12 @@ const SegmentationGroupTable = ({
)}
<div className="bg-primary-dark ">
{segmentations?.length === 0 ? (
<div className="select-none bg-black pt-[5px] pb-[5px]">
<div className="select-none bg-black py-[3px]">
{showAddSegmentation && !disableEditing && (
<NoSegmentationRow onSegmentationAdd={onSegmentationAdd} />
<NoSegmentationRow
onSegmentationAdd={onSegmentationAdd}
addSegmentationClassName={addSegmentationClassName}
/>
)}
</div>
) : (
@@ -118,6 +122,7 @@ const SegmentationGroupTable = ({
onSegmentationDownloadRTSS={onSegmentationDownloadRTSS}
storeSegmentation={storeSegmentation}
onSegmentationAdd={onSegmentationAdd}
addSegmentationClassName={addSegmentationClassName}
onToggleSegmentationVisibility={onToggleSegmentationVisibility}
/>
{!disableEditing && showAddSegment && (
@@ -51,7 +51,7 @@ const Thumbnail = ({
<div ref={drag}>
<div
className={classnames(
'min-h-32 flex flex-1 items-center justify-center overflow-hidden rounded-md bg-black text-base text-white',
'flex h-32 flex-1 items-center justify-center overflow-hidden rounded-md bg-black text-base text-white',
isActive
? 'border-primary-light border-2'
: 'border-secondary-light border hover:border-blue-300'
@@ -64,7 +64,7 @@ const Thumbnail = ({
<img
src={imageSrc}
alt={imageAltText}
className="min-h-32 object-contain"
className="h-full w-full object-contain"
crossOrigin="anonymous"
/>
) : (
@@ -16,7 +16,7 @@ const ThumbnailList = ({
return (
<div
id="ohif-thumbnail-list"
className="ohif-scrollbar study-min-height overflow-y-hidden bg-black py-3"
className="ohif-scrollbar study-min-height overflow-y-hidden bg-black py-5"
>
{thumbnails.map(
({
@@ -32,10 +32,7 @@ function ThumbnailTracked({
return (
<div
className={classnames(
'flex flex-1 cursor-pointer flex-row px-3 py-2 outline-none',
className
)}
className={classnames('flex flex-1 cursor-pointer flex-row px-3 outline-none', className)}
id={`thumbnail-${displaySetInstanceUID}`}
>
<div className="flex-2 flex flex-col items-center">
@@ -65,6 +65,7 @@ function Toolbox({ servicesManager, buttonSectionId, commandsManager, title, ...
cmds.forEach(command => {
const isString = typeof command === 'string';
const isObject = typeof command === 'object';
const isFunction = typeof command === 'function';
if (isString) {
commandsManager.run(command, { value });
@@ -73,6 +74,8 @@ function Toolbox({ servicesManager, buttonSectionId, commandsManager, title, ...
...command,
commandOptions: { ...command.commandOptions, ...option, value },
});
} else if (isFunction) {
command({ value, commandsManager, servicesManager });
}
});
},
@@ -92,7 +92,7 @@ export default function CineProvider({ children, service }) {
setCine,
setIsCineEnabled: isCineEnabled => service.setIsCineEnabled(isCineEnabled),
playClip: (element, playClipOptions) => service.playClip(element, playClipOptions),
stopClip: element => service.stopClip(element),
stopClip: (element, stopClipOptions) => service.stopClip(element, stopClipOptions),
};
return <CineContext.Provider value={[state, api]}>{children}</CineContext.Provider>;
@@ -28,6 +28,7 @@ interface Layout {
interface DefaultState {
activeViewportId: string | null;
layout: Layout;
isHangingProtocolLayout: boolean;
viewports: Map<string, Viewport>;
}
@@ -38,6 +39,16 @@ const DEFAULT_STATE: DefaultState = {
numCols: 0,
layoutType: 'grid',
},
// this flag is used to determine if the hanging protocol layout is active
// so that we can inherit the viewport options from the previous state
// otherwise we will not allow that. Basically the issue is that we need
// to be able to come out of the hanging protocol layout and go back to the
// regular layout e.g., if we are in the MPR hanging protocol, and someone use
// 1x1 layout by custom layout selector, there is no way to drag and drop
// a non-reconstructible series to the viewport since it will always
// inherit the hanging protocol layout options (volume viewport),
// so we need to be able to switch back to the regular layout.
isHangingProtocolLayout: false,
// Viewports structure has been changed to Map (previously it was
// tied to the viewportIndex which caused multiple issues. Now we have
// moved completely to viewportId which is unique for each viewport.
@@ -152,7 +163,7 @@ export function ViewportGridProvider({ children, service }) {
// Use the newly provide viewportOptions and display set options
// when provided, and otherwise fall back to the previous ones.
// That allows for easy updates of just the display set.
const viewportOptions = merge(
let viewportOptions = merge(
{},
previousViewport?.viewportOptions,
updatedViewport?.viewportOptions
@@ -161,18 +172,29 @@ export function ViewportGridProvider({ children, service }) {
const displaySetOptions = updatedViewport.displaySetOptions || [];
if (!displaySetOptions.length) {
// Copy all the display set options, assuming a full set of displaySet UID's is provided.
displaySetOptions.push(...previousViewport.displaySetOptions);
if (state.isHangingProtocolLayout) {
displaySetOptions.push(...(previousViewport.displaySetOptions || []));
}
if (!displaySetOptions.length) {
displaySetOptions.push({});
}
}
// if it is not part of the hanging protocol layout, we should remove the toolGroupId
// and viewportType from the viewportOptions so that it doesn't
// inherit the hanging protocol layout options
if (!state.isHangingProtocolLayout) {
viewportOptions = {
viewportId: viewportOptions.viewportId,
};
}
const newViewport = {
...previousViewport,
displaySetInstanceUIDs,
viewportOptions,
displaySetOptions,
viewportLabel: getViewportLabel(viewports, viewportId),
// viewportLabel: getViewportLabel(viewports, viewportId),
};
viewportOptions.presentationIds = ViewportGridService.getPresentationIds(
@@ -196,6 +218,7 @@ export function ViewportGridProvider({ children, service }) {
layoutType = 'grid',
activeViewportId,
findOrCreateViewport,
isHangingProtocolLayout,
} = action.payload;
// If empty viewportOptions, we use numRow and numCols to calculate number of viewports
@@ -283,6 +306,7 @@ export function ViewportGridProvider({ children, service }) {
layoutType,
},
viewports,
isHangingProtocolLayout,
};
return ret;
}
@@ -373,6 +397,7 @@ export function ViewportGridProvider({ children, service }) {
layoutOptions = [],
activeViewportId,
findOrCreateViewport,
isHangingProtocolLayout,
}) =>
dispatch({
type: 'SET_LAYOUT',
@@ -383,6 +408,7 @@ export function ViewportGridProvider({ children, service }) {
layoutOptions,
activeViewportId,
findOrCreateViewport,
isHangingProtocolLayout,
},
}),
[dispatch]