fix/chore: Various style improvements (#2015)

* Update all the icons

* Turn off global tool sync; watch as the world burns

* Shift a bunch of things around so we can start tracking/setting per element

* ToolBarService to initiate one of three different calls; callback passed to all button types

* SplitButton and Toolbar Button to use new `onInteraction` prop and new toolbar state

* Changes to toolbar button interface and config

* Fix broken layout selector

* Update SR viewport to activate tools in viewport component

* Duplicate activation logic in measurement tracking extension

* Add alternative/dashed variants for SR Viewport

* pass through "setToolActive" commands for other viewport types

* fix small overlay bugs (no wwwc or scale info)

* Show SpacingBetweenSlices instead of PixelSpacing in patient information dialog

* Fix prop-types

* Update tracked viewport to have alternative tracked styling

* alt styling for SR viewports

* Update to support isLocked + isRehydratable

* fix broken logic

* fix broken logic

* switch icon style

* fix icon styles

* hover and click to start flow

* expedited workflow when data is not dirty (just after SR hydration)

* fix: setting elliptical roi tool

* fix arrow annotate dialog

* fix: do not show learn more button for now

* remove dead code

* simpler cache invalidation

* simpler cache invalidation part 2

* Fix for unable to spand study cards on separate pages

* fix: viewport grid area top padding should align with sidepanel gap

* fix: don't wrap split button list item text

* fix: cine player moz/chrome styles

* fix: split button arrows

* fix: tighten toolbar and splitbutton styles

* chore: clean up worklist sort logic
This commit is contained in:
Danny Brown 2020-09-21 04:42:53 -04:00 committed by GitHub
parent ba38cebcd7
commit 2fc7169fa4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 101 additions and 63 deletions

View File

@ -163,7 +163,7 @@ function ViewerLayout({
</Header> </Header>
<div <div
className="flex flex-row flex-no-wrap items-stretch w-full overflow-hidden" className="flex flex-row flex-no-wrap items-stretch w-full overflow-hidden"
style={{ height: 'calc(100vh - 57px' }} style={{ height: 'calc(100vh - 52px' }}
> >
{/* LEFT SIDEPANELS */} {/* LEFT SIDEPANELS */}
{leftPanelComponents.length && ( {leftPanelComponents.length && (
@ -177,7 +177,7 @@ function ViewerLayout({
)} )}
{/* TOOLBAR + GRID */} {/* TOOLBAR + GRID */}
<div className="flex flex-col flex-1 h-full"> <div className="flex flex-col flex-1 h-full">
<div className="flex items-center justify-center flex-1 h-full pt-1 pb-2 overflow-hidden bg-black"> <div className="flex items-center justify-center flex-1 h-full -mt-1 overflow-hidden bg-black">
<ErrorBoundary context="Grid"> <ErrorBoundary context="Grid">
<ViewportGridComp <ViewportGridComp
servicesManager={servicesManager} servicesManager={servicesManager}

View File

@ -120,9 +120,9 @@ export default [
), ),
secondary: { secondary: {
icon: 'chevron-down', icon: 'chevron-down',
label: '', label: 'W/L Manual',
isActive: true, isActive: true,
tooltip: 'More Measure Tools', tooltip: 'W/L Presets',
}, },
isAction: true, // ? isAction: true, // ?
renderer: WindowLevelMenuItem, renderer: WindowLevelMenuItem,
@ -177,7 +177,7 @@ export default [
icon: 'chevron-down', icon: 'chevron-down',
label: '', label: '',
isActive: true, isActive: true,
tooltip: 'More Measure Tools', tooltip: 'More Tools',
}, },
items: [ items: [
_createActionButton( _createActionButton(

View File

@ -13,7 +13,7 @@ const CinePlayer = ({
frameRate: defaultFrameRate, frameRate: defaultFrameRate,
onFrameRateChange, onFrameRateChange,
onPlayPauseChange, onPlayPauseChange,
onClose onClose,
}) => { }) => {
const [frameRate, setFrameRate] = useState(defaultFrameRate); const [frameRate, setFrameRate] = useState(defaultFrameRate);
const debouncedSetFrameRate = debounce(onFrameRateChange, 300); const debouncedSetFrameRate = debounce(onFrameRateChange, 300);
@ -28,21 +28,21 @@ const CinePlayer = ({
const action = { const action = {
false: { icon: 'old-play' }, false: { icon: 'old-play' },
true: { icon: 'old-stop' } true: { icon: 'old-stop' },
}; };
return ( return (
<div className="CinePlayer flex flex-row h-10 items-center justify-center border border-primary-light rounded-full"> <div className="flex flex-row items-center justify-center h-10 border rounded-full CinePlayer border-primary-light">
<IconButton <IconButton
variant="text" variant="text"
color="inherit" color="inherit"
size="initial" size="initial"
className="mr-3 ml-4 text-primary-active" className="ml-4 mr-3 text-primary-active"
onClick={onPlayPauseChangeHandler} onClick={onPlayPauseChangeHandler}
> >
<Icon width="15px" height="15px" name={action[isPlaying].icon} /> <Icon width="15px" height="15px" name={action[isPlaying].icon} />
</IconButton> </IconButton>
<div className="flex flex-col h-full justify-center pt-2 mr-3 pl-1 pr-1"> <div className="flex flex-col justify-center h-full pt-2 pl-1 pr-1 mr-3">
<input <input
type="range" type="range"
name="frameRate" name="frameRate"
@ -52,12 +52,14 @@ const CinePlayer = ({
value={frameRate} value={frameRate}
onChange={onFrameRateChangeHandler} onChange={onFrameRateChangeHandler}
/> />
<p className="mt-1 text-sm text-primary-light">{`${frameRate.toFixed(1)} fps`}</p> <p className="-mt-2 text-sm text-primary-light">{`${frameRate.toFixed(
1
)} fps`}</p>
</div> </div>
<IconButton <IconButton
color="inherit" color="inherit"
size="initial" size="initial"
className="mr-3 text-primary-active border border-primary-active rounded-full" className="mr-3 border rounded-full text-primary-active border-primary-active"
onClick={onClose} onClick={onClose}
> >
<Icon name="close" width="15px" height="15px" /> <Icon name="close" width="15px" height="15px" />
@ -66,7 +68,7 @@ const CinePlayer = ({
); );
}; };
const noop = () => { }; const noop = () => {};
CinePlayer.defaultProps = { CinePlayer.defaultProps = {
isPlaying: false, isPlaying: false,
@ -76,7 +78,7 @@ CinePlayer.defaultProps = {
frameRate: 24, frameRate: 24,
onPlayPauseChange: noop, onPlayPauseChange: noop,
onFrameRateChange: noop, onFrameRateChange: noop,
onClose: noop onClose: noop,
}; };
CinePlayer.propTypes = { CinePlayer.propTypes = {

View File

@ -3,17 +3,19 @@
* written in plain CSS with color variables from tailwind * written in plain CSS with color variables from tailwind
* to avoid complex compatibility configuration. * to avoid complex compatibility configuration.
*/ */
.CinePlayer input[type=range] { .CinePlayer input[type='range'] {
-webkit-appearance: none; -webkit-appearance: none;
background: transparent; background: transparent;
width: 100%; width: 100%;
height: 20px;
z-index: 5;
} }
.CinePlayer input[type=range]:focus { .CinePlayer input[type='range']:focus {
outline: none; outline: none;
} }
.CinePlayer input[type=range]::-webkit-slider-runnable-track { .CinePlayer input[type='range']::-webkit-slider-runnable-track {
width: 100%; width: 100%;
height: 2px; height: 2px;
cursor: pointer; cursor: pointer;
@ -24,7 +26,7 @@
border: 0px solid #000000; border: 0px solid #000000;
} }
.CinePlayer input[type=range]::-webkit-slider-thumb { .CinePlayer input[type='range']::-webkit-slider-thumb {
box-shadow: 0px 0px 0px #000000; box-shadow: 0px 0px 0px #000000;
border: 4px solid #000000; border: 4px solid #000000;
height: 18px; height: 18px;
@ -36,11 +38,11 @@
margin-top: -9px; margin-top: -9px;
} }
.CinePlayer input[type=range]:focus::-webkit-slider-runnable-track { .CinePlayer input[type='range']:focus::-webkit-slider-runnable-track {
@apply bg-primary-light; @apply bg-primary-light;
} }
.CinePlayer input[type=range]::-moz-range-track { .CinePlayer input[type='range']::-moz-range-track {
width: 100%; width: 100%;
height: 2px; height: 2px;
cursor: pointer; cursor: pointer;
@ -51,17 +53,17 @@
border: 0px solid #000000; border: 0px solid #000000;
} }
.CinePlayer input[type=range]::-moz-range-thumb { .CinePlayer input[type='range']::-moz-range-thumb {
box-shadow: 0px 0px 0px #000000; box-shadow: 0px 0px 0px #000000;
border: 4px solid #000000; border: 2px solid #000000;
height: 18px; height: 12px;
width: 17px; width: 12px;
border-radius: 50px; border-radius: 50px;
@apply bg-primary-light; @apply bg-primary-light;
cursor: pointer; cursor: pointer;
} }
.CinePlayer input[type=range]::-ms-track { .CinePlayer input[type='range']::-ms-track {
width: 100%; width: 100%;
height: 2px; height: 2px;
cursor: pointer; cursor: pointer;
@ -71,21 +73,21 @@
color: transparent; color: transparent;
} }
.CinePlayer input[type=range]::-ms-fill-lower { .CinePlayer input[type='range']::-ms-fill-lower {
@apply bg-primary-light; @apply bg-primary-light;
border: 0px solid #000000; border: 0px solid #000000;
border-radius: 10px; border-radius: 10px;
box-shadow: 0px 0px 0px #000000; box-shadow: 0px 0px 0px #000000;
} }
.CinePlayer input[type=range]::-ms-fill-upper { .CinePlayer input[type='range']::-ms-fill-upper {
@apply bg-primary-light; @apply bg-primary-light;
border: 0px solid #000000; border: 0px solid #000000;
border-radius: 10px; border-radius: 10px;
box-shadow: 0px 0px 0px #000000; box-shadow: 0px 0px 0px #000000;
} }
.CinePlayer input[type=range]::-ms-thumb { .CinePlayer input[type='range']::-ms-thumb {
margin-top: 1px; margin-top: 1px;
box-shadow: 0px 0px 0px #000000; box-shadow: 0px 0px 0px #000000;
border: 4px solid #000000; border: 4px solid #000000;
@ -96,10 +98,10 @@
cursor: pointer; cursor: pointer;
} }
.CinePlayer input[type=range]:focus::-ms-fill-lower { .CinePlayer input[type='range']:focus::-ms-fill-lower {
@apply bg-primary-light; @apply bg-primary-light;
} }
.CinePlayer input[type=range]:focus::-ms-fill-upper { .CinePlayer input[type='range']:focus::-ms-fill-upper {
@apply bg-primary-light; @apply bg-primary-light;
} }

View File

@ -62,12 +62,14 @@ const sizeClasses = {
medium: 'py-3 px-3 text-lg', medium: 'py-3 px-3 text-lg',
large: 'py-4 px-4 text-xl', large: 'py-4 px-4 text-xl',
initial: '', initial: '',
toolbar: 'text-lg',
}; };
const iconSizeClasses = { const iconSizeClasses = {
small: 'w-4 h-4', small: 'w-4 h-4',
medium: 'w-5 h-5', medium: 'w-5 h-5',
large: 'w-6 h-6', large: 'w-6 h-6',
toolbar: 'w-5 h-5',
}; };
const fullWidthClasses = { const fullWidthClasses = {
@ -106,6 +108,9 @@ const IconButton = ({
disabledClasses[disabled], disabledClasses[disabled],
className className
)} )}
style={{
padding: size === 'toolbar' ? '10px' : null,
}}
ref={buttonElement} ref={buttonElement}
onClick={handleOnClick} onClick={handleOnClick}
type={type} type={type}

View File

@ -9,11 +9,12 @@ const NavBar = ({ className, children, isSticky }) => {
return ( return (
<div <div
className={classnames( className={classnames(
'flex flex-row items-center bg-secondary-dark px-3 py-1 min-h-16 border-b-4 border-black z-20', 'flex flex-row items-center bg-secondary-dark px-3 border-b-4 border-black z-20',
isSticky && stickyClasses, isSticky && stickyClasses,
!isSticky && notStickyClasses, !isSticky && notStickyClasses,
className className
)} )}
style={{ paddingTop: '4px', paddingBottom: '4px', minHeight: '52px' }}
> >
{children} {children}
</div> </div>

View File

@ -7,7 +7,7 @@ import { Icon, Tooltip, ListMenu } from '@ohif/ui';
const baseClasses = { const baseClasses = {
Button: Button:
'h-12 flex items-center rounded-md border-transparent border-2 cursor-pointer', 'flex items-center rounded-md border-transparent border-2 cursor-pointer',
Primary: Primary:
'h-full flex flex-1 items-center rounded-md rounded-tr-none rounded-br-none', 'h-full flex flex-1 items-center rounded-md rounded-tr-none rounded-br-none',
Secondary: Secondary:
@ -15,7 +15,7 @@ const baseClasses = {
PrimaryIcon: 'w-5 h-5', PrimaryIcon: 'w-5 h-5',
SecondaryIcon: 'w-4 h-full stroke-1', SecondaryIcon: 'w-4 h-full stroke-1',
Separator: 'border-l pt-2 pb-2', Separator: 'border-l pt-2 pb-2',
Content: 'absolute z-10 top-0 mt-16', Content: 'absolute z-10 top-0 mt-12',
}; };
const classes = { const classes = {
@ -154,6 +154,7 @@ const SplitButton = ({
...state, ...state,
primary: { isActive: isPrimaryActive }, primary: { isActive: isPrimaryActive },
})} })}
style={{ height: '40px' }}
onMouseEnter={onMouseEnterHandler} onMouseEnter={onMouseEnterHandler}
onMouseLeave={onMouseLeaveHandler} onMouseLeave={onMouseLeaveHandler}
> >
@ -169,7 +170,10 @@ const SplitButton = ({
isDisabled={!state.primary.tooltip} isDisabled={!state.primary.tooltip}
content={state.primary.tooltip} content={state.primary.tooltip}
> >
<div className="flex items-center justify-center w-full h-full p-3"> <div
className="flex items-center justify-center w-full h-full"
style={{ padding: '10px' }}
>
<Icon <Icon
name={state.primary.icon} name={state.primary.icon}
className={classes.PrimaryIcon({ className={classes.PrimaryIcon({
@ -225,10 +229,12 @@ const DefaultListItemRenderer = ({ icon, label, isActive }) => (
isActive && 'bg-primary-dark' isActive && 'bg-primary-dark'
)} )}
> >
<span className="mr-4 text-base text-common-bright"> <span className="mr-4 text-base whitespace-pre text-common-bright">
<Icon name={icon} className="w-5 h-5 text-common-bright" /> <Icon name={icon} className="w-5 h-5 text-common-bright" />
</span> </span>
<span className="mr-5 text-base text-common-bright">{label}</span> <span className="mr-5 text-base whitespace-pre text-common-bright">
{label}
</span>
</div> </div>
); );

View File

@ -24,7 +24,7 @@ import { SplitButton, WindowLevelMenuItem } from '@ohif/ui';
primary: { primary: {
tooltip: 'W/L', tooltip: 'W/L',
icon: 'tool-window-level', icon: 'tool-window-level',
onClick: (args) => console.debug('Primary click!', args) onClick: args => console.debug('Primary click!', args),
}, },
secondary: { secondary: {
icon: 'chevron-down', icon: 'chevron-down',
@ -33,23 +33,43 @@ import { SplitButton, WindowLevelMenuItem } from '@ohif/ui';
tooltip: 'More Measure Tools', tooltip: 'More Measure Tools',
}, },
items: [ items: [
{ id: '1', icon: 'tool-layout', label: 'Layout', onClick: (args) => console.debug('Item click!', args) }, {
{ id: '2', icon: 'tool-window-level', label: 'W/L', onClick: (args) => console.debug('Item click!', args) }, id: '1',
{ id: '3', icon: 'tool-length', label: 'Length', onClick: (args) => console.debug('Item click!', args) } icon: 'tool-layout',
label: 'Layout',
onClick: args => console.debug('Item click!', args),
},
{
id: '2',
icon: 'tool-window-level',
label: 'W/L',
onClick: args => console.debug('Item click!', args),
},
{
id: '3',
icon: 'tool-length',
label: 'Length',
onClick: args => console.debug('Item click!', args),
},
], ],
onClick: (args) => console.debug('Any click!', args) onClick: args => console.debug('Any click!', args),
}; };
return ( return (
<div className="mb-2"> <div className="mb-2">
<div className="flex flex-row min-h-16 w-full justify-center items-center bg-secondary-dark"> <div className="flex flex-row w-full justify-center items-center bg-secondary-dark">
<SplitButton {...mockedProps} isRadio /> <SplitButton {...mockedProps} isRadio />
<SplitButton {...mockedProps} isAction renderer={WindowLevelMenuItem} items={[ <SplitButton
{...mockedProps}
isAction
renderer={WindowLevelMenuItem}
items={[
{ id: '1', value: 1, title: 'Soft tissue', subtitle: '400 / 40' }, { id: '1', value: 1, title: 'Soft tissue', subtitle: '400 / 40' },
{ id: '2', value: 2, title: 'Lung', subtitle: '1500 / -600' }, { id: '2', value: 2, title: 'Lung', subtitle: '1500 / -600' },
{ id: '3', value: 3, title: 'Liver', subtitle: '150 / 90' }, { id: '3', value: 3, title: 'Liver', subtitle: '150 / 90' },
{ id: '4', value: 4, title: 'Bone', subtitle: '80 / 40' }, { id: '4', value: 4, title: 'Bone', subtitle: '80 / 40' },
{ id: '5', value: 5, title: 'Brain', subtitle: '2500 / 480' }, { id: '5', value: 5, title: 'Brain', subtitle: '2500 / 480' },
]} /> ]}
/>
<SplitButton {...mockedProps} /> <SplitButton {...mockedProps} />
</div> </div>
</div> </div>

View File

@ -61,7 +61,7 @@ const StudyListFilter = ({
</div> </div>
<div <div
className="sticky z-10 border-b-4 border-black" className="sticky z-10 border-b-4 border-black"
style={{ top: '57px' }} style={{ top: '52px' }}
> >
<div className="pt-3 pb-3 bg-primary-dark "> <div className="pt-3 pb-3 bg-primary-dark ">
<InputGroup <InputGroup

View File

@ -41,6 +41,7 @@ const ToolbarButton = ({
> >
<IconButton <IconButton
variant={isActive ? 'contained' : 'text'} variant={isActive ? 'contained' : 'text'}
size="toolbar"
className={classnames('mx-1', classes.type[type])} className={classnames('mx-1', classes.type[type])}
onClick={() => { onClick={() => {
onInteraction({ onInteraction({

View File

@ -11,7 +11,7 @@ const Viewer = () => {
<Header /> <Header />
<div <div
className="flex flex-row flex-no-wrap items-stretch flex-1 w-full overflow-hidden" className="flex flex-row flex-no-wrap items-stretch flex-1 w-full overflow-hidden"
style={{ height: 'calc(100vh - 57px' }} style={{ height: 'calc(100vh - 52px' }}
> >
<SidePanel <SidePanel
side="left" side="left"

View File

@ -57,7 +57,7 @@ import { tabs } from './studyBrowserMockData';
<Header /> <Header />
<div <div
className="flex flex-row flex-no-wrap items-stretch overflow-hidden w-full" className="flex flex-row flex-no-wrap items-stretch overflow-hidden w-full"
style={{ height: 'calc(100vh - 57px' }} style={{ height: 'calc(100vh - 52px' }}
> >
{/* LEFT SIDEPANELS */} {/* LEFT SIDEPANELS */}
<SidePanel <SidePanel

View File

@ -62,21 +62,21 @@ function WorkList({
* The default sort value keep the filters synchronized with runtime conditional sorting * The default sort value keep the filters synchronized with runtime conditional sorting
* Only applied if no other sorting is specified and there are less than 101 studies * Only applied if no other sorting is specified and there are less than 101 studies
*/ */
let defaultSortValues = {};
const sortedStudies = studies const canSort = studiesTotal < STUDIES_LIMIT;
// TOOD: Move sort to DataSourceWrapper? const shouldUseDefaultSort = sortBy === '' || !sortBy;
// TODO: MOTIVATION, this is triggered on every render, even if list/sort does not change
.sort((s1, s2) => {
const noSortApplied = sortBy === '' || !sortBy;
const sortModifier = sortDirection === 'descending' ? 1 : -1; const sortModifier = sortDirection === 'descending' ? 1 : -1;
const defaultSortValues =
shouldUseDefaultSort && canSort
? { sortBy: 'studyDate', sortDirection: 'ascending' }
: {};
const sortedStudies = studies;
if (noSortApplied && studiesTotal < STUDIES_LIMIT) { if (canSort) {
studies.sort((s1, s2) => {
if (shouldUseDefaultSort) {
const ascendingSortModifier = -1; const ascendingSortModifier = -1;
defaultSortValues = { sortBy: 'studyDate', sortDirection: 'ascending' };
return _sortStringDates(s1, s2, ascendingSortModifier); return _sortStringDates(s1, s2, ascendingSortModifier);
} else if (noSortApplied) {
return 0;
} }
const s1Prop = s1[sortBy]; const s1Prop = s1[sortBy];
@ -96,6 +96,7 @@ function WorkList({
return 0; return 0;
}); });
}
// ~ Rows & Studies // ~ Rows & Studies
const [expandedRows, setExpandedRows] = useState([]); const [expandedRows, setExpandedRows] = useState([]);