Files
ohif-viewer/platform/ui/src/components/studyBrowser/Thumbnail.js
T

284 lines
7.6 KiB
JavaScript
Raw Normal View History

import React, { useState, useEffect } from 'react';
2019-10-30 16:53:58 -03:00
import PropTypes from 'prop-types';
import throttle from 'lodash.throttle';
2019-10-30 16:53:58 -03:00
import { useDrag } from 'react-dnd';
2021-09-09 04:58:24 -03:00
import { classes } from '@ohif/core';
2019-10-30 16:53:58 -03:00
import ImageThumbnail from './ImageThumbnail';
import classNames from 'classnames';
import { Icon } from './../../elements/Icon';
import { Tooltip } from './../tooltip';
import { OverlayTrigger } from './../overlayTrigger';
2019-10-30 16:53:58 -03:00
import './Thumbnail.styl';
2021-09-09 04:58:24 -03:00
const StudyLoadingListener = classes.StudyLoadingListener;
2019-10-30 16:53:58 -03:00
function ThumbnailFooter({
SeriesDescription,
SeriesNumber,
2019-10-30 16:53:58 -03:00
numImageFrames,
2021-09-09 04:58:24 -03:00
hasWarnings,
hasDerivedDisplaySets,
2019-10-30 16:53:58 -03:00
}) {
const [inconsistencyWarnings, inconsistencyWarningsSet] = useState([]);
const [derivedDisplaySetsActive, derivedDisplaySetsActiveSet] = useState([]);
useEffect(() => {
2021-09-09 04:58:24 -03:00
let unmounted = false;
hasWarnings.then(response => {
if (!unmounted) {
2021-09-09 04:58:24 -03:00
inconsistencyWarningsSet(response);
}
2021-09-09 04:58:24 -03:00
});
hasDerivedDisplaySets.then(response => {
if (!unmounted) {
derivedDisplaySetsActiveSet(response);
}
});
return () => {
2021-09-09 04:58:24 -03:00
unmounted = true;
};
}, [hasWarnings, hasDerivedDisplaySets]);
const infoOnly = !SeriesDescription;
2019-10-30 16:53:58 -03:00
const getInfo = (value, icon, className = '') => {
return (
<div className={classNames('item item-series', className)}>
<div className="icon">{icon}</div>
<div className="value">{value}</div>
</div>
);
};
2021-09-09 04:58:24 -03:00
const getWarningContent = inconsistencyWarnings => {
if (Array.isArray(inconsistencyWarnings)) {
const listedWarnings = inconsistencyWarnings.map((warn, index) => {
return <li key={index}>{warn}</li>;
});
return <ol>{listedWarnings}</ol>;
} else {
return <React.Fragment>{inconsistencyWarnings}</React.Fragment>;
}
};
const getWarningInfo = (SeriesNumber, inconsistencyWarnings) => {
2021-09-09 04:58:24 -03:00
return (
<React.Fragment>
{inconsistencyWarnings && inconsistencyWarnings.length != 0 ? (
<OverlayTrigger
key={SeriesNumber}
placement="left"
overlay={
<Tooltip
placement="left"
className="in tooltip-warning"
id="tooltip-left"
>
<div className="warningTitle">Series Inconsistencies</div>
2021-09-09 04:58:24 -03:00
<div className="warningContent">
{getWarningContent(inconsistencyWarnings)}
</div>
</Tooltip>
}
>
<div className={classNames('warning')}>
<span className="warning-icon">
<Icon name="exclamation-triangle" />
</span>
</div>
</OverlayTrigger>
) : (
<React.Fragment></React.Fragment>
2021-09-09 04:58:24 -03:00
)}
</React.Fragment>
2021-09-09 04:58:24 -03:00
);
};
const getDerivedInfo = derivedDisplaySetsActive => {
return (
<React.Fragment>
{derivedDisplaySetsActive ? (
<div className="derived">
<Icon name="link" />
</div>
) : (
<React.Fragment></React.Fragment>
)}
</React.Fragment>
);
};
2019-10-30 16:53:58 -03:00
const getSeriesInformation = (
SeriesNumber,
numImageFrames,
inconsistencyWarnings,
derivedDisplaySetsActive
2019-10-30 16:53:58 -03:00
) => {
if (!SeriesNumber && !numImageFrames) {
2019-10-30 16:53:58 -03:00
return;
}
2021-09-09 04:58:24 -03:00
const seriesInformation = (
2019-10-30 16:53:58 -03:00
<div className="series-information">
<React.Fragment>
{SeriesNumber !== undefined ? (
getInfo(SeriesNumber, 'S:')
) : (
<React.Fragment></React.Fragment>
)}
</React.Fragment>
<React.Fragment>
{numImageFrames !== undefined ? (
getInfo(numImageFrames, '', 'image-frames')
) : (
<React.Fragment></React.Fragment>
)}
</React.Fragment>
{getDerivedInfo(derivedDisplaySetsActive)}
{getWarningInfo(SeriesNumber, inconsistencyWarnings)}
2019-10-30 16:53:58 -03:00
</div>
2021-09-09 04:58:24 -03:00
);
2021-09-09 04:58:24 -03:00
return seriesInformation;
2019-10-30 16:53:58 -03:00
};
return (
<div className={classNames('series-details', { 'info-only': infoOnly })}>
<div className="series-description">{SeriesDescription}</div>
2021-09-09 04:58:24 -03:00
{getSeriesInformation(
SeriesNumber,
numImageFrames,
inconsistencyWarnings,
derivedDisplaySetsActive
2021-09-09 04:58:24 -03:00
)}
2019-10-30 16:53:58 -03:00
</div>
);
}
function Thumbnail(props) {
const {
active,
altImageText,
error,
displaySetInstanceUID,
2019-10-30 16:53:58 -03:00
imageId,
imageSrc,
StudyInstanceUID,
2019-10-30 16:53:58 -03:00
onClick,
onDoubleClick,
onMouseDown,
supportsDrag,
2021-09-09 04:58:24 -03:00
showProgressBar,
2019-10-30 16:53:58 -03:00
} = props;
2021-09-09 04:58:24 -03:00
const [stackPercentComplete, setStackPercentComplete] = useState(0);
useEffect(() => {
const onProgressChange = throttle(({ detail }) => {
2021-09-09 04:58:24 -03:00
const { progressId, progressData } = detail;
if (`StackProgress:${displaySetInstanceUID}` === progressId) {
const percent = progressData ? progressData.percentComplete : 0;
2022-04-06 18:23:02 +02:00
if (percent > stackPercentComplete) {
setStackPercentComplete(percent);
}
2021-09-09 04:58:24 -03:00
}
}, 100);
2021-09-09 04:58:24 -03:00
document.addEventListener(
StudyLoadingListener.events.OnProgress,
onProgressChange
);
return () => {
document.removeEventListener(
StudyLoadingListener.events.OnProgress,
onProgressChange
);
};
}, [displaySetInstanceUID]);
2019-10-30 16:53:58 -03:00
const [collectedProps, drag, dragPreview] = useDrag({
// `droppedItem` in `dropTarget`
// The only data it will have access to
item: {
StudyInstanceUID,
displaySetInstanceUID,
2019-10-30 16:53:58 -03:00
type: 'thumbnail', // Has to match `dropTarget`'s type
},
canDrag: function(monitor) {
return supportsDrag;
},
});
const hasImage = imageSrc || imageId;
const hasAltText = altImageText !== undefined;
return (
<div
ref={drag}
className={classNames('thumbnail', { active: active })}
onClick={onClick}
onDoubleClick={onDoubleClick}
onMouseDown={onMouseDown}
>
{/* SHOW IMAGE */}
{hasImage && (
<ImageThumbnail
active={active}
2019-10-30 16:53:58 -03:00
imageSrc={imageSrc}
imageId={imageId}
error={error}
stackPercentComplete={stackPercentComplete}
2021-09-09 04:58:24 -03:00
showProgressBar={showProgressBar}
2019-10-30 16:53:58 -03:00
/>
)}
{/* SHOW TEXT ALTERNATIVE */}
{!hasImage && hasAltText && (
<div className={'alt-image-text p-x-1'}>
<h1>{altImageText}</h1>
</div>
)}
{ThumbnailFooter(props)}
</div>
);
}
const noop = () => {};
Thumbnail.propTypes = {
supportsDrag: PropTypes.bool,
id: PropTypes.string.isRequired,
displaySetInstanceUID: PropTypes.string.isRequired,
StudyInstanceUID: PropTypes.string.isRequired,
2019-10-30 16:53:58 -03:00
imageSrc: PropTypes.string,
imageId: PropTypes.string,
error: PropTypes.bool,
active: PropTypes.bool,
stackPercentComplete: PropTypes.number,
/**
altImageText will be used when no imageId or imageSrc is provided.
It will be displayed inside the <div>. This is useful when it is difficult
2019-10-30 16:53:58 -03:00
to make a preview for a type of DICOM series (e.g. DICOM-SR)
*/
altImageText: PropTypes.string,
SeriesDescription: PropTypes.string,
SeriesNumber: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
hasWarnings: PropTypes.instanceOf(Promise),
hasDerivedDisplaySets: PropTypes.instanceOf(Promise),
2019-10-30 16:53:58 -03:00
numImageFrames: PropTypes.number,
onDoubleClick: PropTypes.func,
onClick: PropTypes.func,
onMouseDown: PropTypes.func,
2021-09-09 04:58:24 -03:00
showProgressBar: PropTypes.bool,
2019-10-30 16:53:58 -03:00
};
Thumbnail.defaultProps = {
supportsDrag: false,
active: false,
error: false,
stackPercentComplete: 0,
onDoubleClick: noop,
onClick: noop,
onMouseDown: noop,
};
export { Thumbnail };