fix(colorbar): Allow for AdvancedRenderingControls to be placed at the top or bottom of the viewport. (#5270)
This commit is contained in:
parent
1cc78112e5
commit
d24fe54d2b
@ -1,6 +1,12 @@
|
|||||||
import { useToolbar, useViewportMousePosition } from '@ohif/core/src/hooks';
|
import { useToolbar, useViewportMousePosition } from '@ohif/core/src/hooks';
|
||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import { useViewportRendering } from '../../hooks';
|
import { useViewportRendering } from '../../hooks';
|
||||||
|
import { ButtonLocation } from '@ohif/core/src/services/ToolBarService/ToolbarService';
|
||||||
|
|
||||||
|
const mouseNearControlsRanges = {
|
||||||
|
[ButtonLocation.TopMiddle]: { minX: 0, minY: 0, maxX: 1, maxY: 0.1 },
|
||||||
|
[ButtonLocation.BottomMiddle]: { minX: 0, minY: 0.9, maxX: 1, maxY: 1 },
|
||||||
|
};
|
||||||
|
|
||||||
function AdvancedRenderingControls({
|
function AdvancedRenderingControls({
|
||||||
viewportId,
|
viewportId,
|
||||||
@ -24,7 +30,7 @@ function AdvancedRenderingControls({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const mousePosition = useViewportMousePosition(viewportId);
|
const mousePosition = useViewportMousePosition(viewportId);
|
||||||
const [isAtBottom, setIsAtBottom] = useState(false);
|
const [isMouseNearControls, setIsMouseNearControls] = useState(false);
|
||||||
const [showAllIcons, setShowAllIcons] = useState(true);
|
const [showAllIcons, setShowAllIcons] = useState(true);
|
||||||
const firstMountRef = useRef(true);
|
const firstMountRef = useRef(true);
|
||||||
const { hasColorbar } = useViewportRendering(viewportId);
|
const { hasColorbar } = useViewportRendering(viewportId);
|
||||||
@ -43,10 +49,11 @@ function AdvancedRenderingControls({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!showAllIcons && mousePosition.isInViewport) {
|
if (!showAllIcons && mousePosition.isInViewport) {
|
||||||
if (mousePosition.isInBottomPercentage(10)) {
|
const mouseHoverLocation = mouseNearControlsRanges[location];
|
||||||
setIsAtBottom(true);
|
if (mousePosition.isWithinNormalizedBox(mouseHoverLocation)) {
|
||||||
|
setIsMouseNearControls(true);
|
||||||
} else {
|
} else {
|
||||||
setIsAtBottom(false);
|
setIsMouseNearControls(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [mousePosition, showAllIcons]);
|
}, [mousePosition, showAllIcons]);
|
||||||
@ -97,7 +104,7 @@ function AdvancedRenderingControls({
|
|||||||
|
|
||||||
// Always show all icons on first mount for 3 seconds
|
// Always show all icons on first mount for 3 seconds
|
||||||
// After that, always show Colorbar, show others only when mouse is at bottom
|
// After that, always show Colorbar, show others only when mouse is at bottom
|
||||||
const shouldBeVisible = showAllIcons || id === 'Colorbar' || isAtBottom;
|
const shouldBeVisible = showAllIcons || id === 'Colorbar' || isMouseNearControls;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -27,6 +27,9 @@ type ColorbarProps = {
|
|||||||
numColorbars: number;
|
numColorbars: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const isHorizontal = (position: ColorbarPositionType): boolean =>
|
||||||
|
position === 'top' || position === 'bottom';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ViewportColorbar Component
|
* ViewportColorbar Component
|
||||||
* A React wrapper for the cornerstone ViewportColorbar that adds a close button
|
* A React wrapper for the cornerstone ViewportColorbar that adds a close button
|
||||||
@ -143,8 +146,9 @@ const ViewportColorbar = memo(function ViewportColorbar({
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
pointerEvents: 'auto',
|
pointerEvents: 'auto',
|
||||||
minWidth: position === 'bottom' ? width / 2.5 : '17px',
|
minWidth: isHorizontal(position) ? width / 2.5 : '17px',
|
||||||
minHeight: position === 'bottom' ? '20px' : numColorbars === 1 ? height / 3 : height / 4,
|
minHeight: isHorizontal(position) ? '20px' : numColorbars === 1 ? height / 3 : height / 4,
|
||||||
|
height: '1px', // sometimes flex items with min-height need a starting point for its height calculation
|
||||||
...positionStylesFromConfig,
|
...positionStylesFromConfig,
|
||||||
}}
|
}}
|
||||||
></div>
|
></div>
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import React, { useEffect, useState, useMemo, memo, useCallback } from 'react';
|
|||||||
import { useSystem } from '@ohif/core';
|
import { useSystem } from '@ohif/core';
|
||||||
import { ColorbarCustomization } from '../../types/Colorbar';
|
import { ColorbarCustomization } from '../../types/Colorbar';
|
||||||
import type { ColorMapPreset } from '../../types/Colormap';
|
import type { ColorMapPreset } from '../../types/Colormap';
|
||||||
import ViewportColorbar from './ViewportColorbar';
|
import ViewportColorbar, { isHorizontal } from './ViewportColorbar';
|
||||||
import useViewportRendering from '../../hooks/useViewportRendering';
|
import useViewportRendering from '../../hooks/useViewportRendering';
|
||||||
import { useViewportDisplaySets } from '../../hooks/useViewportDisplaySets';
|
import { useViewportDisplaySets } from '../../hooks/useViewportDisplaySets';
|
||||||
type ViewportColorbarsContainerProps = {
|
type ViewportColorbarsContainerProps = {
|
||||||
@ -74,10 +74,8 @@ const ViewportColorbarsContainer = memo(function ViewportColorbarsContainer({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isBottom = position === 'bottom';
|
|
||||||
|
|
||||||
const isSingleViewport = viewportDisplaySets.length === 1;
|
const isSingleViewport = viewportDisplaySets.length === 1;
|
||||||
const showFullList = isSingleViewport || !isBottom;
|
const showFullList = isSingleViewport || !isHorizontal(position);
|
||||||
|
|
||||||
const colorbarsToUse = showFullList
|
const colorbarsToUse = showFullList
|
||||||
? colorbars
|
? colorbars
|
||||||
|
|||||||
@ -2,24 +2,33 @@ import { useState, useEffect } from 'react';
|
|||||||
import { useViewportRef } from './useViewportRef';
|
import { useViewportRef } from './useViewportRef';
|
||||||
import { useViewportSize } from './useViewportSize';
|
import { useViewportSize } from './useViewportSize';
|
||||||
|
|
||||||
|
interface NormalizedBox {
|
||||||
|
minX: number;
|
||||||
|
minY: number;
|
||||||
|
maxX: number;
|
||||||
|
maxY: number;
|
||||||
|
}
|
||||||
|
|
||||||
interface MousePosition {
|
interface MousePosition {
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
isInViewport: boolean;
|
isInViewport: boolean;
|
||||||
|
relativeX: number;
|
||||||
relativeY: number; // Position as percentage from top (0-1)
|
relativeY: number; // Position as percentage from top (0-1)
|
||||||
isInBottomPercentage: (percentage: number) => boolean;
|
isWithinNormalizedBox?: (normalizedBox: NormalizedBox) => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function useViewportMousePosition(viewportId: string): MousePosition {
|
function useViewportMousePosition(viewportId: string): MousePosition {
|
||||||
const viewportRef = useViewportRef(viewportId);
|
const viewportRef = useViewportRef(viewportId);
|
||||||
const { height, clientRect } = useViewportSize(viewportId);
|
const { width, height, clientRect } = useViewportSize(viewportId);
|
||||||
|
|
||||||
const [mousePosition, setMousePosition] = useState<MousePosition>({
|
const [mousePosition, setMousePosition] = useState<MousePosition>({
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
isInViewport: false,
|
isInViewport: false,
|
||||||
relativeY: 0,
|
relativeY: 0,
|
||||||
isInBottomPercentage: (percentage: number) => false,
|
relativeX: 0,
|
||||||
|
isWithinNormalizedBox: (normalizedBox: NormalizedBox) => false,
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -38,18 +47,25 @@ function useViewportMousePosition(viewportId: string): MousePosition {
|
|||||||
|
|
||||||
const isInViewport = x >= 0 && x <= clientRect.width && y >= 0 && y <= clientRect.height;
|
const isInViewport = x >= 0 && x <= clientRect.width && y >= 0 && y <= clientRect.height;
|
||||||
|
|
||||||
|
const relativeX = Math.max(0, Math.min(1, x / width));
|
||||||
const relativeY = Math.max(0, Math.min(1, y / height));
|
const relativeY = Math.max(0, Math.min(1, y / height));
|
||||||
|
|
||||||
const isInBottomPercentage = (percentage: number) => {
|
const isWithinNormalizedBox = (normalizedBox: NormalizedBox) => {
|
||||||
return relativeY >= 1 - percentage / 100;
|
return (
|
||||||
|
relativeX >= normalizedBox.minX &&
|
||||||
|
relativeX <= normalizedBox.maxX &&
|
||||||
|
relativeY >= normalizedBox.minY &&
|
||||||
|
relativeY <= normalizedBox.maxY
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
setMousePosition({
|
setMousePosition({
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
isInViewport,
|
isInViewport,
|
||||||
|
relativeX,
|
||||||
relativeY,
|
relativeY,
|
||||||
isInBottomPercentage,
|
isWithinNormalizedBox,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -58,7 +74,7 @@ function useViewportMousePosition(viewportId: string): MousePosition {
|
|||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('mousemove', handleMouseMove);
|
document.removeEventListener('mousemove', handleMouseMove);
|
||||||
};
|
};
|
||||||
}, [viewportRef, height, clientRect]);
|
}, [viewportRef, height, clientRect, width]);
|
||||||
|
|
||||||
return mousePosition;
|
return mousePosition;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,8 @@ test.beforeEach(async ({ page }) => {
|
|||||||
test('should hydrate SEG reports correctly', async ({ page }) => {
|
test('should hydrate SEG reports correctly', async ({ page }) => {
|
||||||
await page.getByTestId('side-panel-header-right').click();
|
await page.getByTestId('side-panel-header-right').click();
|
||||||
await page.getByTestId('study-browser-thumbnail-no-image').dblclick();
|
await page.getByTestId('study-browser-thumbnail-no-image').dblclick();
|
||||||
|
|
||||||
|
await page.waitForTimeout(5000);
|
||||||
await checkForScreenshot(page, page, screenShotPaths.segHydration.segPreHydration);
|
await checkForScreenshot(page, page, screenShotPaths.segHydration.segPreHydration);
|
||||||
|
|
||||||
await page.evaluate(() => {
|
await page.evaluate(() => {
|
||||||
@ -32,5 +34,7 @@ test('should hydrate SEG reports correctly', async ({ page }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await page.getByTestId('yes-hydrate-btn').click();
|
await page.getByTestId('yes-hydrate-btn').click();
|
||||||
|
|
||||||
|
await page.waitForTimeout(5000);
|
||||||
await checkForScreenshot(page, page, screenShotPaths.segHydration.segPostHydration);
|
await checkForScreenshot(page, page, screenShotPaths.segHydration.segPostHydration);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -13,19 +13,23 @@ test('should properly display MPR for MR', async ({ page }) => {
|
|||||||
await page.getByTestId('Layout').click();
|
await page.getByTestId('Layout').click();
|
||||||
await page.getByTestId('MPR').click();
|
await page.getByTestId('MPR').click();
|
||||||
|
|
||||||
|
await page.waitForTimeout(5000);
|
||||||
await checkForScreenshot(page, page, screenShotPaths.segHydrationFromMPR.mprBeforeSEG);
|
await checkForScreenshot(page, page, screenShotPaths.segHydrationFromMPR.mprBeforeSEG);
|
||||||
|
|
||||||
await page.getByTestId('study-browser-thumbnail-no-image').dblclick();
|
await page.getByTestId('study-browser-thumbnail-no-image').dblclick();
|
||||||
|
|
||||||
|
await page.waitForTimeout(5000);
|
||||||
await checkForScreenshot(page, page, screenShotPaths.segHydrationFromMPR.mprAfterSEG);
|
await checkForScreenshot(page, page, screenShotPaths.segHydrationFromMPR.mprAfterSEG);
|
||||||
|
|
||||||
await page.getByTestId('yes-hydrate-btn').click();
|
await page.getByTestId('yes-hydrate-btn').click();
|
||||||
|
|
||||||
|
await page.waitForTimeout(5000);
|
||||||
await checkForScreenshot(page, page, screenShotPaths.segHydrationFromMPR.mprAfterSegHydrated);
|
await checkForScreenshot(page, page, screenShotPaths.segHydrationFromMPR.mprAfterSegHydrated);
|
||||||
|
|
||||||
await page.getByTestId('Layout').click();
|
await page.getByTestId('Layout').click();
|
||||||
await page.getByTestId('Axial Primary').click();
|
await page.getByTestId('Axial Primary').click();
|
||||||
|
|
||||||
|
await page.waitForTimeout(5000);
|
||||||
await checkForScreenshot(
|
await checkForScreenshot(
|
||||||
page,
|
page,
|
||||||
page,
|
page,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user