fix: UI tweaks to satisfy OHIF-161, OHIF-254, OHIF-167, OHIF-249, OHIF-170, OHIF-162

* add group hover for borderColor

* measurements action buttons hover state

* fixes and button/buttongroup/iconButton refactor

* OHIF-161: Fix Tracked/Untracked Text in Thumbnail's Tooltip

* OHIF-254: Format SR series date

* OHIF-167 - Fix Study Date Time format

* OHIF-249: Fix viewport border thickness and action bar min height

* pass viewportIdentificator to SR Thumbnail

* show tooltip when hover over the SR thumbnail

* OHIF-170: Display tooltip when hovering over an untracked series in the Viewport Action Bar

* Undo accidental date change

* Sneak in "Create Report" messaging for createReport notifications

* Fix text clipping

* Add no-select text styles for action bar, thumbnail, and thumbnail no image (sr)

* fix for OHIF-162

Modified to remove Viewport Identifier. Text now only reads: “Structured Report”This was partly to address a bug where the Viewport Identifier was incorrect for all but the first displayed SR. Component API changed for ThumbnailNoImage to accomodate any generic “modalityTooltip”

* Some ViewportPane resize magic

Co-authored-by: dannyrb <danny.ri.brown@gmail.com>
This commit is contained in:
Rodrigo Antinarelli 2020-07-02 22:41:55 -03:00 committed by GitHub
parent a156eadcb7
commit 491afaa43d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 139 additions and 60 deletions

View File

@ -25,13 +25,13 @@ async function createReportAsync(servicesManager, dataSource, measurements) {
madeInClient: true, madeInClient: true,
}); });
UINotificationService.show({ UINotificationService.show({
title: 'STOW SR', title: 'Create Report',
message: 'Measurements saved successfully', message: 'Measurements saved successfully',
type: 'success', type: 'success',
}); });
} catch (error) { } catch (error) {
UINotificationService.show({ UINotificationService.show({
title: 'STOW SR', title: 'Create Report',
message: error.message || 'Failed to store measurements', message: error.message || 'Failed to store measurements',
type: 'error', type: 'error',
}); });

View File

@ -5,27 +5,22 @@ import { Button, ButtonGroup, Icon, IconButton } from '@ohif/ui';
function ActionButtons({ onExportClick, onCreateReportClick }) { function ActionButtons({ onExportClick, onCreateReportClick }) {
return ( return (
<React.Fragment> <React.Fragment>
<ButtonGroup onClick={onExportClick}> <ButtonGroup color="black" onClick={onExportClick} size="inherit">
<Button <Button className="text-base px-2 py-2">Export</Button>
className="px-2 py-2 text-base text-white bg-black border-primary-main"
size="initial"
color="inherit"
>
Export
</Button>
<IconButton <IconButton
className="px-2 text-white bg-black border-primary-main" className="px-2"
color="inherit" color="black"
size="initial" size="initial"
variant="outlined"
> >
<Icon name="arrow-down" /> <Icon name="arrow-down" />
</IconButton> </IconButton>
</ButtonGroup> </ButtonGroup>
<Button <Button
className="px-2 py-2 ml-2 text-base text-white bg-black border border-primary-main" className="ml-2 text-base"
variant="outlined" variant="outlined"
size="initial" size="small"
color="inherit" color="black"
onClick={onCreateReportClick} onClick={onCreateReportClick}
> >
Create Report Create Report

View File

@ -381,7 +381,7 @@ function _mapDisplaySets(
description: ds.SeriesDescription, description: ds.SeriesDescription,
seriesNumber: ds.SeriesNumber, seriesNumber: ds.SeriesNumber,
modality: ds.Modality, modality: ds.Modality,
seriesDate: ds.SeriesDate, seriesDate: formatDate(ds.SeriesDate),
numInstances: ds.numImageFrames, numInstances: ds.numImageFrames,
StudyInstanceUID: ds.StudyInstanceUID, StudyInstanceUID: ds.StudyInstanceUID,
componentType, componentType,

View File

@ -38,6 +38,8 @@ const variantClasses = {
'text-secondary-light hover:bg-secondary-light hover:text-white active:opacity-80 focus:bg-secondary-light focus:text-white', 'text-secondary-light hover:bg-secondary-light hover:text-white active:opacity-80 focus:bg-secondary-light focus:text-white',
white: white:
'text-white hover:bg-white hover:text-black active:opacity-80 focus:bg-white focus:text-black', 'text-white hover:bg-white hover:text-black active:opacity-80 focus:bg-white focus:text-black',
black:
'text-black hover:bg-black hover:text-white focus:bg-black focus:text-white active:opacity-80',
}, },
outlined: { outlined: {
default: default:
@ -48,6 +50,8 @@ const variantClasses = {
'border bg-transparent border-secondary-light text-secondary-light hover:opacity-80 active:opacity-100 focus:opacity-80', 'border bg-transparent border-secondary-light text-secondary-light hover:opacity-80 active:opacity-100 focus:opacity-80',
white: white:
'border bg-transparent border-white text-white hover:opacity-80 active:opacity-100 focus:opacity-80', 'border bg-transparent border-white text-white hover:opacity-80 active:opacity-100 focus:opacity-80',
black:
'border bg-black border-primary-main text-white hover:bg-primary-main focus:bg-primary-main hover:border-black focus:border-black',
}, },
contained: { contained: {
default: default:
@ -58,6 +62,8 @@ const variantClasses = {
'bg-secondary-light text-white hover:opacity-80 active:opacity-100 focus:opacity-80', 'bg-secondary-light text-white hover:opacity-80 active:opacity-100 focus:opacity-80',
white: white:
'bg-white text-black hover:opacity-80 active:opacity-100 focus:opacity-80', 'bg-white text-black hover:opacity-80 active:opacity-100 focus:opacity-80',
black:
'bg-black text-white hover:opacity-80 active:opacity-100 focus:opacity-80',
}, },
}; };
@ -104,7 +110,7 @@ const Button = ({
); );
const buttonElement = useRef(null); const buttonElement = useRef(null);
const handleOnClick = (e) => { const handleOnClick = e => {
buttonElement.current.blur(); buttonElement.current.blur();
if (rest.onClick) { if (rest.onClick) {
rest.onClick(e); rest.onClick(e);
@ -144,6 +150,7 @@ Button.propTypes = {
'primary', 'primary',
'secondary', 'secondary',
'white', 'white',
'black',
'inherit', 'inherit',
]), ]),
fullWidth: PropTypes.bool, fullWidth: PropTypes.bool,

View File

@ -2,7 +2,7 @@ import React, { useRef } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classnames from 'classnames'; import classnames from 'classnames';
const baseButtonClass = 'border'; const baseButtonClass = 'border outline-none';
const roundedClasses = { const roundedClasses = {
vertical: { vertical: {
none: '', none: '',
@ -41,18 +41,22 @@ const variantClasses = {
primary: 'border-primary-main', primary: 'border-primary-main',
secondary: 'border-secondary-light', secondary: 'border-secondary-light',
white: 'border-white', white: 'border-white',
black: 'border-primary-main',
}, },
outlined: { outlined: {
default: '', default: '',
primary: '', primary: '',
secondary: '', secondary: '',
white: '', white: '',
black:
'border-primary-main group-hover:bg-primary-main group-hover:border-black',
}, },
contained: { contained: {
default: 'border-white', default: 'border-white',
primary: 'border-white', primary: 'border-white',
secondary: 'border-white', secondary: 'border-white',
white: 'border-black', white: 'border-black',
black: 'border-primary-main',
}, },
}; };
@ -89,6 +93,7 @@ const ButtonGroup = ({
<div <div
role="group" role="group"
className={classnames( className={classnames(
'group',
className, className,
orientationClasses[orientation], orientationClasses[orientation],
fullWidth ? fullWidthDisplayClass : baseDisplayClass fullWidth ? fullWidthDisplayClass : baseDisplayClass
@ -96,7 +101,7 @@ const ButtonGroup = ({
ref={ref} ref={ref}
{...other} {...other}
> >
{React.Children.map(children, (child) => { {React.Children.map(children, child => {
if (!React.isValidElement(child)) { if (!React.isValidElement(child)) {
return null; return null;
} }
@ -118,7 +123,14 @@ const ButtonGroup = ({
ButtonGroup.propTypes = { ButtonGroup.propTypes = {
children: PropTypes.node.isRequired, children: PropTypes.node.isRequired,
className: PropTypes.string, className: PropTypes.string,
color: PropTypes.oneOf(['default', 'inherit', 'primary', 'secondary']), color: PropTypes.oneOf([
'default',
'inherit',
'primary',
'secondary',
'white',
'black',
]),
disabled: PropTypes.bool, disabled: PropTypes.bool,
fullWidth: PropTypes.bool, fullWidth: PropTypes.bool,
orientation: PropTypes.oneOf(['vertical', 'horizontal']), orientation: PropTypes.oneOf(['vertical', 'horizontal']),

View File

@ -28,6 +28,8 @@ const variantClasses = {
'text-secondary-light hover:bg-secondary-light hover:text-white active:opacity-80 focus:bg-secondary-light focus:text-white', 'text-secondary-light hover:bg-secondary-light hover:text-white active:opacity-80 focus:bg-secondary-light focus:text-white',
white: white:
'text-white hover:bg-white hover:text-black active:opacity-80 focus:bg-white focus:text-black', 'text-white hover:bg-white hover:text-black active:opacity-80 focus:bg-white focus:text-black',
black:
'text-black hover:bg-black hover:text-white focus:bg-black focus:text-white active:opacity-80',
}, },
outlined: { outlined: {
default: default:
@ -38,6 +40,8 @@ const variantClasses = {
'border bg-transparent border-secondary-light text-secondary-light hover:opacity-80 active:opacity-100 focus:opacity-80', 'border bg-transparent border-secondary-light text-secondary-light hover:opacity-80 active:opacity-100 focus:opacity-80',
white: white:
'border bg-transparent border-white text-white hover:opacity-80 active:opacity-100 focus:opacity-80', 'border bg-transparent border-white text-white hover:opacity-80 active:opacity-100 focus:opacity-80',
black:
'border bg-black border-primary-main text-white hover:bg-primary-main focus:bg-primary-main hover:border-black focus:border-black',
}, },
contained: { contained: {
default: default:
@ -48,6 +52,8 @@ const variantClasses = {
'bg-secondary-light text-white hover:opacity-80 active:opacity-100 focus:opacity-80', 'bg-secondary-light text-white hover:opacity-80 active:opacity-100 focus:opacity-80',
white: white:
'bg-white text-black hover:opacity-80 active:opacity-100 focus:opacity-80', 'bg-white text-black hover:opacity-80 active:opacity-100 focus:opacity-80',
black:
'bg-black text-white hover:opacity-80 active:opacity-100 focus:opacity-80',
}, },
}; };
@ -113,7 +119,7 @@ const IconButton = ({
}; };
IconButton.defaultProps = { IconButton.defaultProps = {
onClick: () => { }, onClick: () => {},
color: 'default', color: 'default',
disabled: false, disabled: false,
fullWidth: false, fullWidth: false,
@ -133,6 +139,7 @@ IconButton.propTypes = {
'primary', 'primary',
'secondary', 'secondary',
'white', 'white',
'black',
'inherit', 'inherit',
]), ]),
fullWidth: PropTypes.bool, fullWidth: PropTypes.bool,

View File

@ -39,7 +39,7 @@ const Thumbnail = ({
ref={thumbnailElement} ref={thumbnailElement}
className={classnames( className={classnames(
className, className,
'flex flex-col flex-1 px-3 mb-8 cursor-pointer outline-none group' 'flex flex-col flex-1 px-3 mb-8 cursor-pointer outline-none select-none group'
)} )}
id={`thumbnail-${displaySetInstanceUID}`} id={`thumbnail-${displaySetInstanceUID}`}
onClick={onClick} onClick={onClick}

View File

@ -79,12 +79,14 @@ const ThumbnailList = ({
displaySetInstanceUID={displaySetInstanceUID} displaySetInstanceUID={displaySetInstanceUID}
dragData={dragData} dragData={dragData}
modality={modality} modality={modality}
modalityTooltip={_getModalityTooltip(modality)}
seriesDate={seriesDate} seriesDate={seriesDate}
description={description} description={description}
onClick={() => onThumbnailClick(displaySetInstanceUID)} onClick={() => onThumbnailClick(displaySetInstanceUID)}
onDoubleClick={() => onDoubleClick={() =>
onThumbnailDoubleClick(displaySetInstanceUID) onThumbnailDoubleClick(displaySetInstanceUID)
} }
viewportIdentificator={viewportIdentificator}
/> />
); );
default: default:
@ -132,4 +134,17 @@ ThumbnailList.propTypes = {
onClickUntrack: PropTypes.func.isRequired, onClickUntrack: PropTypes.func.isRequired,
}; };
// TODO: Support "Viewport Identificator"?
function _getModalityTooltip(modality) {
if (_modalityTooltips.hasOwnProperty(modality)) {
return _modalityTooltips[modality];
}
return 'Unknown';
}
const _modalityTooltips = {
SR: 'Structured Report',
};
export default ThumbnailList; export default ThumbnailList;

View File

@ -2,7 +2,7 @@ import React, { useRef } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useDrag } from 'react-dnd'; import { useDrag } from 'react-dnd';
import { Icon } from '@ohif/ui'; import { Icon, Tooltip, Typography } from '@ohif/ui';
import blurHandlerListener from '../../utils/blurHandlerListener'; import blurHandlerListener from '../../utils/blurHandlerListener';
const ThumbnailNoImage = ({ const ThumbnailNoImage = ({
@ -10,10 +10,12 @@ const ThumbnailNoImage = ({
description, description,
seriesDate, seriesDate,
modality, modality,
modalityTooltip,
onClick, onClick,
onDoubleClick, onDoubleClick,
dragData, dragData,
isActive, isActive,
viewportIdentificator = '',
}) => { }) => {
const [collectedProps, drag, dragPreview] = useDrag({ const [collectedProps, drag, dragPreview] = useDrag({
item: { ...dragData }, item: { ...dragData },
@ -29,7 +31,7 @@ const ThumbnailNoImage = ({
ref={thumbnailElement} ref={thumbnailElement}
onFocus={() => blurHandlerListener(thumbnailElement)} onFocus={() => blurHandlerListener(thumbnailElement)}
className={classnames( className={classnames(
'flex flex-row flex-1 px-4 py-3 cursor-pointer outline-none border-transparent hover:border-blue-300 focus:border-blue-300 rounded', 'flex flex-row flex-1 px-4 py-3 cursor-pointer outline-none border-transparent hover:border-blue-300 focus:border-blue-300 rounded select-none',
isActive ? 'border-2 border-primary-light' : 'border' isActive ? 'border-2 border-primary-light' : 'border'
)} )}
id={`thumbnail-${displaySetInstanceUID}`} id={`thumbnail-${displaySetInstanceUID}`}
@ -42,10 +44,15 @@ const ThumbnailNoImage = ({
<div className="flex flex-col flex-1"> <div className="flex flex-col flex-1">
<div className="flex flex-row items-center flex-1 mb-2"> <div className="flex flex-row items-center flex-1 mb-2">
<Icon name="list-bullets" className="w-12 text-secondary-light" /> <Icon name="list-bullets" className="w-12 text-secondary-light" />
<div className="px-3 mr-4 text-lg text-white rounded-sm bg-primary-main"> <Tooltip
{modality} position="bottom"
</div> content={<Typography>{modalityTooltip}</Typography>}
<span className="text-base text-blue-300">{seriesDate}</span> >
<div className="px-3 text-lg text-white rounded-sm bg-primary-main">
{modality}
</div>
</Tooltip>
<span className="ml-4 text-base text-blue-300">{seriesDate}</span>
</div> </div>
<div className="ml-12 text-base text-white break-all"> <div className="ml-12 text-base text-white break-all">
{description} {description}
@ -71,10 +78,13 @@ ThumbnailNoImage.propTypes = {
}), }),
description: PropTypes.string.isRequired, description: PropTypes.string.isRequired,
modality: PropTypes.string.isRequired, modality: PropTypes.string.isRequired,
/* Tooltip message to display when modality text is hovered */
modalityTooltip: PropTypes.string.isRequired,
seriesDate: PropTypes.string.isRequired, seriesDate: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired, onClick: PropTypes.func.isRequired,
onDoubleClick: PropTypes.func.isRequired, onDoubleClick: PropTypes.func.isRequired,
isActive: PropTypes.bool.isRequired, isActive: PropTypes.bool.isRequired,
viewportIdentificator: PropTypes.string,
}; };
export default ThumbnailNoImage; export default ThumbnailNoImage;

View File

@ -43,7 +43,10 @@ const ThumbnailTracked = ({
<div className="flex flex-row flex-1"> <div className="flex flex-row flex-1">
<div className="flex flex-col flex-1 pr-4"> <div className="flex flex-col flex-1 pr-4">
<span> <span>
Series is <span className="text-white">tracked</span> Series is
<span className="text-white">
{isTracked ? ' tracked' : ' untracked'}
</span>
</span> </span>
{viewportIdentificator && ( {viewportIdentificator && (
<span> <span>
@ -73,7 +76,7 @@ const ThumbnailTracked = ({
)} )}
</div> </div>
<Thumbnail <Thumbnail
displaySetInstanceUID={displaySetInstanceUID} displaySetInstanceUID={displaySetInstanceUID}
imageSrc={imageSrc} imageSrc={imageSrc}
imageAltText={imageAltText} imageAltText={imageAltText}
dragData={dragData} dragData={dragData}

View File

@ -80,37 +80,46 @@ const ViewportActionBar = ({
); );
} }
const trackedIcon = isTracked ? 'tracked' : 'dotted-circle';
return ( return (
<div className="relative"> <div className="relative">
{!isTracked ? ( <Tooltip
<Icon name="dotted-circle" className="w-6 text-primary-light" /> position="bottom-left"
) : ( content={
<Tooltip <div className="flex py-2">
position="bottom-left" <div className="flex pt-1">
content={ <Icon name="info-link" className="w-4 text-primary-main" />
<div className="flex py-2">
<div className="flex pt-1">
<Icon name="info-link" className="w-4 text-primary-main" />
</div>
<div className="flex ml-4">
<span className="text-base text-common-light">
Series is
<span className="font-bold text-white"> tracked</span> and
can be viewed <br /> in the measurement panel
</span>
</div>
</div> </div>
} <div className="flex ml-4">
> <span className="text-base text-common-light">
<Icon name="tracked" className="w-6 text-primary-light" /> {isTracked ? (
</Tooltip> <>
)} Series is
<span className="font-bold text-white"> tracked</span> and
can be viewed <br /> in the measurement panel
</>
) : (
<>
Measurements for
<span className="font-bold text-white"> untracked </span>
series <br /> will not be shown in the <br /> measurements
panel
</>
)}
</span>
</div>
</div>
}
>
<Icon name={trackedIcon} className="w-6 text-primary-light" />
</Tooltip>
</div> </div>
); );
}; };
return ( return (
<div className="flex items-center p-2 border-b border-primary-light"> <div className="flex items-center p-2 border-b select-none border-primary-light min-h-12">
<div className="flex flex-grow"> <div className="flex flex-grow">
<div className="flex items-center"> <div className="flex items-center">
{renderIconStatus()} {renderIconStatus()}

View File

@ -64,11 +64,18 @@ function ViewportPane({
'flex flex-col', 'flex flex-col',
'rounded-lg hover:border-primary-light transition duration-300 outline-none overflow-hidden', 'rounded-lg hover:border-primary-light transition duration-300 outline-none overflow-hidden',
{ {
'border-2 border-primary-light m-0': isActive, 'border-2 border-primary-light': isActive,
'border border-secondary-light': !isActive, 'border border-secondary-light': !isActive,
}, },
className className
)} )}
// Normally, we'd use tailwindcss classes here, but margin and border classes use different units
// m-# (rem), border-# (px). To make sure we don't change the box size of our viewports
// and trigger a canvas resize, we have to use this little trick for margin.
// Assumes a :root font-fize of `16px`
style={{
margin: isActive ? '3px' : '4px',
}}
> >
{children} {children}
</div> </div>
@ -90,7 +97,7 @@ ViewportPane.propTypes = {
onInteraction: PropTypes.func.isRequired, onInteraction: PropTypes.func.isRequired,
}; };
const noop = () => { }; const noop = () => {};
ViewportPane.defaultProps = { ViewportPane.defaultProps = {
onInteraction: noop, onInteraction: noop,

View File

@ -12,5 +12,5 @@
-webkit-line-clamp: 2; -webkit-line-clamp: 2;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
line-height: 1.3; line-height: 1.3;
height: 2rem; height: 2.1rem;
} }

View File

@ -330,7 +330,7 @@ module.exports = {
full: '100%', full: '100%',
viewport: '0.5rem', viewport: '0.5rem',
'1/2': '50%', '1/2': '50%',
'viewport-scrollbar': '1.3rem' 'viewport-scrollbar': '1.3rem',
}, },
letterSpacing: { letterSpacing: {
tighter: '-0.05em', tighter: '-0.05em',
@ -684,7 +684,7 @@ module.exports = {
transitionProperty: { transitionProperty: {
none: 'none', none: 'none',
all: 'all', all: 'all',
'height': 'height', height: 'height',
default: default:
'background-color, border-color, color, fill, stroke, opacity, box-shadow, transform', 'background-color, border-color, color, fill, stroke, opacity, box-shadow, transform',
colors: 'background-color, border-color, color, fill, stroke', colors: 'background-color, border-color, color, fill, stroke',
@ -716,12 +716,26 @@ module.exports = {
alignSelf: ['responsive'], alignSelf: ['responsive'],
appearance: ['responsive'], appearance: ['responsive'],
backgroundAttachment: ['responsive'], backgroundAttachment: ['responsive'],
backgroundColor: ['responsive', 'hover', 'focus', 'active', 'group-hover'], backgroundColor: [
'responsive',
'hover',
'focus',
'active',
'group-focus',
'group-hover',
],
backgroundPosition: ['responsive'], backgroundPosition: ['responsive'],
backgroundRepeat: ['responsive'], backgroundRepeat: ['responsive'],
backgroundSize: ['responsive'], backgroundSize: ['responsive'],
borderCollapse: ['responsive'], borderCollapse: ['responsive'],
borderColor: ['responsive', 'hover', 'focus', 'active', 'group-focus'], borderColor: [
'responsive',
'hover',
'focus',
'active',
'group-focus',
'group-hover',
],
borderRadius: ['responsive', 'focus', 'first', 'last'], borderRadius: ['responsive', 'focus', 'first', 'last'],
borderStyle: ['responsive', 'focus'], borderStyle: ['responsive', 'focus'],
borderWidth: ['responsive', 'focus', 'first', 'last'], borderWidth: ['responsive', 'focus', 'first', 'last'],