Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(worklist): New worklist buttons and tooltips #3989

Merged
merged 3 commits into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions modes/basic-dev-mode/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,15 @@ function modeFactory({ modeConfiguration }) {
]);
},
onModeExit: ({ servicesManager }) => {
const { toolGroupService, measurementService, toolbarService } = servicesManager.services;

const {
toolGroupService,
measurementService,
toolbarService,
uiDialogService,
uiModalService,
} = servicesManager.services;
uiDialogService.dismissAll();
uiModalService.hide();
toolGroupService.destroy();
},
validationTags: {
Expand All @@ -145,7 +152,10 @@ function modeFactory({ modeConfiguration }) {
const modalities_list = modalities.split('\\');

// Slide Microscopy modality not supported by basic mode yet
return !modalities_list.includes('SM');
return {
valid: !modalities_list.includes('SM'),
description: 'The mode does not support the following modalities: SM',
};
},
routes: [
{
Expand Down
12 changes: 10 additions & 2 deletions modes/basic-test-mode/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,12 @@ function modeFactory() {
syncGroupService,
segmentationService,
cornerstoneViewportService,
uiDialogService,
uiModalService,
} = servicesManager.services;

uiDialogService.dismissAll();
uiModalService.hide();
toolGroupService.destroy();
syncGroupService.destroy();
segmentationService.destroy();
Expand All @@ -159,8 +163,12 @@ function modeFactory() {
const modalities_list = modalities.split('\\');

// Exclude non-image modalities
return !!modalities_list.filter(modality => NON_IMAGE_MODALITIES.indexOf(modality) === -1)
.length;
return {
valid: !!modalities_list.filter(modality => NON_IMAGE_MODALITIES.indexOf(modality) === -1)
.length,
description:
'The mode does not support studies that ONLY include the following modalities: SM, ECG, SR, SEG',
};
},
routes: [
{
Expand Down
12 changes: 10 additions & 2 deletions modes/longitudinal/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,11 +179,15 @@ function modeFactory({ modeConfiguration }) {
toolbarService,
segmentationService,
cornerstoneViewportService,
uiDialogService,
uiModalService,
} = servicesManager.services;

_activatePanelTriggersSubscriptions.forEach(sub => sub.unsubscribe());
_activatePanelTriggersSubscriptions = [];

uiDialogService.dismissAll();
uiModalService.hide();
toolGroupService.destroy();
syncGroupService.destroy();
segmentationService.destroy();
Expand All @@ -198,8 +202,12 @@ function modeFactory({ modeConfiguration }) {
const modalities_list = modalities.split('\\');

// Exclude non-image modalities
return !!modalities_list.filter(modality => NON_IMAGE_MODALITIES.indexOf(modality) === -1)
.length;
return {
valid: !!modalities_list.filter(modality => NON_IMAGE_MODALITIES.indexOf(modality) === -1)
.length,
description:
'The mode does not support studies that ONLY include the following modalities: SM, ECG, SR, SEG, RTSTRUCT',
};
},
routes: [
{
Expand Down
10 changes: 7 additions & 3 deletions modes/microscopy/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,10 @@ function modeFactory({ modeConfiguration }) {
},

onModeExit: ({ servicesManager }) => {
const { toolbarService } = servicesManager.services;
const { toolbarService, uiDialogService, uiModalService } = servicesManager.services;

uiDialogService.dismissAll();
uiModalService.hide();
toolbarService.reset();
},

Expand All @@ -69,8 +71,10 @@ function modeFactory({ modeConfiguration }) {
isValidMode: ({ modalities }) => {
const modalities_list = modalities.split('\\');

// Slide Microscopy and ECG modality not supported by basic mode yet
return modalities_list.includes('SM');
return {
valid: modalities_list.includes('SM'),
description: 'Microscopy mode only supports the SM modality',
};
},

routes: [
Expand Down
11 changes: 7 additions & 4 deletions modes/segmentation/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,12 @@
toolbarService,
segmentationService,
cornerstoneViewportService,
uiDialogService,
uiModalService,
} = servicesManager.services;

uiDialogService.dismissAll();
uiModalService.hide();
toolGroupService.destroy();
syncGroupService.destroy();
segmentationService.destroy();
Expand All @@ -127,11 +131,10 @@
// Don't show the mode if the selected studies have only one modality
// that is not supported by the mode
const modalitiesArray = modalities.split('\\');
if (modalitiesArray.length === 1) {
return !['SM', 'US', 'MG', 'OT', 'DOC', 'CR'].includes(modalitiesArray[0]);
return {
valid: modalitiesArray.length === 1 ? !['SM', 'US', 'MG', 'OT', 'DOC', 'CR'].includes(modalitiesArray[0]) : true,
description: 'The mode does not support studies that ONLY include the following modalities: SM, US, MG, OT, DOC, CR',

Check failure on line 136 in modes/segmentation/src/index.tsx

View workflow job for this annotation

GitHub Actions / Check for spelling errors

OT ==> TO, OF, OR, NOT
}

return true;
},
/**
* Mode Routes are used to define the mode's behavior. A list of Mode Route
Expand Down
9 changes: 8 additions & 1 deletion modes/tmtv/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,13 @@ function modeFactory({ modeConfiguration }) {
syncGroupService,
segmentationService,
cornerstoneViewportService,
uiDialogService,
uiModalService,
} = servicesManager.services;

unsubscriptions.forEach(unsubscribe => unsubscribe());
uiDialogService.dismissAll();
uiModalService.hide();
toolGroupService.destroy();
syncGroupService.destroy();
segmentationService.destroy();
Expand All @@ -193,7 +197,10 @@ function modeFactory({ modeConfiguration }) {
study.studyInstanceUid !== '1.3.6.1.4.1.12842.1.1.14.3.20220915.105557.468.2963630849';

// there should be both CT and PT modalities and the modality should not be SM
return isValid;
return {
valid: isValid,
description: 'The mode requires both PT and CT series in the study',
};
},
routes: [
{
Expand Down
1 change: 1 addition & 0 deletions platform/app/public/config/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ window.config = {
showCPUFallbackMessage: true,
showLoadingIndicator: true,
strictZSpacingForVolumeViewport: true,
groupEnabledModesFirst: true,
maxNumRequests: {
interaction: 100,
thumbnail: 75,
Expand Down
49 changes: 40 additions & 9 deletions platform/app/src/routes/WorkList/WorkList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import {
UserPreferences,
LoadingIndicatorProgress,
useSessionStorage,
Button,
ButtonEnums,
} from '@ohif/ui';

import i18n from '@ohif/i18n';
Expand Down Expand Up @@ -254,11 +256,13 @@ function WorkList({
const studyDate =
date &&
moment(date, ['YYYYMMDD', 'YYYY.MM.DD'], true).isValid() &&
moment(date, ['YYYYMMDD', 'YYYY.MM.DD']).format(t('Common:localDateFormat','MMM-DD-YYYY'));
moment(date, ['YYYYMMDD', 'YYYY.MM.DD']).format(t('Common:localDateFormat', 'MMM-DD-YYYY'));
const studyTime =
time &&
moment(time, ['HH', 'HHmm', 'HHmmss', 'HHmmss.SSS']).isValid() &&
moment(time, ['HH', 'HHmm', 'HHmmss', 'HHmmss.SSS']).format(t('Common:localTimeFormat', 'hh:mm A'));
moment(time, ['HH', 'HHmm', 'HHmmss', 'HHmmss.SSS']).format(
t('Common:localTimeFormat', 'hh:mm A')
);

return {
dataCY: `studyRow-${studyInstanceUid}`,
Expand Down Expand Up @@ -346,10 +350,24 @@ function WorkList({
}
>
<div className="flex flex-row gap-2">
{appConfig.loadedModes.map((mode, i) => {
{(appConfig.groupEnabledModesFirst
? appConfig.loadedModes.sort((a, b) => {
const isValidA = a.isValidMode({
modalities: modalities.replaceAll('/', '\\'),
study,
}).valid;
const isValidB = b.isValidMode({
modalities: modalities.replaceAll('/', '\\'),
study,
}).valid;

return isValidB - isValidA;
})
: appConfig.loadedModes
).map((mode, i) => {
const modalitiesToCheck = modalities.replaceAll('/', '\\');

const isValidMode = mode.isValidMode({
const { valid: isValidMode, description: invalidModeDescription } = mode.isValidMode({
modalities: modalitiesToCheck,
study,
});
Expand Down Expand Up @@ -382,16 +400,29 @@ function WorkList({
// to={`${mode.routeName}/dicomweb?StudyInstanceUIDs=${studyInstanceUid}`}
>
{/* TODO revisit the completely rounded style of buttons used for launching a mode from the worklist later - for now use LegacyButton*/}
<LegacyButton
rounded="full"
variant={isValidMode ? 'contained' : 'disabled'}
<Button
type={ButtonEnums.type.primary}
size={ButtonEnums.size.medium}
disabled={!isValidMode}
endIcon={<Icon name="launch-arrow" />} // launch-arrow | launch-info
startIconTooltip={
!isValidMode ? (
<div className="font-inter flex w-[206px] whitespace-normal text-left text-xs font-normal text-white ">
{invalidModeDescription}
</div>
) : null
}
startIcon={
<Icon
className="!h-[20px] !w-[20px] text-black"
name={isValidMode ? 'launch-arrow' : 'launch-info'}
/>
} // launch-arrow | launch-info
onClick={() => {}}
data-cy={`mode-${mode.routeName}-${studyInstanceUid}`}
className={isValidMode ? 'text-[13px]' : 'bg-[#222d44] text-[13px]'}
>
{mode.displayName}
</LegacyButton>
</Button>
</Link>
)
);
Expand Down
4 changes: 4 additions & 0 deletions platform/cli/templates/mode/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,12 @@ function modeFactory({ modeConfiguration }) {
toolbarService,
segmentationService,
cornerstoneViewportService,
uiDialogService,
uiModalService,
} = servicesManager.services;

uiDialogService.dismissAll();
uiModalService.hide();
toolGroupService.destroy();
syncGroupService.destroy();
segmentationService.destroy();
Expand Down
6 changes: 3 additions & 3 deletions platform/ui/src/assets/icons/launch-arrow.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 5 additions & 4 deletions platform/ui/src/assets/icons/launch-info.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 11 additions & 4 deletions platform/ui/src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useRef } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import * as ButtonEnums from './ButtonEnums';
import Tooltip from '../Tooltip/Tooltip';

const sizeClasses = {
[ButtonEnums.size.small]: 'h-[26px] text-[13px]',
Expand Down Expand Up @@ -66,19 +67,21 @@ const Button = ({
name,
className,
onClick,
startIconTooltip = null,
endIconTooltip = null,
}) => {
const startIcon = startIconProp && (
<>
{React.cloneElement(startIconProp, {
className: classnames('w-4 h-4 fill-current'),
className: classnames('w-4 h-4 fill-current', startIconProp?.props?.className),
})}
</>
);

const endIcon = endIconProp && (
<>
{React.cloneElement(endIconProp, {
className: classnames('w-4 h-4 fill-current'),
className: classnames('w-4 h-4 fill-current', endIconProp?.props?.className),
})}
</>
);
Expand Down Expand Up @@ -108,9 +111,9 @@ const Button = ({
onClick={handleOnClick}
data-cy={`${name}-btn`}
>
{startIcon}
{startIconTooltip ? <Tooltip content={startIconTooltip}>{startIcon}</Tooltip> : startIcon}
{children}
{endIcon}
{endIconTooltip ? <Tooltip content={endIconTooltip}>{endIcon}</Tooltip> : endIcon}
</button>
);
};
Expand Down Expand Up @@ -141,6 +144,10 @@ Button.propTypes = {
endIcon: PropTypes.node,
/** Additional TailwindCSS classnames */
className: PropTypes.string,
/** Tooltip for the start icon */
startIconTooltip: PropTypes.node,
/** Tooltip for the end icon */
endIconTooltip: PropTypes.node,
};

export default Button;
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const StudyListTableRow = props => {
className={classnames(
'w-full transition duration-300',
{
'border-primary-light hover:border-secondary-light mb-2 overflow-hidden rounded border':
'border-primary-light hover:border-secondary-light mb-2 overflow-visible rounded border':
isExpanded,
},
{
Expand Down
Loading